import { useLayoutEffect } from 'react' import { afterEach, describe, expect, it, vi } from 'vitest' import { fireEvent, renderWithProviders, screen } from '../../../test/utils' import { VIEWER_ACTION_CLASS } from './viewerChromeClasses' import ViewerShell from './ViewerShell' const dialogPrototype = HTMLDialogElement.prototype const originalDialogDescriptors = { close: Object.getOwnPropertyDescriptor(dialogPrototype, 'close'), showModal: Object.getOwnPropertyDescriptor(dialogPrototype, 'showModal'), } const defineDialogMethod = ( name: 'close' | 'showModal', implementation: () => void ) => { Object.defineProperty(dialogPrototype, name, { configurable: true, value: implementation, writable: true, }) } const restoreDialogMethod = ( name: 'close' | 'showModal', descriptor: PropertyDescriptor | undefined ) => { if (descriptor) Object.defineProperty(dialogPrototype, name, descriptor) else Reflect.deleteProperty(dialogPrototype, name) } const PreOpenDialog = () => { useLayoutEffect(() => { document.querySelector('[data-testid="viewer"]')?.setAttribute('open', '') }, []) return null } afterEach(() => { restoreDialogMethod('close', originalDialogDescriptors.close) restoreDialogMethod('showModal', originalDialogDescriptors.showModal) vi.restoreAllMocks() }) describe('ViewerShell', () => { it('drives open state, chrome content, and forwarded attributes', () => { const { rerender } = renderWithProviders( Action} ariaLabel="report.pdf" data-testid="viewer" > content ) const dialog = screen.getByTestId('viewer') expect(dialog).toHaveAttribute('open') expect(dialog).toHaveAttribute('aria-label', 'report.pdf') expect(dialog).toHaveTextContent('2 / 3') expect(screen.getByRole('button', { name: 'Action' })).toBeInTheDocument() expect(screen.getByRole('button', { name: 'Close viewer' })).toHaveClass( VIEWER_ACTION_CLASS ) rerender( content ) expect(dialog).not.toHaveAttribute('open') }) it('focuses close on open and restores the previous focus on close', () => { const onClose = vi.fn() const previouslyFocused = document.createElement('button') document.body.appendChild(previouslyFocused) previouslyFocused.focus() const { rerender } = renderWithProviders( content ) rerender( content ) expect(screen.getByRole('button', { name: 'Close viewer' })).toHaveFocus() rerender( content ) expect(previouslyFocused).toHaveFocus() previouslyFocused.remove() }) it('falls back to the open attribute when dialog methods are absent', () => { Reflect.deleteProperty(dialogPrototype, 'showModal') Reflect.deleteProperty(dialogPrototype, 'close') const { rerender } = renderWithProviders( content ) const dialog = screen.getByTestId('viewer') expect(dialog).toHaveAttribute('open') rerender( content ) expect(dialog).not.toHaveAttribute('open') }) it('falls back when showModal throws', () => { const showModal = vi.fn(() => { throw new Error('not supported') }) defineDialogMethod('showModal', showModal) const { container } = renderWithProviders( content ) expect(container.querySelector('dialog')).toHaveAttribute('open') expect(showModal).toHaveBeenCalledOnce() }) it('falls back when close throws during cleanup', () => { const close = vi.fn(() => { throw new Error('not supported') }) defineDialogMethod('close', close) Reflect.deleteProperty(dialogPrototype, 'showModal') const { rerender } = renderWithProviders( content ) const dialog = screen.getByTestId('viewer') rerender( content ) expect(close).toHaveBeenCalledOnce() expect(dialog).not.toHaveAttribute('open') }) it('falls back when close throws in the closed branch', () => { const close = vi.fn(() => { throw new Error('not supported') }) defineDialogMethod('close', close) renderWithProviders( ) expect(close).toHaveBeenCalledOnce() expect(screen.getByTestId('viewer')).not.toHaveAttribute('open') }) it('does not call showModal when the dialog is already open', () => { const showModal = vi.fn() defineDialogMethod('showModal', showModal) renderWithProviders( ) expect(screen.getByTestId('viewer')).toHaveAttribute('open') expect(showModal).not.toHaveBeenCalled() }) it('does not restore focus to a detached element', () => { const previouslyFocused = document.createElement('button') document.body.appendChild(previouslyFocused) previouslyFocused.focus() const { rerender } = renderWithProviders( content ) rerender( content ) previouslyFocused.remove() expect(() => rerender( content ) ).not.toThrow() expect(document.activeElement).not.toBe(previouslyFocused) }) it('uses the default accessible label', () => { renderWithProviders( content ) expect(screen.getByTestId('viewer')).toHaveAttribute( 'aria-label', 'Attachment viewer' ) }) it('does not close a dialog that was never opened', () => { const close = vi.fn() defineDialogMethod('close', close) renderWithProviders( content ) expect(close).not.toHaveBeenCalled() }) it('closes from the close, native close, and backdrop events', () => { const onClose = vi.fn() renderWithProviders( content ) const dialog = screen.getByTestId('viewer') fireEvent.click(screen.getByRole('button', { name: 'Close viewer' })) fireEvent(dialog, new Event('close')) fireEvent.click(dialog) expect(onClose).toHaveBeenCalledTimes(3) onClose.mockClear() fireEvent.click(screen.getByText('content')) expect(onClose).not.toHaveBeenCalled() }) })