295 lines · 9.0 KB · TypeScript
RawDownload
import type * as Electron from "electron";import { assert, beforeEach, expect, it, vi } from "vite-plus/test"; import type { ActiveWindow } from "./ActiveWindow.ts";import type { RegionSnapShotChild, RegionSnapShotProcess } from "./RegionSnapShot.ts"; const { createFromBufferMock, resizeMock } = vi.hoisted(() => ({  createFromBufferMock: vi.fn(),  resizeMock: vi.fn(),})); vi.mock("electron", () => ({  nativeImage: { createFromBuffer: createFromBufferMock },})); import {  captureRegionWindowSnapshot,  makeRegionSnapShotPool,  startRegionSnapShotProcess,} from "./RegionSnapShot.ts"; beforeEach(() => {  vi.clearAllMocks();  createFromBufferMock.mockReturnValue({ resize: resizeMock });}); const active = {  title: "Editor",  owner: { name: "Editor", processId: 123 },  bounds: { x: 10, y: 20, width: 800, height: 600 },} as ActiveWindow; it("returns a small capture's PNG untouched", async () => {  const png = Buffer.from([1, 2, 3]);  const capture = vi.fn(async () => ({ width: 400, height: 300, png }));  const region = { x: 5, y: 10, width: 400, height: 300 };   const result = await captureRegionWindowSnapshot({ capture }, active, region, region);   assert.deepEqual(capture.mock.calls, [[region]]);  assert.strictEqual(result.png, png);  assert.deepEqual(result.source, { name: "Editor" });  assert.lengthOf(createFromBufferMock.mock.calls, 0);}); it.each([  { width: 600, height: 400, expected: { width: 240, height: 160 } },  { width: 400, height: 600, expected: { width: 107, height: 160 } },  { width: 600, height: 1, expected: { width: 256, height: 1 } },])(  "bounds $width x $height physical pixels before encoding",  async ({ width, height, expected }) => {    const png = Buffer.from([1, 2, 3]);    const resizedPng = Buffer.from([4, 5, 6]);    const capture = vi.fn(async () => ({ width, height, png }));    resizeMock.mockReturnValue({ toPNG: () => resizedPng });     const result = await captureRegionWindowSnapshot({ capture }, active, active.bounds, {      width: 256,      height: 160,    });     assert.deepEqual(createFromBufferMock.mock.calls, [[png]]);    assert.deepEqual(resizeMock.mock.calls, [[{ ...expected, quality: "best" }]]);    assert.strictEqual(result.png, resizedPng);  },); function fakeChild() {  const messageListeners: Array<(message: unknown) => void> = [];  const exitListeners: Array<() => void> = [];  const send = vi.fn();  const kill = vi.fn();  const child: RegionSnapShotChild = {    send,    onMessage: (listener) => messageListeners.push(listener),    onExit: (listener) => exitListeners.push(listener),    kill,  };  return {    child,    send,    kill,    emit: (message: unknown) => {      for (const listener of messageListeners) listener(message);    },    exit: () => {      for (const listener of exitListeners) listener();    },  };} const region = { x: 0, y: 0, width: 2, height: 1 }; it("resolves the decoded capture once the child is ready and replies", async () => {  const fake = fakeChild();  const fork = vi.fn(() => fake.child);  const png = Buffer.from([9, 8, 7]);  const child = startRegionSnapShotProcess("worker.cjs", fork);  assert.deepEqual(fork.mock.calls, [["worker.cjs"]]);  assert.isTrue(child.alive);   const capture = child.capture(region);  await Promise.resolve();  assert.lengthOf(fake.send.mock.calls, 0);  fake.emit("ready");  await vi.waitFor(() => assert.deepEqual(fake.send.mock.calls, [[{ region }]]));  fake.emit({ type: "result", width: 2, height: 1, png: png.toString("base64") });   assert.deepEqual(await capture, { width: 2, height: 1, png });  assert.isFalse(child.alive);  assert.lengthOf(fake.kill.mock.calls, 1);}); it("rejects with the child's error message", async () => {  const fake = fakeChild();  const child = startRegionSnapShotProcess("worker.cjs", () => fake.child);  const capture = child.capture(region);  fake.emit("ready");  fake.emit({ type: "error", message: "Screen capture failed" });   await expect(capture).rejects.toThrow("Screen capture failed");  assert.isFalse(child.alive);}); it("rejects when the child exits before replying", async () => {  const fake = fakeChild();  const child = startRegionSnapShotProcess("worker.cjs", () => fake.child);  const capture = child.capture(region);  fake.emit("ready");  fake.exit();   await expect(capture).rejects.toThrow(/helper exited/);}); it("serves a single capture per child", async () => {  const fake = fakeChild();  const child = startRegionSnapShotProcess("worker.cjs", () => fake.child);  const first = child.capture(region);  fake.emit("ready");  await vi.waitFor(() => assert.lengthOf(fake.send.mock.calls, 1));  fake.emit({ type: "result", width: 1, height: 1, png: Buffer.from([1]).toString("base64") });  await first;   await expect(child.capture(region)).rejects.toThrow(/already used/);  assert.lengthOf(fake.send.mock.calls, 1);}); it("cancels a pending capture on close and kills the child", async () => {  const fake = fakeChild();  const child = startRegionSnapShotProcess("worker.cjs", () => fake.child);  const capture = child.capture(region);  child.close();   await expect(capture).rejects.toThrow(/cancelled/);  assert.lengthOf(fake.kill.mock.calls, 1);  assert.isFalse(child.alive);  fake.emit("ready");  assert.lengthOf(fake.send.mock.calls, 0);}); it("reports an unavailable helper when the child cannot be forked", async () => {  const child = startRegionSnapShotProcess("worker.cjs", () => {    throw new Error("fork failed");  });   assert.isFalse(child.alive);  await expect(child.capture(region)).rejects.toThrow(/unavailable/);}); it("times out a stalled capture", async () => {  vi.useFakeTimers();  try {    const fake = fakeChild();    const child = startRegionSnapShotProcess("worker.cjs", () => fake.child);    const failure = expect(child.capture(region)).rejects.toThrow(/timed out/);    fake.emit("ready");    await vi.advanceTimersByTimeAsync(15_000);     await failure;    assert.lengthOf(fake.kill.mock.calls, 1);  } finally {    vi.useRealTimers();  }}); function fakeProcess() {  const result = Promise.withResolvers<{ width: number; height: number; png: Buffer }>();  void result.promise.catch(() => undefined);  let alive = true;  const close = vi.fn(() => {    alive = false;    result.reject(new Error("Windows window capture cancelled."));  });  const capture = vi.fn((_region: Electron.Rectangle) => result.promise);  const process: RegionSnapShotProcess = {    get alive() {      return alive;    },    capture,    close,  };  return {    process,    capture,    close,    resolve: (value: { width: number; height: number; png: Buffer }) => {      alive = false;      result.resolve(value);    },  };} function fakePool() {  const processes: Array<ReturnType<typeof fakeProcess>> = [];  const start = vi.fn(() => {    const process = fakeProcess();    processes.push(process);    return process.process;  });  return { pool: makeRegionSnapShotPool("worker.cjs", start), processes, start };} it("warms one standby child and keeps it while alive", () => {  const { pool, start } = fakePool();   pool.warm();  pool.warm();   assert.deepEqual(start.mock.calls, [["worker.cjs"]]);}); it("captures with the standby child and warms a replacement afterwards", async () => {  const { pool, processes, start } = fakePool();  pool.warm();  const shot = { width: 1, height: 1, png: Buffer.from([1]) };   const capture = pool.capture(region);  assert.deepEqual(processes[0]!.capture.mock.calls, [[region]]);  assert.lengthOf(start.mock.calls, 1);  processes[0]!.resolve(shot);   assert.deepEqual(await capture, shot);  assert.lengthOf(start.mock.calls, 2);  assert.lengthOf(processes[1]!.capture.mock.calls, 0);}); it("starts a child on demand when no standby is warm", async () => {  const { pool, processes, start } = fakePool();  const shot = { width: 1, height: 1, png: Buffer.from([1]) };   const capture = pool.capture(region);  assert.lengthOf(start.mock.calls, 1);  processes[0]!.resolve(shot);   assert.deepEqual(await capture, shot);}); it("rejects a second capture while one is in flight", async () => {  const { pool, processes } = fakePool();  const first = pool.capture(region);   await expect(pool.capture(region)).rejects.toThrow(/still in progress/);  assert.lengthOf(processes, 1);   processes[0]!.resolve({ width: 1, height: 1, png: Buffer.from([1]) });  await first;}); it("cools the standby child", () => {  const { pool, processes, start } = fakePool();  pool.warm();   pool.cool();   assert.lengthOf(processes[0]!.close.mock.calls, 1);  pool.warm();  assert.lengthOf(start.mock.calls, 2);}); it("closes the standby and the in-flight child", async () => {  const { pool, processes } = fakePool();  pool.warm();  const capture = pool.capture(region);  pool.warm();  assert.lengthOf(processes, 2);   pool.close();   await expect(capture).rejects.toThrow(/cancelled/);  assert.lengthOf(processes[0]!.close.mock.calls, 1);  assert.lengthOf(processes[1]!.close.mock.calls, 1);  await expect(pool.capture(region)).rejects.toThrow(/unavailable/);  assert.lengthOf(processes, 2);});

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t