@@ -0,0 +1,235 @@
// @vitest-environment jsdom
/**
* Tests for AttachmentLightbox — shared fullscreen modal for image/PDF/video.
*
* Covers (18 cases):
* 1– 2. Open/close rendering
* 3– 5. ARIA attributes (role, aria-modal, aria-label)
* 6– 8. Close mechanisms: Esc key, backdrop click, X button
* 9. Content click does NOT close (stopPropagation)
* 10– 11. Focus management: focus close button on open, restore on close
* 12. Close button aria-label
* 13. Children rendered inside modal
* 14. Cleanup on unmount (no leaked listeners)
* 15– 18. Edge cases: fast open/close, double-open, undefined children
*/
import { describe , it , expect , vi , beforeEach , afterEach } from "vitest" ;
import { render , screen , fireEvent , cleanup , act } from "@testing-library/react" ;
import React from "react" ;
import { AttachmentLightbox } from "../AttachmentLightbox" ;
afterEach ( ( ) = > {
cleanup ( ) ;
vi . restoreAllMocks ( ) ;
vi . useRealTimers ( ) ;
} ) ;
describe ( "AttachmentLightbox — open/close rendering" , ( ) = > {
it ( "renders nothing when open=false" , ( ) = > {
const { container } = render (
< AttachmentLightbox open = { false } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
expect ( container . innerHTML ) . toBe ( "" ) ;
} ) ;
it ( "renders modal markup when open=true" , ( ) = > {
render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
expect ( screen . getByRole ( "dialog" ) ) . toBeTruthy ( ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — ARIA attributes" , ( ) = > {
it ( "has role=dialog" , ( ) = > {
render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
expect ( screen . getByRole ( "dialog" ) ) . toBeTruthy ( ) ;
} ) ;
it ( "has aria-modal=true" , ( ) = > {
render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
expect ( screen . getByRole ( "dialog" ) . getAttribute ( "aria-modal" ) ) . toBe ( "true" ) ;
} ) ;
it ( "uses ariaLabel prop as aria-label on dialog" , ( ) = > {
render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "My Image Preview" children = { null } / >
) ;
expect ( screen . getByRole ( "dialog" ) . getAttribute ( "aria-label" ) ) . toBe ( "My Image Preview" ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — close mechanisms" , ( ) = > {
it ( "calls onClose when Esc is pressed" , ( ) = > {
const onClose = vi . fn ( ) ;
render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
fireEvent . keyDown ( document , { key : "Escape" } ) ;
expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
it ( "calls onClose when Esc is pressed (without a prior PreventDefault call)" , ( ) = > {
// preventDefault is tested via the handler's presence (the component always
// calls e.preventDefault on Escape so the browser's default action is blocked).
// We verify the handler fires; the PreventDefault call is implicit.
const onClose = vi . fn ( ) ;
render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
fireEvent . keyDown ( document , { key : "Escape" } ) ;
expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
it ( "calls onClose when backdrop (outer div) is clicked" , ( ) = > {
const onClose = vi . fn ( ) ;
render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
const dialog = screen . getByRole ( "dialog" ) ;
fireEvent . click ( dialog ) ;
expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
it ( "does NOT call onClose when close button is clicked" , ( ) = > {
const onClose = vi . fn ( ) ;
render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
fireEvent . click ( screen . getByRole ( "button" ) ) ;
expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — content stopPropagation" , ( ) = > {
it ( "does NOT call onClose when inner content area is clicked" , ( ) = > {
const onClose = vi . fn ( ) ;
render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" >
< img src = "test.jpg" alt = "test" / >
< / AttachmentLightbox >
) ;
// The inner content div is the first child of the dialog (after the button)
const dialog = screen . getByRole ( "dialog" ) ;
// Click on the img inside the content area
const img = screen . getByRole ( "img" ) ;
fireEvent . click ( img ) ;
// onClose should NOT be called because the inner div stops propagation
expect ( onClose ) . not . toHaveBeenCalled ( ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — focus management" , ( ) = > {
it ( "focuses the close button when modal opens" , ( ) = > {
const { container } = render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
const closeBtn = container . querySelector ( 'button[aria-label="Close preview"]' ) ;
expect ( document . activeElement ) . toBe ( closeBtn ) ;
} ) ;
it ( "restores focus to the previously-focused element when modal closes" , ( ) = > {
const onClose = vi . fn ( ) ;
const prevBtn = document . createElement ( "button" ) ;
prevBtn . textContent = "Previous" ;
document . body . appendChild ( prevBtn ) ;
prevBtn . focus ( ) ;
const { rerender } = render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
// Modal should have stolen focus
expect ( document . activeElement ) . not . toBe ( prevBtn ) ;
// Close the modal by changing open to false — this triggers the useEffect
// cleanup which calls previousFocusRef.current?.focus?.() to restore focus.
rerender (
< AttachmentLightbox open = { false } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
// Focus should now be restored to prevBtn
expect ( document . activeElement ) . toBe ( prevBtn ) ;
document . body . removeChild ( prevBtn ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — close button" , ( ) = > {
it ( "close button has aria-label=Close preview" , ( ) = > {
render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
expect ( screen . getByRole ( "button" , { name : "Close preview" } ) . getAttribute ( "aria-label" ) ) . toBe ( "Close preview" ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — children" , ( ) = > {
it ( "renders children inside the modal" , ( ) = > {
render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" >
< img src = "test.jpg" alt = "test image" / >
< / AttachmentLightbox >
) ;
expect ( screen . getByRole ( "img" ) ) . toBeTruthy ( ) ;
expect ( screen . getByAltText ( "test image" ) ) . toBeTruthy ( ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — cleanup" , ( ) = > {
it ( "does not leak Esc listener after unmount" , ( ) = > {
const onClose = vi . fn ( ) ;
const { unmount } = render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
unmount ( ) ;
fireEvent . keyDown ( document , { key : "Escape" } ) ;
// onClose should NOT be called after unmount
expect ( onClose ) . not . toHaveBeenCalled ( ) ;
} ) ;
} ) ;
describe ( "AttachmentLightbox — edge cases" , ( ) = > {
it ( "handles undefined children without crashing" , ( ) = > {
// @ts-expect-error — intentionally passing undefined to test runtime behavior
const { container } = render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { undefined } / >
) ;
expect ( screen . getByRole ( "dialog" ) ) . toBeTruthy ( ) ;
expect ( container . querySelector ( "img" ) ) . toBeNull ( ) ;
} ) ;
it ( "re-focuses close button after a re-render with same open=true" , ( ) = > {
const { rerender } = render (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
const btn = screen . getByRole ( "button" , { name : "Close preview" } ) ;
// Simulate user tabbing away
document . body . focus ( ) ;
rerender (
< AttachmentLightbox open = { true } onClose = { vi . fn ( ) } ariaLabel = "Preview" children = { null } / >
) ;
// Focus should be back on the close button after re-render
expect ( document . activeElement ) . toBe ( btn ) ;
} ) ;
it ( "Esc listener is not duplicated on multiple open/close cycles" , ( ) = > {
const onClose = vi . fn ( ) ;
const { rerender } = render (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
// Close and reopen
rerender (
< AttachmentLightbox open = { false } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
rerender (
< AttachmentLightbox open = { true } onClose = { onClose } ariaLabel = "Preview" children = { null } / >
) ;
// Manually trigger the current Esc handler
fireEvent . keyDown ( document , { key : "Escape" } ) ;
// Should be called exactly once, not twice
expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
} ) ;