Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dialogs): Remove history hacks in dialog #1041

Merged
merged 6 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 0 additions & 58 deletions src/__tests__/dialog-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ const TestComponent = () => {
);
};

beforeEach(() => {
// The history object is not cleared between tests. This way we put the history position at the end
window.history.pushState({}, '', '/');
});

test('does not render anything initially', () => {
const {asFragment} = render(<ThemeContextProvider theme={makeTheme()} />);
expect(asFragment()).toMatchInlineSnapshot(`<DocumentFragment />`);
Expand Down Expand Up @@ -281,56 +276,3 @@ test('when webview bridge is available nativeConfirm is shown', async () => {
});
});
});

test('history restored after closing a dialog using back', async () => {
const pushStateSpy = jest.spyOn(window.history, 'pushState');
const backSpy = jest.spyOn(window.history, 'back');
const initialHistoryLength = window.history.length;

render(
<ThemeContextProvider theme={makeTheme()}>
<TestComponent />
</ThemeContextProvider>
);

const alertButton = await screen.findByRole('button', {name: 'Alert'});
await userEvent.click(alertButton);

expect(await screen.findByRole('dialog')).toBeInTheDocument();
expect(window.history.length).toBe(initialHistoryLength + 1);

act(() => {
window.history.back();
});

await waitForElementToBeRemoved(() => screen.queryByRole('dialog', {hidden: true}));

expect(pushStateSpy).toHaveBeenCalledTimes(1);
expect(backSpy).toHaveBeenCalledTimes(1);
});

test('history restored after closing a dialog using a button', async () => {
const pushStateSpy = jest.spyOn(window.history, 'pushState');
const backSpy = jest.spyOn(window.history, 'back');
const initialHistoryLength = window.history.length;

render(
<ThemeContextProvider theme={makeTheme()}>
<TestComponent />
</ThemeContextProvider>
);

const alertButton = await screen.findByRole('button', {name: 'Alert'});
await userEvent.click(alertButton);

expect(await screen.findByRole('dialog')).toBeInTheDocument();
expect(window.history.length).toBe(initialHistoryLength + 1);

const acceptButton = await screen.findByRole('button', {name: 'Yay!'});
await userEvent.click(acceptButton);

await waitForElementToBeRemoved(() => screen.queryByRole('dialog', {hidden: true}));

expect(pushStateSpy).toHaveBeenCalledTimes(1);
expect(backSpy).toHaveBeenCalledTimes(1);
});
40 changes: 11 additions & 29 deletions src/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,7 @@ const ModalDialog = (props: ModalDialogProps): JSX.Element => {
/** this flag is used to disable user interactions while the component is animating */
const [isReady, setIsReady] = React.useState<boolean>(false);
const dialogWasAcceptedRef = React.useRef<boolean>(false);
const historyWasPushedRef = React.useRef<boolean>(false);
const animationDurationRef = React.useRef<number>(shouldAnimate() ? styles.ANIMATION_DURATION_MS : 0);
const handleBackNavigationRef = React.useRef(() => {});

const shouldRenderNative = props.type !== 'dialog' && isWebViewBridgeAvailable();
const shouldDismissOnPressOverlay = props.type === 'dialog';
Expand Down Expand Up @@ -254,11 +252,6 @@ const ModalDialog = (props: ModalDialogProps): JSX.Element => {
}, [onAccept, onCancel, onDestroy]);

const startClosing = React.useCallback(() => {
window.removeEventListener('popstate', handleBackNavigationRef.current);
if (historyWasPushedRef.current) {
historyWasPushedRef.current = false;
window.history.back();
}
let timeout: NodeJS.Timeout;
if (!isClosingRef.current && isReady) {
isClosingRef.current = true;
Expand Down Expand Up @@ -294,28 +287,6 @@ const ModalDialog = (props: ModalDialogProps): JSX.Element => {
}
}, [handleAccept, handleCancel, shouldAcceptOnDismiss]);

const handleBackNavigation = React.useCallback(() => {
if (historyWasPushedRef.current) {
historyWasPushedRef.current = false;
dismiss();
}
}, [dismiss]);

React.useEffect(() => {
if (shouldRenderNative) {
return;
}
if (!historyWasPushedRef.current && !isClosingRef.current) {
historyWasPushedRef.current = true;
window.history.pushState(null, document.title, window.location.href);
}
handleBackNavigationRef.current = handleBackNavigation;
window.addEventListener('popstate', handleBackNavigationRef.current);
return () => {
window.removeEventListener('popstate', handleBackNavigationRef.current);
};
}, [handleBackNavigation, shouldRenderNative]);

const handleKeyDown = React.useCallback(
(event: KeyboardEvent) => {
if (event.key === ESC) {
Expand All @@ -337,6 +308,17 @@ const ModalDialog = (props: ModalDialogProps): JSX.Element => {
};
}, [handleKeyDown, shouldRenderNative]);

React.useEffect(() => {
if (shouldRenderNative) {
return;
}
// Consider any page navigation as a dismiss
window.addEventListener('popstate', dismiss);
return () => {
window.removeEventListener('popstate', dismiss);
};
}, [dismiss, shouldRenderNative]);

const handleOverlayPress = React.useCallback(
(event: React.MouseEvent) => {
event.stopPropagation();
Expand Down
Loading