/** * KHAELOR * File: tests/tui/fuzzy.test.ts * Description: Fuzzy matcher tests — subsequence matching, ranking bonuses, stability, matched indices. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import { describe, expect, it } from "vitest"; import { fuzzyFilter, fuzzyMatch } from "../../src/tui/composer/fuzzy.js"; describe("fuzzyMatch", () => { it("matches subsequences case-insensitively", () => { expect(fuzzyMatch("agt", "src/kernel/Agent.ts")).not.toBeNull(); expect(fuzzyMatch("zzz", "src/kernel/agent.ts")).toBeNull(); }); it("returns matched indices for underlining", () => { const m = fuzzyMatch("se", "sessions"); expect(m?.indices).toEqual([0, 1]); }); it("empty query matches everything with score 0", () => { expect(fuzzyMatch("", "anything")).toEqual({ score: 0, indices: [] }); }); it("exact prefix outranks a scattered match", () => { const prefix = fuzzyMatch("diff", "diff viewer") as { score: number }; const scattered = fuzzyMatch("diff", "docs/if_frame.ts") as { score: number }; expect(prefix.score).toBeGreaterThan(scattered.score); }); it("boundary matches outrank mid-word matches", () => { const boundary = fuzzyMatch("ag", "src/agent.ts") as { score: number }; const midWord = fuzzyMatch("ag", "src/flagpole.ts") as { score: number }; expect(boundary.score).toBeGreaterThan(midWord.score); }); }); describe("fuzzyFilter", () => { const items = ["src/kernel/agent.ts", "src/agents/agent-runtime.ts", "tests/agent.test.ts"]; it("excludes non-matches and ranks by score", () => { const ranked = fuzzyFilter("agent", items, (s) => s); expect(ranked.length).toBe(3); expect(ranked.every((r) => r.score > 0)).toBe(true); }); it("shorter target wins ties (length penalty)", () => { const ranked = fuzzyFilter("cost", ["/cost-explorer", "/cost"], (s) => s); expect(ranked[0]?.item).toBe("/cost"); }); it("keeps provider order for equal scores (stable)", () => { const ranked = fuzzyFilter("", items, (s) => s); expect(ranked.map((r) => r.item)).toEqual(items); }); it("filters slash commands by fragment", () => { const cmds = ["/sessions", "/new", "/resume", "/cost"]; const ranked = fuzzyFilter("se", cmds, (s) => s); expect(ranked.map((r) => r.item)).toContain("/sessions"); expect(ranked.map((r) => r.item)).not.toContain("/cost"); }); });