import { describe, expect, test } from "vitest"; import { fuzzyFilter, fuzzyMatch } from "../src/utils/fuzzy.js"; describe("fuzzyMatch", () => { test("empty query matches everything with score 0", () => { const result = fuzzyMatch("", "anything"); expect(result.matches).toBe(true); expect(result.score).toBe(0); }); test("query longer than text does not match", () => { const result = fuzzyMatch("longquery", "short"); expect(result.matches).toBe(false); }); test("exact match has good score", () => { const result = fuzzyMatch("test", "test"); expect(result.matches).toBe(true); expect(result.score).toBeLessThan(0); // Should be negative due to consecutive bonuses }); test("characters must appear in order", () => { const matchInOrder = fuzzyMatch("abc", "aXbXc"); expect(matchInOrder.matches).toBe(true); const matchOutOfOrder = fuzzyMatch("abc", "cba"); expect(matchOutOfOrder.matches).toBe(false); }); test("case insensitive matching", () => { const result = fuzzyMatch("ABC", "abc"); expect(result.matches).toBe(true); const result2 = fuzzyMatch("abc", "ABC"); expect(result2.matches).toBe(true); }); test("consecutive matches score better than scattered matches", () => { const consecutive = fuzzyMatch("foo", "foobar"); const scattered = fuzzyMatch("foo", "f_o_o_bar"); expect(consecutive.matches).toBe(true); expect(scattered.matches).toBe(true); expect(consecutive.score).toBeLessThan(scattered.score); }); test("word boundary matches score better", () => { const atBoundary = fuzzyMatch("fb", "foo-bar"); const notAtBoundary = fuzzyMatch("fb", "afbx"); expect(atBoundary.matches).toBe(true); expect(notAtBoundary.matches).toBe(true); expect(atBoundary.score).toBeLessThan(notAtBoundary.score); }); }); describe("fuzzyFilter", () => { test("empty query returns all items unchanged", () => { const items = ["apple", "banana", "cherry"]; const result = fuzzyFilter(items, "", (x) => x); expect(result).toEqual(items); }); test("filters out non-matching items", () => { const items = ["apple", "banana", "cherry"]; const result = fuzzyFilter(items, "an", (x) => x); expect(result).toContain("banana"); expect(result).not.toContain("apple"); expect(result).not.toContain("cherry"); }); test("sorts results by match quality", () => { const items = ["a_p_p", "app", "application"]; const result = fuzzyFilter(items, "app", (x) => x); // "app" should be first (exact consecutive match at start) expect(result[0]).toBe("app"); }); test("works with custom getText function", () => { const items = [ { name: "foo", id: 1 }, { name: "bar", id: 2 }, { name: "foobar", id: 3 }, ]; const result = fuzzyFilter(items, "foo", (item) => item.name); expect(result.length).toBe(2); expect(result.map((r) => r.name)).toContain("foo"); expect(result.map((r) => r.name)).toContain("foobar"); }); });