diff --git a/components/settings/index/searchFilter.test.ts b/components/settings/index/searchFilter.test.ts new file mode 100644 index 000000000..7dc42f3c6 --- /dev/null +++ b/components/settings/index/searchFilter.test.ts @@ -0,0 +1,22 @@ +import { expect, test } from "bun:test"; +import { matchesQuery, normalize } from "./searchFilter"; + +test("normalize strips accents and lowercases", () => { + expect(normalize("Légèreté")).toBe("legerete"); + expect(normalize(" AUDIO ")).toBe("audio"); +}); + +test("matchesQuery matches title case/accent-insensitively", () => { + expect(matchesQuery({ title: "Apparence", keywords: [] }, "appar")).toBe( + true, + ); + expect( + matchesQuery({ title: "Audio", keywords: ["sous-titres"] }, "SOUS"), + ).toBe(true); + expect(matchesQuery({ title: "Music", keywords: [] }, "xyz")).toBe(false); +}); + +test("matchesQuery returns true for empty query", () => { + expect(matchesQuery({ title: "Anything" }, "")).toBe(true); + expect(matchesQuery({ title: "Anything" }, " ")).toBe(true); +}); diff --git a/components/settings/index/searchFilter.ts b/components/settings/index/searchFilter.ts new file mode 100644 index 000000000..3b9f8ef92 --- /dev/null +++ b/components/settings/index/searchFilter.ts @@ -0,0 +1,14 @@ +export const normalize = (s: string): string => + s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim(); + +export interface Searchable { + title: string; + keywords?: string[]; +} + +export const matchesQuery = (item: Searchable, query: string): boolean => { + const q = normalize(query); + if (!q) return true; + const hay = normalize([item.title, ...(item.keywords ?? [])].join(" ")); + return hay.includes(q); +}; diff --git a/tsconfig.json b/tsconfig.json index 69354e8d0..d0e3d11b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,6 +39,8 @@ "node_modules", "babel.config.js", "metro.config.js", - "utils/jellyseerr/**/*" + "utils/jellyseerr/**/*", + "**/*.test.ts", + "**/*.test.tsx" ] }