mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
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<Snippet> & { 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(
|
|
[],
|
|
);
|
|
});
|
|
});
|