1import { ProjectId, ThreadId, type DesktopAppActivationRequest } from "@t3tools/contracts";
2import { describe, expect, it, vi } from "vite-plus/test";
3
4import { DesktopAppActivationBroker } from "./DesktopAppActivationBroker.ts";
5
6const request: DesktopAppActivationRequest = {
7 version: 1,
8 requestId: "request-1",
9 type: "open-workspace",
10 workspaceRoot: "/workspace/project",
11 platform: "linux",
12};
13
14describe("DesktopAppActivationBroker", () => {
15 it("focuses immediately and waits for renderer readiness", async () => {
16 const activate = vi.fn();
17 const send = vi.fn();
18 const broker = new DesktopAppActivationBroker({ requestTimeoutMs: 1_000, activate });
19
20 const response = broker.request(request);
21 expect(activate).toHaveBeenCalledOnce();
22 expect(send).not.toHaveBeenCalled();
23
24 broker.registerRenderer(send);
25 expect(send).toHaveBeenCalledWith(request);
26 broker.complete({
27 version: 1,
28 requestId: request.requestId,
29 ok: true,
30 projectId: ProjectId.make("project-1"),
31 threadId: ThreadId.make("thread-1"),
32 });
33
34 await expect(response).resolves.toMatchObject({ ok: true, projectId: "project-1" });
35 broker.close();
36 });
37
38 it("fails an in-flight request when the renderer goes away", async () => {
39 const broker = new DesktopAppActivationBroker({ requestTimeoutMs: 1_000, activate: vi.fn() });
40 broker.registerRenderer(vi.fn());
41
42 const response = broker.request(request);
43 broker.clearRenderer();
44
45 await expect(response).resolves.toMatchObject({
46 ok: false,
47 code: "renderer-unavailable",
48 });
49 broker.close();
50 });
51
52 it("queues requests after unsubscribe until a new renderer registers", async () => {
53 const previousSend = vi.fn();
54 const nextSend = vi.fn();
55 const broker = new DesktopAppActivationBroker({ requestTimeoutMs: 1_000, activate: vi.fn() });
56 broker.registerRenderer(previousSend);
57 broker.clearRenderer();
58
59 const response = broker.request(request);
60 expect(previousSend).not.toHaveBeenCalled();
61 expect(nextSend).not.toHaveBeenCalled();
62
63 broker.registerRenderer(nextSend);
64 expect(nextSend).toHaveBeenCalledWith(request);
65 broker.complete({
66 version: 1,
67 requestId: request.requestId,
68 ok: true,
69 projectId: ProjectId.make("project-1"),
70 threadId: ThreadId.make("thread-1"),
71 });
72
73 await expect(response).resolves.toMatchObject({ ok: true });
74 broker.close();
75 });
76
77 it("removes a queued request when its CLI connection closes", async () => {
78 const send = vi.fn();
79 const broker = new DesktopAppActivationBroker({ requestTimeoutMs: 1_000, activate: vi.fn() });
80
81 const response = broker.request(request);
82 broker.cancel(request.requestId);
83 broker.registerRenderer(send);
84
85 await expect(response).resolves.toMatchObject({ ok: false, code: "renderer-unavailable" });
86 expect(send).not.toHaveBeenCalled();
87 broker.close();
88 });
89
90 it("never sends a canceled request that was queued behind another request", async () => {
91 const send = vi.fn();
92 const broker = new DesktopAppActivationBroker({ requestTimeoutMs: 1_000, activate: vi.fn() });
93 broker.registerRenderer(send);
94 const secondRequest = { ...request, requestId: "request-2" };
95
96 const firstResponse = broker.request(request);
97 const secondResponse = broker.request(secondRequest);
98 expect(send).toHaveBeenCalledTimes(1);
99 expect(send).toHaveBeenLastCalledWith(request);
100
101 broker.cancel(secondRequest.requestId);
102 broker.complete({
103 version: 1,
104 requestId: request.requestId,
105 ok: true,
106 projectId: ProjectId.make("project-1"),
107 threadId: ThreadId.make("thread-1"),
108 });
109
110 await expect(firstResponse).resolves.toMatchObject({ ok: true });
111 await expect(secondResponse).resolves.toMatchObject({ ok: false });
112 expect(send).toHaveBeenCalledTimes(1);
113 broker.close();
114 });
115
116 it("times out a request without polling", async () => {
117 vi.useFakeTimers();
118 try {
119 const broker = new DesktopAppActivationBroker({ requestTimeoutMs: 1_000, activate: vi.fn() });
120 const response = broker.request(request);
121
122 await vi.advanceTimersByTimeAsync(1_000);
123
124 await expect(response).resolves.toMatchObject({ ok: false, code: "request-timeout" });
125 broker.close();
126 } finally {
127 vi.useRealTimers();
128 }
129 });
130});