| 1 | import { it as effectIt } from "@effect/vitest"; |
| 2 | import { |
| 3 | DEFAULT_BROWSER_PROFILE_ID, |
| 4 | INCOGNITO_BROWSER_PROFILE_ID, |
| 5 | PreviewAutomationStatus, |
| 6 | } from "@t3tools/contracts"; |
| 7 | import * as Cause from "effect/Cause"; |
| 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Exit from "effect/Exit"; |
| 10 | import * as Option from "effect/Option"; |
| 11 | import * as Schema from "effect/Schema"; |
| 12 | import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; |
| 13 | |
| 14 | import * as PreviewManager from "../../preview/Manager.ts"; |
| 15 | import * as BrowserImport from "../../preview/BrowserImport/BrowserImport.ts"; |
| 16 | import * as PreviewIpc from "./preview.ts"; |
| 17 | |
| 18 | const { fromPartition } = vi.hoisted(() => ({ |
| 19 | fromPartition: vi.fn(() => { |
| 20 | throw new Error("Session can only be received when app is ready"); |
| 21 | }), |
| 22 | })); |
| 23 | |
| 24 | vi.mock("electron", () => ({ |
| 25 | BrowserWindow: { |
| 26 | getAllWindows: vi.fn(() => []), |
| 27 | }, |
| 28 | session: { |
| 29 | fromPartition, |
| 30 | }, |
| 31 | webContents: { |
| 32 | fromId: vi.fn(() => null), |
| 33 | }, |
| 34 | })); |
| 35 | |
| 36 | describe("preview IPC methods", () => { |
| 37 | beforeEach(() => { |
| 38 | fromPartition.mockClear(); |
| 39 | }); |
| 40 | |
| 41 | it("does not access the Electron session while the module loads", async () => { |
| 42 | await expect(import("./preview.ts")).resolves.toBeDefined(); |
| 43 | expect(fromPartition).not.toHaveBeenCalled(); |
| 44 | }); |
| 45 | |
| 46 | it("derives distinct partition scopes when identifiers contain the delimiter", () => { |
| 47 | const first = PreviewIpc.resolvePartitionScope("a", "b::c"); |
| 48 | const second = PreviewIpc.resolvePartitionScope("a::b", "c"); |
| 49 | |
| 50 | expect(first).toEqual({ scope: '["a","b::c"]', persistent: true, namespace: "profile" }); |
| 51 | expect(second).toEqual({ scope: '["a::b","c"]', persistent: true, namespace: "profile" }); |
| 52 | expect(first.scope).not.toBe(second.scope); |
| 53 | }); |
| 54 | |
| 55 | it("preserves lone surrogates without collapsing them to replacement characters", () => { |
| 56 | const highSurrogate = PreviewIpc.resolvePartitionScope("environment", "profile-\ud800"); |
| 57 | const lowSurrogate = PreviewIpc.resolvePartitionScope("environment", "profile-\udc00"); |
| 58 | const replacement = PreviewIpc.resolvePartitionScope("environment", "profile-�"); |
| 59 | |
| 60 | expect(highSurrogate.scope).toBe('["environment","profile-\\ud800"]'); |
| 61 | expect(lowSurrogate.scope).toBe('["environment","profile-\\udc00"]'); |
| 62 | expect(highSurrogate.scope).not.toBe(lowSurrogate.scope); |
| 63 | expect(highSurrogate.scope).not.toBe(replacement.scope); |
| 64 | expect(lowSurrogate.scope).not.toBe(replacement.scope); |
| 65 | }); |
| 66 | |
| 67 | it("keeps the legacy default partition scope and incognito persistence", () => { |
| 68 | expect(PreviewIpc.resolvePartitionScope("environment::legacy", undefined)).toEqual({ |
| 69 | scope: "environment::legacy", |
| 70 | persistent: true, |
| 71 | }); |
| 72 | expect( |
| 73 | PreviewIpc.resolvePartitionScope("environment::legacy", DEFAULT_BROWSER_PROFILE_ID), |
| 74 | ).toEqual({ scope: "environment::legacy", persistent: true }); |
| 75 | expect( |
| 76 | PreviewIpc.resolvePartitionScope("environment::legacy", INCOGNITO_BROWSER_PROFILE_ID), |
| 77 | ).toEqual({ |
| 78 | scope: '["environment::legacy","incognito"]', |
| 79 | persistent: false, |
| 80 | namespace: "profile", |
| 81 | }); |
| 82 | }); |
| 83 | |
| 84 | effectIt.effect("targets imports at the same partition tuple as the renderer", () => { |
| 85 | const received: Array<Parameters<BrowserImport.BrowserImport["Service"]["importCookies"]>[0]> = |
| 86 | []; |
| 87 | const browserImport = BrowserImport.BrowserImport.of({ |
| 88 | listSources: Effect.succeed([]), |
| 89 | importCookies: (input) => |
| 90 | Effect.sync(() => { |
| 91 | received.push(input); |
| 92 | return { imported: 0, skipped: 0, skippedDomains: [] }; |
| 93 | }), |
| 94 | }); |
| 95 | const request = (environmentId: string, targetProfileId: string) => |
| 96 | PreviewIpc.importBrowserCookies.handler({ |
| 97 | environmentId, |
| 98 | sourceId: "helium", |
| 99 | sourceProfileDirectory: "Default", |
| 100 | targetProfileId, |
| 101 | }); |
| 102 | |
| 103 | return Effect.gen(function* () { |
| 104 | yield* request("a", "b"); |
| 105 | yield* request("a::b", DEFAULT_BROWSER_PROFILE_ID); |
| 106 | |
| 107 | expect(received[0]).toMatchObject(PreviewIpc.resolvePartitionScope("a", "b")); |
| 108 | expect(received[1]).toMatchObject( |
| 109 | PreviewIpc.resolvePartitionScope("a::b", DEFAULT_BROWSER_PROFILE_ID), |
| 110 | ); |
| 111 | expect(received[0]?.namespace).toBe("profile"); |
| 112 | expect(received[1]?.namespace).toBeUndefined(); |
| 113 | }).pipe(Effect.provideService(BrowserImport.BrowserImport, browserImport)); |
| 114 | }); |
| 115 | |
| 116 | effectIt.effect("rejects invalid webContents ids before resolving the preview service", () => |
| 117 | Effect.map( |
| 118 | PreviewIpc.registerWebview |
| 119 | .handler({ tabId: "tab-1", webContentsId: 0 }) |
| 120 | .pipe(Effect.provideService(PreviewManager.PreviewManager, null as never), Effect.exit), |
| 121 | (exit) => { |
| 122 | expect(Exit.isFailure(exit)).toBe(true); |
| 123 | if (Exit.isSuccess(exit)) return; |
| 124 | const error = Cause.findErrorOption(exit.cause); |
| 125 | expect(Option.isSome(error) && Schema.isSchemaError(error.value)).toBe(true); |
| 126 | expect(fromPartition).not.toHaveBeenCalled(); |
| 127 | }, |
| 128 | ), |
| 129 | ); |
| 130 | |
| 131 | it("keeps the public automation status tab id limit", () => { |
| 132 | const encode = Schema.encodeUnknownSync(PreviewAutomationStatus); |
| 133 | const tabId = "t".repeat(129); |
| 134 | |
| 135 | expect(() => |
| 136 | encode({ |
| 137 | available: false, |
| 138 | visible: true, |
| 139 | tabId, |
| 140 | url: null, |
| 141 | title: null, |
| 142 | loading: false, |
| 143 | }), |
| 144 | ).toThrow(); |
| 145 | }); |
| 146 | }); |