1099 lines · 43.1 KB · TypeScript
RawDownload
import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, describe, it } from "@effect/vitest";import * as Deferred from "effect/Deferred";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 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 { 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 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<string, (...args: readonly unknown[]) => void>();  const webContentsListeners = new Map<string, (...args: readonly unknown[]) => 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(),    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(),    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,    setAutoHideCursor: window.setAutoHideCursor,    webContentsListeners,    windowListeners,  };} 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[];}) {  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()),    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,        desktopAppSettingsLayer,        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<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,                  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,          DesktopAppSettings.layerTest(),          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("restores bounds only when the window fits within a connected display", () => {    const persistedBounds = { x: 2040, y: 80, width: 1320, height: 880 };    const displays = [      { x: 0, y: 0, width: 1920, height: 1080 },      { x: 1920, y: 0, width: 2560, height: 1440 },    ];     assert.deepEqual(      DesktopWindow.resolveInitialMainWindowBounds(persistedBounds, displays),      persistedBounds,    );    assert.deepEqual(      DesktopWindow.resolveInitialMainWindowBounds(persistedBounds, [displays[0]!]),      DesktopAppSettings.DEFAULT_MAIN_WINDOW_SIZE,    );  });   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.Option<Electron.BrowserWindow>>(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.equal(createdWindowOptions[0]?.width, 1100);        assert.equal(createdWindowOptions[0]?.height, 780);        assert.isUndefined(createdWindowOptions[0]?.x);        assert.isUndefined(createdWindowOptions[0]?.y);        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("uses the persisted main window bounds when opening the window", () =>    Effect.gen(function* () {      const fakeWindow = makeFakeBrowserWindow();      const createCount = yield* Ref.make(0);      const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());      const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = [];      const layer = makeTestLayer({        window: fakeWindow.window,        createCount,        mainWindow,        createdWindowOptions,        desktopSettings: {          ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,          mainWindowBounds: { x: 120, y: 80, width: 1320, height: 880 },        },      });       yield* Effect.gen(function* () {        const desktopWindow = yield* DesktopWindow.DesktopWindow;        yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773"));         assert.equal(createdWindowOptions[0]?.width, 1320);        assert.equal(createdWindowOptions[0]?.height, 880);        assert.equal(createdWindowOptions[0]?.x, 120);        assert.equal(createdWindowOptions[0]?.y, 80);      }).pipe(Effect.provide(layer));    }),  );   it.effect("restores the persisted maximized state", () =>    Effect.gen(function* () {      const fakeWindow = makeFakeBrowserWindow();      const createCount = yield* Ref.make(0);      const mainWindow = yield* Ref.make<Option.Option<Electron.BrowserWindow>>(Option.none());      const layer = makeTestLayer({        window: fakeWindow.window,        createCount,        mainWindow,        desktopSettings: {          ...DesktopAppSettings.DEFAULT_DESKTOP_SETTINGS,          mainWindowBounds: { x: 120, y: 80, width: 1320, height: 880 },          mainWindowMaximized: true,        },      });       yield* Effect.gen(function* () {        const desktopWindow = yield* DesktopWindow.DesktopWindow;        yield* desktopWindow.handleBackendReady(new URL("http://127.0.0.1:3773"));         assert.equal(fakeWindow.maximize.mock.calls.length, 0);        const readyToShow = fakeWindow.windowListeners.get("ready-to-show");        if (!readyToShow) {          return yield* Effect.die("window ready-to-show listener was not registered");        }        readyToShow();        assert.equal(fakeWindow.maximize.mock.calls.length, 1);      }).pipe(Effect.provide(layer));    }),  );   it.effect("debounces move and resize bounds updates", () =>    Effect.gen(function* () {

Lines 1–500 of 1099.