import { beforeEach, describe, expect, it, vi } from 'vitest'; import { DataView } from './data-view'; import { DATA_VIEW_CHANGE_EVENT } from './data-view.types'; const ROWS = [ { name: 'charlie', age: '30' }, { name: 'alice', age: '25' }, { name: 'bob', age: '40' }, { name: 'dave', age: '20' }, ]; function markup(): string { const rows = ROWS.map( (r) => `
${r.name}
`, ).join(''); return `
${rows}
`; } function setup(): HTMLElement { document.body.innerHTML = markup(); return document.body.firstElementChild as HTMLElement; } function visibleNames(root: HTMLElement): string[] { return Array.from(root.querySelectorAll('[data-c42-dataview-row]')) .filter((r) => !r.hasAttribute('hidden')) .map((r) => r.getAttribute('data-field-name')!); } describe('DataView', () => { beforeEach(() => { document.body.innerHTML = ''; }); it('paginates rows to the page size', () => { const root = setup(); new DataView(root, { pageSize: 2 }); expect(visibleNames(root)).toHaveLength(2); expect(root.querySelector('[data-c42-dataview-pages]')!.textContent).toBe('2'); expect(root.querySelector('[data-c42-dataview-info]')!.textContent).toBe('1-2 of 4'); }); it('disables prev on the first page and next on the last', () => { const root = setup(); const dv = new DataView(root, { pageSize: 2 }); expect((root.querySelector('[data-c42-dataview-prev]') as HTMLButtonElement).disabled).toBe(true); dv.setPage(2); expect((root.querySelector('[data-c42-dataview-next]') as HTMLButtonElement).disabled).toBe(true); }); it('navigates pages with next/prev buttons', () => { const root = setup(); new DataView(root, { pageSize: 2 }); root.querySelector('[data-c42-dataview-next]')!.click(); expect(root.querySelector('[data-c42-dataview-page]')!.textContent).toBe('2'); expect(root.querySelector('[data-c42-dataview-info]')!.textContent).toBe('3-4 of 4'); }); it('filters rows by search and resets to page 1', () => { const root = setup(); const dv = new DataView(root, { pageSize: 2 }); dv.setPage(2); const input = root.querySelector('[data-c42-dataview-search]')!; input.value = 'a'; input.dispatchEvent(new Event('input', { bubbles: true })); // names containing "a": charlie, alice, dave expect(visibleNames(root).length).toBeGreaterThan(0); expect(dv.getState().page).toBe(1); expect(dv.getState().query).toBe('a'); }); it('sorts ascending then descending on header click', () => { const root = setup(); new DataView(root, { pageSize: 10 }); const ageBtn = root.querySelector('[data-c42-dataview-sort-field="age"]')!; ageBtn.click(); // asc by age expect(visibleNames(root)).toEqual(['dave', 'alice', 'charlie', 'bob']); expect(ageBtn.getAttribute('aria-sort')).toBe('ascending'); ageBtn.click(); // desc expect(visibleNames(root)).toEqual(['bob', 'charlie', 'alice', 'dave']); expect(ageBtn.getAttribute('aria-sort')).toBe('descending'); }); it('sorts strings alphabetically', () => { const root = setup(); const dv = new DataView(root, { pageSize: 10 }); dv.setSort('name', 'asc'); expect(visibleNames(root)).toEqual(['alice', 'bob', 'charlie', 'dave']); }); it('emits change with the full state', () => { const root = setup(); const dv = new DataView(root, { pageSize: 2 }); const spy = vi.fn(); dv.on(DATA_VIEW_CHANGE_EVENT, spy); dv.setPage(2); const detail = (spy.mock.calls[0][0] as CustomEvent).detail; expect(detail).toMatchObject({ page: 2, pageSize: 2, total: 4, totalPages: 2 }); }); it('supports server-driven mode with a manual total and no rows', () => { document.body.innerHTML = `
`; const root = document.body.firstElementChild as HTMLElement; const dv = new DataView(root, { pageSize: 10, total: 35 }); expect(dv.getState().totalPages).toBe(4); dv.setPage(3); expect(dv.getState().page).toBe(3); }); it('honors defaultSort and defaultQuery', () => { const root = setup(); const dv = new DataView(root, { pageSize: 10, defaultSort: 'name:desc', defaultQuery: 'a' }); expect(dv.getState().sortField).toBe('name'); expect(dv.getState().sortOrder).toBe('desc'); expect(dv.getState().query).toBe('a'); }); it('stops reacting after destroy', () => { const root = setup(); const dv = new DataView(root, { pageSize: 2 }); dv.destroy(); root.querySelector('[data-c42-dataview-next]')!.click(); expect(dv.getState().page).toBe(1); }); });