返回 DeepSeek-Reasonix
at-matches.test.ts
根目录 / desktop / frontend / src / __tests__ / at-matches.test.ts
1 // Run: tsx src/__tests__/at-matches.test.ts
2 //
3 // Regression coverage for the Composer @-menu filter (issue #3769).
4 // The frontend must mirror the backend fuzzy @-search contract:
5 // a fragment like "planind" should surface "src/planind/index.tsx"
6 // because the search results return full slash-normalized relative
7 // paths, not basenames.
8
9 import { filterAtMatches } from "../lib/atMatches";
10 import type { DirEntry } from "../lib/types";
11
12 let passed = 0;
13 let failed = 0;
14
15 function eq(a: unknown, b: unknown, label: string) {
16 if (JSON.stringify(a) === JSON.stringify(b)) {
17 process.stdout.write(` PASS ${label}\n`);
18 passed += 1;
19 } else {
20 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
21 failed += 1;
22 }
23 }
24
25 function entry(name: string, isDir = false): DirEntry {
26 return { name, isDir };
27 }
28
29 function externalEntry(path: string, displayName: string): DirEntry {
30 return { name: displayName, path, displayName, isDir: false };
31 }
32
33 console.log("\nat-matches filter");
34
35 // 1. Nested file surfaces when fragment matches an intermediate segment.
36 {
37 const entries: DirEntry[] = [];
38 const searchEntries = [entry("src/planind/index.tsx")];
39 const got = filterAtMatches(entries, searchEntries, "planind");
40 eq(
41 got.map((e) => e.name),
42 ["src/planind/index.tsx"],
43 "src/planind/index.tsx + planind → surfaces the nested file",
44 );
45 }
46
47 // 2. Basename hit is preserved (regression guard for the legacy v1 path).
48 {
49 const entries: DirEntry[] = [];
50 const searchEntries = [entry("src/planind/index.tsx")];
51 const got = filterAtMatches(entries, searchEntries, "index");
52 eq(
53 got.map((e) => e.name),
54 ["src/planind/index.tsx"],
55 "src/planind/index.tsx + index → still surfaces via basename",
56 );
57 }
58
59 // 3. Local ListDir hit and fuzzy Search hit with the same name are de-duped
60 // to a single entry, with the local one taking precedence (it appears first).
61 {
62 const entries = [entry("planind.go")];
63 const searchEntries = [entry("planind.go")];
64 const got = filterAtMatches(entries, searchEntries, "planind");
65 eq(
66 got.map((e) => e.name),
67 ["planind.go"],
68 "entries ∩ searchEntries with same name dedup to one",
69 );
70 }
71
72 // 4. Local ListDir entries with a matching fragment appear first; fuzzy hits
73 // fill the rest in the order the backend returned them.
74 {
75 const entries = [entry("planind.go"), entry("planind.md")];
76 const searchEntries = [entry("src/planind/index.tsx")];
77 const got = filterAtMatches(entries, searchEntries, "planind");
78 eq(
79 got.map((e) => e.name),
80 ["planind.go", "planind.md", "src/planind/index.tsx"],
81 "local entries precede fuzzy search hits, no dupes",
82 );
83 }
84
85 // 5. Empty fragment matches every entry because includes("") is true; this
86 // pins the current behavior so a future change that re-introduces
87 // basename-only matching or skips empty fragments is caught immediately.
88 {
89 const entries = [entry("a.ts"), entry("b.ts")];
90 const searchEntries = [entry("src/c.ts")];
91 const got = filterAtMatches(entries, searchEntries, "");
92 eq(
93 got.map((e) => e.name),
94 ["a.ts", "b.ts", "src/c.ts"],
95 "empty fragment includes every entry (legacy behavior preserved)",
96 );
97 }
98
99 // 6. External-folder search results can match against a friendly display name
100 // even when the submitted path is an opaque session token.
101 {
102 const entries: DirEntry[] = [];
103 const searchEntries = [externalEntry("__reasonix_external_folder/abc/Folder/src/outside.txt", "Folder With Spaces/src/outside.txt")];
104 const got = filterAtMatches(entries, searchEntries, "spaces");
105 eq(
106 got.map((e) => e.path),
107 ["__reasonix_external_folder/abc/Folder/src/outside.txt"],
108 "external display name participates in filtering while preserving token path",
109 );
110 }
111
112 // 7. Dedup uses the submitted path when present.
113 {
114 const entries = [externalEntry("__reasonix_external_folder/abc/Folder/src/outside.txt", "outside.txt")];
115 const searchEntries = [externalEntry("__reasonix_external_folder/abc/Folder/src/outside.txt", "Folder With Spaces/src/outside.txt")];
116 const got = filterAtMatches(entries, searchEntries, "outside");
117 eq(got.length, 1, "external entries with the same submitted path dedup to one");
118 }
119
120 console.log(`\n${passed} passed, ${failed} failed\n`);
121 process.exit(failed === 0 ? 0 : 1);
122
122 lines TYPESCRIPT