extract test output parsing

Parsing no longer depends on ui widget
This commit is contained in:
Willem van den Ende 2026-06-28 11:59:52 +02:00
parent 093c79009d
commit ed147f6470
4 changed files with 100 additions and 23 deletions

View File

@ -1,5 +1,5 @@
import {type ExtensionAPI, isBashToolResult} from "@earendil-works/pi-coding-agent"; import {type ExtensionAPI, isBashToolResult} from "@earendil-works/pi-coding-agent";
import {isTestCommand} from "./parsing/typescript-command-output"; import {isTestCommand, parseTestOutput} from "./parsing/typescript-command-output";
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
export default function testStatusExtension(pi: ExtensionAPI) { export default function testStatusExtension(pi: ExtensionAPI) {
@ -13,30 +13,15 @@ export default function testStatusExtension(pi: ExtensionAPI) {
const output = textContent?.text ?? ""; const output = textContent?.text ?? "";
const theme = ctx.ui.theme; const theme = ctx.ui.theme;
// Detect pass/fail from vitest summary output const testResult = parseTestOutput(output);
const testsPassed =
/Tests\s+.+passed/.test(output) && !/Tests\s+.+failed/.test(output);
const testsFailed =
/Tests\s+.+failed/.test(output) || event.isError;
if (testsPassed) {
// Extract summary line for inline detail (e.g., "1 passed (242ms)")
const summaryLine = output
.split("\n")
.find((line: string) => /Test Files\s+.+passed/.test(line));
const summary = summaryLine?.trim() ?? "All tests passed";
if (testResult.status === "Success") {
ctx.ui.setWidget("test-status", [ ctx.ui.setWidget("test-status", [
`${theme.fg("success", "●")} ${theme.fg("text", summary)}`, `${theme.fg("success", "●")} ${theme.fg("text", testResult.summary)}`,
]); ]);
} else if (testsFailed) { } else if (testResult.status === "Error" || event.isError) {
const summaryLine = output
.split("\n")
.find((line: string) => /Tests\s+.+failed/.test(line));
const summary = summaryLine?.trim() ?? "Tests failed";
ctx.ui.setWidget("test-status", [ ctx.ui.setWidget("test-status", [
`${theme.fg("error", "●")} ${theme.fg("text", summary)}`, `${theme.fg("error", "●")} ${theme.fg("text", testResult.summary)}`,
]); ]);
} }
}); });

View File

@ -0,0 +1,60 @@
import { describe, it, expect } from "vitest";
import { parseTestOutput } from "../typescript-command-output";
describe("parseTestOutput", () => {
it("parses a passing vitest run", () => {
const output = `
RAN v3.2.6 /path
src/greet.test.ts (1 test) 1ms
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 11:12:54
Duration 308ms
`;
const result = parseTestOutput(output);
expect(result.status).toBe("Success");
expect(result.summary).toContain("1 passed");
});
it("parses a failing vitest run", () => {
const output = `
RAN v3.2.6 /path
src/greet.test.ts (1 test) 1ms
src/foo.test.ts (1 test) 2ms
Test Files 1 failed (1)
Tests 1 failed (1)
Start at 11:15:00
Duration 400ms
`;
const result = parseTestOutput(output);
expect(result.status).toBe("Error");
expect(result.summary).toContain("1 failed");
});
it("returns Error and a fallback summary for unrecognised output", () => {
const result = parseTestOutput("some random output");
expect(result.status).toBe("Error");
expect(result.summary).toBe("Tests failed");
});
it("returns Success with a fallback summary when passed but no detail line", () => {
const output = `
src/greet.test.ts (1 test) 1ms
Tests 1 passed (242ms)
`;
const result = parseTestOutput(output);
expect(result.status).toBe("Success");
expect(result.summary).toBe("All tests passed");
});
});

View File

@ -1,3 +1,25 @@
export type TestResult =
| { status: "Success"; summary: string }
| { status: "Error"; summary: string };
export const isTestCommand = (command: string) => { export const isTestCommand = (command: string) => {
return command.includes("npm test") || command.includes("vitest") return command.includes("npm test") || command.includes("vitest");
};
export function parseTestOutput(output: string): TestResult {
const testsPassed =
/Tests\s+.+passed/.test(output) && !/Tests\s+.+failed/.test(output);
const testsFailed = /Tests\s+.+failed/.test(output);
if (testsPassed && !testsFailed) {
const line = output
.split("\n")
.find((l) => /Test Files\s+.+passed/.test(l));
return { status: "Success", summary: line?.trim() ?? "All tests passed" };
}
const line = output
.split("\n")
.find((l) => /Tests\s+.+failed/.test(l));
return { status: "Error", summary: line?.trim() ?? "Tests failed" };
} }

10
vitest.config.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: [
"src/**/*.test.ts",
"pi-test-status/extensions/**/*.test.ts",
],
},
});