a calendar of a few hundred events and an address book of a few hundred cards are both lists you scroll until you give up. one box on each header narrows them. CLIENT SIDE, because the data is already here: both screens fetch a whole collection in one request and hold it, so filtering is a string comparison over what is in hand. a round trip per keystroke would be slower, would need a search endpoint on the sidecar, and would put a spinner where a person expects a list to narrow as they type. ACCENT-INSENSITIVE, and that is the half that decides whether this is usable here: these contacts are portuguese, and a box where "jose" does not find "José" is a box nobody uses twice. NFD splits the letter from its accent and the range strips what is left, so it matches in both directions — "josé" finds "Jose" too. every term must match somewhere, not all in one field, so "ana lisboa" finds the ana whose address is in lisbon. events match on their start as well, which is an ISO string, so "2026-08-29" narrows to a day without anything having to parse it. the count in the header becomes "12 of 340" while filtering, and a filter that matches nothing says so by name — otherwise it reads as a calendar that lost its events. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
36 lines
1.3 KiB
TypeScript
36 lines
1.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { matches } from './useFilter';
|
|
|
|
// The accent rule is the one that matters on this machine: an address book of Portuguese names is
|
|
// unsearchable if "jose" does not find "José".
|
|
|
|
describe('matches', () => {
|
|
test('an empty query keeps everything', () => {
|
|
expect(matches('', 'anything')).toBe(true);
|
|
expect(matches(' ', 'anything')).toBe(true);
|
|
});
|
|
|
|
test('is case insensitive', () => {
|
|
expect(matches('RAMIRO', 'Cervejaria Ramiro')).toBe(true);
|
|
});
|
|
|
|
test('ignores accents in BOTH directions', () => {
|
|
expect(matches('jose', 'José Conceição')).toBe(true);
|
|
expect(matches('josé', 'Jose Conceicao')).toBe(true);
|
|
expect(matches('conceicao', 'José Conceição')).toBe(true);
|
|
});
|
|
|
|
test('every term must match, but they may land in different fields', () => {
|
|
expect(matches('ana lisboa', 'Ana Silva', 'Lisboa')).toBe(true);
|
|
expect(matches('ana porto', 'Ana Silva', 'Lisboa')).toBe(false);
|
|
});
|
|
|
|
test('skips absent fields rather than matching on "undefined"', () => {
|
|
expect(matches('undefined', 'Ana Silva', undefined)).toBe(false);
|
|
});
|
|
|
|
test('a date is searchable because start is an ISO string', () => {
|
|
expect(matches('2026-08-29', 'Dinner', undefined, undefined, '2026-08-29T19:00:00Z')).toBe(true);
|
|
});
|
|
});
|