mirror of https://github.com/openclaw/openclaw.git
test: add clipboard and package helper coverage
This commit is contained in:
parent
ba9fb4d994
commit
c84c76ee66
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const runCommandWithTimeoutMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
|
vi.mock("../process/exec.js", () => ({
|
||||||
|
runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { copyToClipboard } = await import("./clipboard.js");
|
||||||
|
|
||||||
|
describe("copyToClipboard", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
runCommandWithTimeoutMock.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns true on the first successful clipboard command", async () => {
|
||||||
|
runCommandWithTimeoutMock.mockResolvedValueOnce({ code: 0, killed: false });
|
||||||
|
|
||||||
|
await expect(copyToClipboard("hello")).resolves.toBe(true);
|
||||||
|
expect(runCommandWithTimeoutMock).toHaveBeenCalledWith(["pbcopy"], {
|
||||||
|
timeoutMs: 3000,
|
||||||
|
input: "hello",
|
||||||
|
});
|
||||||
|
expect(runCommandWithTimeoutMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls through failed attempts until a later command succeeds", async () => {
|
||||||
|
runCommandWithTimeoutMock
|
||||||
|
.mockRejectedValueOnce(new Error("missing pbcopy"))
|
||||||
|
.mockResolvedValueOnce({ code: 1, killed: false })
|
||||||
|
.mockResolvedValueOnce({ code: 0, killed: false });
|
||||||
|
|
||||||
|
await expect(copyToClipboard("hello")).resolves.toBe(true);
|
||||||
|
expect(runCommandWithTimeoutMock.mock.calls.map((call) => call[0])).toEqual([
|
||||||
|
["pbcopy"],
|
||||||
|
["xclip", "-selection", "clipboard"],
|
||||||
|
["wl-copy"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false when every clipboard backend fails or is killed", async () => {
|
||||||
|
runCommandWithTimeoutMock
|
||||||
|
.mockResolvedValueOnce({ code: 0, killed: true })
|
||||||
|
.mockRejectedValueOnce(new Error("missing xclip"))
|
||||||
|
.mockResolvedValueOnce({ code: 1, killed: false })
|
||||||
|
.mockRejectedValueOnce(new Error("missing clip.exe"))
|
||||||
|
.mockResolvedValueOnce({ code: 2, killed: false });
|
||||||
|
|
||||||
|
await expect(copyToClipboard("hello")).resolves.toBe(false);
|
||||||
|
expect(runCommandWithTimeoutMock).toHaveBeenCalledTimes(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { normalizePackageTagInput } from "./package-tag.js";
|
||||||
|
|
||||||
|
describe("normalizePackageTagInput", () => {
|
||||||
|
const packageNames = ["openclaw", "@openclaw/plugin"] as const;
|
||||||
|
|
||||||
|
it("returns null for blank inputs", () => {
|
||||||
|
expect(normalizePackageTagInput(undefined, packageNames)).toBeNull();
|
||||||
|
expect(normalizePackageTagInput(" ", packageNames)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips known package-name prefixes before returning the tag", () => {
|
||||||
|
expect(normalizePackageTagInput("openclaw@beta", packageNames)).toBe("beta");
|
||||||
|
expect(normalizePackageTagInput("@openclaw/plugin@2026.2.24", packageNames)).toBe("2026.2.24");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns trimmed raw values when no package prefix matches", () => {
|
||||||
|
expect(normalizePackageTagInput(" latest ", packageNames)).toBe("latest");
|
||||||
|
expect(normalizePackageTagInput("@other/plugin@beta", packageNames)).toBe("@other/plugin@beta");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveStableNodePath } from "./stable-node-path.js";
|
||||||
|
|
||||||
|
describe("resolveStableNodePath", () => {
|
||||||
|
it("returns non-cellar paths unchanged", async () => {
|
||||||
|
await expect(resolveStableNodePath("/usr/local/bin/node")).resolves.toBe("/usr/local/bin/node");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers the Homebrew opt symlink for default and versioned formulas", async () => {
|
||||||
|
const prefix = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-stable-node-"));
|
||||||
|
const defaultNode = path.join(prefix, "Cellar", "node", "25.7.0", "bin", "node");
|
||||||
|
const versionedNode = path.join(prefix, "Cellar", "node@22", "22.17.0", "bin", "node");
|
||||||
|
const optDefault = path.join(prefix, "opt", "node", "bin", "node");
|
||||||
|
const optVersioned = path.join(prefix, "opt", "node@22", "bin", "node");
|
||||||
|
|
||||||
|
await fs.mkdir(path.dirname(optDefault), { recursive: true });
|
||||||
|
await fs.mkdir(path.dirname(optVersioned), { recursive: true });
|
||||||
|
await fs.writeFile(optDefault, "", "utf8");
|
||||||
|
await fs.writeFile(optVersioned, "", "utf8");
|
||||||
|
|
||||||
|
await expect(resolveStableNodePath(defaultNode)).resolves.toBe(optDefault);
|
||||||
|
await expect(resolveStableNodePath(versionedNode)).resolves.toBe(optVersioned);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to the bin symlink for the default formula, otherwise original path", async () => {
|
||||||
|
const prefix = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-stable-node-"));
|
||||||
|
const defaultNode = path.join(prefix, "Cellar", "node", "25.7.0", "bin", "node");
|
||||||
|
const versionedNode = path.join(prefix, "Cellar", "node@22", "22.17.0", "bin", "node");
|
||||||
|
const binNode = path.join(prefix, "bin", "node");
|
||||||
|
|
||||||
|
await fs.mkdir(path.dirname(binNode), { recursive: true });
|
||||||
|
await fs.writeFile(binNode, "", "utf8");
|
||||||
|
|
||||||
|
await expect(resolveStableNodePath(defaultNode)).resolves.toBe(binNode);
|
||||||
|
await expect(resolveStableNodePath(versionedNode)).resolves.toBe(versionedNode);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue