import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; import * as TestClock from "effect/testing/TestClock"; import type * as Electron from "electron"; import { vi } from "vite-plus/test"; vi.mock("electron", async (importOriginal) => ({ ...(await importOriginal()), session: { fromPartition: vi.fn(() => ({ getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/1.2.3"), setPermissionRequestHandler: vi.fn(), setUserAgent: vi.fn(), })), }, })); 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 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, 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 void>(); const webContentsListeners = new Map void>(); const webContents = { copyImageAt: vi.fn(), getURL: vi.fn(() => "t3code-dev://app/"), isLoadingMainFrame: vi.fn(() => false), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { webContentsListeners.set(eventName, listener); }), once: vi.fn(), openDevTools: vi.fn(), reload: vi.fn(), replaceMisspelling: vi.fn(), send: vi.fn(), setWindowOpenHandler: vi.fn(), }; const window = { close: vi.fn(), focus: vi.fn(), isDestroyed: vi.fn(() => false), isFullScreen: vi.fn(() => false), isMinimized: vi.fn(() => false), isVisible: vi.fn(() => true), loadURL: vi.fn(() => Promise.resolve()), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { windowListeners.set(eventName, listener); }), once: vi.fn(), restore: vi.fn(), setBackgroundColor: vi.fn(), setAutoHideCursor: vi.fn(), setTitle: vi.fn(), setTitleBarOverlay: vi.fn(), show: vi.fn(), webContents, }; return { window: window as unknown as Electron.BrowserWindow, loadURL: window.loadURL, openDevTools: webContents.openDevTools, reload: webContents.reload, send: webContents.send, setAutoHideCursor: window.setAutoHideCursor, webContentsListeners, windowListeners, }; } const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { iconPaths: Effect.succeed({ ico: Option.none(), icns: Option.none(), png: Option.none(), }), resolveResourcePath: () => Effect.succeed(Option.none()), } 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", }), ), ), ); function makeTestLayer(input: { readonly window: Electron.BrowserWindow; readonly createCount: Ref.Ref; readonly mainWindow: Ref.Ref>; readonly createdWindowOptions?: Electron.BrowserWindowConstructorOptions[]; readonly openedExternalUrls?: unknown[]; }) { 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()), reveal: () => Effect.void, 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, desktopServerExposureLayer, DesktopState.layer, electronMenuLayer, Layer.succeed(ElectronShell.ElectronShell, { openExternal: (url) => Effect.sync(() => { input.openedExternalUrls?.push(url); return true; }), copyText: () => Effect.void, } 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"), }), ), ), ); } // 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([]); const createCalls = yield* Ref.make(0); const mainWindow = yield* Ref.make>(Option.none()); const revealedWindows = yield* Ref.make([]); 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, 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()), 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, desktopServerExposureLayer, electronMenuLayer, Layer.succeed(ElectronShell.ElectronShell, { openExternal: () => 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; }); describe("DesktopWindow", () => { it("recognizes only same-origin renderer navigations", () => { assert.isTrue( DesktopWindow.isSameOriginRendererNavigation({ applicationUrl: "t3code://app/", navigationUrl: "t3code://app/settings/connections", }), ); assert.isFalse( DesktopWindow.isSameOriginRendererNavigation({ applicationUrl: "t3code://app/", navigationUrl: "https://accounts.microsoft.com/oauth", }), ); assert.isFalse( DesktopWindow.isSameOriginRendererNavigation({ applicationUrl: "t3code://app/", navigationUrl: "not a url", }), ); }); it.effect("does not open a development window until the backend is ready", () => Effect.gen(function* () { const fakeWindow = makeFakeBrowserWindow(); const createCount = yield* Ref.make(0); const mainWindow = yield* Ref.make>(Option.none()); const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = []; const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, createdWindowOptions, }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.activate; assert.equal(yield* Ref.get(createCount), 0); yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")); assert.equal(yield* Ref.get(createCount), 1); assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor); assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]); assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["t3code-dev://app/"]); assert.equal(fakeWindow.openDevTools.mock.calls.length, 1); }).pipe(Effect.provide(layer)); }), ); it.effect("publishes native macOS fullscreen changes to the renderer", () => Effect.gen(function* () { const fakeWindow = makeFakeBrowserWindow(); const createCount = yield* Ref.make(0); const mainWindow = yield* Ref.make>(Option.none()); const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")); const enterFullscreen = fakeWindow.windowListeners.get("enter-full-screen"); const leaveFullscreen = fakeWindow.windowListeners.get("leave-full-screen"); if (!enterFullscreen || !leaveFullscreen) { return yield* Effect.die("fullscreen listeners were not registered"); } enterFullscreen(); leaveFullscreen(); assert.deepEqual(fakeWindow.send.mock.calls, [ [WINDOW_FULLSCREEN_STATE_CHANNEL, true], [WINDOW_FULLSCREEN_STATE_CHANNEL, false], ]); }).pipe(Effect.provide(layer)); }), ); it.effect("recovers when the development renderer is temporarily unreachable", () => Effect.gen(function* () { const fakeWindow = makeFakeBrowserWindow(); const createCount = yield* Ref.make(0); const mainWindow = yield* Ref.make>(Option.none()); const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")); const didFailLoad = fakeWindow.webContentsListeners.get("did-fail-load"); const didFinishLoad = fakeWindow.webContentsListeners.get("did-finish-load"); if (!didFailLoad || !didFinishLoad) { return yield* Effect.die("renderer load listeners were not registered"); } didFailLoad({}, -9, "ERR_UNEXPECTED", "t3code-dev://app/", true); assert.equal(fakeWindow.loadURL.mock.calls.length, 1); yield* TestClock.adjust(100); assert.deepEqual(fakeWindow.loadURL.mock.calls, [ ["t3code-dev://app/"], ["t3code-dev://app/"], ]); assert.equal(fakeWindow.reload.mock.calls.length, 0); didFailLoad({}, -9, "ERR_UNEXPECTED", "t3code-dev://app/", true); didFinishLoad(); yield* TestClock.adjust(250); assert.equal(fakeWindow.loadURL.mock.calls.length, 2); assert.equal(fakeWindow.reload.mock.calls.length, 0); }).pipe(Effect.provide(layer)); }), ); it("retries only transient failures for the development renderer", () => { assert.isTrue( DesktopWindow.isRetryableDevelopmentRendererLoadFailure({ applicationUrl: "t3code-dev://app/", errorCode: -102, isMainFrame: true, validatedUrl: "t3code-dev://app/", }), ); assert.isFalse( DesktopWindow.isRetryableDevelopmentRendererLoadFailure({ applicationUrl: "t3code-dev://app/", errorCode: -3, isMainFrame: true, validatedUrl: "t3code-dev://app/", }), ); assert.isFalse( DesktopWindow.isRetryableDevelopmentRendererLoadFailure({ applicationUrl: "t3code-dev://app/", errorCode: -102, isMainFrame: true, validatedUrl: "https://example.com/", }), ); }); it.effect("opens safe off-origin renderer navigations in the system browser", () => Effect.gen(function* () { const fakeWindow = makeFakeBrowserWindow(); const createCount = yield* Ref.make(0); const mainWindow = yield* Ref.make>(Option.none()); const openedExternalUrls: unknown[] = []; const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, openedExternalUrls, }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")); const willNavigate = fakeWindow.webContentsListeners.get("will-navigate"); if (!willNavigate) { return yield* Effect.die("will-navigate listener was not registered"); } let prevented = false; willNavigate( { preventDefault: () => { prevented = true; }, }, "https://accounts.microsoft.com/oauth", ); yield* Effect.promise(() => Promise.resolve()); assert.isTrue(prevented); assert.deepEqual(openedExternalUrls, ["https://accounts.microsoft.com/oauth"]); }).pipe(Effect.provide(layer)); }), ); it.effect( "retries opening the real main on activate when a failed post-readiness open left only the splash", () => Effect.gen(function* () { const splash = makeFakeBrowserWindow(); const main = makeFakeBrowserWindow(); // create #1 -> splash, #2 -> fails (the pool swallows this post-readiness // window-open error), #3 -> the real main on activate's retry. const scenario = yield* makeSplashScenario([splash.window, null, main.window]); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; // 1. WSL-only boot shows the connecting splash. yield* desktopWindow.showConnectingSplash; assert.equal(yield* Ref.get(scenario.createCalls), 1); // 2. Backend reports ready, but opening the real main fails. The pool // swallows that error in production, so handleBackendReady fails // here without a registered main window -- only the splash is open. const readyExit = yield* Effect.exit( desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")), ); assert.equal(readyExit._tag, "Failure"); assert.equal(yield* Ref.get(scenario.createCalls), 2); assert.isTrue(Option.isNone(yield* Ref.get(scenario.mainWindow))); // 3. Activating must not mistake the splash for the main window: it // retries the open and brings up the real main instead of leaving // the user stranded on "Connecting to WSL". yield* desktopWindow.activate; assert.equal(yield* Ref.get(scenario.createCalls), 3); const registeredMain = yield* Ref.get(scenario.mainWindow); assert.isTrue(Option.isSome(registeredMain)); assert.equal(Option.getOrThrow(registeredMain), main.window); }).pipe(Effect.provide(scenario.layer)); }), ); it.effect( "re-reveals the connecting splash on activate while the backend is still cold-booting", () => Effect.gen(function* () { const splash = makeFakeBrowserWindow(); // Only the splash is ever created; the backend never reports ready. const scenario = yield* makeSplashScenario([splash.window]); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.showConnectingSplash; assert.equal(yield* Ref.get(scenario.createCalls), 1); // Taskbar/dock activation during cold boot must bring the splash back // rather than no-op and leave it hidden until the backend finishes. yield* desktopWindow.activate; assert.equal(yield* Ref.get(scenario.createCalls), 1); assert.deepEqual(yield* Ref.get(scenario.revealedWindows), [splash.window]); }).pipe(Effect.provide(scenario.layer)); }), ); it.effect("does not dispatch menu actions to the splash before the backend is ready", () => Effect.gen(function* () { const splash = makeFakeBrowserWindow(); const main = makeFakeBrowserWindow(); const scenario = yield* makeSplashScenario([splash.window, main.window]); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.showConnectingSplash; yield* desktopWindow.dispatchMenuAction("open-settings"); assert.equal(yield* Ref.get(scenario.createCalls), 1); assert.equal(splash.send.mock.calls.length, 0); assert.equal(main.send.mock.calls.length, 0); }).pipe(Effect.provide(scenario.layer)); }), ); it.effect("dispatches menu actions after backend readiness when no main window exists", () => Effect.gen(function* () { const splash = makeFakeBrowserWindow(); const main = makeFakeBrowserWindow(); const scenario = yield* makeSplashScenario([splash.window, null, main.window]); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.showConnectingSplash; const readyExit = yield* Effect.exit( desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773")), ); assert.equal(readyExit._tag, "Failure"); yield* desktopWindow.dispatchMenuAction("open-settings"); assert.equal(yield* Ref.get(scenario.createCalls), 3); assert.deepEqual(main.send.mock.calls, [[MENU_ACTION_CHANNEL, "open-settings"]]); }).pipe(Effect.provide(scenario.layer)); }), ); });