diff --git a/web/src/lib/components/AlphabeticalGrid.svelte b/web/src/lib/components/AlphabeticalGrid.svelte new file mode 100644 index 00000000..33f2c687 --- /dev/null +++ b/web/src/lib/components/AlphabeticalGrid.svelte @@ -0,0 +1,70 @@ + + +
+ {#each segments as seg, i (i)} + {#if seg.divider} +
+ {seg.divider} + +
+ {/if} +
+ {@render item(seg.item)} +
+ {/each} +
+ + diff --git a/web/src/lib/components/AlphabeticalGrid.test.ts b/web/src/lib/components/AlphabeticalGrid.test.ts new file mode 100644 index 00000000..c2822b8a --- /dev/null +++ b/web/src/lib/components/AlphabeticalGrid.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import { createRawSnippet } from 'svelte'; +import AlphabeticalGrid from './AlphabeticalGrid.svelte'; + +type Item = { id: string; key: string; label: string }; + +const items: Item[] = [ + { id: '1', key: 'A', label: 'Apple' }, + { id: '2', key: 'A', label: 'Avocado' }, + { id: '3', key: 'B', label: 'Banana' }, + { id: '4', key: 'D', label: 'Date' } // skips C — divider must jump to D directly +]; + +// createRawSnippet receives getters: each param is () => ParamType +const itemSnippet = createRawSnippet<[Item]>((getIt) => ({ + render: () => `${getIt().label}` +})); + +// The generic component's T parameter can't be inferred by testing-library's render, +// so we cast props to avoid a spurious unknown-assignability error. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyProps = any; + +describe('AlphabeticalGrid', () => { + test('inserts a divider for each new key letter', () => { + render(AlphabeticalGrid, { + props: { + items, + getKey: (it: Item) => it.key, + item: itemSnippet + } as AnyProps + }); + // Three dividers (A, B, D); C is absent because no items use C. + expect(screen.getByText('A')).toBeInTheDocument(); + expect(screen.getByText('B')).toBeInTheDocument(); + expect(screen.getByText('D')).toBeInTheDocument(); + expect(screen.queryByText('C')).not.toBeInTheDocument(); + }); + + test('renders items in given order', () => { + const { container } = render(AlphabeticalGrid, { + props: { + items, + getKey: (it: Item) => it.key, + item: itemSnippet + } as AnyProps + }); + expect(container.textContent).toMatch(/A.*Apple.*Avocado.*B.*Banana.*D.*Date/s); + }); +});