3650 lines · 123.0 KB · TypeScript
RawDownload
import { assert, it } from "@effect/vitest";import {  DEFAULT_CLIENT_SETTINGS,  DesktopPendingSnapShot,  type ClientSettings,  type DesktopSnapShotEvent,} from "@t3tools/contracts";import * as Crypto from "effect/Crypto";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as FileSystem from "effect/FileSystem";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 PlatformError from "effect/PlatformError";import * as Schema from "effect/Schema";import * as TestClock from "effect/testing/TestClock";import type * as Electron from "electron";import type { PortalShortcutState } from "./PortalCaptureShortcut.ts";import { beforeEach, vi } from "vite-plus/test"; beforeEach(() => {  portalShortcutInstances.length = 0;  nextPortalState.value = undefined;  vi.stubEnv("NIRI_SOCKET", "");  vi.stubEnv("XDG_CURRENT_DESKTOP", "test-desktop");  transitionCapturePageMock.mockReset().mockResolvedValue(undefined);  transitionSnapshotMock.mockReset().mockResolvedValue(undefined);  prepareCaptureRevealMock.mockReset();}); const {  activeWindowMock,  animationSettingsMock,  accessibilityProcessWarmMock,  accessibilityProcessCloseMock,  accessibilityProcessCoolMock,  accessibilityProcessReadMock,  accessibilityByPidMock,  accessibilityForegroundMock,  accessibilityTrustedMock,  allWindowsMock,  flashWindows,  focusedWindowMock,  getFileIconMock,  getSourcesMock,  macCaptureMock,  linuxCaptureMock,  linuxBackendMock,  niriShortcutMock,  niriShortcutStopMock,  mediaAccessStatusMock,  openExternalMock,  registerShortcutMock,  unregisterShortcutMock,  portalShortcutInstances,  prepareCaptureRevealMock,  nextPortalState,  regionCaptureMock,  screenToDipRectMock,  shortcutForkArgs,  shortcutForkOptions,  shortcutProcesses,  spawnedPollers,  thumbnailFromPathMock,  transitionCapturePageMock,  transitionScriptState,  transitionShowMock,  transitionSnapshotMock,} = vi.hoisted(() => ({  activeWindowMock: vi.fn(),  animationSettingsMock: vi.fn(() => ({    prefersReducedMotion: true,    shouldRenderRichAnimation: false,  })),  accessibilityProcessWarmMock: vi.fn(),  accessibilityProcessCloseMock: vi.fn(),  accessibilityProcessCoolMock: vi.fn(),  accessibilityProcessReadMock: vi.fn<    (request: import("./SnapShotAccessibility.ts").SnapShotAccessibilityRequest) => {      started: Promise<void>;      result: Promise<        import("./SnapShotAccessibility.ts").CapturedWindowAccessibilityContext | undefined      >;    }  >(),  accessibilityByPidMock: vi.fn(),  accessibilityForegroundMock: vi.fn(),  accessibilityTrustedMock: vi.fn((_prompt = false) => true),  allWindowsMock: vi.fn(    () =>      [] as Array<{        getBounds: () => Electron.Rectangle;        isDestroyed: () => boolean;      }>,  ),  flashWindows: [] as Array<{    bounds: Electron.Rectangle | null;    destroyed: boolean;    kind: "base" | "browser";    loadCount: number;    loadedUrls: Array<string>;    opacities: Array<number>;    options: Electron.BrowserWindowConstructorOptions;    scripts: Array<string>;    capturedRegions: Array<Electron.Rectangle>;    showCount: number;    resizeCount: number;    alwaysOnTopCalls: Array<[boolean, string | undefined]>;  }>,  focusedWindowMock: vi.fn(),  getFileIconMock: vi.fn(),  getSourcesMock: vi.fn(),  macCaptureMock: vi.fn(),  linuxCaptureMock: vi.fn<    () => Promise<import("./LinuxSnapShot.ts").LinuxWindowSnapshot | undefined>  >(async () => undefined),  linuxBackendMock: vi.fn(async () => "picker"),  niriShortcutStopMock: vi.fn(),  niriShortcutMock:    vi.fn<(appId: string, trigger: () => void, fail: () => void) => Promise<() => void>>(),  mediaAccessStatusMock: vi.fn(() => "not-determined"),  openExternalMock: vi.fn(() => Promise.resolve()),  registerShortcutMock: vi.fn(),  unregisterShortcutMock: vi.fn(),  nextPortalState: { value: undefined as PortalShortcutState | undefined },  portalShortcutInstances: [] as Array<{    state: PortalShortcutState;    onCapture: () => Promise<void>;    onStateChanged: () => void;    close: ReturnType<typeof vi.fn>;    configure: ReturnType<typeof vi.fn>;    hasSession: boolean;  }>,  prepareCaptureRevealMock: vi.fn(),  regionCaptureMock:    vi.fn<      (region: Electron.Rectangle) => Promise<{ width: number; height: number; png: Buffer }>    >(),  screenToDipRectMock: vi.fn((_window: unknown, bounds: Electron.Rectangle) => bounds),  shortcutForkArgs: [] as Array<ReadonlyArray<string>>,  shortcutForkOptions: [] as Array<{ env?: NodeJS.ProcessEnv }>,  shortcutProcesses: [] as Array<{    emit: (event: string, value?: unknown) => void;    kill: ReturnType<typeof vi.fn>;    on: (event: string, listener: (value: unknown) => void) => unknown;    once: (event: string, listener: (value: unknown) => void) => unknown;  }>,  spawnedPollers: [] as Array<{    args: ReadonlyArray<string>;    kill: ReturnType<typeof vi.fn>;    emitStderr: (text: string) => void;    emitExit: (code: number) => void;  }>,  thumbnailFromPathMock: vi.fn(),  transitionCapturePageMock: vi.fn<() => Promise<void>>(),  transitionScriptState: {    rejectFlight: false,    heldFlights: null as Array<() => void> | null,  },  transitionShowMock: vi.fn(),  transitionSnapshotMock: vi.fn<() => Promise<void>>(),})); vi.mock("./SnapShotAccessibilityProcess.ts", () => ({  makeSnapShotAccessibilityProcessPool: () => ({    warm: accessibilityProcessWarmMock,    cool: accessibilityProcessCoolMock,    read: accessibilityProcessReadMock,    close: accessibilityProcessCloseMock,  }),}));vi.mock("./ActiveWindow.ts", () => ({ activeWindow: activeWindowMock }));vi.mock("./RegionSnapShot.ts", async (original) => ({  ...(await original<typeof import("./RegionSnapShot.ts")>()),  makeRegionSnapShotPool: () => ({    warm: vi.fn(),    cool: vi.fn(),    close: vi.fn(),    capture: regionCaptureMock,  }),}));vi.mock("./WindowsCaptureFeedback.ts", () => ({  showWindowsCaptureOverlay: (window: Electron.BaseWindow) => window.showInactive(),}));vi.mock("./MacSnapShot.ts", () => ({ captureMacWindowSnapshot: macCaptureMock }));vi.mock("./LinuxSnapShot.ts", () => ({  captureLinuxWindow: linuxCaptureMock,  getLinuxCaptureSupport: async () => ({    linuxBackend: await linuxBackendMock(),    linuxFeedbackAvailable: false,  }),}));vi.mock("./NiriCaptureShortcut.ts", async (original) => ({  ...(await original<typeof import("./NiriCaptureShortcut.ts")>()),  startNiriCaptureShortcut: niriShortcutMock,}));vi.mock("./PortalCaptureShortcut.ts", async (original) => ({  ...(await original<typeof import("./PortalCaptureShortcut.ts")>()),  PortalCaptureShortcut: class {    state: PortalShortcutState = nextPortalState.value ?? {      shortcutRegistered: true,      shortcutPending: false,      shortcutLabel: "Ctrl+Shift+2",      shortcutMessage: "Desktop shortcut: Ctrl+Shift+2",    };    close = vi.fn();    configure = vi.fn(async () => {});    hasSession = true;    onCapture: () => Promise<void>;    onStateChanged: () => void;    constructor(      _appId: string,      _shortcut: unknown,      onCapture: () => Promise<void>,      onStateChanged: () => void,    ) {      this.onCapture = onCapture;      this.onStateChanged = onStateChanged;      portalShortcutInstances.push(this);    }  },}));vi.mock("node:child_process", () => ({  spawn: (_command: string, args: ReadonlyArray<string>) => {    const stderrListeners: Array<(chunk: Buffer) => void> = [];    const onceListeners = new Map<string, Array<(value?: unknown) => void>>();    const record = {      args,      kill: vi.fn(() => true),      emitStderr: (text: string) => {        for (const listener of stderrListeners) listener(Buffer.from(text));      },      emitExit: (code: number) => {        for (const listener of onceListeners.get("exit") ?? []) listener(code);      },    };    spawnedPollers.push(record);    const child = {      stderr: {        on: (_event: "data", listener: (chunk: Buffer) => void) => {          stderrListeners.push(listener);          return child;        },      },      once: (event: string, listener: (value?: unknown) => void) => {        onceListeners.set(event, [...(onceListeners.get(event) ?? []), listener]);        return child;      },      kill: record.kill,    };    queueMicrotask(() => record.emitStderr("ready\n"));    return child;  },  fork: (_path: string, args: ReadonlyArray<string>, options: { env?: NodeJS.ProcessEnv }) => {    const listeners = new Map<string, Array<(value: unknown) => void>>();    const process = {      emit: (event: string, value?: unknown) => {        for (const listener of listeners.get(event) ?? []) listener(value);      },      kill: vi.fn(() => true),      on: (event: string, listener: (value: unknown) => void) => {        listeners.set(event, [...(listeners.get(event) ?? []), listener]);        return process;      },      once: (event: string, listener: (value: unknown) => void) => {        const wrapped = (value: unknown) => {          listeners.set(            event,            (listeners.get(event) ?? []).filter((candidate) => candidate !== wrapped),          );          listener(value);        };        listeners.set(event, [...(listeners.get(event) ?? []), wrapped]);        return process;      },    };    shortcutForkArgs.push(args);    shortcutForkOptions.push(options);    shortcutProcesses.push(process);    queueMicrotask(() => process.emit("message", "ready"));    return process;  },}));vi.mock("electron", () => {  class BaseWindow {    protected readonly state: (typeof flashWindows)[number];     constructor(options: Electron.BrowserWindowConstructorOptions) {      this.state = {        bounds:          options.x === undefined ||          options.y === undefined ||          options.width === undefined ||          options.height === undefined            ? null            : { x: options.x, y: options.y, width: options.width, height: options.height },        destroyed: false,        kind: "base",        loadCount: 0,        loadedUrls: [],        opacities: options.opacity === undefined ? [] : [options.opacity],        options,        scripts: [],        capturedRegions: [],        showCount: 0,        resizeCount: 0,        alwaysOnTopCalls: [],      };      flashWindows.push(this.state);    }     destroy() {      this.state.destroyed = true;    }     getBounds() {      return this.state.bounds;    }     isDestroyed() {      return this.state.destroyed;    }     setBounds(bounds: Electron.Rectangle) {      this.state.resizeCount++;      this.state.bounds = bounds;    }     setIgnoreMouseEvents() {}     setOpacity(opacity: number) {      this.state.opacities.push(opacity);    }     setAlwaysOnTop(flag: boolean, level?: string) {      this.state.alwaysOnTopCalls.push([flag, level]);    }     showInactive() {      const shownBounds = transitionShowMock(this.state.bounds);      if (shownBounds !== undefined) this.state.bounds = shownBounds;      this.state.showCount += 1;    }  }   class BrowserWindow extends BaseWindow {    static getFocusedWindow() {      return focusedWindowMock();    }     static getAllWindows() {      return allWindowsMock();    }     readonly webContents;     constructor(options: Electron.BrowserWindowConstructorOptions) {      super(options);      this.state.kind = "browser";      this.webContents = {        executeJavaScript: async (script: string) => {          this.state.scripts.push(script);          if (script.startsWith("window.setCaptureSnapshot")) {            await transitionSnapshotMock();          }          if (script !== "window.playCaptureTransition()") return;          if (transitionScriptState.rejectFlight) {            transitionScriptState.rejectFlight = false;            throw new Error("transition failed");          }          const held = transitionScriptState.heldFlights;          if (held) await new Promise<void>((resolve) => held.push(resolve));        },        capturePage: async (bounds: Electron.Rectangle) => {          this.state.capturedRegions.push(bounds);          await transitionCapturePageMock();        },      };    }     loadURL(url: string) {      this.state.loadCount += 1;      this.state.loadedUrls.push(url);      return Promise.resolve();    }  }   return {    BaseWindow,    BrowserWindow,    app: { getFileIcon: getFileIconMock },    desktopCapturer: { getSources: getSourcesMock },    nativeImage: { createThumbnailFromPath: thumbnailFromPathMock },    globalShortcut: { register: registerShortcutMock, unregister: unregisterShortcutMock },    screen: {      getDisplayMatching: (bounds: Electron.Rectangle) =>        bounds.x < 0          ? { id: 1, bounds: { x: -1_920, y: 0, width: 1_920, height: 1_080 } }          : { id: 2, bounds: { x: 0, y: -200, width: 1_440, height: 900 } },      getAllDisplays: () => [        { id: 1, bounds: { x: -1_920, y: 0, width: 1_920, height: 1_080 } },        { id: 2, bounds: { x: 0, y: -200, width: 1_440, height: 900 } },      ],      getCursorScreenPoint: () => ({ x: 500, y: 500 }),      getDisplayNearestPoint: () => ({        bounds: { x: 100, y: 100, width: 800, height: 600 },      }),      getPrimaryDisplay: () => ({ bounds: { x: 0, y: 0, width: 1_440, height: 900 } }),      screenToDipRect: screenToDipRectMock,    },    shell: { openExternal: openExternalMock },    systemPreferences: {      getAnimationSettings: () => animationSettingsMock(),      getMediaAccessStatus: () => mediaAccessStatusMock(),      isTrustedAccessibilityClient: (prompt: boolean) => accessibilityTrustedMock(prompt),    },  };}); import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";import * as DesktopClientSettings from "../settings/DesktopClientSettings.ts";import * as DesktopWindow from "../window/DesktopWindow.ts";import * as DesktopSnapShot from "./DesktopSnapShot.ts";import * as SnapShotAccessibility from "./SnapShotAccessibility.ts"; // The accessibility reader normally runs in a worker with the real xa11y `App`.// Tests hand it this stand-in so the mocks above drive window lookups.const accessibilityApp = {  byPid: accessibilityByPidMock,  foreground: accessibilityForegroundMock,} as unknown as Parameters<typeof SnapShotAccessibility.readAccessibleWindowContextWithApp>[0];const readAccessibleWindowContext = (  active: SnapShotAccessibility.AccessibleWindowIdentity,  platform: NodeJS.Platform,  sourceTitle: string,  imageSize: Electron.Size = {    width: Math.max(1, Math.round(active.bounds.width)),    height: Math.max(1, Math.round(active.bounds.height)),  },) =>  SnapShotAccessibility.readAccessibleWindowContextWithApp(accessibilityApp, {    active,    platform,    sourceTitle,    imageSize,  });const readAccessibleWindowText = async (  active: SnapShotAccessibility.AccessibleWindowIdentity,  platform: NodeJS.Platform,  sourceTitle: string,) => (await readAccessibleWindowContext(active, platform, sourceTitle))?.accessibleText;accessibilityProcessReadMock.mockImplementation((request) => ({  started: Promise.resolve(),  result: readAccessibleWindowContext(    request.active,    request.platform,    request.sourceTitle,    request.imageSize,  ),}));vi.mock("./GnomeCaptureSetup.ts", async (importOriginal) => ({  ...(await importOriginal<typeof import("./GnomeCaptureSetup.ts")>()),  GnomeCaptureSetup: class {    state = async () => ({ status: "enabled" as const, message: "Extension running" });    perform = async () => undefined;    close = () => undefined;  },})); const decodePendingMetadata = Schema.decodeUnknownEffect(  Schema.fromJsonString(DesktopPendingSnapShot),); import {  SnapShotTransition,  snapShotAnimationDurationMs,  snapShotAnimationDisplayBounds,  snapShotAnimationOverlayBounds,} from "./SnapShotTransition.ts";const testLayer = (  platform: NodeJS.Platform,  fileSystemOverrides: Parameters<typeof FileSystem.layerNoop>[0] = {},  initialSettings: Option.Option<ClientSettings> = Option.none(),  settingsGet: Effect.Effect<    Option.Option<ClientSettings>,    DesktopClientSettings.DesktopClientSettingsReadError  > = Effect.succeed(initialSettings),) =>  Layer.mergeAll(    Layer.succeed(      DesktopEnvironment.DesktopEnvironment,      DesktopEnvironment.DesktopEnvironment.of({        platform,        stateDir: "/state",        linuxDesktopEntryName: "com.t3tools.T3Code.desktop",        appRoot: "/repo",        linuxApplicationsDir: "/test-data/applications",      } as DesktopEnvironment.DesktopEnvironment["Service"]),

Lines 1–500 of 3650.