import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { renderToString } from 'react-dom/server'; import { Accordion as AccordionController } from '@42/core/accordion'; import { Accordion } from './components/Accordion'; /** * SSR-safety: `useC42` defers all DOM work to `useEffect`, which never runs on * the server. Rendering an adapter component with `react-dom/server` must: * 1. not throw, * 2. never construct the controller (no DOM access), and * 3. produce static markup without any of the controller's runtime mutations * (ARIA attributes, `data-state`, wrapped panel content). */ describe('react-dom/server SSR safety', () => { beforeEach(() => { // Reset jsdom DOM so we can assert the render touched nothing. document.body.innerHTML = ''; }); afterEach(() => { vi.restoreAllMocks(); }); it('does not construct the controller during server render', () => { const ctor = vi.spyOn(AccordionController.prototype, 'destroy'); let html = ''; expect(() => { html = renderToString(
Panel A
, ); }).not.toThrow(); // Static markup is present... expect(html).toContain('data-c42-accordion'); expect(html).toContain('data-c42-accordion-trigger'); // ...but none of the controller's runtime DOM mutations are. expect(html).not.toContain('aria-expanded'); expect(html).not.toContain('data-state'); expect(html).not.toContain('data-c42-accordion-panel-content'); // The controller's lifecycle never ran (destroy belongs to a live instance). expect(ctor).not.toHaveBeenCalled(); }); it('does not touch the document while rendering on the server', () => { expect(document.body.childElementCount).toBe(0); renderToString(
Panel A
, ); // renderToString returns a string; it must not mount anything into the DOM. expect(document.body.childElementCount).toBe(0); }); });