import { describe, expect, it, vi } from 'vitest'; import { h, render, mount, getComponentKey, mountCallback } from 'lithent'; import { createBoundary } from '../createBoundary'; describe('HMR component lifecycle', () => { it('cleans up the previous mount exactly once before reusing its component key', async () => { const boundary = createBoundary('lifecycle'); const cleanup = vi.fn(); const mounted = vi.fn(); const Component = mount(() => { const unregister = boundary.register(getComponentKey()!); mountCallback(() => { mounted(); return () => { cleanup(); unregister(); }; }); return () => component; }); const container = document.createElement('div'); const dispose = render(, container); try { for (let i = 1; i <= 3; i++) { expect(boundary.update(Component)).toBe(true); await Promise.resolve(); expect(mounted).toHaveBeenCalledTimes(i + 1); expect(cleanup).toHaveBeenCalledTimes(i); expect(container.querySelectorAll('span')).toHaveLength(1); } dispose(); expect(cleanup).toHaveBeenCalledTimes(4); expect(container.innerHTML).toBe(''); expect(boundary.update(Component)).toBe(false); } finally { boundary.dispose(); } }); it('rerenders a direct caller while retaining its closure state and mount effects', async () => { const boundary = createBoundary('direct-caller'); const cleanup = vi.fn(); const mounted = vi.fn(); let label = 'original'; const Parent = mount(renew => { let count = 0; const unregister = boundary.register(getComponentKey()!, Parent); mountCallback(() => { mounted(); return () => { cleanup(); unregister(); }; }); return () => (
{label}
); }); const container = document.createElement('div'); const dispose = render(, container); try { container.querySelector('button')!.click(); await Promise.resolve(); expect(container.querySelector('button')!.textContent).toBe('1'); label = 'updated'; boundary.update(() => should not replace the parent); await Promise.resolve(); expect(container.querySelector('main span')!.textContent).toBe('updated'); expect(container.querySelector('button')!.textContent).toBe('1'); expect(mounted).toHaveBeenCalledTimes(1); expect(cleanup).not.toHaveBeenCalled(); boundary.dispose(); expect(cleanup).not.toHaveBeenCalled(); expect(container.querySelector('button')!.textContent).toBe('1'); } finally { dispose(); boundary.dispose(); } expect(cleanup).toHaveBeenCalledTimes(1); }); });