返回 CodeWhale
search-utils.test.ts
根目录 / web / lib / search-utils.test.ts
1 import { describe, it, expect } from "vitest";
2 import {
3 docTopicHaystack,
4 filterDocTopics,
5 normalizeQuery,
6 matches,
7 } from "./search-utils";
8 import { DOC_TOPICS } from "./docs-map";
9
10 describe("normalizeQuery", () => {
11 it("trims and lowercases", () => {
12 expect(normalizeQuery(" Hello World ")).toBe("hello world");
13 });
14
15 it("returns empty string for whitespace-only input", () => {
16 expect(normalizeQuery(" ")).toBe("");
17 });
18 });
19
20 describe("matches", () => {
21 it("returns true for empty query (shows everything)", () => {
22 expect(matches("anything", "")).toBe(true);
23 expect(matches("anything", " ")).toBe(true);
24 });
25
26 it("does case-insensitive substring matching", () => {
27 expect(matches("Install Guide", "install")).toBe(true);
28 expect(matches("install guide", "INSTALL")).toBe(true);
29 expect(matches("Configuration", "config")).toBe(true);
30 });
31
32 it("returns false when no match", () => {
33 expect(matches("Install", "docker")).toBe(false);
34 });
35 });
36
37 describe("docTopicHaystack", () => {
38 it("includes the topic id and slug", () => {
39 const install = DOC_TOPICS.find((t) => t.id === "install")!;
40 const hay = docTopicHaystack(install);
41 expect(hay).toContain("install");
42 });
43
44 it("includes both EN and ZH labels", () => {
45 const mcp = DOC_TOPICS.find((t) => t.id === "mcp")!;
46 const hay = docTopicHaystack(mcp);
47 expect(hay).toContain("mcp");
48 // ZH label is also "MCP" but description has Chinese
49 expect(hay).toContain("stdio");
50 expect(hay).toContain("工具"); // tools in Chinese description
51 });
52
53 it("includes source file paths", () => {
54 const config = DOC_TOPICS.find((t) => t.id === "configuration")!;
55 const hay = docTopicHaystack(config);
56 expect(hay).toContain("docs/configuration.md");
57 expect(hay).toContain("docs/legacy_paths.md");
58 });
59
60 it("includes category name in both locales", () => {
61 const install = DOC_TOPICS.find((t) => t.id === "install")!;
62 const hay = docTopicHaystack(install);
63 expect(hay).toContain("getting-started");
64 expect(hay).toContain("入门"); // ZH for getting-started
65 });
66 });
67
68 describe("filterDocTopics", () => {
69 it("returns all indices when query is empty", () => {
70 const result = filterDocTopics(DOC_TOPICS, "");
71 expect(result.length).toBe(DOC_TOPICS.length);
72 });
73
74 it("returns all indices when query is whitespace", () => {
75 const result = filterDocTopics(DOC_TOPICS, " ");
76 expect(result.length).toBe(DOC_TOPICS.length);
77 });
78
79 it("filters by English keyword", () => {
80 const result = filterDocTopics(DOC_TOPICS, "install");
81 const ids = result.map((i) => DOC_TOPICS[i].id);
82 expect(ids).toContain("install");
83 expect(ids.length).toBeGreaterThanOrEqual(1);
84 });
85
86 it("filters by Chinese keyword", () => {
87 const result = filterDocTopics(DOC_TOPICS, "沙箱"); // sandbox
88 const ids = result.map((i) => DOC_TOPICS[i].id);
89 expect(ids).toContain("sandbox");
90 });
91
92 it("filters by source file name", () => {
93 const result = filterDocTopics(DOC_TOPICS, "configuration.md");
94 const ids = result.map((i) => DOC_TOPICS[i].id);
95 expect(ids).toContain("configuration");
96 });
97
98 it("filters by category", () => {
99 const result = filterDocTopics(DOC_TOPICS, "extending");
100 const ids = result.map((i) => DOC_TOPICS[i].id);
101 // extending category includes mcp, hooks, runtime-api
102 expect(ids).toContain("mcp");
103 expect(ids).toContain("hooks");
104 expect(ids).toContain("runtime-api");
105 });
106
107 it("is case-insensitive", () => {
108 const lower = filterDocTopics(DOC_TOPICS, "mcp");
109 const upper = filterDocTopics(DOC_TOPICS, "MCP");
110 expect(lower).toEqual(upper);
111 });
112
113 it("returns empty array for gibberish query", () => {
114 const result = filterDocTopics(DOC_TOPICS, "zzzzzzz_nonexistent");
115 expect(result).toEqual([]);
116 });
117
118 it("matches partial keywords", () => {
119 const result = filterDocTopics(DOC_TOPICS, "tool");
120 const ids = result.map((i) => DOC_TOPICS[i].id);
121 // "tool" should match "tools" topic (id contains "tool")
122 expect(ids).toContain("tools");
123 });
124
125 it("matches across locales (EN query matches ZH content)", () => {
126 // "安装" is the ZH label for "install"
127 const result = filterDocTopics(DOC_TOPICS, "安装");
128 const ids = result.map((i) => DOC_TOPICS[i].id);
129 expect(ids).toContain("install");
130 });
131 });
132
132 lines TYPESCRIPT