1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 insertRankedSearchResult,
5 normalizeSearchQuery,
6 scoreQueryMatch,
7 scoreSubsequenceMatch,
8} from "./searchRanking.ts";
9
10describe("normalizeSearchQuery", () => {
11 it("trims and lowercases queries", () => {
12 expect(normalizeSearchQuery(" UI ")).toBe("ui");
13 });
14
15 it("can strip leading trigger characters", () => {
16 expect(normalizeSearchQuery(" $ui", { trimLeadingPattern: /^\$+/ })).toBe("ui");
17 });
18});
19
20describe("scoreQueryMatch", () => {
21 it("prefers exact matches over broader contains matches", () => {
22 expect(
23 scoreQueryMatch({
24 value: "ui",
25 query: "ui",
26 exactBase: 0,
27 prefixBase: 10,
28 includesBase: 20,
29 }),
30 ).toBe(0);
31
32 expect(
33 scoreQueryMatch({
34 value: "building native ui",
35 query: "ui",
36 exactBase: 0,
37 prefixBase: 10,
38 boundaryBase: 20,
39 includesBase: 30,
40 }),
41 ).toBeGreaterThan(0);
42 });
43
44 it("treats boundary matches as stronger than generic contains matches", () => {
45 const boundaryScore = scoreQueryMatch({
46 value: "gh-fix-ci",
47 query: "fix",
48 exactBase: 0,
49 prefixBase: 10,
50 boundaryBase: 20,
51 includesBase: 30,
52 boundaryMarkers: ["-"],
53 });
54 const containsScore = scoreQueryMatch({
55 value: "highfixci",
56 query: "fix",
57 exactBase: 0,
58 prefixBase: 10,
59 boundaryBase: 20,
60 includesBase: 30,
61 boundaryMarkers: ["-"],
62 });
63
64 expect(boundaryScore).not.toBeNull();
65 expect(containsScore).not.toBeNull();
66 expect(boundaryScore!).toBeLessThan(containsScore!);
67 });
68});
69
70describe("scoreSubsequenceMatch", () => {
71 it("scores tighter subsequences ahead of looser ones", () => {
72 const compact = scoreSubsequenceMatch("ghfixci", "gfc");
73 const spread = scoreSubsequenceMatch("github-fix-ci", "gfc");
74
75 expect(compact).not.toBeNull();
76 expect(spread).not.toBeNull();
77 expect(compact!).toBeLessThan(spread!);
78 });
79});
80
81describe("insertRankedSearchResult", () => {
82 it("keeps the best-ranked candidates within the limit", () => {
83 const ranked = [
84 { item: "b", score: 20, tieBreaker: "b" },
85 { item: "d", score: 40, tieBreaker: "d" },
86 ];
87
88 insertRankedSearchResult(ranked, { item: "a", score: 10, tieBreaker: "a" }, 2);
89 insertRankedSearchResult(ranked, { item: "c", score: 30, tieBreaker: "c" }, 2);
90
91 expect(ranked.map((entry) => entry.item)).toEqual(["a", "b"]);
92 });
93});