mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-11-05 12:42:48 -05:00
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>
113 lines
4.1 KiB
React
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;
|
|
});
|
|
});
|