diff options
Diffstat (limited to 'src/lib/shared')
| -rw-r--r-- | src/lib/shared/filter-items.test.ts | 53 | ||||
| -rw-r--r-- | src/lib/shared/filter-items.ts | 16 |
2 files changed, 69 insertions, 0 deletions
diff --git a/src/lib/shared/filter-items.test.ts b/src/lib/shared/filter-items.test.ts new file mode 100644 index 0000000..3477cd1 --- /dev/null +++ b/src/lib/shared/filter-items.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { filterByName } from './filter-items'; + +const routines = [ + { id: 1, name: 'Cuisine/Repas' }, + { id: 2, name: 'Cuisine/Vaisselle' }, + { id: 3, name: 'Salle de bain/Douche' }, + { id: 4, name: 'Étirements' }, + { id: 5, name: 'Course' }, +]; + +describe('filterByName', () => { + it('filters by a namespace query like "Cuisine/"', () => { + const res = filterByName(routines, 'Cuisine/'); + expect(res.map((r) => r.name)).toEqual([ + 'Cuisine/Repas', + 'Cuisine/Vaisselle', + ]); + }); + + it('matches a substring anywhere in the name', () => { + const res = filterByName(routines, 'epas'); + expect(res.map((r) => r.name)).toEqual(['Cuisine/Repas']); + }); + + it('is case-insensitive', () => { + expect(filterByName(routines, 'cUiSiNe/')).toHaveLength(2); + }); + + it('folds accents on both sides', () => { + expect(filterByName(routines, 'etirements').map((r) => r.name)).toEqual([ + 'Étirements', + ]); + expect(filterByName(routines, 'Étirements')).toHaveLength(1); + }); + + it('returns everything for an empty or whitespace query, order preserved', () => { + expect(filterByName(routines, '')).toEqual(routines); + expect(filterByName(routines, ' ')).toEqual(routines); + }); + + it('returns nothing when no name matches', () => { + expect(filterByName(routines, 'garage')).toEqual([]); + }); + + it('has no result cap', () => { + const many = Array.from({ length: 40 }, (_, i) => ({ + id: i, + name: `Cuisine/Tâche ${i}`, + })); + expect(filterByName(many, 'Cuisine/')).toHaveLength(40); + }); +}); diff --git a/src/lib/shared/filter-items.ts b/src/lib/shared/filter-items.ts new file mode 100644 index 0000000..e7e103b --- /dev/null +++ b/src/lib/shared/filter-items.ts @@ -0,0 +1,16 @@ +/** Lowercase and strip diacritics so "etirements" matches "Étirements". */ +function fold(value: string): string { + return value.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase(); +} + +/** Filter `items` to those whose `name` contains `query` as a literal + * substring, case- and accent-insensitive. Order is preserved and no result + * cap applies. Empty/whitespace query returns everything. */ +export function filterByName<T extends { name: string }>( + items: T[], + query: string, +): T[] { + const q = fold(query.trim()); + if (q === '') return items; + return items.filter((item) => fold(item.name).includes(q)); +} |
