1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 claimWorkspaceBasenameLookup,
5 pickProjectLinkMatch,
6 pickWorkspaceBasenameMatch,
7 pickWorkspaceLinkMatch,
8 workspaceLinkLookupQuery,
9} from "@t3tools/client-runtime/workspace-basename-lookup";
10
11describe("pickWorkspaceBasenameMatch", () => {
12 const entries = [
13 { path: "apps/web/src/components/ChatView.test.tsx", kind: "file" as const },
14 { path: "apps/web/src/components/ChatView.tsx", kind: "file" as const },
15 ];
16
17 it("takes the first exact filename match, not the closest fuzzy one", () => {
18 expect(pickWorkspaceBasenameMatch("ChatView.tsx", entries)).toBe(
19 "apps/web/src/components/ChatView.tsx",
20 );
21 });
22
23 // regression: #6297 a bare filename resolves to a file, never a same-named directory
24 it("ignores directories", () => {
25 expect(
26 pickWorkspaceBasenameMatch("components", [
27 { path: "apps/web/src/components", kind: "directory" },
28 { path: "apps/web/src/components/components", kind: "file" },
29 ]),
30 ).toBe("apps/web/src/components/components");
31 });
32
33 it("prefers the exactly-cased file over a case-only twin", () => {
34 expect(
35 pickWorkspaceBasenameMatch("foo.ts", [
36 { path: "src/Foo.ts", kind: "file" },
37 { path: "src/foo.ts", kind: "file" },
38 ]),
39 ).toBe("src/foo.ts");
40 });
41
42 it("falls back to case-insensitive when only the casing differs", () => {
43 expect(pickWorkspaceBasenameMatch("chatview.tsx", entries)).toBe(
44 "apps/web/src/components/ChatView.tsx",
45 );
46 });
47
48 it("returns null when the case-insensitive fallback is ambiguous", () => {
49 expect(
50 pickWorkspaceBasenameMatch("FOO.ts", [
51 { path: "src/Foo.ts", kind: "file" },
52 { path: "src/foo.ts", kind: "file" },
53 ]),
54 ).toBeNull();
55 });
56});
57
58describe("chat file links an agent shortened", () => {
59 const cwd = "/home/user/acme";
60 const entries = [
61 { path: "packages/server/src/cloud/http.ts", kind: "file" as const },
62 { path: "packages/server/src/cloud", kind: "directory" as const },
63 { path: "packages/web/src/api/http.ts", kind: "file" as const },
64 { path: "packages/server/src/index.ts", kind: "file" as const },
65 { path: "packages/web/src/index.ts", kind: "file" as const },
66 ];
67
68 it("searches for package-relative paths and repeated prefixes, not exact ones", () => {
69 expect(workspaceLinkLookupQuery("cloud/http.ts", cwd)).toBe("cloud/http.ts");
70 expect(workspaceLinkLookupQuery("acme/packages/web/src/index.ts", cwd)).toBe(
71 "packages/web/src/index.ts",
72 );
73 expect(workspaceLinkLookupQuery("./cloud/http.ts", cwd)).toBeNull();
74 expect(workspaceLinkLookupQuery("~/acme/src/config.ts", cwd)).toBeNull();
75 expect(workspaceLinkLookupQuery("packages/server/", cwd)).toBeNull();
76 });
77
78 it("opens the one file a package-relative path is the suffix of", () => {
79 expect(pickWorkspaceLinkMatch("cloud/http.ts", cwd, entries)).toBe(
80 "packages/server/src/cloud/http.ts",
81 );
82 expect(pickWorkspaceLinkMatch("acme/packages/web/src/index.ts", cwd, entries)).toBe(
83 "packages/web/src/index.ts",
84 );
85 });
86
87 it("opens nothing when the suffix names several files or splits a segment", () => {
88 expect(pickWorkspaceLinkMatch("src/index.ts", cwd, entries)).toBeNull();
89 expect(pickWorkspaceLinkMatch("ud/http.ts", cwd, entries)).toBeNull();
90 });
91});
92
93describe("chat file links into another project", () => {
94 const appB = {
95 root: "/home/user/app-b",
96 entries: [{ path: "src/api/rules/categories.ts", kind: "file" as const }],
97 };
98
99 // regression: #10553 a path the agent cited inside another registered project
100 it("resolves to the absolute path in the one project that has the file", () => {
101 expect(
102 pickProjectLinkMatch("rules/categories.ts", [
103 { root: "/home/user/app-c", entries: [] },
104 appB,
105 ]),
106 ).toBe("/home/user/app-b/src/api/rules/categories.ts");
107 });
108
109 it("opens nothing when several projects have the file", () => {
110 expect(
111 pickProjectLinkMatch("rules/categories.ts", [appB, { ...appB, root: "/home/user/app-c" }]),
112 ).toBeNull();
113 });
114});
115
116describe("claimWorkspaceBasenameLookup", () => {
117 it("keeps only the newest claim, whatever order the lookups settle in", () => {
118 const first = claimWorkspaceBasenameLookup();
119 const second = claimWorkspaceBasenameLookup();
120
121 expect(second()).toBe(true);
122 expect(first()).toBe(false);
123 });
124});