extract test output parsing
Parsing no longer depends on ui widget
This commit is contained in:
parent
093c79009d
commit
ed147f6470
@ -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)}`,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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
10
vitest.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: [
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"pi-test-status/extensions/**/*.test.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user