import { describe, expect, test } from 'vitest'; import { createSnippet, type Snippet } from './snippet'; import { filterAndSortSnippets, snippetMatchesQuery, snippetSortSize, sortSnippets, type SortState, } from './snippet-sort'; /** Build a snippet with the fields the sort/search cares about. */ function snip(over: Partial & { id: string }): Snippet { const base = createSnippet({ id: over.id, now: new Date('2026-01-01T00:00:00Z') }); return { ...base, ...over }; } describe('sortSnippets · fields', () => { const a = snip({ id: 'a', name: 'banana', created: '2026-01-03T00:00:00Z', modified: '2026-01-05T00:00:00Z', spec: '{"x":1}', }); const b = snip({ id: 'b', name: 'Apple', created: '2026-01-01T00:00:00Z', modified: '2026-01-09T00:00:00Z', spec: '{"longer":true,"value":2}', }); const c = snip({ id: 'c', name: 'cherry', created: '2026-01-08T00:00:00Z', modified: '2026-01-02T00:00:00Z', spec: '{}', }); const all = [a, b, c]; const order = (s: SortState) => sortSnippets(all, s).map((x) => x.id); test('modified desc / asc', () => { expect(order({ sortBy: 'modified', sortOrder: 'desc' })).toEqual(['b', 'a', 'c']); expect(order({ sortBy: 'modified', sortOrder: 'asc' })).toEqual(['c', 'a', 'b']); }); test('created desc / asc', () => { expect(order({ sortBy: 'created', sortOrder: 'desc' })).toEqual(['c', 'a', 'b']); expect(order({ sortBy: 'created', sortOrder: 'asc' })).toEqual(['b', 'a', 'c']); }); test('name asc / desc — case-insensitive (Apple sorts before banana)', () => { expect(order({ sortBy: 'name', sortOrder: 'asc' })).toEqual(['b', 'a', 'c']); expect(order({ sortBy: 'name', sortOrder: 'desc' })).toEqual(['c', 'a', 'b']); }); test('size sorts by published-spec character length', () => { expect(snippetSortSize(c)).toBeLessThan(snippetSortSize(a)); expect(snippetSortSize(a)).toBeLessThan(snippetSortSize(b)); expect(order({ sortBy: 'size', sortOrder: 'asc' })).toEqual(['c', 'a', 'b']); expect(order({ sortBy: 'size', sortOrder: 'desc' })).toEqual(['b', 'a', 'c']); }); test('does not mutate the input array', () => { const input = [a, b, c]; sortSnippets(input, { sortBy: 'name', sortOrder: 'asc' }); expect(input.map((x) => x.id)).toEqual(['a', 'b', 'c']); }); }); describe('sortSnippets · stable tiebreak', () => { test('equal keys break by id deterministically', () => { const ts = '2026-01-01T00:00:00Z'; const x = snip({ id: 'x', modified: ts }); const y = snip({ id: 'y', modified: ts }); const z = snip({ id: 'z', modified: ts }); // Whatever the input order, equal modified times resolve by id ascending. expect( sortSnippets([z, x, y], { sortBy: 'modified', sortOrder: 'asc' }).map((s) => s.id), ).toEqual(['x', 'y', 'z']); expect( sortSnippets([y, z, x], { sortBy: 'modified', sortOrder: 'asc' }).map((s) => s.id), ).toEqual(['x', 'y', 'z']); }); }); describe('snippetMatchesQuery', () => { const s = snip({ id: 'a', name: 'Sales Bar Chart', comment: 'Quarterly revenue', draftSpec: '{"mark":"bar","encoding":{"x":{"field":"category"}}}', }); test('matches across name, comment, and spec content — case-insensitive', () => { expect(snippetMatchesQuery(s, 'sales')).toBe(true); // name, lowercased query expect(snippetMatchesQuery(s, 'REVENUE')).toBe(true); // comment, uppercased query expect(snippetMatchesQuery(s, 'category')).toBe(true); // inside the spec text expect(snippetMatchesQuery(s, 'bar')).toBe(true); }); test('empty / whitespace query matches everything', () => { expect(snippetMatchesQuery(s, '')).toBe(true); expect(snippetMatchesQuery(s, ' ')).toBe(true); }); test('no match returns false', () => { expect(snippetMatchesQuery(s, 'pie')).toBe(false); }); }); describe('filterAndSortSnippets', () => { const a = snip({ id: 'a', name: 'Alpha', comment: '', draftSpec: '{}', modified: '2026-01-01T00:00:00Z', }); const b = snip({ id: 'b', name: 'Beta', comment: 'alpha note', draftSpec: '{}', modified: '2026-01-02T00:00:00Z', }); const c = snip({ id: 'c', name: 'Gamma', comment: '', draftSpec: '{}', modified: '2026-01-03T00:00:00Z', }); test('filters then sorts', () => { // "alpha" matches a (name) and b (comment); modified-desc puts b first. expect( filterAndSortSnippets([a, b, c], 'alpha', { sortBy: 'modified', sortOrder: 'desc' }).map( (x) => x.id, ), ).toEqual(['b', 'a']); }); test('empty query returns all, sorted', () => { expect( filterAndSortSnippets([a, b, c], '', { sortBy: 'modified', sortOrder: 'asc' }).map( (x) => x.id, ), ).toEqual(['a', 'b', 'c']); }); test('no matches returns an empty array', () => { expect(filterAndSortSnippets([a, b, c], 'zzz', { sortBy: 'name', sortOrder: 'asc' })).toEqual( [], ); }); });