111 lines
3.0 KiB
JavaScript
111 lines
3.0 KiB
JavaScript
/**
|
|
* test_samurai_vitest_reporter.mjs
|
|
*
|
|
* Custom Vitest 2.x reporter for test-samurai.
|
|
*
|
|
* Emits one line per completed test case to stdout:
|
|
* TSAMURAI_RESULT <json>
|
|
*
|
|
* Payload schema:
|
|
* {
|
|
* name: string -- Describe-hierarchy + test title joined with "/"
|
|
* status: "passed" | "failed" | "skipped"
|
|
* file: string | null -- absolute file path
|
|
* location: { line: number, column: number } | null
|
|
* output: string[] -- error messages / stack traces (failures only)
|
|
* }
|
|
*/
|
|
|
|
const RESULT_PREFIX = 'TSAMURAI_RESULT ';
|
|
|
|
export default class TestSamuraiVitestReporter {
|
|
constructor() {
|
|
this._consoleLogs = new Map();
|
|
}
|
|
|
|
onUserConsoleLog(log) {
|
|
if (!log.taskId) return;
|
|
if (!this._consoleLogs.has(log.taskId)) {
|
|
this._consoleLogs.set(log.taskId, []);
|
|
}
|
|
const lines = log.content.split('\n');
|
|
for (const line of lines) {
|
|
if (line !== '') {
|
|
this._consoleLogs.get(log.taskId).push(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
onTestCaseResult(testCase) {
|
|
const name = buildListingName(testCase);
|
|
const status = mapStatus(testCase.task);
|
|
if (!status) return;
|
|
|
|
const consoleLogs = this._consoleLogs.get(testCase.task?.id) ?? [];
|
|
const output = buildOutput(testCase.task, consoleLogs);
|
|
|
|
const payload = {
|
|
name,
|
|
status,
|
|
file: testCase.file?.filepath ?? null,
|
|
location: testCase.location ?? null,
|
|
output,
|
|
};
|
|
|
|
process.stdout.write(`${RESULT_PREFIX}${JSON.stringify(payload)}\n`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds a slash-separated name from the describe hierarchy.
|
|
* E.g. describe('MyComponent') > describe('renders') > it('the button')
|
|
* → "MyComponent/renders/the button"
|
|
*/
|
|
function buildListingName(testCase) {
|
|
const parts = [testCase.name];
|
|
let current = testCase.parent;
|
|
while (current && current.type === 'suite') {
|
|
parts.unshift(current.name);
|
|
current = current.parent;
|
|
}
|
|
return parts.filter(Boolean).join('/');
|
|
}
|
|
|
|
/**
|
|
* Maps Vitest task result states to test-samurai status strings.
|
|
* Falls back to task.mode if result.state is missing.
|
|
*/
|
|
function mapStatus(task) {
|
|
if (!task) return null;
|
|
const state = task.result?.state;
|
|
if (state === 'pass') return 'passed';
|
|
if (state === 'fail') return 'failed';
|
|
if (state === 'skip') return 'skipped';
|
|
const mode = task.mode;
|
|
if (mode === 'skip' || mode === 'todo') return 'skipped';
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Collects console logs and error messages/stack traces for tests.
|
|
* Console logs appear first, then error details.
|
|
*/
|
|
function buildOutput(task, consoleLogs = []) {
|
|
const lines = [...consoleLogs];
|
|
if (!task) return lines;
|
|
const errors = task.result?.errors ?? [];
|
|
for (const err of errors) {
|
|
if (typeof err.message === 'string' && err.message.length > 0) {
|
|
err.message.split('\n').forEach((line) => {
|
|
if (line !== '') lines.push(line);
|
|
});
|
|
}
|
|
if (typeof err.stack === 'string' && err.stack.length > 0) {
|
|
err.stack.split('\n').forEach((line) => {
|
|
if (line !== '') lines.push(line);
|
|
});
|
|
}
|
|
}
|
|
return lines;
|
|
}
|