A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1import { describe, it, expect, afterEach } from 'vitest';
2import { act, createElement } from 'react';
3import { createRoot, type Root } from 'react-dom/client';
4import RecordJsonViewer from './RecordJsonViewer';
5
6// react-dom/client + act need this flag so React treats vitest's jsdom as a test env.
7( globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean } ).IS_REACT_ACT_ENVIRONMENT = true;
8
9const HTML =
10 '<code class="hljs language-json"><span class="hljs-attr">"title"</span>: <span class="hljs-string">"Hi"</span></code>';
11
12let container: HTMLDivElement;
13let root: Root;
14
15async function mount() {
16 container = document.createElement( 'div' );
17 document.body.appendChild( container );
18 root = createRoot( container );
19 await act( async () => {
20 root.render( createElement( RecordJsonViewer, { highlightedHtml: HTML } ) );
21 } );
22}
23
24afterEach( () => {
25 act( () => root.unmount() );
26 container.remove();
27} );
28
29const trigger = () => container.querySelector( '.record-json__trigger' ) as HTMLButtonElement;
30const dialog = () => document.querySelector( '[role="dialog"]' );
31
32describe( 'RecordJsonViewer interaction', () => {
33 it( 'renders a labelled trigger and no dialog initially', async () => {
34 await mount();
35 expect( trigger() ).not.toBeNull();
36 expect( trigger().getAttribute( 'aria-label' ) ).toBe( 'View record JSON' );
37 expect( dialog() ).toBeNull();
38 } );
39
40 it( 'opens the dialog with the highlighted JSON on click', async () => {
41 await mount();
42 await act( async () => {
43 trigger().click();
44 } );
45 expect( dialog() ).not.toBeNull();
46 expect( dialog()!.innerHTML ).toContain( 'hljs-string' );
47 } );
48
49 it( 'closes via the close button and restores focus to the trigger', async () => {
50 await mount();
51 await act( async () => {
52 trigger().click();
53 } );
54 const closeBtn = document.querySelector( '.record-json__close' ) as HTMLButtonElement;
55 await act( async () => {
56 closeBtn.click();
57 } );
58 expect( dialog() ).toBeNull();
59 expect( document.activeElement ).toBe( trigger() );
60 } );
61
62 it( 'closes when the backdrop is clicked', async () => {
63 await mount();
64 await act( async () => {
65 trigger().click();
66 } );
67 const backdrop = document.querySelector( '.record-json__backdrop' ) as HTMLElement;
68 await act( async () => {
69 backdrop.click();
70 } );
71 expect( dialog() ).toBeNull();
72 } );
73
74 it( 'closes on Escape', async () => {
75 await mount();
76 await act( async () => {
77 trigger().click();
78 } );
79 await act( async () => {
80 document.dispatchEvent( new KeyboardEvent( 'keydown', { key: 'Escape' } ) );
81 } );
82 expect( dialog() ).toBeNull();
83 } );
84} );