1import { assert, beforeEach, expect, it, vi } from "vite-plus/test";
2
3const { createFromBufferMock, execFileMock, readFileMock, writeFileMock } = vi.hoisted(() => ({
4 createFromBufferMock: vi.fn(),
5 execFileMock: vi.fn(),
6 readFileMock: vi.fn(),
7 writeFileMock: vi.fn(),
8}));
9
10vi.mock("node:child_process", () => ({ execFile: execFileMock }));
11vi.mock("node:fs/promises", () => ({ readFile: readFileMock, writeFile: writeFileMock }));
12vi.mock("electron", () => ({ nativeImage: { createFromBuffer: createFromBufferMock } }));
13
14import { captureMacWindowSnapshot, macSnapShotArguments } from "./MacSnapShot.ts";
15
16beforeEach(() => {
17 execFileMock
18 .mockReset()
19 .mockImplementation(
20 (
21 _path: string,
22 _args: ReadonlyArray<string>,
23 _options: { readonly timeout: number },
24 callback: (error: Error | null) => void,
25 ) => callback(null),
26 );
27 readFileMock.mockReset();
28 writeFileMock.mockReset();
29 createFromBufferMock.mockReset().mockReturnValue({
30 getSize: () => ({ width: 100, height: 80 }),
31 isEmpty: () => false,
32 });
33});
34
35it("captures one macOS window directly as a silent shadowless PNG", async () => {
36 const png = Buffer.concat([
37 Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
38 Buffer.from("image"),
39 ]);
40 readFileMock.mockResolvedValue(png);
41 const active = {
42 id: 42,
43 title: "Editor",
44 owner: { name: "Code" },
45 } as Parameters<typeof captureMacWindowSnapshot>[0];
46
47 const capture = await captureMacWindowSnapshot(active, "/tmp/window.png", {
48 width: 100,
49 height: 80,
50 });
51
52 assert.deepEqual(execFileMock.mock.calls[0]?.slice(0, 3), [
53 "/usr/sbin/screencapture",
54 macSnapShotArguments(42, "/tmp/window.png"),
55 { timeout: 15_000 },
56 ]);
57 assert.deepEqual(readFileMock.mock.calls, [["/tmp/window.png"]]);
58 assert.lengthOf(writeFileMock.mock.calls, 0);
59 assert.strictEqual(capture.png, png);
60 assert.deepEqual(capture.source, { name: "Editor" });
61});
62
63it("bounds Retina captures without changing their aspect ratio", async () => {
64 const capturedPng = Buffer.concat([
65 Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
66 Buffer.from("retina"),
67 ]);
68 const resizedPng = Buffer.from("bounded png");
69 const resizeMock = vi.fn().mockReturnValue({ toPNG: () => resizedPng });
70 readFileMock.mockResolvedValue(capturedPng);
71 createFromBufferMock.mockReturnValue({
72 getSize: () => ({ width: 2_000, height: 1_200 }),
73 isEmpty: () => false,
74 resize: resizeMock,
75 });
76 const active = {
77 id: 42,
78 title: "Editor",
79 owner: { name: "Code" },
80 } as Parameters<typeof captureMacWindowSnapshot>[0];
81
82 const capture = await captureMacWindowSnapshot(active, "/tmp/window.png", {
83 width: 1_000,
84 height: 1_000,
85 });
86
87 assert.deepEqual(resizeMock.mock.calls, [[{ width: 1_000, height: 600, quality: "best" }]]);
88 assert.deepEqual(writeFileMock.mock.calls, [["/tmp/window.png", resizedPng]]);
89 assert.strictEqual(capture.png, resizedPng);
90});
91
92it("rejects a successful command that did not produce a PNG", async () => {
93 readFileMock.mockResolvedValue(Buffer.from("not a png"));
94 const active = {
95 id: 42,
96 title: "",
97 owner: { name: "Code" },
98 } as Parameters<typeof captureMacWindowSnapshot>[0];
99
100 await expect(
101 captureMacWindowSnapshot(active, "/tmp/window.png", { width: 100, height: 80 }),
102 ).rejects.toThrow("macOS returned an invalid snapshot.");
103});