Skip to content

Commit 058fe93

Browse files
committed
Add color to test output
Green ✓ / red ✗ per check, a bold suite header, dim detail labels, and a colored summary line. Coloring honors FORCE_COLOR / NO_COLOR and falls back to plain text when stdout isn't a TTY, so CI logs stay clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B24Kfw4PGCgm4NQDmUVEMN
1 parent 01d1e1a commit 058fe93

1 file changed

Lines changed: 31 additions & 9 deletions

File tree

test/run.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ const fs = require("fs") as typeof import("fs");
1717
const path = require("path") as typeof import("path");
1818
const ts = require("typescript") as typeof import("typescript");
1919

20+
// Minimal ANSI coloring. Honors FORCE_COLOR / NO_COLOR and falls back to plain text when
21+
// stdout isn't a TTY (e.g. CI logs, pipes), so the output degrades gracefully.
22+
const useColor =
23+
(process.env.FORCE_COLOR ?? "") !== "" || (!!process.stdout.isTTY && (process.env.NO_COLOR ?? "") === "");
24+
const paint =
25+
(code: string) =>
26+
(s: string): string =>
27+
useColor ? `\x1b[${code}m${s}\x1b[0m` : s;
28+
const green = paint("32");
29+
const red = paint("31");
30+
const cyan = paint("36");
31+
const bold = paint("1");
32+
const dim = paint("2");
33+
const tick = green("✓");
34+
const cross = red("✗");
35+
2036
const TEST_DIR = __dirname;
2137
const ROOT = path.resolve(TEST_DIR, "..");
2238

@@ -74,8 +90,11 @@ interface Result {
7490
}
7591

7692
const results: Array<Result> = [];
93+
let total = 0;
7794
let failures = 0;
7895

96+
console.log(bold(`Type-level tests · ${caseFiles.length} suite${caseFiles.length === 1 ? "" : "s"}`));
97+
7998
for (const caseFile of caseFiles) {
8099
const source = program.getSourceFile(caseFile);
81100
if (!source) {
@@ -132,28 +151,31 @@ for (const caseFile of caseFiles) {
132151

133152
// ----- report for this file -----
134153

135-
console.log(`\n${relName}`);
154+
console.log(`\n${bold(cyan(relName))}`);
136155
for (const r of results.filter(r => r.file === relName)) {
137-
console.log(` [${r.ok ? "PASS" : "FAIL"}] ${r.name} (line ${r.line})`);
156+
total++;
157+
console.log(` ${r.ok ? tick : cross} ${r.name} ${dim(`(line ${r.line})`)}`);
138158
if (!r.ok) {
139-
console.log(` expected: ${r.expected}`);
140-
console.log(` actual: ${r.actual}`);
159+
console.log(` ${dim("expected:")} ${green(r.expected)}`);
160+
console.log(` ${dim("actual: ")} ${red(r.actual)}`);
141161
}
142162
}
143163
for (const line of errorLines) {
164+
total++;
144165
const expected = expectedErrorLines.has(line);
145166
const actual = actualErrors.has(line);
146167
const ok = expected && actual;
147168
if (!ok) failures++;
148169
const stmt = (rawLines[line] ?? "").trim();
149-
console.log(` [${ok ? "PASS" : "FAIL"}] expect-error: ${stmt} (line ${line + 1})`);
150-
if (expected && !actual) console.log(` expected a type error here, but none was reported`);
151-
if (!expected && actual) console.log(` unexpected type error: ${actualErrors.get(line)}`);
170+
console.log(` ${ok ? tick : cross} ${dim("expect-error:")} ${stmt} ${dim(`(line ${line + 1})`)}`);
171+
if (expected && !actual) console.log(` ${red("expected a type error here, but none was reported")}`);
172+
if (!expected && actual) console.log(` ${red(`unexpected type error: ${actualErrors.get(line)}`)}`);
152173
}
153174
}
154175

176+
const passed = total - failures;
155177
if (failures > 0) {
156-
console.error(`\n${failures} check(s) failed.`);
178+
console.error(`\n${cross} ${bold(red(`${failures} of ${total} checks failed`))} ${dim(`(${passed} passed)`)}`);
157179
process.exit(1);
158180
}
159-
console.log("\nAll checks passed.");
181+
console.log(`\n${tick} ${bold(green(`All ${total} checks passed`))}`);

0 commit comments

Comments
 (0)