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();
});
});