import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; // A plugin's dialog is a real modal for the keyboard: the focus moves in to // the safe choice, Tab can't leave it, and the focus comes back to where it // was once the dialog is answered. vi.mock('next-intl', () => ({ useTranslations: () => (key: string) => key })); vi.mock('../plugin-iframe-slot', () => ({ PluginIframeSlot: () =>
})); import { PluginDialogHost } from '../plugin-dialog-host'; import { awaitDialog, awaitPrompt, head, resetDialogLimits, resolveHead } from '@/lib/plugin-sandbox/host-dialog'; let opener: HTMLButtonElement; beforeEach(() => { while (head()) resolveHead(null); resetDialogLimits(); opener = document.createElement('button'); opener.textContent = 'opener'; document.body.appendChild(opener); opener.focus(); }); afterEach(() => opener.remove()); describe('plugin dialog focus', () => { it('starts on Cancel for a destructive confirm, keeps Tab inside, and returns the focus', async () => { render(); let answer: Promise; act(() => { answer = awaitDialog({ pluginId: 'p', kind: 'confirm', title: 'Delete it?', message: '', danger: true }); }); const cancel = screen.getByRole('button', { name: 'cancel' }); expect(cancel).toHaveFocus(); const dialog = screen.getByRole('dialog', { name: 'Delete it?' }); fireEvent.keyDown(cancel, { key: 'Tab', shiftKey: true }); expect(dialog).toContainElement(document.activeElement as HTMLElement); await act(async () => { fireEvent.click(cancel); }); await expect(answer!).resolves.toBe(false); await waitFor(() => expect(opener).toHaveFocus()); }); it('starts in the first field of a prompt and marks required fields', async () => { render(); let answer: Promise | null>; act(() => { answer = awaitPrompt({ pluginId: 'p', kind: 'prompt', title: 'Name', message: '', fields: [{ name: 'n', label: 'Name', required: true }] }); }); const input = screen.getByRole('textbox', { name: 'Name' }); expect(input).toHaveFocus(); expect(input).toHaveAttribute('aria-required', 'true'); act(() => { fireEvent.keyDown(document, { key: 'Escape' }); }); await expect(answer!).resolves.toBeNull(); await waitFor(() => expect(opener).toHaveFocus()); }); });