import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Skeleton } from './skeleton'; import { SKELETON_CHANGE_EVENT, type SkeletonChangeEvent } from './skeleton.types'; const MARKUP = `
`; function setup(): HTMLElement { document.body.innerHTML = MARKUP; return document.body.firstElementChild as HTMLElement; } function placeholder(root: HTMLElement): HTMLElement { return root.querySelector('[data-c42-skeleton-placeholder]')!; } function content(root: HTMLElement): HTMLElement { return root.querySelector('[data-c42-skeleton-content]')!; } describe('Skeleton', () => { beforeEach(() => { document.body.innerHTML = ''; }); it('shows the placeholder and hides content while loading', () => { const root = setup(); new Skeleton(root); expect(root.dataset.state).toBe('loading'); expect(root.getAttribute('aria-busy')).toBe('true'); expect(placeholder(root).hasAttribute('hidden')).toBe(false); expect(content(root).hasAttribute('hidden')).toBe(true); }); it('swaps to content when loading is turned off', () => { const root = setup(); const skeleton = new Skeleton(root); skeleton.setLoading(false); expect(root.dataset.state).toBe('ready'); expect(root.getAttribute('aria-busy')).toBe('false'); expect(placeholder(root).hasAttribute('hidden')).toBe(true); expect(content(root).hasAttribute('hidden')).toBe(false); }); it('can start in the ready state', () => { const root = setup(); const skeleton = new Skeleton(root, { loading: false }); expect(skeleton.loading).toBe(false); expect(content(root).hasAttribute('hidden')).toBe(false); }); it('throws when the placeholder is missing', () => { document.body.innerHTML = '
'; const root = document.body.firstElementChild as HTMLElement; expect(() => new Skeleton(root)).toThrow(/data-c42-skeleton-placeholder/); }); it('emits change events on setLoading', () => { const root = setup(); const skeleton = new Skeleton(root); const spy = vi.fn(); skeleton.on(SKELETON_CHANGE_EVENT, spy); skeleton.setLoading(false); expect(spy).toHaveBeenCalledOnce(); expect(spy.mock.calls[0][0].detail).toEqual({ loading: false }); }); it('is a no-op when setting the current state', () => { const root = setup(); const skeleton = new Skeleton(root); const spy = vi.fn(); skeleton.on(SKELETON_CHANGE_EVENT, spy); skeleton.setLoading(true); expect(spy).not.toHaveBeenCalled(); }); it('removes listeners on destroy', () => { const root = setup(); const skeleton = new Skeleton(root); const spy = vi.fn(); skeleton.on(SKELETON_CHANGE_EVENT, spy); skeleton.destroy(); skeleton.setLoading(false); expect(spy).not.toHaveBeenCalled(); }); });