Files
buzz-sheet/lib/search.test.ts
T
gunshiz 92b57e0ed3
CI / Verify (push) Failing after 58s
CI / Build immutable images and deploy (push) Has been skipped
feat : add fuzzy matching for searching
2026-09-12 16:07:16 +07:00

59 lines
2.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { searchItems } from "./search";
const items = [
{ name: "Mualani", aliases: ["Shark Surfer"], overview: "A hydro guide", key: "internal" },
{ name: "Obsidian Codex", aliases: ["Codex"], overview: "An artifact", key: "obsidian" },
];
const search = (query: string) => searchItems(items, query,
(item) => `${item.name} ${item.aliases.join(" ")} ${item.overview} ${item.key}`,
(item) => [item.name, ...item.aliases]);
const names = (items: string[], query: string) => searchItems(items, query, (item) => item, (item) => [item]);
describe("fuzzy search fallback", () => {
it("preserves direct substring matches, case, whitespace, and empty queries", () => {
expect(search(" MUALA ")).toEqual({ items: [items[0]], fuzzy: false });
expect(search(" ")).toEqual({ items, fuzzy: false });
expect(search("hydro").items).toEqual([items[0]]);
});
it("does not append typo matches when another item matches directly", () => {
expect(names(["Mualani", "Mulani Guide"], "mulani")).toEqual({ items: ["Mulani Guide"], fuzzy: false });
});
it.each(["mulani", "muallani", "mualoni", "mualnai"])("finds missing, extra, substituted, or swapped characters: %s", (query) => {
expect(search(query)).toEqual({ items: [items[0]], fuzzy: true });
});
it("matches aliases and individual words in multiword names", () => {
expect(search("surfre")).toEqual({ items: [items[0]], fuzzy: true });
expect(search("codxe")).toEqual({ items: [items[1]], fuzzy: true });
expect(search("obsidain codex")).toEqual({ items: [items[1]], fuzzy: true });
});
it("does not fuzzy match overview prose or internal keys", () => {
expect(search("hydor")).toEqual({ items: [], fuzzy: false });
expect(search("interanl")).toEqual({ items: [], fuzzy: false });
});
it("keeps short queries strict and enforces edit limits", () => {
expect(names(["Jean"], "jen")).toEqual({ items: [], fuzzy: false });
expect(names(["Mualani"], "muloni").items).toEqual([]);
expect(names(["abcdefgh"], "abxdxfgh")).toEqual({ items: ["abcdefgh"], fuzzy: true });
expect(names(["abcdefgh"], "abxdxygh").items).toEqual([]);
expect(search("unrelated").items).toEqual([]);
});
it("normalizes composed Unicode and handles Thai code points", () => {
expect(names(["Café"], "Cafe\u0301")).toEqual({ items: ["Café"], fuzzy: false });
expect(names(["ตัวละคร"], "ตัวลคร")).toEqual({ items: ["ตัวละคร"], fuzzy: true });
expect(names(["😀abcd"], "😀abdc")).toEqual({ items: ["😀abcd"], fuzzy: true });
});
it("ranks by distance with stable ties and retains item identities", () => {
expect(names(["abxdxfgh", "abcxefgh", "abcdxfgh"], "abcdefgh").items)
.toEqual(["abcxefgh", "abcdxfgh", "abxdxfgh"]);
expect(search("mulani").items[0]).toBe(items[0]);
});
});