import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, describe, it } from "@effect/vitest";import * as Deferred from "effect/Deferred";import { DesktopSnapShotId } from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as Layer from "effect/Layer";import * as Logger from "effect/Logger";import * as Option from "effect/Option";import * as Queue from "effect/Queue";import * as Ref from "effect/Ref";import * as References from "effect/References";import * as Schema from "effect/Schema";import * as TestClock from "effect/testing/TestClock"; import * as Electron from "electron";import * as NodeEvents from "node:events";import { vi } from "vite-plus/test"; vi.mock("electron", async (importOriginal) => ({ ...(await importOriginal<typeof import("electron")>()), session: { fromPartition: vi.fn(() => ({ getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/1.2.3"), setPermissionRequestHandler: vi.fn(), setUserAgent: vi.fn(), })), }, screen: { getAllDisplays: vi.fn(() => [ { bounds: { x: 0, y: 0, width: 1920, height: 1080 }, }, ]), },})); import * as DesktopAssets from "../app/DesktopAssets.ts";import * as DesktopConfig from "../app/DesktopConfig.ts";import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";import * as DesktopState from "../app/DesktopState.ts";import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";import * as DesktopClientSettings from "../settings/DesktopClientSettings.ts";import * as ElectronApp from "../electron/ElectronApp.ts";import * as ElectronMenu from "../electron/ElectronMenu.ts";import * as ElectronShell from "../electron/ElectronShell.ts";import * as ElectronTheme from "../electron/ElectronTheme.ts";import * as ElectronWindow from "../electron/ElectronWindow.ts";import { MENU_ACTION_CHANNEL, SNAP_SHOT_EVENT_CHANNEL, WINDOW_FULLSCREEN_STATE_CHANNEL,} from "../ipc/channels.ts";import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts";import * as DesktopWindow from "./DesktopWindow.ts";import * as PreviewManager from "../preview/Manager.ts"; const environmentInput = { dirname: "/repo/apps/desktop/dist-electron", homeDirectory: "/Users/alice", platform: "darwin", processArch: "arm64", appVersion: "1.2.3", appPath: "/repo", isPackaged: false, resourcesPath: "/repo/resources", runningUnderArm64Translation: false,} satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; function makeFakeBrowserWindow() { const windowListeners = new Map<string, (...args: readonly unknown[]) => void>(); const webContentsListeners = new Map<string, (...args: readonly unknown[]) => void>(); let zoomLevel = 0; const webContents = { copyImageAt: vi.fn(), focus: vi.fn(), isDestroyed: vi.fn(() => false), getURL: vi.fn(() => "t3code-dev://app/"), getZoomLevel: vi.fn(() => zoomLevel), setZoomLevel: vi.fn((level: number) => { zoomLevel = level; }), isLoadingMainFrame: vi.fn(() => false), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { webContentsListeners.set(eventName, listener); }), once: vi.fn<(eventName: string, listener: (...args: readonly unknown[]) => void) => void>(), openDevTools: vi.fn(), reload: vi.fn(), replaceMisspelling: vi.fn(), send: vi.fn(), setBackgroundThrottling: vi.fn(), setWindowOpenHandler: vi.fn(), }; const window = { close: vi.fn(), focus: vi.fn(), getBounds: vi.fn(() => ({ x: 0, y: 0, width: 1100, height: 780 })), getNormalBounds: vi.fn(() => ({ x: 0, y: 0, width: 1100, height: 780 })), isDestroyed: vi.fn(() => false), isFullScreen: vi.fn(() => false), isMaximized: vi.fn(() => false), isMinimized: vi.fn(() => false), isVisible: vi.fn(() => true), loadURL: vi.fn(() => Promise.resolve()), maximize: vi.fn(), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { windowListeners.set(eventName, listener); }), once: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { windowListeners.set(eventName, listener); }), restore: vi.fn(), setBackgroundColor: vi.fn(), setAutoHideCursor: vi.fn(), setFullScreen: vi.fn(), setOpacity: vi.fn(), setTitle: vi.fn(), setTitleBarOverlay: vi.fn(), show: vi.fn(), webContents, }; return { window: window as unknown as Electron.BrowserWindow, getBounds: window.getBounds, getNormalBounds: window.getNormalBounds, isDestroyed: window.isDestroyed, isFullScreen: window.isFullScreen, isMaximized: window.isMaximized, isMinimized: window.isMinimized, loadURL: window.loadURL, maximize: window.maximize, openDevTools: webContents.openDevTools, reload: webContents.reload, send: webContents.send, setZoomLevel: webContents.setZoomLevel, setBackgroundThrottling: webContents.setBackgroundThrottling, setAutoHideCursor: window.setAutoHideCursor, setFullScreen: window.setFullScreen, setOpacity: window.setOpacity, webContentsListeners, webContentsOnce: webContents.once, windowListeners, };} const desktopClientSettingsLayer = Layer.mock(DesktopClientSettings.DesktopClientSettings)({ get: Effect.succeed(Option.none()),}); const electronAppLayer = Layer.mock(ElectronApp.ElectronApp)({ quit: Effect.void,}); const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { iconPaths: Effect.succeed({ ico: Option.none<string>(), icns: Option.none<string>(), png: Option.none<string>(), }), resolveResourcePath: () => Effect.succeed(Option.none<string>()),} satisfies DesktopAssets.DesktopAssets["Service"]); const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { getState: Effect.die("unexpected getState"), backendConfig: Effect.succeed({ port: 3773, bindHost: "127.0.0.1", httpBaseUrl: new URL("http://127.0.0.1:3773"), tailscaleServeEnabled: false, tailscaleServePort: 443, }), configureFromSettings: () => Effect.die("unexpected configureFromSettings"), setMode: () => Effect.die("unexpected setMode"), setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"), getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"),} satisfies DesktopServerExposure.DesktopServerExposure["Service"]); const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: () => Effect.void, popupTemplate: () => Effect.void, showContextMenu: () => Effect.succeed(Option.none()),} satisfies ElectronMenu.ElectronMenu["Service"]); const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { shouldUseDarkColors: Effect.succeed(false), setSource: () => Effect.void, onUpdated: () => Effect.void,} satisfies ElectronTheme.ElectronTheme["Service"]); const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe( Layer.provide( Layer.mergeAll( NodeServices.layer, DesktopConfig.layerTest({ T3CODE_PORT: "3773", VITE_DEV_SERVER_URL: "http://127.0.0.1:5733", }), ), ),); const desktopWindowBoundsEquivalence = Schema.toEquivalence( DesktopAppSettings.DesktopWindowBoundsSchema,); function makeTestLayer(input: { readonly window: Electron.BrowserWindow; readonly createCount: Ref.Ref<number>; readonly mainWindow: Ref.Ref<Option.Option<Electron.BrowserWindow>>; readonly createdWindowOptions?: Electron.BrowserWindowConstructorOptions[]; readonly desktopSettings?: DesktopAppSettings.DesktopSettings; readonly mainWindowBoundsUpdates?: DesktopAppSettings.DesktopWindowBounds[]; readonly mainWindowMaximizedUpdates?: boolean[]; readonly beforeMainWindowBoundsUpdate?: ( bounds: DesktopAppSettings.DesktopWindowBounds, ) => Effect.Effect<void>; readonly openedExternalUrls?: unknown[]; readonly copiedTexts?: string[]; readonly onPopupTemplate?: (input: ElectronMenu.ElectronMenuTemplateInput) => Effect.Effect<void>; readonly previewZoomReapplies?: number[]; readonly onReveal?: (window: Electron.BrowserWindow) => void;}) { let desktopSettings = input.desktopSettings ?? DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS; const desktopAppSettingsLayer = Layer.succeed(DesktopAppSettings.DesktopAppSettings, { get: Effect.sync(() => desktopSettings), load: Effect.sync(() => desktopSettings), setMainWindowBounds: (bounds, isMaximized) => Effect.gen(function* () { if (input.beforeMainWindowBoundsUpdate) { yield* input.beforeMainWindowBoundsUpdate(bounds); } const changed = desktopSettings.mainWindowBounds === null || !desktopWindowBoundsEquivalence(desktopSettings.mainWindowBounds, bounds) || desktopSettings.mainWindowMaximized !== isMaximized; if (changed) { desktopSettings = { ...desktopSettings, mainWindowBounds: bounds, mainWindowMaximized: isMaximized, }; input.mainWindowBoundsUpdates?.push(bounds); input.mainWindowMaximizedUpdates?.push(isMaximized); } return { settings: desktopSettings, changed }; }), setServerExposureMode: () => Effect.die("unexpected server exposure update"), setTailscaleServe: () => Effect.die("unexpected Tailscale Serve update"), setUpdateChannel: () => Effect.die("unexpected update channel change"), setWslBackendEnabled: () => Effect.die("unexpected WSL backend toggle"), setWslDistro: () => Effect.die("unexpected WSL distro change"), setWslOnly: () => Effect.die("unexpected WSL-only toggle"), applyWslWindowsFallback: Effect.die("unexpected WSL Windows fallback"), applyWslWindowsFallbackInMemory: Effect.die("unexpected WSL Windows fallback"), } satisfies DesktopAppSettings.DesktopAppSettings["Service"]); const electronWindowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { create: (options) => Effect.sync(() => { input.createdWindowOptions?.push(options); }).pipe( Effect.andThen(Ref.update(input.createCount, (count) => count + 1)), Effect.as(input.window), ), main: Ref.get(input.mainWindow), currentMainOrFirst: Ref.get(input.mainWindow), focusedMainOrFirst: Ref.get(input.mainWindow), setMain: (window) => Ref.set(input.mainWindow, Option.some(window)), clearMain: () => Ref.set(input.mainWindow, Option.none()), prepareReveal: () => Effect.succeed(false), reveal: (window) => Effect.sync(() => input.onReveal?.(window)), sendAll: () => Effect.void, destroyAll: Effect.void, syncAllAppearance: (sync) => sync(input.window), } satisfies ElectronWindow.ElectronWindow["Service"]); return DesktopWindow.layer.pipe( Layer.provide( Layer.mergeAll( desktopAssetsLayer, desktopEnvironmentLayer, desktopAppSettingsLayer, desktopClientSettingsLayer, desktopServerExposureLayer, DesktopState.layer, electronAppLayer, Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: () => Effect.void, showContextMenu: () => Effect.succeed(Option.none()), popupTemplate: input.onPopupTemplate ?? (() => Effect.void), }), Layer.succeed(ElectronShell.ElectronShell, { openExternal: (url) => Effect.sync(() => { input.openedExternalUrls?.push(url); return true; }), openSystemSettings: () => Effect.succeed(true), copyText: (text) => Effect.sync(() => { input.copiedTexts?.push(text); }), } satisfies ElectronShell.ElectronShell["Service"]), electronThemeLayer, electronWindowLayer, Layer.mock(PreviewManager.PreviewManager)({ getBrowserSession: () => Effect.succeed({} as Electron.Session), setMainWindow: () => Effect.void, isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"), getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"), reapplyZoom: () => Effect.sync(() => { input.previewZoomReapplies?.push(input.window.webContents.getZoomLevel()); }), }), ), ), );} // Builds a DesktopWindow over a fake ElectronWindow whose `create` returns the// given outcomes in order (null => simulated open failure), and whose// currentMainOrFirst mirrors the real fallback to the first live window (the// splash, before any main is registered). Reveal targets are recorded so tests// can assert what activation actually surfaced.const makeSplashScenario = (createOutcomes: readonly (Electron.BrowserWindow | null)[]) => Effect.gen(function* () { const createdWindows = yield* Ref.make<Electron.BrowserWindow[]>([]); const createCalls = yield* Ref.make(0); const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); const revealedWindows = yield* Ref.make<Electron.BrowserWindow[]>([]); const fallbackWindow = createOutcomes.find( (window): window is Electron.BrowserWindow => window !== null, ); const currentMainOrFirst = Effect.gen(function* () { const registered = yield* Ref.get(mainWindow); if (Option.isSome(registered)) { return registered; } const created = yield* Ref.get(createdWindows); return Option.fromNullishOr(created[0] ?? null); }); const electronWindowShape = { create: () => Effect.gen(function* () { const index = yield* Ref.getAndUpdate(createCalls, (count) => count + 1); const outcome = createOutcomes[index] ?? null; if (outcome === null) { return yield* new ElectronWindow.ElectronWindowCreateError({ options: { title: null, width: null, height: null, minWidth: null, minHeight: null, show: null, modal: null, frame: null, transparent: null, backgroundColor: null, webPreferences: { preload: null, partition: null, backgroundThrottling: null, sandbox: null, contextIsolation: null, nodeIntegration: null, webviewTag: null, }, }, cause: new Error("simulated window-open failure"), }); } yield* Ref.update(createdWindows, (windows) => [...windows, outcome]); return outcome; }), main: Ref.get(mainWindow), currentMainOrFirst, focusedMainOrFirst: currentMainOrFirst, setMain: (window) => Ref.set(mainWindow, Option.some(window)), clearMain: () => Ref.set(mainWindow, Option.none()), prepareReveal: () => Effect.succeed(false), reveal: (window) => Ref.update(revealedWindows, (windows) => [...windows, window]), sendAll: () => Effect.void, destroyAll: Effect.void, syncAllAppearance: (sync) => (fallbackWindow ? sync(fallbackWindow) : Effect.void), } satisfies ElectronWindow.ElectronWindow["Service"]; const layer = DesktopWindow.layer.pipe( Layer.provide( Layer.mergeAll( desktopAssetsLayer, desktopEnvironmentLayer, DesktopAppSettings.layerTest(), desktopClientSettingsLayer, desktopServerExposureLayer, electronAppLayer, electronMenuLayer, Layer.succeed(ElectronShell.ElectronShell, { openExternal: () => Effect.succeed(true), openSystemSettings: () => Effect.succeed(true), copyText: () => Effect.void, } satisfies ElectronShell.ElectronShell["Service"]), electronThemeLayer, Layer.succeed(ElectronWindow.ElectronWindow, electronWindowShape), Layer.mock(PreviewManager.PreviewManager)({ getBrowserSession: () => Effect.succeed({} as Electron.Session), setMainWindow: () => Effect.void, isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"), getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"), }), ), ), ); return { layer, createCalls, mainWindow, revealedWindows } as const; }); const captureOne = DesktopSnapShotId.make("11111111-1111-4111-8111-111111111111");const captureTwo = DesktopSnapShotId.make("22222222-2222-4222-8222-222222222222"); describe("DesktopWindow", () => { it.effect("shows native context menus for browser guests and sign-in popups", () => Effect.gen(function* () { const host = makeFakeBrowserWindow(); const popup = makeFakeBrowserWindow(); let focusedContents: unknown = host.window.webContents; const makeContents = () => { const contents = Object.assign(new NodeEvents.EventEmitter(), { isDestroyed: vi.fn(() => false), focus: vi.fn(() => { focusedContents = contents; }), copyImageAt: vi.fn(), replaceMisspelling: vi.fn(), }); return contents; }; const guest = makeContents(); const popupContents = makeContents(); const popupWindow = { ...popup.window, webContents: popupContents }; const menus = yield* Queue.unbounded<{ input: ElectronMenu.ElectronMenuTemplateInput; focusedContents: unknown; }>(); const copiedTexts: string[] = []; const layer = makeTestLayer({ window: host.window, createCount: yield* Ref.make(0), mainWindow: yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()), copiedTexts, onPopupTemplate: (input) => Queue.offer(menus, { input, focusedContents }).pipe(Effect.asVoid), }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")); const attach = host.webContentsListeners.get("did-attach-webview"); assert.isDefined(attach); attach({}, guest); attach({}, guest); guest.emit("did-create-window", popupWindow); guest.emit("did-create-window", popupWindow); for (const [contents, owner] of [ [guest, host.window], [popupContents, popupWindow], ] as const) { const frame = { routingId: 7 } as Electron.WebFrameMain; const preventDefault = vi.fn(); const params = { frame, x: 12, y: 34, misspelledWord: "helo", dictionarySuggestions: ["hello"], linkURL: "", mediaType: "none", editFlags: { canCut: false, canCopy: true, canPaste: true, canSelectAll: true }, }; focusedContents = host.window.webContents; contents.emit("context-menu", { preventDefault }, params); const menu = yield* Queue.take(menus); assert.strictEqual(menu.input.window, owner); assert.strictEqual(menu.input.frame, frame); assert.strictEqual(menu.focusedContents, contents); assert.equal(preventDefault.mock.calls.length, 1); assert.deepEqual( menu.input.template.filter((item) => item.role), [ { role: "cut", enabled: false }, { role: "copy", enabled: true }, { role: "paste", enabled: true }, { role: "selectAll", enabled: true },Lines 1–500 of 1539. Load more
import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; import * as Deferred from "effect/Deferred"; import { DesktopSnapShotId } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Logger from "effect/Logger"; import * as Option from "effect/Option"; import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as References from "effect/References"; import * as Schema from "effect/Schema"; import * as TestClock from "effect/testing/TestClock"; import * as Electron from "electron"; import * as NodeEvents from "node:events"; import { vi } from "vite-plus/test"; vi.mock("electron", async (importOriginal) => ({ ...(await importOriginal<typeof import("electron")>()), session: { fromPartition: vi.fn(() => ({ getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/1.2.3"), setPermissionRequestHandler: vi.fn(), setUserAgent: vi.fn(), })), }, screen: { getAllDisplays: vi.fn(() => [ { bounds: { x: 0, y: 0, width: 1920, height: 1080 }, }, ]), }, })); import * as DesktopAssets from "../app/DesktopAssets.ts"; import * as DesktopConfig from "../app/DesktopConfig.ts"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; import * as DesktopState from "../app/DesktopState.ts"; import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts"; import * as DesktopClientSettings from "../settings/DesktopClientSettings.ts"; import * as ElectronApp from "../electron/ElectronApp.ts"; import * as ElectronMenu from "../electron/ElectronMenu.ts"; import * as ElectronShell from "../electron/ElectronShell.ts"; import * as ElectronTheme from "../electron/ElectronTheme.ts"; import * as ElectronWindow from "../electron/ElectronWindow.ts"; import { MENU_ACTION_CHANNEL, SNAP_SHOT_EVENT_CHANNEL, WINDOW_FULLSCREEN_STATE_CHANNEL, } from "../ipc/channels.ts"; import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts"; import * as DesktopWindow from "./DesktopWindow.ts"; import * as PreviewManager from "../preview/Manager.ts"; const environmentInput = { dirname: "/repo/apps/desktop/dist-electron", homeDirectory: "/Users/alice", platform: "darwin", processArch: "arm64", appVersion: "1.2.3", appPath: "/repo", isPackaged: false, resourcesPath: "/repo/resources", runningUnderArm64Translation: false, } satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; function makeFakeBrowserWindow() { const windowListeners = new Map<string, (...args: readonly unknown[]) => void>(); const webContentsListeners = new Map<string, (...args: readonly unknown[]) => void>(); let zoomLevel = 0; const webContents = { copyImageAt: vi.fn(), focus: vi.fn(), isDestroyed: vi.fn(() => false), getURL: vi.fn(() => "t3code-dev://app/"), getZoomLevel: vi.fn(() => zoomLevel), setZoomLevel: vi.fn((level: number) => { zoomLevel = level; }), isLoadingMainFrame: vi.fn(() => false), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { webContentsListeners.set(eventName, listener); }), once: vi.fn<(eventName: string, listener: (...args: readonly unknown[]) => void) => void>(), openDevTools: vi.fn(), reload: vi.fn(), replaceMisspelling: vi.fn(), send: vi.fn(), setBackgroundThrottling: vi.fn(), setWindowOpenHandler: vi.fn(), }; const window = { close: vi.fn(), focus: vi.fn(), getBounds: vi.fn(() => ({ x: 0, y: 0, width: 1100, height: 780 })), getNormalBounds: vi.fn(() => ({ x: 0, y: 0, width: 1100, height: 780 })), isDestroyed: vi.fn(() => false), isFullScreen: vi.fn(() => false), isMaximized: vi.fn(() => false), isMinimized: vi.fn(() => false), isVisible: vi.fn(() => true), loadURL: vi.fn(() => Promise.resolve()), maximize: vi.fn(), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { windowListeners.set(eventName, listener); }), once: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { windowListeners.set(eventName, listener); }), restore: vi.fn(), setBackgroundColor: vi.fn(), setAutoHideCursor: vi.fn(), setFullScreen: vi.fn(), setOpacity: vi.fn(), setTitle: vi.fn(), setTitleBarOverlay: vi.fn(), show: vi.fn(), webContents, }; return { window: window as unknown as Electron.BrowserWindow, getBounds: window.getBounds, getNormalBounds: window.getNormalBounds, isDestroyed: window.isDestroyed, isFullScreen: window.isFullScreen, isMaximized: window.isMaximized, isMinimized: window.isMinimized, loadURL: window.loadURL, maximize: window.maximize, openDevTools: webContents.openDevTools, reload: webContents.reload, send: webContents.send, setZoomLevel: webContents.setZoomLevel, setBackgroundThrottling: webContents.setBackgroundThrottling, setAutoHideCursor: window.setAutoHideCursor, setFullScreen: window.setFullScreen, setOpacity: window.setOpacity, webContentsListeners, webContentsOnce: webContents.once, windowListeners, }; } const desktopClientSettingsLayer = Layer.mock(DesktopClientSettings.DesktopClientSettings)({ get: Effect.succeed(Option.none()), }); const electronAppLayer = Layer.mock(ElectronApp.ElectronApp)({ quit: Effect.void, }); const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { iconPaths: Effect.succeed({ ico: Option.none<string>(), icns: Option.none<string>(), png: Option.none<string>(), }), resolveResourcePath: () => Effect.succeed(Option.none<string>()), } satisfies DesktopAssets.DesktopAssets["Service"]); const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { getState: Effect.die("unexpected getState"), backendConfig: Effect.succeed({ port: 3773, bindHost: "127.0.0.1", httpBaseUrl: new URL("http://127.0.0.1:3773"), tailscaleServeEnabled: false, tailscaleServePort: 443, }), configureFromSettings: () => Effect.die("unexpected configureFromSettings"), setMode: () => Effect.die("unexpected setMode"), setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"), getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"), } satisfies DesktopServerExposure.DesktopServerExposure["Service"]); const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: () => Effect.void, popupTemplate: () => Effect.void, showContextMenu: () => Effect.succeed(Option.none()), } satisfies ElectronMenu.ElectronMenu["Service"]); const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { shouldUseDarkColors: Effect.succeed(false), setSource: () => Effect.void, onUpdated: () => Effect.void, } satisfies ElectronTheme.ElectronTheme["Service"]); const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe( Layer.provide( Layer.mergeAll( NodeServices.layer, DesktopConfig.layerTest({ T3CODE_PORT: "3773", VITE_DEV_SERVER_URL: "http://127.0.0.1:5733", }), ), ), ); const desktopWindowBoundsEquivalence = Schema.toEquivalence( DesktopAppSettings.DesktopWindowBoundsSchema, ); function makeTestLayer(input: { readonly window: Electron.BrowserWindow; readonly createCount: Ref.Ref<number>; readonly mainWindow: Ref.Ref<Option.Option<Electron.BrowserWindow>>; readonly createdWindowOptions?: Electron.BrowserWindowConstructorOptions[]; readonly desktopSettings?: DesktopAppSettings.DesktopSettings; readonly mainWindowBoundsUpdates?: DesktopAppSettings.DesktopWindowBounds[]; readonly mainWindowMaximizedUpdates?: boolean[]; readonly beforeMainWindowBoundsUpdate?: ( bounds: DesktopAppSettings.DesktopWindowBounds, ) => Effect.Effect<void>; readonly openedExternalUrls?: unknown[]; readonly copiedTexts?: string[]; readonly onPopupTemplate?: (input: ElectronMenu.ElectronMenuTemplateInput) => Effect.Effect<void>; readonly previewZoomReapplies?: number[]; readonly onReveal?: (window: Electron.BrowserWindow) => void; }) { let desktopSettings = input.desktopSettings ?? DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS; const desktopAppSettingsLayer = Layer.succeed(DesktopAppSettings.DesktopAppSettings, { get: Effect.sync(() => desktopSettings), load: Effect.sync(() => desktopSettings), setMainWindowBounds: (bounds, isMaximized) => Effect.gen(function* () { if (input.beforeMainWindowBoundsUpdate) { yield* input.beforeMainWindowBoundsUpdate(bounds); } const changed = desktopSettings.mainWindowBounds === null || !desktopWindowBoundsEquivalence(desktopSettings.mainWindowBounds, bounds) || desktopSettings.mainWindowMaximized !== isMaximized; if (changed) { desktopSettings = { ...desktopSettings, mainWindowBounds: bounds, mainWindowMaximized: isMaximized, }; input.mainWindowBoundsUpdates?.push(bounds); input.mainWindowMaximizedUpdates?.push(isMaximized); } return { settings: desktopSettings, changed }; }), setServerExposureMode: () => Effect.die("unexpected server exposure update"), setTailscaleServe: () => Effect.die("unexpected Tailscale Serve update"), setUpdateChannel: () => Effect.die("unexpected update channel change"), setWslBackendEnabled: () => Effect.die("unexpected WSL backend toggle"), setWslDistro: () => Effect.die("unexpected WSL distro change"), setWslOnly: () => Effect.die("unexpected WSL-only toggle"), applyWslWindowsFallback: Effect.die("unexpected WSL Windows fallback"), applyWslWindowsFallbackInMemory: Effect.die("unexpected WSL Windows fallback"), } satisfies DesktopAppSettings.DesktopAppSettings["Service"]); const electronWindowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { create: (options) => Effect.sync(() => { input.createdWindowOptions?.push(options); }).pipe( Effect.andThen(Ref.update(input.createCount, (count) => count + 1)), Effect.as(input.window), ), main: Ref.get(input.mainWindow), currentMainOrFirst: Ref.get(input.mainWindow), focusedMainOrFirst: Ref.get(input.mainWindow), setMain: (window) => Ref.set(input.mainWindow, Option.some(window)), clearMain: () => Ref.set(input.mainWindow, Option.none()), prepareReveal: () => Effect.succeed(false), reveal: (window) => Effect.sync(() => input.onReveal?.(window)), sendAll: () => Effect.void, destroyAll: Effect.void, syncAllAppearance: (sync) => sync(input.window), } satisfies ElectronWindow.ElectronWindow["Service"]); return DesktopWindow.layer.pipe( Layer.provide( Layer.mergeAll( desktopAssetsLayer, desktopEnvironmentLayer, desktopAppSettingsLayer, desktopClientSettingsLayer, desktopServerExposureLayer, DesktopState.layer, electronAppLayer, Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: () => Effect.void, showContextMenu: () => Effect.succeed(Option.none()), popupTemplate: input.onPopupTemplate ?? (() => Effect.void), }), Layer.succeed(ElectronShell.ElectronShell, { openExternal: (url) => Effect.sync(() => { input.openedExternalUrls?.push(url); return true; }), openSystemSettings: () => Effect.succeed(true), copyText: (text) => Effect.sync(() => { input.copiedTexts?.push(text); }), } satisfies ElectronShell.ElectronShell["Service"]), electronThemeLayer, electronWindowLayer, Layer.mock(PreviewManager.PreviewManager)({ getBrowserSession: () => Effect.succeed({} as Electron.Session), setMainWindow: () => Effect.void, isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"), getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"), reapplyZoom: () => Effect.sync(() => { input.previewZoomReapplies?.push(input.window.webContents.getZoomLevel()); }), }), ), ), ); } // Builds a DesktopWindow over a fake ElectronWindow whose `create` returns the // given outcomes in order (null => simulated open failure), and whose // currentMainOrFirst mirrors the real fallback to the first live window (the // splash, before any main is registered). Reveal targets are recorded so tests // can assert what activation actually surfaced. const makeSplashScenario = (createOutcomes: readonly (Electron.BrowserWindow | null)[]) => Effect.gen(function* () { const createdWindows = yield* Ref.make<Electron.BrowserWindow[]>([]); const createCalls = yield* Ref.make(0); const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()); const revealedWindows = yield* Ref.make<Electron.BrowserWindow[]>([]); const fallbackWindow = createOutcomes.find( (window): window is Electron.BrowserWindow => window !== null, ); const currentMainOrFirst = Effect.gen(function* () { const registered = yield* Ref.get(mainWindow); if (Option.isSome(registered)) { return registered; } const created = yield* Ref.get(createdWindows); return Option.fromNullishOr(created[0] ?? null); }); const electronWindowShape = { create: () => Effect.gen(function* () { const index = yield* Ref.getAndUpdate(createCalls, (count) => count + 1); const outcome = createOutcomes[index] ?? null; if (outcome === null) { return yield* new ElectronWindow.ElectronWindowCreateError({ options: { title: null, width: null, height: null, minWidth: null, minHeight: null, show: null, modal: null, frame: null, transparent: null, backgroundColor: null, webPreferences: { preload: null, partition: null, backgroundThrottling: null, sandbox: null, contextIsolation: null, nodeIntegration: null, webviewTag: null, }, }, cause: new Error("simulated window-open failure"), }); } yield* Ref.update(createdWindows, (windows) => [...windows, outcome]); return outcome; }), main: Ref.get(mainWindow), currentMainOrFirst, focusedMainOrFirst: currentMainOrFirst, setMain: (window) => Ref.set(mainWindow, Option.some(window)), clearMain: () => Ref.set(mainWindow, Option.none()), prepareReveal: () => Effect.succeed(false), reveal: (window) => Ref.update(revealedWindows, (windows) => [...windows, window]), sendAll: () => Effect.void, destroyAll: Effect.void, syncAllAppearance: (sync) => (fallbackWindow ? sync(fallbackWindow) : Effect.void), } satisfies ElectronWindow.ElectronWindow["Service"]; const layer = DesktopWindow.layer.pipe( Layer.provide( Layer.mergeAll( desktopAssetsLayer, desktopEnvironmentLayer, DesktopAppSettings.layerTest(), desktopClientSettingsLayer, desktopServerExposureLayer, electronAppLayer, electronMenuLayer, Layer.succeed(ElectronShell.ElectronShell, { openExternal: () => Effect.succeed(true), openSystemSettings: () => Effect.succeed(true), copyText: () => Effect.void, } satisfies ElectronShell.ElectronShell["Service"]), electronThemeLayer, Layer.succeed(ElectronWindow.ElectronWindow, electronWindowShape), Layer.mock(PreviewManager.PreviewManager)({ getBrowserSession: () => Effect.succeed({} as Electron.Session), setMainWindow: () => Effect.void, isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"), getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"), }), ), ), ); return { layer, createCalls, mainWindow, revealedWindows } as const; }); const captureOne = DesktopSnapShotId.make("11111111-1111-4111-8111-111111111111"); const captureTwo = DesktopSnapShotId.make("22222222-2222-4222-8222-222222222222"); describe("DesktopWindow", () => { it.effect("shows native context menus for browser guests and sign-in popups", () => Effect.gen(function* () { const host = makeFakeBrowserWindow(); const popup = makeFakeBrowserWindow(); let focusedContents: unknown = host.window.webContents; const makeContents = () => { const contents = Object.assign(new NodeEvents.EventEmitter(), { isDestroyed: vi.fn(() => false), focus: vi.fn(() => { focusedContents = contents; }), copyImageAt: vi.fn(), replaceMisspelling: vi.fn(), }); return contents; }; const guest = makeContents(); const popupContents = makeContents(); const popupWindow = { ...popup.window, webContents: popupContents }; const menus = yield* Queue.unbounded<{ input: ElectronMenu.ElectronMenuTemplateInput; focusedContents: unknown; }>(); const copiedTexts: string[] = []; const layer = makeTestLayer({ window: host.window, createCount: yield* Ref.make(0), mainWindow: yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none()), copiedTexts, onPopupTemplate: (input) => Queue.offer(menus, { input, focusedContents }).pipe(Effect.asVoid), }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")); const attach = host.webContentsListeners.get("did-attach-webview"); assert.isDefined(attach); attach({}, guest); attach({}, guest); guest.emit("did-create-window", popupWindow); guest.emit("did-create-window", popupWindow); for (const [contents, owner] of [ [guest, host.window], [popupContents, popupWindow], ] as const) { const frame = { routingId: 7 } as Electron.WebFrameMain; const preventDefault = vi.fn(); const params = { frame, x: 12, y: 34, misspelledWord: "helo", dictionarySuggestions: ["hello"], linkURL: "", mediaType: "none", editFlags: { canCut: false, canCopy: true, canPaste: true, canSelectAll: true }, }; focusedContents = host.window.webContents; contents.emit("context-menu", { preventDefault }, params); const menu = yield* Queue.take(menus); assert.strictEqual(menu.input.window, owner); assert.strictEqual(menu.input.frame, frame); assert.strictEqual(menu.focusedContents, contents); assert.equal(preventDefault.mock.calls.length, 1); assert.deepEqual( menu.input.template.filter((item) => item.role), [ { role: "cut", enabled: false }, { role: "copy", enabled: true }, { role: "paste", enabled: true }, { role: "selectAll", enabled: true },