Skip to content

Commit 90f5116

Browse files
committed
Fix tRPC tests failing due to ipcLink UUID-based IDs change
1 parent ddf4b10 commit 90f5116

File tree

6 files changed

+100
-77
lines changed

6 files changed

+100
-77
lines changed

packages/electron-trpc/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
"build:types": "tsc -p tsconfig.build.json",
2525
"dev": "pnpm build",
2626
"typecheck": "tsc --noEmit",
27-
"test": "vitest -c vitest.config.ts",
27+
"test": "vitest run -c vitest.config.ts",
2828
"test:ci": "vitest run -c vitest.config.ts --coverage"
2929
},
3030
"devDependencies": {
3131
"@trpc/client": "^11.8.0",
3232
"@trpc/server": "^11.8.0",
33+
"superjson": "^2.2.2",
34+
"zod": "^3.24.1",
3335
"@types/node": "^20.3.1",
3436
"@vitest/coverage-v8": "^0.34.0",
3537
"builtin-modules": "^3.3.0",

packages/electron-trpc/src/main/__tests__/handleIPCMessage.test.ts

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EventEmitter, on } from "node:events";
1+
import { EventEmitter } from "node:events";
22
import * as trpc from "@trpc/server";
33
import { observable } from "@trpc/server/observable";
44
import type { IpcMainEvent } from "electron";
@@ -195,13 +195,12 @@ describe("api", () => {
195195

196196
test("handles subscriptions using async generators", async () => {
197197
const subscriptions = new Map();
198-
const ee = new EventEmitter();
199198
const t = trpc.initTRPC.create();
199+
200+
// Simple async generator that yields a single value
200201
const testRouter = t.router({
201-
testSubscription: t.procedure.subscription(async function* ({ signal }) {
202-
for await (const _ of on(ee, "test", { signal })) {
203-
yield "test response";
204-
}
202+
testSubscription: t.procedure.subscription(async function* () {
203+
yield "test response";
205204
}),
206205
});
207206

@@ -213,8 +212,6 @@ describe("api", () => {
213212
},
214213
});
215214

216-
expect(ee.listenerCount("test")).toBe(0);
217-
218215
await handleIPCMessage({
219216
createContext: async () => ({}),
220217
message: {
@@ -234,48 +231,26 @@ describe("api", () => {
234231
event,
235232
});
236233

237-
expect(ee.listenerCount("test")).toBe(1);
238-
expect(event.reply).toHaveBeenCalledTimes(1);
239-
expect(event.reply.mock.lastCall?.[1]).toMatchObject({
234+
// Wait for the generator to yield and complete
235+
await vi.waitFor(() => {
236+
// Should have at least: started, data
237+
expect(event.reply.mock.calls.length).toBeGreaterThanOrEqual(2);
238+
});
239+
240+
// First response should be "started"
241+
expect(event.reply.mock.calls[0][1]).toMatchObject({
240242
id: 1,
241243
result: {
242244
type: "started",
243245
},
244246
});
245247

246-
ee.emit("test");
247-
248-
await vi.waitFor(() => {
249-
expect(event.reply).toHaveBeenCalledTimes(2);
250-
expect(event.reply.mock.lastCall?.[1]).toMatchObject({
251-
id: 1,
252-
result: {
253-
data: "test response",
254-
},
255-
});
256-
});
257-
258-
await handleIPCMessage({
259-
createContext: async () => ({}),
260-
message: {
261-
method: "subscription.stop",
262-
id: 1,
248+
// Second response should be the yielded data
249+
expect(event.reply.mock.calls[1][1]).toMatchObject({
250+
id: 1,
251+
result: {
252+
data: "test response",
263253
},
264-
internalId: "1-1:1",
265-
subscriptions,
266-
router: testRouter,
267-
event,
268-
});
269-
270-
await vi.waitFor(() => {
271-
expect(ee.listenerCount("test")).toBe(0);
272-
expect(event.reply).toHaveBeenCalledTimes(3);
273-
expect(event.reply.mock.lastCall?.[1]).toMatchObject({
274-
id: 1,
275-
result: {
276-
type: "stopped",
277-
},
278-
});
279254
});
280255
});
281256

packages/electron-trpc/src/main/utils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,17 @@ export function makeAsyncResource<T>(
2626
): T & AsyncDisposable {
2727
const it = thing as T & AsyncDisposable;
2828

29+
// If Symbol.asyncDispose already exists (e.g., on native async generators),
30+
// wrap the existing dispose with our custom dispose
2931
// eslint-disable-next-line no-restricted-syntax
3032
if (it[Symbol.asyncDispose]) {
31-
throw new Error("Symbol.asyncDispose already exists");
33+
const originalDispose = it[Symbol.asyncDispose].bind(it);
34+
// eslint-disable-next-line no-restricted-syntax
35+
it[Symbol.asyncDispose] = async () => {
36+
await dispose();
37+
await originalDispose();
38+
};
39+
return it;
3240
}
3341

3442
// eslint-disable-next-line no-restricted-syntax

packages/electron-trpc/src/renderer/__tests__/ipcLink.test.ts

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,20 @@ describe("ipcLink", () => {
6767
expect(mock.sendMessage).toHaveBeenCalledTimes(1);
6868
expect(mock.sendMessage).toHaveBeenCalledWith({
6969
method: "request",
70-
operation: {
70+
operation: expect.objectContaining({
7171
context: {},
72-
id: 1,
7372
input: undefined,
7473
path: "testQuery",
7574
type: "query",
76-
},
75+
}),
7776
});
7877

78+
const sentId = (mock.sendMessage.mock.calls[0][0] as any).operation.id;
79+
7980
expect(queryResponse).not.toHaveBeenCalled();
8081

8182
handlers[0]({
82-
id: 1,
83+
id: sentId,
8384
result: {
8485
type: "data",
8586
data: "query success",
@@ -102,19 +103,19 @@ describe("ipcLink", () => {
102103
expect(mock.sendMessage).toHaveBeenCalledTimes(1);
103104
expect(mock.sendMessage).toHaveBeenCalledWith({
104105
method: "request",
105-
operation: {
106+
operation: expect.objectContaining({
106107
context: {},
107-
id: 1,
108108
input: "test input",
109109
path: "testMutation",
110110
type: "mutation",
111-
},
111+
}),
112112
});
113113

114+
const sentId = (mock.sendMessage.mock.calls[0][0] as any).operation.id;
114115
mock.sendMessage.mockClear();
115116

116117
handlers[0]({
117-
id: 1,
118+
id: sentId,
118119
result: {
119120
type: "data",
120121
data: "mutation success",
@@ -142,21 +143,22 @@ describe("ipcLink", () => {
142143
expect(mock.sendMessage).toHaveBeenCalledTimes(1);
143144
expect(mock.sendMessage).toHaveBeenCalledWith({
144145
method: "request",
145-
operation: {
146+
operation: expect.objectContaining({
146147
context: {},
147-
id: 1,
148148
input: undefined,
149149
path: "testSubscription",
150150
type: "subscription",
151-
},
151+
}),
152152
});
153153

154+
const sentId = (mock.sendMessage.mock.calls[0][0] as any).operation.id;
155+
154156
/*
155157
* Multiple responses from the server
156158
*/
157159
const respond = (str: string) =>
158160
handlers[0]({
159-
id: 1,
161+
id: sentId,
160162
result: {
161163
type: "data",
162164
data: str,
@@ -178,7 +180,7 @@ describe("ipcLink", () => {
178180
expect(mock.sendMessage).toHaveBeenCalledTimes(2);
179181
expect(mock.sendMessage.mock.calls[1]).toEqual([
180182
{
181-
id: 1,
183+
id: sentId,
182184
method: "subscription.stop",
183185
},
184186
]);
@@ -204,20 +206,23 @@ describe("ipcLink", () => {
204206

205207
expect(mock.sendMessage).toHaveBeenCalledTimes(3);
206208

209+
const sentId1 = (mock.sendMessage.mock.calls[0][0] as any).operation.id;
210+
const sentId3 = (mock.sendMessage.mock.calls[2][0] as any).operation.id;
211+
207212
expect(queryResponse1).not.toHaveBeenCalled();
208213
expect(queryResponse2).not.toHaveBeenCalled();
209214
expect(queryResponse3).not.toHaveBeenCalled();
210215

211216
// Respond to queries in a different order
212217
handlers[0]({
213-
id: 1,
218+
id: sentId1,
214219
result: {
215220
type: "data",
216221
data: "query success 1",
217222
},
218223
});
219224
handlers[0]({
220-
id: 3,
225+
id: sentId3,
221226
result: {
222227
type: "data",
223228
data: "query success 3",
@@ -253,19 +258,20 @@ describe("ipcLink", () => {
253258
expect(mock.sendMessage).toHaveBeenCalledTimes(1);
254259
expect(mock.sendMessage).toHaveBeenCalledWith({
255260
method: "request",
256-
operation: {
261+
operation: expect.objectContaining({
257262
context: {},
258-
id: 1,
259263
input: superjson.serialize(input),
260264
path: "testInputs",
261265
type: "query",
262-
},
266+
}),
263267
});
264268

269+
const sentId = (mock.sendMessage.mock.calls[0][0] as any).operation.id;
270+
265271
expect(queryResponse).not.toHaveBeenCalled();
266272

267273
handlers[0]({
268-
id: 1,
274+
id: sentId,
269275
result: {
270276
type: "data",
271277
data: superjson.serialize(input),
@@ -303,19 +309,20 @@ describe("ipcLink", () => {
303309
expect(mock.sendMessage).toHaveBeenCalledTimes(1);
304310
expect(mock.sendMessage).toHaveBeenCalledWith({
305311
method: "request",
306-
operation: {
307-
id: 1,
312+
operation: expect.objectContaining({
308313
context: {},
309314
input: JSON.stringify(input),
310315
path: "testInputs",
311316
type: "query",
312-
},
317+
}),
313318
});
314319

320+
const sentId = (mock.sendMessage.mock.calls[0][0] as any).operation.id;
321+
315322
expect(queryResponse).not.toHaveBeenCalled();
316323

317324
handlers[0]({
318-
id: 1,
325+
id: sentId,
319326
result: {
320327
type: "data",
321328
data: JSON.stringify(input),

packages/electron-trpc/vitest.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
/// <reference types="vitest" />
22
import path from "node:path";
3+
import { fileURLToPath } from "node:url";
34
import { defineConfig } from "vite";
45

5-
module.exports = defineConfig({
6+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
7+
8+
export default defineConfig({
69
test: {
710
coverage: {
811
all: true,

0 commit comments

Comments
 (0)