import { afterEach, describe, expect, it, vi } from 'vitest'; import { cleanup, fireEvent, render } from '@testing-library/react'; import { Accordion as AccordionController, type AccordionOptions } from '@42/core/accordion'; import { useC42 } from './useC42'; import { Accordion } from './components/Accordion'; /** * Markup the headless `Accordion` controller enhances. Mirrors the structure * the controller expects: a root with items, each holding a trigger + panel. */ function AccordionHarness({ options }: { options?: AccordionOptions }) { const ref = useC42(AccordionController, options); return (
Panel A
Panel B
); } afterEach(() => { cleanup(); vi.restoreAllMocks(); }); describe('useC42', () => { it('instantiates the controller and the markup responds to interaction', () => { const { container } = render(); const trigger = container.querySelector('[data-c42-accordion-trigger]')!; // The controller ran inside useEffect: it added ARIA wiring to the markup. expect(trigger.getAttribute('aria-expanded')).toBe('false'); // Clicking the enhanced trigger toggles the panel — proof the live instance // is bound to the DOM, not just the static React output. fireEvent.click(trigger); expect(trigger.getAttribute('aria-expanded')).toBe('true'); }); it('calls destroy() when the component unmounts', () => { const destroySpy = vi.spyOn(AccordionController.prototype, 'destroy'); const { unmount } = render(); expect(destroySpy).not.toHaveBeenCalled(); unmount(); expect(destroySpy).toHaveBeenCalledTimes(1); }); it('tears down listeners so the detached controller no longer reacts', () => { const { container, unmount } = render(); const trigger = container.querySelector('[data-c42-accordion-trigger]')!; // Keep a reference to the node so we can probe it after React detaches it. const detached = trigger; unmount(); fireEvent.click(detached); // destroy() removed the click listener, so state is frozen at its last value. expect(detached.getAttribute('aria-expanded')).toBe('false'); }); }); describe(' generated wrapper', () => { it('re-emits the accordion:change DOM event as the onChange callback', () => { const onChange = vi.fn(); const { container } = render(
Panel A
Panel B
, ); const triggers = container.querySelectorAll( '[data-c42-accordion-trigger]', ); fireEvent.click(triggers[0]!); expect(onChange).toHaveBeenCalledTimes(1); // The wrapper unwraps event.detail and forwards (detail, event). const [detail, event] = onChange.mock.calls[0]!; expect(detail).toEqual({ value: ['a'] }); expect(event).toBeInstanceOf(CustomEvent); expect(event.type).toBe('accordion:change'); }); });