// @vitest-environment jsdom
import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, cleanup, fireEvent } from '@testing-library/react';
import { useModalDialog } from '@/hooks/useModalDialog';
import AlbumPicker from '../AlbumPicker';
import AlbumDrawer from '../page-builder/AlbumDrawer';
import { StorySettingsModal } from '../journal/StorySettingsModal';
afterEach(() => {
cleanup();
});
function Dialog({
name,
onClose,
children,
}: {
name: string;
onClose: () => void;
children?: React.ReactNode;
}) {
const ref = useModalDialog(onClose);
return (
{children}
);
}
const escape = () => fireEvent.keyDown(document.activeElement ?? document.body, { key: 'Escape' });
describe('useModalDialog with nested dialogs (A-9)', () => {
it('Escape closes only the innermost dialog', () => {
const closeOuter = vi.fn();
const closeInner = vi.fn();
const { rerender } = render();
rerender(
,
);
escape();
expect(closeInner).toHaveBeenCalledTimes(1);
expect(closeOuter).not.toHaveBeenCalled();
// Once the inner one is gone, the outer one answers again.
rerender();
escape();
expect(closeOuter).toHaveBeenCalledTimes(1);
});
it('leaves Escape to an expanded combobox', () => {
const close = vi.fn();
render(
,
);
screen.getByRole('combobox').focus();
escape();
expect(close).not.toHaveBeenCalled();
});
it('leaves Escape to an item picked up with the keyboard (dnd-kit)', () => {
const close = vi.fn();
render(
tile
,
);
screen.getByText('tile').focus();
escape();
expect(close).not.toHaveBeenCalled();
});
});
describe('AlbumPicker from the keyboard (A-10)', () => {
const albums = [
{
id: 'a1',
albumName: 'Alps',
description: '',
thumbnailAssetId: null,
assetCount: 3,
isConfigured: false,
},
{
id: 'a2',
albumName: 'Baltic',
description: '',
thumbnailAssetId: null,
assetCount: 5,
isConfigured: true,
},
];
it('offers each free album as a button and disables the ones in use', () => {
const onSelect = vi.fn();
render(
{}}
usedAlbumIds={new Set(['a2'])}
/>,
);
const alps = screen.getByRole('button', { name: /Alps/ });
const baltic = screen.getByRole('button', { name: /Baltic/ }) as HTMLButtonElement;
expect(baltic.disabled).toBe(true);
fireEvent.click(alps);
expect(onSelect).toHaveBeenCalledWith('a1');
expect(screen.getByRole('button', { name: 'Close' })).toBeTruthy();
});
it('closes on Escape', () => {
const onClose = vi.fn();
render(
{}}
onClose={onClose}
usedAlbumIds={new Set()}
/>,
);
escape();
expect(onClose).toHaveBeenCalledTimes(1);
});
});
describe('AlbumDrawer as a dialog (A-9)', () => {
it('is a labelled modal dialog that closes on Escape', () => {
const onClose = vi.fn();
render(
{}}
onRemove={() => {}}
onClose={onClose}
onPickHero={() => {}}
onEditOrder={() => {}}
/>,
);
const dialog = screen.getByRole('dialog', { name: /Edit Album Details/ });
expect(dialog.getAttribute('aria-modal')).toBe('true');
expect(dialog.contains(document.activeElement)).toBe(true);
escape();
expect(onClose).toHaveBeenCalledTimes(1);
});
});
describe('StorySettingsModal as a dialog (A-9)', () => {
it('is a labelled modal dialog that closes on Escape', () => {
const onClose = vi.fn();
render(
{}}
onPickAsset={() => {}}
onClose={onClose}
/>,
);
const dialog = screen.getByRole('dialog', { name: /Story Settings/ });
expect(dialog.getAttribute('aria-modal')).toBe('true');
escape();
expect(onClose).toHaveBeenCalledTimes(1);
});
});