feat : add fuzzy matching for searching
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
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]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user