mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-05-24 05:44:08 +00:00
148 lines
4.1 KiB
TypeScript
148 lines
4.1 KiB
TypeScript
import { execSync, spawn } from "child_process";
|
|
import { platform } from "os";
|
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
import { copyToClipboard } from "../src/utils/clipboard.js";
|
|
|
|
const mocks = vi.hoisted(() => {
|
|
return {
|
|
clipboard: {
|
|
setText: vi.fn<(text: string) => Promise<void>>(),
|
|
},
|
|
execSync: vi.fn(),
|
|
spawn: vi.fn(),
|
|
platform: vi.fn<() => NodeJS.Platform>(),
|
|
isWaylandSession: vi.fn<() => boolean>(),
|
|
};
|
|
});
|
|
|
|
vi.mock("../src/utils/clipboard-native.js", () => {
|
|
return {
|
|
clipboard: mocks.clipboard,
|
|
};
|
|
});
|
|
|
|
vi.mock("child_process", () => {
|
|
return {
|
|
execSync: mocks.execSync,
|
|
spawn: mocks.spawn,
|
|
};
|
|
});
|
|
|
|
vi.mock("os", () => {
|
|
return {
|
|
platform: mocks.platform,
|
|
};
|
|
});
|
|
|
|
vi.mock("../src/utils/clipboard-image.js", () => {
|
|
return {
|
|
isWaylandSession: mocks.isWaylandSession,
|
|
};
|
|
});
|
|
|
|
const mockedExecSync = vi.mocked(execSync);
|
|
const mockedSpawn = vi.mocked(spawn);
|
|
const mockedPlatform = vi.mocked(platform);
|
|
|
|
let originalWrite: typeof process.stdout.write;
|
|
let stdoutWrites: string[];
|
|
let nativeResolved = false;
|
|
|
|
function osc52Writes(): string[] {
|
|
return stdoutWrites.filter((write) => write.startsWith("\x1b]52;c;"));
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.unstubAllEnvs();
|
|
vi.stubEnv("SSH_CONNECTION", "");
|
|
vi.stubEnv("SSH_CLIENT", "");
|
|
vi.stubEnv("MOSH_CONNECTION", "");
|
|
stdoutWrites = [];
|
|
nativeResolved = false;
|
|
mocks.clipboard.setText.mockReset();
|
|
mocks.execSync.mockReset();
|
|
mocks.spawn.mockReset();
|
|
mocks.platform.mockReset();
|
|
mocks.isWaylandSession.mockReset();
|
|
mockedPlatform.mockReturnValue("darwin");
|
|
mocks.isWaylandSession.mockReturnValue(false);
|
|
mocks.clipboard.setText.mockImplementation(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
nativeResolved = true;
|
|
});
|
|
originalWrite = process.stdout.write.bind(process.stdout);
|
|
process.stdout.write = ((...args: Parameters<typeof process.stdout.write>) => {
|
|
const [chunk] = args;
|
|
if (typeof chunk === "string" && chunk.startsWith("\x1b]52;c;")) {
|
|
stdoutWrites.push(chunk);
|
|
return true;
|
|
}
|
|
return originalWrite(...args);
|
|
}) as typeof process.stdout.write;
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.stdout.write = originalWrite;
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
describe("copyToClipboard", () => {
|
|
test("local native success skips OSC 52 and shell fallbacks", async () => {
|
|
await copyToClipboard("hello");
|
|
|
|
expect(mocks.clipboard.setText).toHaveBeenCalledWith("hello");
|
|
expect(osc52Writes()).toHaveLength(0);
|
|
expect(mockedExecSync).not.toHaveBeenCalled();
|
|
expect(mockedSpawn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("remote native success emits OSC 52 after native write", async () => {
|
|
vi.stubEnv("SSH_CONNECTION", "client server");
|
|
mocks.clipboard.setText.mockImplementation(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
expect(osc52Writes()).toHaveLength(0);
|
|
nativeResolved = true;
|
|
});
|
|
|
|
await copyToClipboard("hello");
|
|
|
|
expect(nativeResolved).toBe(true);
|
|
expect(osc52Writes()).toHaveLength(1);
|
|
expect(mockedExecSync).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("local shell fallback success skips OSC 52", async () => {
|
|
mocks.clipboard.setText.mockRejectedValue(new Error("native failed"));
|
|
mockedExecSync.mockReturnValue(Buffer.alloc(0));
|
|
|
|
await copyToClipboard("hello");
|
|
|
|
expect(mockedExecSync).toHaveBeenCalledWith("pbcopy", {
|
|
input: "hello",
|
|
stdio: ["pipe", "ignore", "ignore"],
|
|
timeout: 5000,
|
|
});
|
|
expect(osc52Writes()).toHaveLength(0);
|
|
});
|
|
|
|
test("uses OSC 52 fallback when native and shell tools fail", async () => {
|
|
mocks.clipboard.setText.mockRejectedValue(new Error("native failed"));
|
|
mockedExecSync.mockImplementation(() => {
|
|
throw new Error("pbcopy failed");
|
|
});
|
|
|
|
await copyToClipboard("hello");
|
|
|
|
expect(osc52Writes()).toHaveLength(1);
|
|
});
|
|
|
|
test("does not emit oversized OSC 52 payloads", async () => {
|
|
mocks.clipboard.setText.mockRejectedValue(new Error("native failed"));
|
|
mockedExecSync.mockImplementation(() => {
|
|
throw new Error("pbcopy failed");
|
|
});
|
|
|
|
await expect(copyToClipboard("x".repeat(80_000))).rejects.toThrow("Failed to copy to clipboard");
|
|
expect(osc52Writes()).toHaveLength(0);
|
|
});
|
|
});
|