mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 10:41:13 +08:00
chore(all): sync with main for beta 7
This commit is contained in:
153
packages/react/src/hooks/__tests__/hooks.spec.tsx
Normal file
153
packages/react/src/hooks/__tests__/hooks.spec.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import { alertController, modalController } from '@ionic/core';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useController } from '../useController';
|
||||
import { useOverlay } from '../useOverlay';
|
||||
|
||||
import { useIonActionSheet } from '../useIonActionSheet';
|
||||
import type { UseIonActionSheetResult } from '../useIonActionSheet';
|
||||
import { useIonAlert } from '../useIonAlert';
|
||||
import type { UseIonAlertResult } from '../useIonAlert';
|
||||
import { useIonLoading } from '../useIonLoading';
|
||||
import type { UseIonLoadingResult } from '../useIonLoading';
|
||||
import { useIonModal } from '../useIonModal';
|
||||
import type { UseIonModalResult } from '../useIonModal';
|
||||
import { useIonPicker } from '../useIonPicker';
|
||||
import type { UseIonPickerResult } from '../useIonPicker';
|
||||
import { useIonPopover } from '../useIonPopover';
|
||||
import type { UseIonPopoverResult } from '../useIonPopover';
|
||||
import { useIonToast } from '../useIonToast';
|
||||
import type { UseIonToastResult } from '../useIonToast';
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
describe('useController', () => {
|
||||
it('should be memorised', () => {
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useController('AlertController', alertController)
|
||||
);
|
||||
|
||||
rerender();
|
||||
|
||||
const [
|
||||
{ present: firstPresent, dismiss: firstDismiss },
|
||||
{ present: secondPresent, dismiss: secondDismiss },
|
||||
] = result.all as ReturnType<typeof useController>[];
|
||||
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIonActionSheet', () => {
|
||||
it('should be memorised', () => {
|
||||
const { result, rerender } = renderHook(() => useIonActionSheet());
|
||||
|
||||
rerender();
|
||||
|
||||
const [[firstPresent, firstDismiss], [secondPresent, secondDismiss]] =
|
||||
result.all as UseIonActionSheetResult[];
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIonAlert', () => {
|
||||
it('should be memorised', () => {
|
||||
const { result, rerender } = renderHook(() => useIonAlert());
|
||||
|
||||
rerender();
|
||||
|
||||
const [[firstPresent, firstDismiss], [secondPresent, secondDismiss]] =
|
||||
result.all as UseIonAlertResult[];
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIonLoading', () => {
|
||||
it('should be memorised', () => {
|
||||
const { result, rerender } = renderHook(() => useIonLoading());
|
||||
|
||||
rerender();
|
||||
|
||||
const [[firstPresent, firstDismiss], [secondPresent, secondDismiss]] =
|
||||
result.all as UseIonLoadingResult[];
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIonModal', () => {
|
||||
it('should be memorised', () => {
|
||||
const ModalComponent = () => <div />;
|
||||
const { result, rerender } = renderHook(() => useIonModal(ModalComponent, {}));
|
||||
|
||||
rerender();
|
||||
|
||||
const [[firstPresent, firstDismiss], [secondPresent, secondDismiss]] =
|
||||
result.all as UseIonModalResult[];
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIonPicker', () => {
|
||||
it('should be memorised', () => {
|
||||
const { result, rerender } = renderHook(() => useIonPicker());
|
||||
|
||||
rerender();
|
||||
|
||||
const [[firstPresent, firstDismiss], [secondPresent, secondDismiss]] =
|
||||
result.all as UseIonPickerResult[];
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIonPopover', () => {
|
||||
it('should be memorised', () => {
|
||||
const PopoverComponent = () => <div />;
|
||||
const { result, rerender } = renderHook(() => useIonPopover(PopoverComponent, {}));
|
||||
|
||||
rerender();
|
||||
|
||||
const [[firstPresent, firstDismiss], [secondPresent, secondDismiss]] =
|
||||
result.all as UseIonPopoverResult[];
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useIonToast', () => {
|
||||
it('should be memorised', () => {
|
||||
const { result, rerender } = renderHook(() => useIonToast());
|
||||
|
||||
rerender();
|
||||
|
||||
const [[firstPresent, firstDismiss], [secondPresent, secondDismiss]] =
|
||||
result.all as UseIonToastResult[];
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useOverlay', () => {
|
||||
it('should be memorised', () => {
|
||||
const OverlayComponent = () => <div />;
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useOverlay('IonModal', modalController, OverlayComponent, {})
|
||||
);
|
||||
|
||||
rerender();
|
||||
|
||||
const [
|
||||
{ present: firstPresent, dismiss: firstDismiss },
|
||||
{ present: secondPresent, dismiss: secondDismiss },
|
||||
] = result.all as ReturnType<typeof useOverlay>[];
|
||||
|
||||
expect(firstPresent).toBe(secondPresent);
|
||||
expect(firstDismiss).toBe(secondDismiss);
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import { OverlayEventDetail } from '@ionic/core/components';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
import { attachProps } from '../components/react-component-lib/utils';
|
||||
|
||||
@ -10,71 +10,53 @@ interface OverlayBase extends HTMLElement {
|
||||
dismiss: (data?: any, role?: string | undefined) => Promise<boolean>;
|
||||
}
|
||||
|
||||
export function useController<
|
||||
OptionsType,
|
||||
OverlayType extends OverlayBase
|
||||
>(
|
||||
export function useController<OptionsType, OverlayType extends OverlayBase>(
|
||||
displayName: string,
|
||||
controller: { create: (options: OptionsType) => Promise<OverlayType> }
|
||||
) {
|
||||
const overlayRef = useRef<OverlayType>();
|
||||
const didDismissEventName = useMemo(
|
||||
() => `on${displayName}DidDismiss`,
|
||||
[displayName]
|
||||
);
|
||||
const didPresentEventName = useMemo(
|
||||
() => `on${displayName}DidPresent`,
|
||||
[displayName]
|
||||
);
|
||||
const willDismissEventName = useMemo(
|
||||
() => `on${displayName}WillDismiss`,
|
||||
[displayName]
|
||||
);
|
||||
const willPresentEventName = useMemo(
|
||||
() => `on${displayName}WillPresent`,
|
||||
[displayName]
|
||||
);
|
||||
const didDismissEventName = useMemo(() => `on${displayName}DidDismiss`, [displayName]);
|
||||
const didPresentEventName = useMemo(() => `on${displayName}DidPresent`, [displayName]);
|
||||
const willDismissEventName = useMemo(() => `on${displayName}WillDismiss`, [displayName]);
|
||||
const willPresentEventName = useMemo(() => `on${displayName}WillPresent`, [displayName]);
|
||||
|
||||
const present = async (options: OptionsType & HookOverlayOptions) => {
|
||||
if (overlayRef.current) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
onDidDismiss,
|
||||
onWillDismiss,
|
||||
onDidPresent,
|
||||
onWillPresent,
|
||||
...rest
|
||||
} = options;
|
||||
|
||||
const handleDismiss = (event: CustomEvent<OverlayEventDetail<any>>) => {
|
||||
if (onDidDismiss) {
|
||||
onDidDismiss(event);
|
||||
const present = useCallback(
|
||||
async (options: OptionsType & HookOverlayOptions) => {
|
||||
if (overlayRef.current) {
|
||||
return;
|
||||
}
|
||||
const { onDidDismiss, onWillDismiss, onDidPresent, onWillPresent, ...rest } = options;
|
||||
|
||||
const handleDismiss = (event: CustomEvent<OverlayEventDetail<any>>) => {
|
||||
if (onDidDismiss) {
|
||||
onDidDismiss(event);
|
||||
}
|
||||
overlayRef.current = undefined;
|
||||
};
|
||||
|
||||
overlayRef.current = await controller.create({
|
||||
...(rest as any),
|
||||
});
|
||||
|
||||
attachProps(overlayRef.current, {
|
||||
[didDismissEventName]: handleDismiss,
|
||||
[didPresentEventName]: (e: CustomEvent) => onDidPresent && onDidPresent(e),
|
||||
[willDismissEventName]: (e: CustomEvent) => onWillDismiss && onWillDismiss(e),
|
||||
[willPresentEventName]: (e: CustomEvent) => onWillPresent && onWillPresent(e),
|
||||
});
|
||||
|
||||
overlayRef.current.present();
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
|
||||
const dismiss = useCallback(
|
||||
() => async () => {
|
||||
overlayRef.current && (await overlayRef.current.dismiss());
|
||||
overlayRef.current = undefined;
|
||||
}
|
||||
|
||||
overlayRef.current = await controller.create({
|
||||
...(rest as any),
|
||||
});
|
||||
|
||||
attachProps(overlayRef.current, {
|
||||
[didDismissEventName]: handleDismiss,
|
||||
[didPresentEventName]: (e: CustomEvent) =>
|
||||
onDidPresent && onDidPresent(e),
|
||||
[willDismissEventName]: (e: CustomEvent) =>
|
||||
onWillDismiss && onWillDismiss(e),
|
||||
[willPresentEventName]: (e: CustomEvent) =>
|
||||
onWillPresent && onWillPresent(e),
|
||||
});
|
||||
|
||||
overlayRef.current.present();
|
||||
};
|
||||
|
||||
const dismiss = async () => {
|
||||
overlayRef.current && await overlayRef.current.dismiss();
|
||||
overlayRef.current = undefined;
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
present,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ActionSheetButton, ActionSheetOptions, actionSheetController } from '@ionic/core/components';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { HookOverlayOptions } from './HookOverlayOptions';
|
||||
import { useController } from './useController';
|
||||
@ -13,23 +14,24 @@ export function useIonActionSheet(): UseIonActionSheetResult {
|
||||
actionSheetController
|
||||
);
|
||||
|
||||
function present(buttons: ActionSheetButton[], header?: string): void;
|
||||
function present(options: ActionSheetOptions & HookOverlayOptions): void;
|
||||
function present(buttonsOrOptions: ActionSheetButton[] | ActionSheetOptions & HookOverlayOptions, header?: string) {
|
||||
if (Array.isArray(buttonsOrOptions)) {
|
||||
controller.present({
|
||||
buttons: buttonsOrOptions,
|
||||
header
|
||||
});
|
||||
} else {
|
||||
controller.present(buttonsOrOptions);
|
||||
}
|
||||
}
|
||||
const present = useCallback(
|
||||
(
|
||||
buttonsOrOptions: ActionSheetButton[] | (ActionSheetOptions & HookOverlayOptions),
|
||||
header?: string
|
||||
) => {
|
||||
if (Array.isArray(buttonsOrOptions)) {
|
||||
controller.present({
|
||||
buttons: buttonsOrOptions,
|
||||
header,
|
||||
});
|
||||
} else {
|
||||
controller.present(buttonsOrOptions);
|
||||
}
|
||||
},
|
||||
[controller.present]
|
||||
);
|
||||
|
||||
return [
|
||||
present,
|
||||
controller.dismiss
|
||||
];
|
||||
return [present, controller.dismiss];
|
||||
}
|
||||
|
||||
export type UseIonActionSheetResult = [
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { AlertButton, AlertOptions, alertController } from '@ionic/core/components';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { HookOverlayOptions } from './HookOverlayOptions';
|
||||
import { useController } from './useController';
|
||||
@ -8,28 +9,23 @@ import { useController } from './useController';
|
||||
* @returns Returns the present and dismiss methods in an array
|
||||
*/
|
||||
export function useIonAlert(): UseIonAlertResult {
|
||||
const controller = useController<AlertOptions, HTMLIonAlertElement>(
|
||||
'IonAlert',
|
||||
alertController
|
||||
const controller = useController<AlertOptions, HTMLIonAlertElement>('IonAlert', alertController);
|
||||
|
||||
const present = useCallback(
|
||||
(messageOrOptions: string | (AlertOptions & HookOverlayOptions), buttons?: AlertButton[]) => {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
message: messageOrOptions,
|
||||
buttons: buttons ?? [{ text: 'Ok' }],
|
||||
});
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
}
|
||||
},
|
||||
[controller.present]
|
||||
);
|
||||
|
||||
function present(message: string, buttons?: AlertButton[]): void;
|
||||
function present(options: AlertOptions & HookOverlayOptions): void;
|
||||
function present(messageOrOptions: string | AlertOptions & HookOverlayOptions, buttons?: AlertButton[]) {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
message: messageOrOptions,
|
||||
buttons: buttons ?? [{ text: 'Ok' }]
|
||||
});
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
present,
|
||||
controller.dismiss
|
||||
];
|
||||
return [present, controller.dismiss];
|
||||
}
|
||||
|
||||
export type UseIonAlertResult = [
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { LoadingOptions, SpinnerTypes, loadingController } from '@ionic/core/components';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { HookOverlayOptions } from './HookOverlayOptions';
|
||||
import { useController } from './useController';
|
||||
@ -13,27 +14,24 @@ export function useIonLoading(): UseIonLoadingResult {
|
||||
loadingController
|
||||
);
|
||||
|
||||
function present(
|
||||
message?: string,
|
||||
duration?: number,
|
||||
spinner?: SpinnerTypes
|
||||
): void;
|
||||
function present(options: LoadingOptions & HookOverlayOptions): void;
|
||||
function present(
|
||||
messageOrOptions: string | (LoadingOptions & HookOverlayOptions) = '',
|
||||
duration?: number,
|
||||
spinner?: SpinnerTypes
|
||||
) {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
message: messageOrOptions,
|
||||
duration,
|
||||
spinner: spinner ?? 'lines',
|
||||
});
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
}
|
||||
}
|
||||
const present = useCallback(
|
||||
(
|
||||
messageOrOptions: string | (LoadingOptions & HookOverlayOptions) = '',
|
||||
duration?: number,
|
||||
spinner?: SpinnerTypes
|
||||
) => {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
message: messageOrOptions,
|
||||
duration,
|
||||
spinner: spinner ?? 'lines',
|
||||
});
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
}
|
||||
},
|
||||
[controller.present]
|
||||
);
|
||||
|
||||
return [present, controller.dismiss];
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ModalOptions, modalController } from '@ionic/core/components';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { HookOverlayOptions } from './HookOverlayOptions';
|
||||
import { ReactComponentOrElement, useOverlay } from './useOverlay';
|
||||
@ -9,7 +10,10 @@ import { ReactComponentOrElement, useOverlay } from './useOverlay';
|
||||
* @param componentProps The props that will be passed to the component, if required
|
||||
* @returns Returns the present and dismiss methods in an array
|
||||
*/
|
||||
export function useIonModal(component: ReactComponentOrElement, componentProps?: any): UseIonModalResult {
|
||||
export function useIonModal(
|
||||
component: ReactComponentOrElement,
|
||||
componentProps?: any
|
||||
): UseIonModalResult {
|
||||
const controller = useOverlay<ModalOptions, HTMLIonModalElement>(
|
||||
'IonModal',
|
||||
modalController,
|
||||
@ -17,14 +21,14 @@ export function useIonModal(component: ReactComponentOrElement, componentProps?:
|
||||
componentProps
|
||||
);
|
||||
|
||||
function present(options: Omit<ModalOptions, 'component' | 'componentProps'> & HookOverlayOptions = {}) {
|
||||
controller.present(options as any);
|
||||
};
|
||||
const present = useCallback(
|
||||
(options: Omit<ModalOptions, 'component' | 'componentProps'> & HookOverlayOptions = {}) => {
|
||||
controller.present(options as any);
|
||||
},
|
||||
[controller.present]
|
||||
);
|
||||
|
||||
return [
|
||||
present,
|
||||
controller.dismiss
|
||||
];
|
||||
return [present, controller.dismiss];
|
||||
}
|
||||
|
||||
export type UseIonModalResult = [
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
PickerOptions,
|
||||
pickerController,
|
||||
} from '@ionic/core/components';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { HookOverlayOptions } from './HookOverlayOptions';
|
||||
import { useController } from './useController';
|
||||
@ -18,12 +19,10 @@ export function useIonPicker(): UseIonPickerResult {
|
||||
pickerController
|
||||
);
|
||||
|
||||
function present(columns: PickerColumn[], buttons?: PickerButton[]): void;
|
||||
function present(options: PickerOptions & HookOverlayOptions): void;
|
||||
function present(
|
||||
const present = useCallback((
|
||||
columnsOrOptions: PickerColumn[] | (PickerOptions & HookOverlayOptions),
|
||||
buttons?: PickerButton[]
|
||||
) {
|
||||
) => {
|
||||
if (Array.isArray(columnsOrOptions)) {
|
||||
controller.present({
|
||||
columns: columnsOrOptions,
|
||||
@ -32,7 +31,7 @@ export function useIonPicker(): UseIonPickerResult {
|
||||
} else {
|
||||
controller.present(columnsOrOptions);
|
||||
}
|
||||
}
|
||||
}, [controller.present]);
|
||||
|
||||
return [present, controller.dismiss];
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PopoverOptions, popoverController } from '@ionic/core/components';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { HookOverlayOptions } from './HookOverlayOptions';
|
||||
import { ReactComponentOrElement, useOverlay } from './useOverlay';
|
||||
@ -17,9 +18,9 @@ export function useIonPopover(component: ReactComponentOrElement, componentProps
|
||||
componentProps
|
||||
);
|
||||
|
||||
function present(options: Omit<PopoverOptions, 'component' | 'componentProps'> & HookOverlayOptions = {}) {
|
||||
const present = useCallback((options: Omit<PopoverOptions, 'component' | 'componentProps'> & HookOverlayOptions = {}) => {
|
||||
controller.present(options as any);
|
||||
};
|
||||
}, [controller.present]);
|
||||
|
||||
return [
|
||||
present,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ToastOptions, toastController } from '@ionic/core/components';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { HookOverlayOptions } from './HookOverlayOptions';
|
||||
import { useController } from './useController';
|
||||
@ -13,9 +14,7 @@ export function useIonToast(): UseIonToastResult {
|
||||
toastController
|
||||
);
|
||||
|
||||
function present(message: string, duration?: number): void;
|
||||
function present(options: ToastOptions & HookOverlayOptions): void;
|
||||
function present(messageOrOptions: string | ToastOptions & HookOverlayOptions, duration?: number) {
|
||||
const present = useCallback((messageOrOptions: string | ToastOptions & HookOverlayOptions, duration?: number) => {
|
||||
if (typeof messageOrOptions === 'string') {
|
||||
controller.present({
|
||||
message: messageOrOptions,
|
||||
@ -24,7 +23,7 @@ export function useIonToast(): UseIonToastResult {
|
||||
} else {
|
||||
controller.present(messageOrOptions);
|
||||
}
|
||||
};
|
||||
}, [controller.present]);
|
||||
|
||||
return [
|
||||
present,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OverlayEventDetail } from '@ionic/core/components';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { attachProps } from '../components/react-component-lib/utils';
|
||||
@ -52,7 +52,7 @@ export function useOverlay<
|
||||
}
|
||||
}, [component, containerElRef.current, isOpen, componentProps]);
|
||||
|
||||
const present = async (options: OptionsType & HookOverlayOptions) => {
|
||||
const present = useCallback(async (options: OptionsType & HookOverlayOptions) => {
|
||||
if (overlayRef.current) {
|
||||
return;
|
||||
}
|
||||
@ -96,13 +96,13 @@ export function useOverlay<
|
||||
containerElRef.current = undefined;
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const dismiss = async () => {
|
||||
const dismiss = useCallback(async () => {
|
||||
overlayRef.current && await overlayRef.current.dismiss();
|
||||
overlayRef.current = undefined;
|
||||
containerElRef.current = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
present,
|
||||
|
Reference in New Issue
Block a user