1import { scopeThreadRef } from "@t3tools/client-runtime/environment";
2import { EnvironmentId, ThreadId } from "@t3tools/contracts";
3import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
4
5import { openDiffFilePrimaryAction, resolveDiffPathForWorkspace } from "./diffFileActions";
6import { selectThreadRightPanelState, useRightPanelStore } from "./rightPanelStore";
7
8const THREAD_REF = scopeThreadRef(
9 EnvironmentId.make("environment-local"),
10 ThreadId.make("thread-1"),
11);
12
13describe("openDiffFilePrimaryAction", () => {
14 beforeEach(() => {
15 useRightPanelStore.setState({ byThreadKey: {}, threadPanelVisibilityByThreadKey: {} });
16 });
17
18 it("opens diff files in the thread file viewer", () => {
19 const openInEditor = vi.fn();
20
21 openDiffFilePrimaryAction({
22 threadRef: THREAD_REF,
23 filePath: "apps/web/src/components/DiffPanel.tsx",
24 activeCwd: "/repo/project",
25 openInEditor,
26 });
27
28 expect(
29 selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, THREAD_REF),
30 ).toMatchObject({
31 isOpen: true,
32 activeSurfaceId: "file:apps/web/src/components/DiffPanel.tsx",
33 });
34 expect(openInEditor).not.toHaveBeenCalled();
35 });
36
37 it("falls back to the editor without thread context", () => {
38 const openInEditor = vi.fn();
39
40 openDiffFilePrimaryAction({
41 threadRef: null,
42 filePath: "apps/web/src/components/DiffPanel.tsx",
43 activeCwd: "/repo/project",
44 openInEditor,
45 });
46
47 expect(openInEditor).toHaveBeenCalledWith(
48 "/repo/project/apps/web/src/components/DiffPanel.tsx",
49 );
50 });
51
52 it("opens repository-relative diff files from a nested project", () => {
53 const openInEditor = vi.fn();
54
55 openDiffFilePrimaryAction({
56 threadRef: THREAD_REF,
57 filePath: "frontend/Dockerfile",
58 activeCwd: "/repo/frontend",
59 repositoryRoot: "/repo",
60 openInEditor,
61 });
62
63 expect(
64 selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, THREAD_REF),
65 ).toMatchObject({
66 isOpen: true,
67 activeSurfaceId: "file:Dockerfile",
68 });
69 expect(openInEditor).not.toHaveBeenCalled();
70 });
71
72 it("preserves repository-relative paths in a separate worktree", () => {
73 expect(
74 resolveDiffPathForWorkspace({
75 filePath: "frontend/Dockerfile",
76 workspaceRoot: "/worktrees/feature",
77 repositoryRoot: "/repo",
78 }),
79 ).toBe("frontend/Dockerfile");
80 });
81
82 it("handles Windows roots and mixed diff separators", () => {
83 expect(
84 resolveDiffPathForWorkspace({
85 filePath: "Frontend/src\\index.ts",
86 workspaceRoot: "C:\\repo\\frontend",
87 repositoryRoot: "C:\\repo",
88 }),
89 ).toBe("src/index.ts");
90 });
91
92 it.each([
93 { workspaceRoot: "/frontend", repositoryRoot: "/" },
94 { workspaceRoot: "C:\\frontend", repositoryRoot: "C:\\" },
95 ])("handles filesystem roots: $repositoryRoot", ({ workspaceRoot, repositoryRoot }) => {
96 expect(
97 resolveDiffPathForWorkspace({
98 filePath: "frontend/index.ts",
99 workspaceRoot,
100 repositoryRoot,
101 }),
102 ).toBe("index.ts");
103 });
104
105 it.each(["backend/server.ts", "frontend2/app.ts", "frontend/../secret.ts", "C:secret.ts"])(
106 "does not open an out-of-project diff path: %s",
107 (filePath) => {
108 const openInEditor = vi.fn();
109
110 openDiffFilePrimaryAction({
111 threadRef: THREAD_REF,
112 filePath,
113 activeCwd: "/repo/frontend",
114 repositoryRoot: "/repo",
115 openInEditor,
116 });
117
118 expect(
119 selectThreadRightPanelState(useRightPanelStore.getState().byThreadKey, THREAD_REF),
120 ).toMatchObject({ isOpen: false });
121 expect(openInEditor).not.toHaveBeenCalled();
122 },
123 );
124});