556 lines
19 KiB
JavaScript
556 lines
19 KiB
JavaScript
import { test } from "node:test";
|
||
import assert from "node:assert/strict";
|
||
import { fileURLToPath } from "node:url";
|
||
import { spawnSync } from "node:child_process";
|
||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||
import { tmpdir } from "node:os";
|
||
import path from "node:path";
|
||
import { readFileSync } from "node:fs";
|
||
|
||
function stripAnsi(text) {
|
||
return text
|
||
.replace(
|
||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nq-uy=><]/g,
|
||
"",
|
||
)
|
||
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
|
||
}
|
||
|
||
function skipIfSpawnBlocked(result, t) {
|
||
if (result.error?.code === "EPERM") {
|
||
t.skip("spawnSync is blocked by sandbox policy in this environment");
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
test("CLI renders expected output for a basic transcript", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const expectedPath = fileURLToPath(
|
||
new URL("./fixtures/expected/render-basic.txt", import.meta.url),
|
||
);
|
||
const expected = readFileSync(expectedPath, "utf8").trimEnd();
|
||
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
// Use a fixed 3-level path for deterministic test output
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
await import("node:fs/promises").then((fs) =>
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
);
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.error, undefined, result.error?.message);
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const normalized = stripAnsi(result.stdout)
|
||
.replace(/\u00A0/g, " ")
|
||
.trimEnd();
|
||
if (process.env.UPDATE_SNAPSHOTS === "1") {
|
||
await writeFile(expectedPath, normalized + "\n", "utf8");
|
||
return;
|
||
}
|
||
assert.equal(normalized, expected);
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI renders added_dirs basenames on the project line", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
const addedDirA = path.join(homeDir, "dev", "apps", "lib-foo");
|
||
const addedDirB = path.join(homeDir, "dev", "apps", "some-other-repo");
|
||
await import("node:fs/promises").then((fs) =>
|
||
Promise.all([
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
fs.mkdir(addedDirA, { recursive: true }),
|
||
fs.mkdir(addedDirB, { recursive: true }),
|
||
]),
|
||
);
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: {
|
||
current_dir: projectDir,
|
||
added_dirs: [addedDirA, addedDirB],
|
||
},
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.error, undefined, result.error?.message);
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const firstLine = stripAnsi(result.stdout).split("\n")[0];
|
||
assert.match(firstLine, /\+lib-foo/);
|
||
assert.match(firstLine, /\+some-other-repo/);
|
||
assert.ok(
|
||
firstLine.indexOf("my-project") < firstLine.indexOf("+lib-foo"),
|
||
"added dirs should come after the project name",
|
||
);
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI omits added dirs section when array is empty", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
await import("node:fs/promises").then((fs) =>
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
);
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { current_dir: projectDir, added_dirs: [] },
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const firstLine = stripAnsi(result.stdout).split("\n")[0];
|
||
assert.doesNotMatch(firstLine, /\+/);
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI tolerates added_dirs: null without crashing", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
await import("node:fs/promises").then((fs) =>
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
);
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { added_dirs: null },
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.error, undefined, result.error?.message);
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const firstLine = stripAnsi(result.stdout).split("\n")[0];
|
||
assert.doesNotMatch(firstLine, /\+/);
|
||
assert.doesNotMatch(stripAnsi(result.stdout), /Added dirs:/);
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI ignores non-string and post-sanitize-empty added_dirs entries", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
const validA = path.join(homeDir, "dev", "apps", "valid-one");
|
||
const validB = path.join(homeDir, "dev", "apps", "valid-two");
|
||
await import("node:fs/promises").then((fs) =>
|
||
Promise.all([
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
fs.mkdir(validA, { recursive: true }),
|
||
fs.mkdir(validB, { recursive: true }),
|
||
]),
|
||
);
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: {
|
||
added_dirs: [validA, null, 42, "", { foo: 1 }, "", validB],
|
||
},
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.error, undefined, result.error?.message);
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const firstLine = stripAnsi(result.stdout).split("\n")[0];
|
||
assert.match(firstLine, /\+valid-one/);
|
||
assert.match(firstLine, /\+valid-two/);
|
||
const plusCount = (firstLine.match(/\+valid-/g) || []).length;
|
||
assert.equal(plusCount, 2, "only the two valid basenames should render");
|
||
assert.doesNotMatch(firstLine, /\+ /, "no bare '+' from control-char-only basename");
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI caps inline added_dirs at 5 with overflow indicator", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
const dirs = Array.from({ length: 7 }, (_, i) =>
|
||
path.join(homeDir, "dev", "apps", `dir-${i + 1}`),
|
||
);
|
||
await import("node:fs/promises").then((fs) =>
|
||
Promise.all([
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
...dirs.map((d) => fs.mkdir(d, { recursive: true })),
|
||
]),
|
||
);
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { added_dirs: dirs },
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const firstLine = stripAnsi(result.stdout).split("\n")[0];
|
||
for (let i = 1; i <= 5; i++) {
|
||
assert.match(firstLine, new RegExp(`\\+dir-${i}\\b`));
|
||
}
|
||
assert.doesNotMatch(firstLine, /\+dir-6\b/);
|
||
assert.doesNotMatch(firstLine, /\+dir-7\b/);
|
||
assert.match(firstLine, /\+2 more/);
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI truncates long inline added_dirs basenames", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
const longName = "a".repeat(40);
|
||
const longDir = path.join(homeDir, "dev", "apps", longName);
|
||
await import("node:fs/promises").then((fs) =>
|
||
Promise.all([
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
fs.mkdir(longDir, { recursive: true }),
|
||
]),
|
||
);
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { added_dirs: [longDir] },
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const firstLine = stripAnsi(result.stdout).split("\n")[0];
|
||
assert.match(firstLine, /\+a+…/, "long basename should be truncated with ellipsis");
|
||
assert.doesNotMatch(firstLine, new RegExp(`\\+${longName}`));
|
||
const m = firstLine.match(/\+(a+…)/);
|
||
assert.ok(m && m[1].length <= 24, `truncated name should be ≤24 chars, got ${m && m[1].length}`);
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
async function writeHudConfig(homeDir, config) {
|
||
const fs = await import("node:fs/promises");
|
||
const dir = path.join(homeDir, ".claude", "plugins", "claude-hud");
|
||
await fs.mkdir(dir, { recursive: true });
|
||
await fs.writeFile(path.join(dir, "config.json"), JSON.stringify(config), "utf8");
|
||
}
|
||
|
||
test("CLI renders line layout 'Added dirs:' on a separate line", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
const dirA = path.join(homeDir, "dev", "apps", "shared-utils");
|
||
const dirB = path.join(homeDir, "dev", "apps", "mobile-app");
|
||
await import("node:fs/promises").then((fs) =>
|
||
Promise.all([
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
fs.mkdir(dirA, { recursive: true }),
|
||
fs.mkdir(dirB, { recursive: true }),
|
||
]),
|
||
);
|
||
await writeHudConfig(homeDir, { display: { addedDirsLayout: "line" } });
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { added_dirs: [dirA, dirB] },
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const lines = stripAnsi(result.stdout).split("\n");
|
||
assert.doesNotMatch(lines[0], /\+shared-utils|\+mobile-app/, "inline + prefix should not appear in line mode");
|
||
const dirsLine = lines.find((l) => l.includes("Added dirs:"));
|
||
assert.ok(dirsLine, `expected an 'Added dirs:' line, got:\n${result.stdout}`);
|
||
assert.match(dirsLine, /shared-utils/);
|
||
assert.match(dirsLine, /mobile-app/);
|
||
assert.match(dirsLine, /shared-utils,\s*mobile-app/);
|
||
assert.doesNotMatch(dirsLine, /\bmore\b/, "two dirs should not trigger overflow");
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI renders inline added_dirs even when showProject is false", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
const addedDir = path.join(homeDir, "dev", "apps", "lib-foo");
|
||
await import("node:fs/promises").then((fs) =>
|
||
Promise.all([
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
fs.mkdir(addedDir, { recursive: true }),
|
||
]),
|
||
);
|
||
await writeHudConfig(homeDir, { display: { showProject: false } });
|
||
try {
|
||
const stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { added_dirs: [addedDir] },
|
||
});
|
||
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const firstLine = stripAnsi(result.stdout).split("\n")[0];
|
||
assert.match(firstLine, /\[Opus\]/, "model bracket should still render (sanity)");
|
||
assert.doesNotMatch(firstLine, /my-project/, "project name should be hidden");
|
||
assert.match(firstLine, /\+lib-foo/, "added dirs should still render");
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI applies caps in line layout (overflow + truncation)", async (t) => {
|
||
const fixturePath = fileURLToPath(
|
||
new URL("./fixtures/transcript-render.jsonl", import.meta.url),
|
||
);
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
const projectDir = path.join(homeDir, "dev", "apps", "my-project");
|
||
const dirs = Array.from({ length: 7 }, (_, i) =>
|
||
path.join(homeDir, "dev", "apps", `dir-${i + 1}`),
|
||
);
|
||
const longName = "b".repeat(40);
|
||
const longDir = path.join(homeDir, "dev", "apps", longName);
|
||
await import("node:fs/promises").then((fs) =>
|
||
Promise.all([
|
||
fs.mkdir(projectDir, { recursive: true }),
|
||
fs.mkdir(longDir, { recursive: true }),
|
||
...dirs.map((d) => fs.mkdir(d, { recursive: true })),
|
||
]),
|
||
);
|
||
await writeHudConfig(homeDir, { display: { addedDirsLayout: "line" } });
|
||
try {
|
||
let stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { added_dirs: dirs },
|
||
});
|
||
let result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
let dirsLine = stripAnsi(result.stdout).split("\n").find((l) => l.includes("Added dirs:"));
|
||
assert.ok(dirsLine, `expected 'Added dirs:' line, got:\n${result.stdout}`);
|
||
assert.match(dirsLine, /dir-1/);
|
||
assert.match(dirsLine, /dir-5/);
|
||
assert.doesNotMatch(dirsLine, /dir-6/);
|
||
assert.doesNotMatch(dirsLine, /dir-7/);
|
||
assert.match(dirsLine, /\+2 more/);
|
||
|
||
stdin = JSON.stringify({
|
||
model: { display_name: "Opus" },
|
||
context_window: {
|
||
context_window_size: 200000,
|
||
current_usage: { input_tokens: 45000 },
|
||
},
|
||
transcript_path: fixturePath,
|
||
cwd: projectDir,
|
||
workspace: { added_dirs: [longDir] },
|
||
});
|
||
result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: stdin,
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
dirsLine = stripAnsi(result.stdout).split("\n").find((l) => l.includes("Added dirs:"));
|
||
assert.ok(dirsLine, `expected 'Added dirs:' line, got:\n${result.stdout}`);
|
||
assert.match(dirsLine, /b+…/);
|
||
assert.doesNotMatch(dirsLine, new RegExp(longName));
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|
||
|
||
test("CLI prints initializing message on empty stdin", async (t) => {
|
||
const homeDir = await mkdtemp(path.join(tmpdir(), "claude-hud-home-"));
|
||
|
||
try {
|
||
const result = spawnSync("node", ["dist/index.js"], {
|
||
cwd: path.resolve(process.cwd()),
|
||
input: "",
|
||
encoding: "utf8",
|
||
env: { ...process.env, HOME: homeDir, LANG: "C" },
|
||
});
|
||
|
||
if (skipIfSpawnBlocked(result, t)) return;
|
||
|
||
assert.equal(result.error, undefined, result.error?.message);
|
||
assert.equal(result.status, 0, result.stderr || "non-zero exit");
|
||
const normalized = stripAnsi(result.stdout)
|
||
.replace(/\u00A0/g, " ")
|
||
.trimEnd();
|
||
assert.ok(
|
||
normalized.startsWith("[claude-hud] Initializing..."),
|
||
`unexpected output: ${normalized}`,
|
||
);
|
||
} finally {
|
||
await rm(homeDir, { recursive: true, force: true });
|
||
}
|
||
});
|