'use strict'; const { ArrayPrototypeJoin, ArrayPrototypePop, ArrayPrototypePush, ArrayPrototypeShift, ArrayPrototypeUnshift, } = primordials; const assert = require('assert'); const Transform = require('internal/streams/transform'); const colors = require('internal/util/colors'); const { kSubtestsFailed } = require('internal/test_runner/test'); const { getCoverageReport } = require('internal/test_runner/utils'); const { relative } = require('path'); const { formatTestReport, indent, reporterColorMap, reporterUnicodeSymbolMap, } = require('internal/test_runner/reporter/utils'); class SpecReporter extends Transform { #stack = []; #reported = []; #failedTests = []; #cwd = process.cwd(); constructor() { super({ __proto__: null, writableObjectMode: true }); colors.refresh(); } #handleTestReportEvent(type, data) { const subtest = ArrayPrototypeShift(this.#stack); // This is the matching `test:start` event if (subtest) { assert(subtest.type === 'test:start'); assert(subtest.data.nesting === data.nesting); assert(subtest.data.name === data.name); } let prefix = ''; while (this.#stack.length) { // Report all the parent `test:start` events const parent = ArrayPrototypePop(this.#stack); assert(parent.type === 'test:start'); const msg = parent.data; ArrayPrototypeUnshift(this.#reported, msg); prefix += `${indent(msg.nesting)}${reporterUnicodeSymbolMap['arrow:right']}${msg.name}\n`; } let hasChildren = false; if (this.#reported[0] && this.#reported[0].nesting === data.nesting && this.#reported[0].name === data.name) { ArrayPrototypeShift(this.#reported); hasChildren = true; } const indentation = indent(data.nesting); return `${formatTestReport(type, data, prefix, indentation, hasChildren)}\n`; } #handleEvent({ type, data }) { switch (type) { case 'test:fail': if (data.details?.error?.failureType !== kSubtestsFailed) { ArrayPrototypePush(this.#failedTests, data); } return this.#handleTestReportEvent(type, data); case 'test:pass': return this.#handleTestReportEvent(type, data); case 'test:start': ArrayPrototypeUnshift(this.#stack, { __proto__: null, data, type }); break; case 'test:stderr': case 'test:stdout': return data.message; case 'test:diagnostic': return `${reporterColorMap[type]}${indent(data.nesting)}${reporterUnicodeSymbolMap[type]}${data.message}${colors.white}\n`; case 'test:coverage': return getCoverageReport(indent(data.nesting), data.summary, reporterUnicodeSymbolMap['test:coverage'], colors.blue, true); } } _transform({ type, data }, encoding, callback) { callback(null, this.#handleEvent({ __proto__: null, type, data })); } _flush(callback) { if (this.#failedTests.length === 0) { callback(null, ''); return; } const results = [`\n${reporterColorMap['test:fail']}${reporterUnicodeSymbolMap['test:fail']}failing tests:${colors.white}\n`]; for (let i = 0; i < this.#failedTests.length; i++) { const test = this.#failedTests[i]; const formattedErr = formatTestReport('test:fail', test); if (test.file) { const relPath = relative(this.#cwd, test.file); const location = `test at ${relPath}:${test.line}:${test.column}`; ArrayPrototypePush(results, location); } ArrayPrototypePush(results, formattedErr); } callback(null, ArrayPrototypeJoin(results, '\n')); } } module.exports = SpecReporter;