import { afterEach, describe, expect, it } from 'vitest'; import { mount } from './index'; const ACCORDION = `
Panel A
Panel B
`; function trigger(): HTMLButtonElement { return document.querySelector('[data-c42-accordion-trigger]')!; } function accordionRoot(): HTMLElement { return document.querySelector('[data-c42-accordion]')!; } /** Flush the microtask queue the MutationObserver drains on. */ function flush(): Promise { return new Promise((resolve) => setTimeout(resolve, 0)); } describe('mount', () => { afterEach(() => { document.body.innerHTML = ''; }); it('mounts an accordion from markup and it responds to clicks', () => { document.body.innerHTML = ACCORDION; const app = mount(); expect(accordionRoot().hasAttribute('data-c42-mounted')).toBe(true); expect(trigger().getAttribute('aria-expanded')).toBe('false'); trigger().click(); expect(trigger().getAttribute('aria-expanded')).toBe('true'); app.unmount(); }); it('unmount() destroys controllers and clears the mounted marker', () => { document.body.innerHTML = ACCORDION; const app = mount(); app.unmount(); expect(accordionRoot().hasAttribute('data-c42-mounted')).toBe(false); // Listeners are gone, so the trigger no longer toggles anything. trigger().click(); expect(trigger().getAttribute('aria-expanded')).toBe('false'); }); it('does not mount the same element twice', () => { document.body.innerHTML = ACCORDION; const first = mount(); // A second mount() over the same DOM must be a no-op (idempotent marker). const second = mount(); trigger().click(); expect(trigger().getAttribute('aria-expanded')).toBe('true'); // The second app tracked nothing, so its unmount leaves the controller live. second.unmount(); expect(accordionRoot().hasAttribute('data-c42-mounted')).toBe(true); first.unmount(); expect(accordionRoot().hasAttribute('data-c42-mounted')).toBe(false); }); it('mounts markup added after mount() via the MutationObserver', async () => { document.body.innerHTML = '
'; const app = mount(); const host = document.getElementById('host')!; host.innerHTML = ACCORDION; await flush(); expect(accordionRoot().hasAttribute('data-c42-mounted')).toBe(true); trigger().click(); expect(trigger().getAttribute('aria-expanded')).toBe('true'); app.unmount(); }); it('destroys controllers when their markup is removed', async () => { document.body.innerHTML = `
${ACCORDION}
`; const app = mount(); const host = document.getElementById('host')!; expect(accordionRoot().hasAttribute('data-c42-mounted')).toBe(true); host.remove(); await flush(); // The detached node's controller was torn down and untracked. expect(host.querySelector('[data-c42-accordion]')!.hasAttribute('data-c42-mounted')).toBe( false, ); app.unmount(); }); });