Skip to content
Source
Sign inSign up

DesktopBrowserChannel.test.ts

112 lines4.5 KB

1import * as NodeServices from "@effect/platform-node/NodeServices";
2import * as NodeStream from "@effect/platform-node/NodeStream";
3import { expect, it } from "@effect/vitest";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import * as Queue from "effect/Queue";
7import * as Stream from "effect/Stream";
8import { vi } from "vite-plus/test";
9​
10import * as ServerConfig from "../config.ts";
11import * as DesktopBrowserChannel from "./DesktopBrowserChannel.ts";
12​
13vi.mock("@effect/platform-node/NodeStream", () => ({ fromReadable: vi.fn() }));
14vi.mock("node:net", () => ({
15 Socket: class {
16 destroy() {}
17 },
18}));
19​
20it.effect("protects unknown desktop visibility until an explicit hidden report arrives", () =>
21 Effect.gen(function* () {
22 const input = yield* Queue.unbounded<Uint8Array>();
23 vi.mocked(NodeStream.fromReadable).mockReturnValue(Stream.fromQueue(input));
24 const config = yield* ServerConfig.ServerConfig;
25 yield* Effect.gen(function* () {
26 const channel = yield* DesktopBrowserChannel.DesktopBrowserChannel;
27 const key = { threadId: "thread-1", tabId: "tab-1" };
28 const send = (event: object) =>
29 Queue.offer(input, new TextEncoder().encode(`${JSON.stringify(event)}\n`));
30​
31 expect(channel.isShown(key)).toBe(true);
32 yield* send({ type: "attached", ...key });
33 yield* channel.awaitAttached(key, "5 seconds");
34 expect(channel.isShown(key)).toBe(true);
35​
36 yield* send({ type: "shown", ...key, shown: false });
37 yield* send({ type: "attached", threadId: key.threadId, tabId: "sentinel-1" });
38 yield* channel.awaitAttached({ threadId: key.threadId, tabId: "sentinel-1" }, "5 seconds");
39 expect(channel.isShown(key)).toBe(false);
40​
41 yield* send({ type: "shown", ...key, shown: true });
42 yield* send({ type: "attached", threadId: key.threadId, tabId: "sentinel-2" });
43 yield* channel.awaitAttached({ threadId: key.threadId, tabId: "sentinel-2" }, "5 seconds");
44 expect(channel.isShown(key)).toBe(true);
45​
46 yield* send({ type: "shown", ...key, shown: false });
47 yield* send({ type: "detached", ...key });
48 yield* send({ type: "attached", threadId: key.threadId, tabId: "sentinel-3" });
49 yield* channel.awaitAttached({ threadId: key.threadId, tabId: "sentinel-3" }, "5 seconds");
50 expect(channel.isShown(key)).toBe(true);
51 }).pipe(
52 Effect.provide(
53 DesktopBrowserChannel.layer.pipe(
54 Layer.provide(
55 ServerConfig.layer({ ...config, desktopBrowserFd: 41, desktopBrowserControlFd: 42 }),
56 ),
57 ),
58 ),
59 );
60 }).pipe(
61 Effect.provide(
62 ServerConfig.layerTest(process.cwd(), { prefix: "desktop-browser-channel-" }).pipe(
63 Layer.provide(NodeServices.layer),
64 ),
65 ),
66 ),
67);
68​
69it.live("closes a tab's relay connection when the desktop detaches the tab", () =>
70 Effect.gen(function* () {
71 const input = yield* Queue.unbounded<Uint8Array>();
72 vi.mocked(NodeStream.fromReadable).mockReturnValue(Stream.fromQueue(input));
73 const config = yield* ServerConfig.ServerConfig;
74 yield* Effect.gen(function* () {
75 const channel = yield* DesktopBrowserChannel.DesktopBrowserChannel;
76 const key = { threadId: "thread-1", tabId: "tab-1" };
77 const send = (event: object) =>
78 Queue.offer(input, new TextEncoder().encode(`${JSON.stringify(event)}\n`));
79​
80 yield* send({ type: "attached", ...key });
81 yield* channel.awaitAttached(key, "5 seconds");
82 const url = yield* channel.endpoint(key);
83 const socket = new WebSocket(url);
84 yield* Effect.addFinalizer(() => Effect.sync(() => socket.close()));
85 const closed = new Promise<void>((resolve) =>
86 socket.addEventListener("close", () => resolve()),
87 );
88 yield* Effect.promise(
89 () => new Promise<void>((resolve) => socket.addEventListener("open", () => resolve())),
90 );
91​
92 // Playwright waits on this socket for CDP replies; it must learn the tab is gone.
93 yield* send({ type: "detached", ...key });
94 yield* Effect.promise(() => closed).pipe(Effect.timeout("2 seconds"));
95 }).pipe(
96 Effect.scoped,
97 Effect.provide(
98 DesktopBrowserChannel.layer.pipe(
99 Layer.provide(
100 ServerConfig.layer({ ...config, desktopBrowserFd: 41, desktopBrowserControlFd: 42 }),
101 ),
102 ),
103 ),
104 );
105 }).pipe(
106 Effect.provide(
107 ServerConfig.layerTest(process.cwd(), { prefix: "desktop-browser-channel-" }).pipe(
108 Layer.provide(NodeServices.layer),
109 ),
110 ),
111 ),
112);