1import { EnvironmentId, ProjectId, ThreadId } from "@t3tools/contracts";
2import { describe, expect, it, vi } from "vite-plus/test";
3
4import {
5 handleDesktopAppActivationRequest,
6 type DesktopAppActivationDependencies,
7} from "./desktopAppActivation";
8
9const environmentId = EnvironmentId.make("primary");
10const existingProjectId = ProjectId.make("project-existing");
11const createdProjectId = ProjectId.make("project-created");
12const threadId = ThreadId.make("thread-1");
13const request = {
14 version: 1,
15 requestId: "request-1",
16 type: "open-workspace",
17 workspaceRoot: "/workspace/project",
18 platform: "linux",
19} as const;
20
21function dependencies(
22 overrides: Partial<DesktopAppActivationDependencies> = {},
23): DesktopAppActivationDependencies {
24 return {
25 getTarget: () => ({ environmentId, platform: "linux" }),
26 findProject: () => ({
27 id: existingProjectId,
28 environmentId,
29 workspaceRoot: request.workspaceRoot,
30 }),
31 createProject: vi.fn(async () => createdProjectId),
32 waitForProject: vi.fn(async () => undefined),
33 openThread: vi.fn(async () => ({ threadId })),
34 ...overrides,
35 };
36}
37
38describe("desktop app activation", () => {
39 it("reuses an existing project and opens a new thread", async () => {
40 const deps = dependencies();
41
42 const response = await handleDesktopAppActivationRequest(request, deps);
43
44 expect(deps.createProject).not.toHaveBeenCalled();
45 expect(deps.openThread).toHaveBeenCalledWith({ environmentId, projectId: existingProjectId });
46 expect(response).toEqual({
47 version: 1,
48 requestId: request.requestId,
49 ok: true,
50 projectId: existingProjectId,
51 threadId,
52 });
53 });
54
55 it("waits for a created project before it opens the thread", async () => {
56 const order: string[] = [];
57 const deps = dependencies({
58 findProject: () => null,
59 createProject: vi.fn(async () => {
60 order.push("create");
61 return createdProjectId;
62 }),
63 waitForProject: vi.fn(async () => {
64 order.push("project-event");
65 }),
66 openThread: vi.fn(async () => {
67 order.push("open-thread");
68 return { threadId };
69 }),
70 });
71
72 const response = await handleDesktopAppActivationRequest(request, deps);
73
74 expect(order).toEqual(["create", "project-event", "open-thread"]);
75 expect(response).toMatchObject({ ok: true, projectId: createdProjectId });
76 });
77
78 it("rejects a Windows path when the primary environment is WSL", async () => {
79 const response = await handleDesktopAppActivationRequest(
80 { ...request, platform: "win32" },
81 dependencies({ getTarget: () => ({ environmentId, platform: "linux" }) }),
82 );
83
84 expect(response).toMatchObject({ ok: false, code: "platform-mismatch" });
85 });
86
87 it("returns a project error without opening a thread", async () => {
88 const openThread = vi.fn(async () => ({ threadId }));
89 const response = await handleDesktopAppActivationRequest(
90 request,
91 dependencies({
92 findProject: () => null,
93 createProject: vi.fn(async () => {
94 throw new Error("Project path is not available.");
95 }),
96 openThread,
97 }),
98 );
99
100 expect(response).toMatchObject({
101 ok: false,
102 code: "project-create-failed",
103 message: "Project path is not available.",
104 });
105 expect(openThread).not.toHaveBeenCalled();
106 });
107});