Files
caldav/web/dav/useFilter.test.ts
T
pastilhasandClaude Opus 5 b578c2aa3b 2026.08.27.03 — a filter on both screens
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
2026-08-27 14:20:50 +00:00

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);
});
});