arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
188 lines • 6.84 kB
JavaScript
/**
* Test runner for verifying slice extraction
*/
import path from "node:path";
import fs from "fs-extra";
import { execa } from "execa";
export class TestRunner {
/**
* Detect the test framework used in the project
*/
async detectTestFramework(cwd = process.cwd()) {
const packageJsonPath = path.join(cwd, "package.json");
if (!(await fs.pathExists(packageJsonPath))) {
return "none";
}
const packageJson = await fs.readJSON(packageJsonPath);
const deps = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
// Check for common test frameworks
if (deps.vitest)
return "vitest";
if (deps.jest)
return "jest";
if (deps.mocha)
return "mocha";
if (deps.pytest)
return "pytest";
if (deps["@testing-library/react"])
return "jest"; // Likely using Jest
return "none";
}
/**
* Run tests and return results
*/
async runTests(cwd = process.cwd(), framework) {
const detectedFramework = framework || (await this.detectTestFramework(cwd));
if (detectedFramework === "none") {
return {
passed: true,
framework: "none",
totalTests: 0,
passedTests: 0,
failedTests: 0,
failedTestNames: [],
output: "No test framework detected",
};
}
try {
const result = await this.runTestCommand(detectedFramework, cwd);
return result;
}
catch (error) {
// Parse error output to extract test results
const errorMessage = error instanceof Error ? error.message : String(error);
return {
passed: false,
framework: detectedFramework,
totalTests: 0,
passedTests: 0,
failedTests: 1,
failedTestNames: ["Test execution failed"],
output: errorMessage,
};
}
}
/**
* Run test command for specific framework
*/
async runTestCommand(framework, cwd) {
let command = "";
let args = [];
switch (framework) {
case "vitest":
command = "vitest";
args = ["run"];
break;
case "jest":
command = "jest";
args = ["--json"];
break;
case "mocha":
command = "mocha";
args = [];
break;
case "pytest":
command = "pytest";
args = ["--json-report"];
break;
default:
throw new Error(`Unknown test framework: ${framework}`);
}
try {
const result = await execa(command, args, {
cwd,
timeout: 120000, // 2 minutes
});
return this.parseTestOutput(result.stdout, framework);
}
catch (error) {
const output = error instanceof Error ? error.message : String(error);
return this.parseTestOutput(output, framework);
}
}
/**
* Parse test output based on framework
*/
parseTestOutput(output, framework) {
let passed = false;
let totalTests = 0;
let passedTests = 0;
let failedTests = 0;
const failedTestNames = [];
try {
if (framework === "jest") {
// Jest outputs JSON
const jsonMatch = output.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const json = JSON.parse(jsonMatch[0]);
passed = json.success;
totalTests = json.numTotalTests || 0;
passedTests = json.numPassedTests || 0;
failedTests = json.numFailedTests || 0;
if (json.testResults) {
for (const suite of json.testResults) {
if (suite.assertionResults) {
for (const test of suite.assertionResults) {
if (test.status === "failed") {
failedTestNames.push(`${suite.name}: ${test.fullName}`);
}
}
}
}
}
}
}
else if (framework === "vitest") {
// Vitest outputs text, parse for patterns
const passMatch = output.match(/(\d+)\s+passed/);
const failMatch = output.match(/(\d+)\s+failed/);
passedTests = passMatch ? parseInt(passMatch[1]) : 0;
failedTests = failMatch ? parseInt(failMatch[1]) : 0;
totalTests = passedTests + failedTests;
passed = failedTests === 0;
// Extract failed test names
const failedMatches = output.matchAll(/✓|×\s+(.+?)(?:\n|$)/g);
for (const match of failedMatches) {
if (match[0].startsWith("×")) {
failedTestNames.push(match[1] || "Unknown test");
}
}
}
else if (framework === "mocha") {
// Mocha format
const passMatch = output.match(/(\d+)\s+passing/);
const failMatch = output.match(/(\d+)\s+failing/);
passedTests = passMatch ? parseInt(passMatch[1]) : 0;
failedTests = failMatch ? parseInt(failMatch[1]) : 0;
totalTests = passedTests + failedTests;
passed = failedTests === 0;
}
else if (framework === "pytest") {
// Pytest format
const passMatch = output.match(/(\d+)\s+passed/);
const failMatch = output.match(/(\d+)\s+failed/);
passedTests = passMatch ? parseInt(passMatch[1]) : 0;
failedTests = failMatch ? parseInt(failMatch[1]) : 0;
totalTests = passedTests + failedTests;
passed = failedTests === 0;
}
}
catch {
// If parsing fails, check for common success indicators
passed = !output.includes("FAILED") && !output.includes("failed");
}
return {
passed,
framework,
totalTests,
passedTests,
failedTests,
failedTestNames,
output,
};
}
}
//# sourceMappingURL=test-runner.js.map