1import { assert, describe, it } from "@effect/vitest";
2import * as Cause from "effect/Cause";
3import * as Effect from "effect/Effect";
4import { vi } from "vite-plus/test";
5
6import * as DesktopIpc from "./DesktopIpc.ts";
7
8const invokeMethod: DesktopIpc.DesktopIpcMethod<never, never> = {
9 channel: "desktop.test.invoke",
10 handler: () => Effect.void,
11};
12
13const syncMethod: DesktopIpc.DesktopSyncIpcMethod<never, never> = {
14 channel: "desktop.test.sync",
15 handler: () => Effect.void,
16};
17
18function makeIpcMain(
19 overrides: Partial<DesktopIpc.DesktopIpcMain> = {},
20): DesktopIpc.DesktopIpcMain {
21 return {
22 removeHandler: vi.fn(),
23 handle: vi.fn(),
24 removeAllListeners: vi.fn(),
25 on: vi.fn(),
26 ...overrides,
27 };
28}
29
30describe("DesktopIpc", () => {
31 it.effect("preserves invoke registration context and cause", () =>
32 Effect.gen(function* () {
33 const cause = new Error("invoke registration failed");
34 const ipcMain = makeIpcMain({
35 handle: () => {
36 throw cause;
37 },
38 });
39 const ipc = DesktopIpc.make(ipcMain);
40
41 const error = yield* Effect.flip(Effect.scoped(ipc.handle(invokeMethod)));
42
43 assert.instanceOf(error, DesktopIpc.DesktopIpcRegistrationError);
44 assert.strictEqual(error.handlerKind, "invoke");
45 assert.strictEqual(error.channel, invokeMethod.channel);
46 assert.strictEqual(error.cause, cause);
47 assert.include(error.message, "invoke");
48 assert.include(error.message, invokeMethod.channel);
49 assert.notInclude(error.message, cause.message);
50 }),
51 );
52
53 it.effect("forwards the invoke sender to the method", () =>
54 Effect.gen(function* () {
55 let listener: DesktopIpc.DesktopIpcHandleListener | undefined;
56 const ipc = DesktopIpc.make(
57 makeIpcMain({
58 handle: (_channel, registered) => {
59 listener = registered;
60 },
61 }),
62 );
63 const sender = { sender: { id: 7 } };
64 let received: DesktopIpc.DesktopIpcInvokeEvent | undefined;
65
66 yield* Effect.scoped(
67 Effect.gen(function* () {
68 yield* ipc.handle({
69 channel: "desktop.test.sender",
70 handler: (_raw, event) =>
71 Effect.sync(() => {
72 received = event;
73 }),
74 });
75 yield* Effect.promise(async () => listener!(sender, undefined));
76 }),
77 );
78
79 assert.strictEqual(received, sender);
80 }),
81 );
82
83 it.effect("preserves sync unregistration context and cause in the finalizer defect", () =>
84 Effect.gen(function* () {
85 const cause = new Error("sync unregistration failed");
86 let removeCount = 0;
87 const ipcMain = makeIpcMain({
88 removeAllListeners: () => {
89 removeCount += 1;
90 if (removeCount === 2) throw cause;
91 },
92 });
93 const ipc = DesktopIpc.make(ipcMain);
94
95 const exit = yield* Effect.exit(Effect.scoped(ipc.handleSync(syncMethod)));
96
97 assert.isTrue(exit._tag === "Failure");
98 if (exit._tag === "Success") return;
99 const error = Cause.squash(exit.cause);
100 assert.instanceOf(error, DesktopIpc.DesktopIpcUnregistrationError);
101 assert.strictEqual(error.handlerKind, "sync");
102 assert.strictEqual(error.channel, syncMethod.channel);
103 assert.strictEqual(error.cause, cause);
104 assert.notInclude(error.message, cause.message);
105 }),
106 );
107});