Files
growstuff/app/javascript/components/AuthDialog.test.jsx
T
Daniel O'Connorandgoogle-labs-jules[bot] 8875e89345 Dismiss responsive offcanvas when opening auth dialogs (#5259) (#5260)
On iPad and tablets, the main navigation menu is rendered as a
responsive Bootstrap offcanvas drawer (`#navbarSupportedContent.offcanvas-lg`).
When users tapped 'Sign in' in the drawer, the auth modal opened while the
offcanvas remained open in the DOM, allowing Bootstrap's active FocusTrap
on the offcanvas to intercept focusin events on document and steal focus
away from the modal inputs, preventing clicking or typing on iPadOS.

Update `closeOpenOffcanvases` in `Modal.jsx` and `RecordDialog.jsx` to
match all open offcanvases including responsive ones
(`.offcanvas.show, [class*="offcanvas-"].show`) and closest offcanvas
containers, invoke it when auth dialog triggers are clicked in
`AuthDialog.jsx`, restore focus correctly when the modal closes, and add
accompanying tests.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-11-03 04:10:38 +10:30

113 lines
4.1 KiB
React

import {fireEvent, render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {describe, expect, it, vi} from 'vitest';
import AuthDialog from './AuthDialog';
const urls = {sign_in: '/s', sign_up: '/u', forgot_password: '/f', sign_in_page: '/si', sign_up_page: '/su', bluesky: '/b'};
function setup(pageUrls = urls) {
render(
<>
<a href="/members/sign_in" data-auth-dialog="sign_in">Log in</a>
<a href="/members/sign_up" data-auth-dialog="sign_up">Join</a>
<a href="/members/sign_in" data-auth-dialog="bluesky">Bluesky</a>
<a href="/about">About</a>
<AuthDialog urls={pageUrls} site_name="Growstuff" />
</>,
);
}
describe('AuthDialog', () => {
it('draws nothing to start with', () => {
setup();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('opens sign in from a sign in link', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'Log in'}));
expect(screen.getByRole('dialog', {name: 'Sign in'})).toBeInTheDocument();
});
it('opens sign up from a sign up link', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'Join'}));
expect(screen.getByRole('dialog', {name: 'Join Growstuff'})).toBeInTheDocument();
});
it('opens the Bluesky dialog from a Bluesky link', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'Bluesky'}));
expect(screen.getByRole('dialog', {name: 'Sign in with Bluesky'})).toBeInTheDocument();
});
it('moves from the sign in dialog to Bluesky', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'Log in'}));
await userEvent.click(screen.getByRole('button', {name: 'Continue with Bluesky'}));
expect(screen.getByRole('dialog', {name: 'Sign in with Bluesky'})).toBeInTheDocument();
});
it('moves from the sign up dialog to Bluesky', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'Join'}));
await userEvent.click(screen.getByRole('button', {name: 'Continue with Bluesky'}));
expect(screen.getByRole('dialog', {name: 'Sign in with Bluesky'})).toBeInTheDocument();
});
it('offers no Bluesky button when it is switched off', async () => {
setup({...urls, bluesky: null});
await userEvent.click(screen.getByRole('link', {name: 'Log in'}));
expect(screen.queryByRole('button', {name: 'Continue with Bluesky'})).not.toBeInTheDocument();
});
it('ignores other links', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'About'}));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('leaves a ctrl-click to open in a new tab', () => {
setup();
fireEvent.click(screen.getByRole('link', {name: 'Log in'}), {ctrlKey: true});
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('switches from sign in to sign up', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'Log in'}));
await userEvent.click(screen.getByRole('link', {name: 'Join free'}));
expect(screen.getByRole('dialog', {name: 'Join Growstuff'})).toBeInTheDocument();
});
it('closes', async () => {
setup();
await userEvent.click(screen.getByRole('link', {name: 'Log in'}));
await userEvent.click(screen.getByRole('button', {name: 'Close'}));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('closes an open offcanvas when an auth link is clicked', async () => {
const hide = vi.fn();
window.bootstrap = {
Offcanvas: {
getInstance: vi.fn(() => ({hide})),
getOrCreateInstance: vi.fn(() => ({hide})),
},
};
const offcanvas = document.createElement('div');
offcanvas.id = 'navbarSupportedContent';
offcanvas.className = 'offcanvas-lg offcanvas-end show';
document.body.appendChild(offcanvas);
setup();
await userEvent.click(screen.getByRole('link', {name: 'Log in'}));
expect(hide).toHaveBeenCalled();
offcanvas.remove();
delete window.bootstrap;
});
});