co-mono/packages/coding-agent/test/clipboard-image.test.ts
2026-02-02 00:23:12 +01:00

136 lines
4.2 KiB
TypeScript

import type { SpawnSyncReturns } from "child_process";
import { beforeEach, describe, expect, test, vi } from "vitest";
const mocks = vi.hoisted(() => {
return {
spawnSync: vi.fn<(command: string, args: string[], options: unknown) => SpawnSyncReturns<Buffer>>(),
clipboard: {
hasImage: vi.fn<() => boolean>(),
getImageBinary: vi.fn<() => Promise<Uint8Array | null>>(),
},
};
});
vi.mock("child_process", () => {
return {
spawnSync: mocks.spawnSync,
};
});
vi.mock("../src/utils/clipboard-native.js", () => {
return {
clipboard: mocks.clipboard,
};
});
function spawnOk(stdout: Buffer): SpawnSyncReturns<Buffer> {
return {
pid: 123,
output: [Buffer.alloc(0), stdout, Buffer.alloc(0)],
stdout,
stderr: Buffer.alloc(0),
status: 0,
signal: null,
};
}
function spawnError(error: Error): SpawnSyncReturns<Buffer> {
return {
pid: 123,
output: [Buffer.alloc(0), Buffer.alloc(0), Buffer.alloc(0)],
stdout: Buffer.alloc(0),
stderr: Buffer.alloc(0),
status: null,
signal: null,
error,
};
}
describe("readClipboardImage", () => {
beforeEach(() => {
vi.resetModules();
mocks.spawnSync.mockReset();
mocks.clipboard.hasImage.mockReset();
mocks.clipboard.getImageBinary.mockReset();
});
test("Wayland: uses wl-paste and never calls clipboard", async () => {
mocks.clipboard.hasImage.mockImplementation(() => {
throw new Error("clipboard.hasImage should not be called on Wayland");
});
mocks.spawnSync.mockImplementation((command, args, _options) => {
if (command === "wl-paste" && args[0] === "--list-types") {
return spawnOk(Buffer.from("text/plain\nimage/png\n", "utf-8"));
}
if (command === "wl-paste" && args[0] === "--type") {
return spawnOk(Buffer.from([1, 2, 3]));
}
throw new Error(`Unexpected spawnSync call: ${command} ${args.join(" ")}`);
});
const { readClipboardImage } = await import("../src/utils/clipboard-image.js");
const result = await readClipboardImage({ platform: "linux", env: { WAYLAND_DISPLAY: "1" } });
expect(result).not.toBeNull();
expect(result?.mimeType).toBe("image/png");
expect(Array.from(result?.bytes ?? [])).toEqual([1, 2, 3]);
});
test("Wayland: falls back to xclip when wl-paste is missing", async () => {
mocks.clipboard.hasImage.mockImplementation(() => {
throw new Error("clipboard.hasImage should not be called on Wayland");
});
const enoent = new Error("spawn ENOENT");
(enoent as { code?: string }).code = "ENOENT";
mocks.spawnSync.mockImplementation((command, args, _options) => {
if (command === "wl-paste") {
return spawnError(enoent);
}
if (command === "xclip" && args.includes("TARGETS")) {
return spawnOk(Buffer.from("image/png\n", "utf-8"));
}
if (command === "xclip" && args.includes("image/png")) {
return spawnOk(Buffer.from([9, 8]));
}
return spawnOk(Buffer.alloc(0));
});
const { readClipboardImage } = await import("../src/utils/clipboard-image.js");
const result = await readClipboardImage({ platform: "linux", env: { XDG_SESSION_TYPE: "wayland" } });
expect(result).not.toBeNull();
expect(result?.mimeType).toBe("image/png");
expect(Array.from(result?.bytes ?? [])).toEqual([9, 8]);
});
test("Non-Wayland: uses clipboard", async () => {
mocks.spawnSync.mockImplementation(() => {
throw new Error("spawnSync should not be called for non-Wayland sessions");
});
mocks.clipboard.hasImage.mockReturnValue(true);
mocks.clipboard.getImageBinary.mockResolvedValue(new Uint8Array([7]));
const { readClipboardImage } = await import("../src/utils/clipboard-image.js");
const result = await readClipboardImage({ platform: "linux", env: {} });
expect(result).not.toBeNull();
expect(result?.mimeType).toBe("image/png");
expect(Array.from(result?.bytes ?? [])).toEqual([7]);
});
test("Non-Wayland: returns null when clipboard has no image", async () => {
mocks.spawnSync.mockImplementation(() => {
throw new Error("spawnSync should not be called for non-Wayland sessions");
});
mocks.clipboard.hasImage.mockReturnValue(false);
const { readClipboardImage } = await import("../src/utils/clipboard-image.js");
const result = await readClipboardImage({ platform: "linux", env: {} });
expect(result).toBeNull();
});
});