1192 lines · 41.2 KB · TypeScript
RawDownload
import { it as effectIt } from "@effect/vitest";import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import * as Cause from "effect/Cause";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";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 Path from "effect/Path";import * as Schema from "effect/Schema";import type * as Scope from "effect/Scope";import { TestClock } from "effect/testing";import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";import * as ElectronWindow from "../electron/ElectronWindow.ts";import * as BrowserSession from "./BrowserSession.ts";import * as PreviewManager from "./Manager.ts"; const {  createFromPath,  fromId,  getFocusedWebContents,  mkdir,  showItemInFolder,  webviewSend,  writeFile,  writeImage,} = vi.hoisted(() => ({  createFromPath: vi.fn((): { readonly isEmpty: () => boolean } => ({ isEmpty: () => false })),  fromId: vi.fn(() => null),  getFocusedWebContents: vi.fn(() => null),  mkdir: vi.fn((_path: string) => undefined),  showItemInFolder: vi.fn(),  webviewSend: vi.fn(),  writeFile: vi.fn((_path: string, _data: Uint8Array) => undefined),  writeImage: vi.fn(),})); vi.mock("electron", () => ({  clipboard: {    writeImage,  },  nativeImage: {    createFromPath,  },  shell: {    showItemInFolder,  },  session: {    fromPartition: vi.fn(),  },  webContents: {    fromId,    getFocusedWebContents,  },})); const browserSessionLayer = Layer.succeed(  BrowserSession.BrowserSession,  BrowserSession.BrowserSession.of({    getPartition: () => Effect.succeed("persist:t3code-preview-test"),    isPartition: (partition) => partition.startsWith("persist:t3code-preview-"),    getSession: () => Effect.die("unexpected getSession"),    clearCookies: () => Effect.void,    clearCache: () => Effect.void,  }),); const environmentLayer = Layer.succeed(  DesktopEnvironment.DesktopEnvironment,  DesktopEnvironment.DesktopEnvironment.of({    browserArtifactsDir: "/tmp/t3/dev/browser-artifacts",  } as DesktopEnvironment.DesktopEnvironment["Service"]),); const fileSystemLayer = FileSystem.layerNoop({  makeDirectory: (path) =>    Effect.sync(() => {      mkdir(path);    }),  writeFile: (path, data) =>    Effect.sync(() => {      writeFile(path, data);    }),}); const layer = PreviewManager.layer.pipe(  Layer.provideMerge(browserSessionLayer),  Layer.provideMerge(environmentLayer),  Layer.provideMerge(fileSystemLayer),  Layer.provideMerge(Path.layer),  Layer.provideMerge(Layer.succeed(HostProcessPlatform, "linux")),);const encodePreviewManagerError = Schema.encodeSync(PreviewManager.PreviewManagerError); const withManager = <A>(  use: (    manager: PreviewManager.PreviewManager["Service"],  ) => Effect.Effect<A, PreviewManager.PreviewManagerError, Scope.Scope>,) =>  Effect.gen(function* () {    const manager = yield* PreviewManager.PreviewManager;    return yield* use(manager);  }).pipe(Effect.provide(layer), Effect.scoped); describe("PreviewManager", () => {  beforeEach(() => {    fromId.mockClear();    getFocusedWebContents.mockReset();    getFocusedWebContents.mockReturnValue(null);    mkdir.mockClear();    writeFile.mockClear();    showItemInFolder.mockClear();    writeImage.mockClear();    createFromPath.mockClear();    webviewSend.mockClear();  });   effectIt.effect("reports an unregistered webview as temporarily unavailable", () =>    withManager((manager) =>      Effect.gen(function* () {        expect(yield* manager.automationStatus("tab_1")).toEqual({          available: false,          visible: true,          tabId: "tab_1",          url: null,          title: null,          loading: false,        });         yield* manager.createTab("tab_1");         expect(yield* manager.automationStatus("tab_1")).toEqual({          available: false,          visible: true,          tabId: "tab_1",          url: null,          title: null,          loading: false,        });        expect(fromId).not.toHaveBeenCalled();      }),    ),  );   effectIt.effect("isolates failed state listeners and continues delivery", () => {    const loggedErrors: Array<unknown> = [];    const logger = Logger.make(({ message }) => {      for (const value of Array.isArray(message) ? message : [message]) {        if (typeof value === "object" && value !== null && "cause" in value) {          loggedErrors.push(Cause.squash(value.cause as Cause.Cause<never>));        }      }    });    const deliveryError = new ElectronWindow.ElectronWindowOperationError({      operation: "send-window-message",      platform: "darwin",      windowId: 42,      channel: "preview:state-change",      cause: new Error("renderer unavailable"),    });    const delivered = vi.fn();     return withManager((manager) =>      Effect.gen(function* () {        yield* manager.subscribeStateChanges(() => Effect.die(deliveryError));        yield* manager.subscribeStateChanges((tabId, state) =>          Effect.sync(() => {            delivered(tabId, state);          }),        );         const state = yield* manager.createTab("tab_listener_failure");         expect(delivered).toHaveBeenCalledOnce();        expect(delivered).toHaveBeenCalledWith("tab_listener_failure", state);        expect(loggedErrors).toHaveLength(1);        expect(loggedErrors[0]).toBeInstanceOf(ElectronWindow.ElectronWindowOperationError);        expect(loggedErrors[0]).toMatchObject({          operation: "send-window-message",          windowId: 42,          channel: "preview:state-change",        });      }),    ).pipe(      Effect.provide(        Logger.layer([logger], {          mergeWithExisting: false,        }),      ),    );  });   effectIt.effect("does not swallow state listener interruption", () =>    withManager((manager) =>      Effect.gen(function* () {        const exit = yield* Effect.scoped(          Effect.gen(function* () {            yield* manager.subscribeStateChanges(() => Effect.interrupt);            return yield* Effect.exit(manager.createTab("tab_interrupted_listener"));          }),        );         expect(Exit.isFailure(exit)).toBe(true);        if (Exit.isFailure(exit)) {          expect(Cause.hasInterrupts(exit.cause)).toBe(true);        }      }),    ),  );   effectIt.effect("queues navigation until the webview registers", () =>    withManager((manager) =>      Effect.gen(function* () {        const loadURL = vi.fn(async () => undefined);        const listeners = new Map<string, (...args: never[]) => void>();        fromId.mockReturnValue({          id: 42,          isDestroyed: () => false,          getType: () => "webview",          getURL: () => "about:blank",          getTitle: () => "",          isLoading: () => false,          getZoomFactor: () => 1,          setZoomFactor: vi.fn(),          loadURL,          on: vi.fn((event: string, listener: (...args: never[]) => void) => {            listeners.set(event, listener);          }),          off: vi.fn(),          ipc: { on: vi.fn(), off: vi.fn() },          send: webviewSend,          navigationHistory: { canGoBack: () => false, canGoForward: () => false },          setWindowOpenHandler: vi.fn(),          debugger: {            isAttached: () => false,            attach: vi.fn(),            sendCommand: vi.fn(async () => undefined),            on: vi.fn(),            off: vi.fn(),          },        } as never);         yield* manager.navigate("tab_pending", "localhost:3200");         expect(yield* manager.automationStatus("tab_pending")).toEqual({          available: false,          visible: true,          tabId: "tab_pending",          url: "http://localhost:3200/",          title: "",          loading: true,        });         yield* manager.registerWebview("tab_pending", 42);        yield* Effect.yieldNow;         expect(loadURL).toHaveBeenCalledOnce();        expect(loadURL).toHaveBeenCalledWith("http://localhost:3200/");      }),    ),  );   effectIt.effect("mirrors Electron's effective zoom across registration and navigation", () =>    withManager((manager) =>      Effect.gen(function* () {        let effectiveZoom = 0.9;        let zoomReadable = true;        let url = "https://example.com";        const listeners = new Map<string, (...args: unknown[]) => void>();        const setZoomFactor = vi.fn();        fromId.mockReturnValue({          id: 42,          isDestroyed: () => false,          getType: () => "webview",          getURL: () => url,          getTitle: () => "Example",          isLoading: () => false,          getZoomFactor: () => {            if (!zoomReadable) throw new Error("zoom unavailable");            return effectiveZoom;          },          setZoomFactor,          on: vi.fn((event: string, listener: (...args: unknown[]) => void) => {            listeners.set(event, listener);          }),          off: vi.fn(),          ipc: { on: vi.fn(), off: vi.fn() },          send: webviewSend,          navigationHistory: { canGoBack: () => false, canGoForward: () => false },          setWindowOpenHandler: vi.fn(),          debugger: {            isAttached: () => false,            attach: vi.fn(),            sendCommand: vi.fn(async () => undefined),            on: vi.fn(),            off: vi.fn(),          },        } as never);        const states: PreviewManager.PreviewTabState[] = [];         yield* manager.subscribeStateChanges((_tabId, state) =>          Effect.sync(() => {            states.push(state);          }),        );        yield* manager.createTab("tab_zoom");        yield* manager.registerWebview("tab_zoom", 42);         expect(states.at(-1)?.zoomFactor).toBe(0.9);        expect(setZoomFactor).not.toHaveBeenCalled();         effectiveZoom = 1.25;        listeners.get("did-navigate")?.();        yield* Effect.yieldNow;         expect(states.at(-1)?.zoomFactor).toBe(1.25);        expect(setZoomFactor).not.toHaveBeenCalled();         zoomReadable = false;        url = "https://example.com/after-zoom-read-failed";        listeners.get("did-navigate")?.();        yield* Effect.yieldNow;         expect(states.at(-1)?.navStatus).toEqual({          kind: "Success",          url,          title: "Example",        });        expect(states.at(-1)?.zoomFactor).toBe(1.25);         const replacementSetZoomFactor = vi.fn();        fromId.mockReturnValue({          id: 43,          isDestroyed: () => false,          getType: () => "webview",          getURL: () => url,          getTitle: () => "Example",          isLoading: () => false,          getZoomFactor: () => 1,          setZoomFactor: replacementSetZoomFactor,          on: vi.fn(),          off: vi.fn(),          ipc: { on: vi.fn(), off: vi.fn() },          send: webviewSend,          navigationHistory: { canGoBack: () => false, canGoForward: () => false },          setWindowOpenHandler: vi.fn(),          debugger: {            isAttached: () => false,            attach: vi.fn(),            sendCommand: vi.fn(async () => undefined),            on: vi.fn(),            off: vi.fn(),          },        } as never);         yield* manager.registerWebview("tab_zoom", 43);         expect(replacementSetZoomFactor).toHaveBeenCalledWith(1.25);        expect(states.at(-1)?.zoomFactor).toBe(1.25);      }),    ),  );   effectIt.effect("emulates prefers-color-scheme and re-applies it across webview swaps", () =>    withManager((manager) =>      Effect.gen(function* () {        const makeWebContents = (id: number) => {          const sendCommand = vi.fn(async () => undefined);          return {            sendCommand,            wc: {              id,              isDestroyed: () => false,              isDevToolsOpened: () => false,              getType: () => "webview",              getURL: () => "https://example.com",              getTitle: () => "Example",              isLoading: () => false,              getZoomFactor: () => 1,              setZoomFactor: vi.fn(),              on: vi.fn(),              off: vi.fn(),              ipc: { on: vi.fn(), off: vi.fn() },              send: webviewSend,              navigationHistory: { canGoBack: () => false, canGoForward: () => false },              setWindowOpenHandler: vi.fn(),              debugger: {                isAttached: () => false,                attach: vi.fn(),                sendCommand,                on: vi.fn(),                off: vi.fn(),              },            } as never,          };        };        const first = makeWebContents(42);        fromId.mockReturnValue(first.wc);        const states: PreviewManager.PreviewTabState[] = [];         yield* manager.subscribeStateChanges((_tabId, state) =>          Effect.sync(() => {            states.push(state);          }),        );        yield* manager.createTab("tab_scheme");        yield* manager.registerWebview("tab_scheme", 42);        yield* Effect.yieldNow;         yield* manager.setColorScheme("tab_scheme", "dark");         expect(first.sendCommand).toHaveBeenCalledWith("Emulation.setEmulatedMedia", {          features: [{ name: "prefers-color-scheme", value: "dark" }],        });        expect(states.at(-1)?.colorScheme).toBe("dark");         const replacement = makeWebContents(43);        fromId.mockReturnValue(replacement.wc);        yield* manager.registerWebview("tab_scheme", 43);        yield* Effect.yieldNow;         expect(replacement.sendCommand).toHaveBeenCalledWith("Emulation.setEmulatedMedia", {          features: [{ name: "prefers-color-scheme", value: "dark" }],        });        expect(states.at(-1)?.colorScheme).toBe("dark");         yield* manager.setColorScheme("tab_scheme", "system");         expect(replacement.sendCommand).toHaveBeenCalledWith("Emulation.setEmulatedMedia", {          features: [{ name: "prefers-color-scheme", value: "" }],        });        expect(states.at(-1)?.colorScheme).toBe("system");      }),    ),  );   effectIt.effect("keeps a main-frame load failure visible until a retry starts", () =>    withManager((manager) =>      Effect.gen(function* () {        const url = "http://localhost:5733/";        let loading = false;        const listeners = new Map<string, (...args: unknown[]) => void>();        fromId.mockReturnValue({          id: 42,          isDestroyed: () => false,          getType: () => "webview",          getURL: () => url,          getTitle: () => "localhost:5733",          isLoading: () => loading,          getZoomFactor: () => 1,          setZoomFactor: vi.fn(),          on: vi.fn((event: string, listener: (...args: unknown[]) => void) => {            listeners.set(event, listener);          }),          off: vi.fn(),          ipc: { on: vi.fn(), off: vi.fn() },          send: webviewSend,          navigationHistory: { canGoBack: () => false, canGoForward: () => false },          setWindowOpenHandler: vi.fn(),          debugger: {            isAttached: () => false,            attach: vi.fn(),            sendCommand: vi.fn(async () => undefined),            on: vi.fn(),            off: vi.fn(),          },        } as never);        const statuses: PreviewManager.PreviewNavStatus[] = [];         yield* manager.subscribeStateChanges((_tabId, state) =>          Effect.sync(() => {            statuses.push(state.navStatus);          }),        );        yield* manager.createTab("tab_failed");        yield* manager.registerWebview("tab_failed", 42);         listeners.get("did-fail-load")?.(          {},          -105,          "ERR_NAME_NOT_RESOLVED",          "https://missing-frame.example/",          false,        );        yield* Effect.yieldNow;        expect(statuses.at(-1)?.kind).toBe("Success");         loading = true;        listeners.get("did-start-loading")?.();        yield* Effect.yieldNow;        expect(statuses.at(-1)?.kind).toBe("Loading");         loading = false;        listeners.get("did-fail-load")?.({}, -102, "ERR_CONNECTION_REFUSED", url, true);        listeners.get("did-stop-loading")?.();        listeners.get("page-title-updated")?.();

Lines 1–500 of 1192.