From e29345607863d39dd38d402de8ada339c2a18422 Mon Sep 17 00:00:00 2001 From: CheadleCheadle Date: Tue, 17 Oct 2023 15:09:32 -0700 Subject: [PATCH 1/6] added optional jsw format to json reporter --- lib/cli/run-option-metadata.js | 3 ++- lib/reporters/json.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/cli/run-option-metadata.js b/lib/cli/run-option-metadata.js index 492608fbdd..58821fa4e7 100644 --- a/lib/cli/run-option-metadata.js +++ b/lib/cli/run-option-metadata.js @@ -49,7 +49,7 @@ const TYPES = (exports.types = { 'sort', 'watch' ], - number: ['retries', 'jobs'], + number: ['retries', 'jobs', 'jsonStringifyWhitespace'], string: [ 'config', 'fgrep', @@ -78,6 +78,7 @@ exports.aliases = { ignore: ['exclude'], invert: ['i'], jobs: ['j'], + jsonStringifyWhitespace: ['jsw'], 'no-colors': ['C'], 'node-option': ['n'], parallel: ['p'], diff --git a/lib/reporters/json.js b/lib/reporters/json.js index 6194d8747d..ff52d3b8a6 100644 --- a/lib/reporters/json.js +++ b/lib/reporters/json.js @@ -78,7 +78,7 @@ function JSONReporter(runner, options = {}) { runner.testResults = obj; - var json = JSON.stringify(obj, null, 2); + var json = JSON.stringify(obj, null, options.jsonStringifyWhitespace ?? 2); if (output) { try { fs.mkdirSync(path.dirname(output), {recursive: true}); From 86ff32187b5462b6882cb4ef006e321856807bf2 Mon Sep 17 00:00:00 2001 From: CheadleCheadle Date: Wed, 18 Oct 2023 16:56:12 -0700 Subject: [PATCH 2/6] added functionality to print Aggregate Errors and Unit Test --- bin/mocha.js | 0 lib/reporters/base.js | 30 ++++++++++++++++++++++++++-- lib/reporters/json.js | 4 +++- package-lock.json | 4 ---- test/reporters/base.spec.js | 39 ++++++++++++++++++++++++------------- 5 files changed, 57 insertions(+), 20 deletions(-) mode change 100644 => 100755 bin/mocha.js diff --git a/bin/mocha.js b/bin/mocha.js old mode 100644 new mode 100755 diff --git a/lib/reporters/base.js b/lib/reporters/base.js index 40b5996461..b52fd0c82b 100644 --- a/lib/reporters/base.js +++ b/lib/reporters/base.js @@ -246,6 +246,7 @@ exports.list = function (failures) { if (test.err && test.err.multiple) { if (multipleTest !== test) { multipleTest = test; + multipleErr = [test.err].concat(test.err.multiple); } err = multipleErr.shift(); @@ -301,11 +302,37 @@ exports.list = function (failures) { } testTitle += str; }); - Base.consoleLog(fmt, i + 1, testTitle, msg, stack); + + // Handles formatting and printing Aggregate errors + if (test.err.errors) { + for (const error of test.err.errors) { + const testTitle = addIndent(`Error ${error.message}`, 0); + const stack = addIndent(error.stack, 3); + + Base.consoleLog(fmt, error.message, testTitle, '', stack); + } + } }); }; +/** + * Adds indentation to a given text string. + * + * @param {string} text - The input text to be indented. + * @param {number} [level=1] - The number of levels of indentation. Default is 1. + * @param {string} [indentString=' '] - The string used for one level of indentation. Default is two spaces. + * @returns {string} The indented text. + */ +function addIndent(text, level = 1, indentString = ' ') { + const indent = indentString.repeat(level); + const indentedText = text + .split('\n') + .map(line => `${indent}${line}`) + .join('\n'); + return indentedText; +} + /** * Constructs a new `Base` reporter instance. * @@ -544,7 +571,6 @@ var objToString = Object.prototype.toString; function sameType(a, b) { return objToString.call(a) === objToString.call(b); } - Base.consoleLog = consoleLog; Base.abstract = true; diff --git a/lib/reporters/json.js b/lib/reporters/json.js index ff52d3b8a6..aaa0d8955f 100644 --- a/lib/reporters/json.js +++ b/lib/reporters/json.js @@ -78,7 +78,7 @@ function JSONReporter(runner, options = {}) { runner.testResults = obj; - var json = JSON.stringify(obj, null, options.jsonStringifyWhitespace ?? 2); + var json = JSON.stringify(obj, null, options.jsonStringifyWhitespace || 2); if (output) { try { fs.mkdirSync(path.dirname(output), {recursive: true}); @@ -105,6 +105,7 @@ function JSONReporter(runner, options = {}) { */ function clean(test) { var err = test.err || {}; + // process.stdout.write('-----------------', err); if (err instanceof Error) { err = errorJSON(err); } @@ -153,6 +154,7 @@ function cleanCycles(obj) { */ function errorJSON(err) { var res = {}; + Object.getOwnPropertyNames(err).forEach(function (key) { res[key] = err[key]; }, err); diff --git a/package-lock.json b/package-lock.json index 730f5dcbe5..3264668051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,10 +109,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/@11ty/dependency-tree": { diff --git a/test/reporters/base.spec.js b/test/reporters/base.spec.js index 6b30b2ccc7..7ec33e6059 100644 --- a/test/reporters/base.spec.js +++ b/test/reporters/base.spec.js @@ -507,21 +507,34 @@ describe('Base reporter', function () { ); }); - describe('when reporter output immune to user test changes', function () { - var baseConsoleLog; + it('should list all the errors within an AggregateError', function () { + var err1 = new Error('1'); + var err2 = new Error('2'); + var aggErr = new AggregateError([err1, err2], '2 errors'); - beforeEach(function () { - sinon.restore(); - sinon.stub(console, 'log'); - baseConsoleLog = sinon.stub(Base, 'consoleLog'); - }); + var test = makeTest(aggErr); + list([test]); - it('should let you stub out console.log without effecting reporters output', function () { - Base.list([]); - baseConsoleLog.restore(); + var errOut = stdout.join('\n').trim(); - expect(baseConsoleLog, 'was called'); - expect(console.log, 'was not called'); - }); + expect(errOut, 'to contain', '1) Error 1:', '2) Error 2:'); + }); +}); + +describe('when reporter output immune to user test changes', function () { + var baseConsoleLog; + + beforeEach(function () { + sinon.restore(); + sinon.stub(console, 'log'); + baseConsoleLog = sinon.stub(Base, 'consoleLog'); + }); + + it('should let you stub out console.log without effecting reporters output', function () { + Base.list([]); + baseConsoleLog.restore(); + + expect(baseConsoleLog, 'was called'); + expect(console.log, 'was not called'); }); }); From 792b5921b0f389a9efd154c723f6eae575e3d66c Mon Sep 17 00:00:00 2001 From: CheadleCheadle Date: Wed, 18 Oct 2023 16:59:22 -0700 Subject: [PATCH 3/6] removed comment --- lib/reporters/json.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/reporters/json.js b/lib/reporters/json.js index aaa0d8955f..b22e9eb4d0 100644 --- a/lib/reporters/json.js +++ b/lib/reporters/json.js @@ -105,7 +105,6 @@ function JSONReporter(runner, options = {}) { */ function clean(test) { var err = test.err || {}; - // process.stdout.write('-----------------', err); if (err instanceof Error) { err = errorJSON(err); } From 690b4708c65724e3ce2e21618e4f6b6471114efd Mon Sep 17 00:00:00 2001 From: CheadleCheadle Date: Fri, 3 Nov 2023 13:54:25 -0700 Subject: [PATCH 4/6] addresses #5018. Base.list handles aggregate errors --- lib/cli/run-option-metadata.js | 2 +- lib/reporters/base.js | 57 ++++++++++++---------------------- lib/reporters/json.js | 3 +- test/reporters/base.spec.js | 2 +- 4 files changed, 23 insertions(+), 41 deletions(-) diff --git a/lib/cli/run-option-metadata.js b/lib/cli/run-option-metadata.js index 58821fa4e7..f38387a695 100644 --- a/lib/cli/run-option-metadata.js +++ b/lib/cli/run-option-metadata.js @@ -49,7 +49,7 @@ const TYPES = (exports.types = { 'sort', 'watch' ], - number: ['retries', 'jobs', 'jsonStringifyWhitespace'], + number: ['retries', 'jobs'], string: [ 'config', 'fgrep', diff --git a/lib/reporters/base.js b/lib/reporters/base.js index b52fd0c82b..e9223f132a 100644 --- a/lib/reporters/base.js +++ b/lib/reporters/base.js @@ -246,12 +246,14 @@ exports.list = function (failures) { if (test.err && test.err.multiple) { if (multipleTest !== test) { multipleTest = test; - multipleErr = [test.err].concat(test.err.multiple); } err = multipleErr.shift(); - } else { + } else if (test.err) { err = test.err; + } else { + // Handles when failures is a list of errors and not test objects. + err = test; } var message; if (typeof err.inspect === 'function') { @@ -293,46 +295,27 @@ exports.list = function (failures) { // indented test title var testTitle = ''; - test.titlePath().forEach(function (str, index) { - if (index !== 0) { - testTitle += '\n '; - } - for (var i = 0; i < index; i++) { - testTitle += ' '; - } - testTitle += str; - }); - Base.consoleLog(fmt, i + 1, testTitle, msg, stack); - - // Handles formatting and printing Aggregate errors - if (test.err.errors) { - for (const error of test.err.errors) { - const testTitle = addIndent(`Error ${error.message}`, 0); - const stack = addIndent(error.stack, 3); + // Incase test is an error object when recursively listing aggregate errors + if (test.titlePath) { + test.titlePath().forEach(function (str, index) { + if (index !== 0) { + testTitle += '\n '; + } + for (var i = 0; i < index; i++) { + testTitle += ' '; + } + testTitle += str; + }); + } - Base.consoleLog(fmt, error.message, testTitle, '', stack); - } + Base.consoleLog(fmt, i + 1, testTitle, msg, stack); + // Handle Aggregate Errors + if (test.err && test.err.errors) { + Base.list(test.err.errors); } }); }; -/** - * Adds indentation to a given text string. - * - * @param {string} text - The input text to be indented. - * @param {number} [level=1] - The number of levels of indentation. Default is 1. - * @param {string} [indentString=' '] - The string used for one level of indentation. Default is two spaces. - * @returns {string} The indented text. - */ -function addIndent(text, level = 1, indentString = ' ') { - const indent = indentString.repeat(level); - const indentedText = text - .split('\n') - .map(line => `${indent}${line}`) - .join('\n'); - return indentedText; -} - /** * Constructs a new `Base` reporter instance. * diff --git a/lib/reporters/json.js b/lib/reporters/json.js index b22e9eb4d0..6194d8747d 100644 --- a/lib/reporters/json.js +++ b/lib/reporters/json.js @@ -78,7 +78,7 @@ function JSONReporter(runner, options = {}) { runner.testResults = obj; - var json = JSON.stringify(obj, null, options.jsonStringifyWhitespace || 2); + var json = JSON.stringify(obj, null, 2); if (output) { try { fs.mkdirSync(path.dirname(output), {recursive: true}); @@ -153,7 +153,6 @@ function cleanCycles(obj) { */ function errorJSON(err) { var res = {}; - Object.getOwnPropertyNames(err).forEach(function (key) { res[key] = err[key]; }, err); diff --git a/test/reporters/base.spec.js b/test/reporters/base.spec.js index 7ec33e6059..d3c87702b0 100644 --- a/test/reporters/base.spec.js +++ b/test/reporters/base.spec.js @@ -517,7 +517,7 @@ describe('Base reporter', function () { var errOut = stdout.join('\n').trim(); - expect(errOut, 'to contain', '1) Error 1:', '2) Error 2:'); + expect(errOut, 'to contain', ' Error: 1', 'Error: 2'); }); }); From b2cd9b9b3778c7f6177953828e2a8f6a5301dd10 Mon Sep 17 00:00:00 2001 From: "Grant A. Cheadle" <108553712+CheadleCheadle@users.noreply.github.com> Date: Fri, 2 Feb 2024 16:15:02 -0800 Subject: [PATCH 5/6] Updated base.js Modified check for if an error is an instance of an AggregateError when recursively printing errors --- lib/reporters/base.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reporters/base.js b/lib/reporters/base.js index e9223f132a..d9f028273b 100644 --- a/lib/reporters/base.js +++ b/lib/reporters/base.js @@ -295,8 +295,8 @@ exports.list = function (failures) { // indented test title var testTitle = ''; - // Incase test is an error object when recursively listing aggregate errors - if (test.titlePath) { + // Incase test is an AggregateError object when recursively listing errors + if (test instanceof AggregateError) { test.titlePath().forEach(function (str, index) { if (index !== 0) { testTitle += '\n '; From b04709cab8ede0ed2b1bda55fd6ac56618e3a01c Mon Sep 17 00:00:00 2001 From: Grant Cheadle Date: Mon, 4 Mar 2024 15:33:13 -0800 Subject: [PATCH 6/6] fixes --- lib/cli/run-option-metadata.js | 1 - lib/reporters/base.js | 2 +- test/reporters/base.spec.js | 39 +++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/cli/run-option-metadata.js b/lib/cli/run-option-metadata.js index f38387a695..492608fbdd 100644 --- a/lib/cli/run-option-metadata.js +++ b/lib/cli/run-option-metadata.js @@ -78,7 +78,6 @@ exports.aliases = { ignore: ['exclude'], invert: ['i'], jobs: ['j'], - jsonStringifyWhitespace: ['jsw'], 'no-colors': ['C'], 'node-option': ['n'], parallel: ['p'], diff --git a/lib/reporters/base.js b/lib/reporters/base.js index d9f028273b..bad9fa22b7 100644 --- a/lib/reporters/base.js +++ b/lib/reporters/base.js @@ -295,7 +295,7 @@ exports.list = function (failures) { // indented test title var testTitle = ''; - // Incase test is an AggregateError object when recursively listing errors + if (test instanceof AggregateError) { test.titlePath().forEach(function (str, index) { if (index !== 0) { diff --git a/test/reporters/base.spec.js b/test/reporters/base.spec.js index d3c87702b0..d17c678f27 100644 --- a/test/reporters/base.spec.js +++ b/test/reporters/base.spec.js @@ -517,7 +517,44 @@ describe('Base reporter', function () { var errOut = stdout.join('\n').trim(); - expect(errOut, 'to contain', ' Error: 1', 'Error: 2'); + // Handle removing system's specific error callStack + errOut = errOut + .split('\n') + .filter(line => !line.trim().startsWith('at ')) + .join('\n') + .replace(/\n/g, ''); + + var expectedFormat = `1) : ${aggErr.name}: ${aggErr.message} 1) : ${err1.name}: ${err1.message} 2) : ${err2.name}: ${err2.message}`; + + expect(errOut, 'to equal', expectedFormat); + }); + + it('should handle Aggregate Error Objects with 0 errors properly', function () { + var aggErr = new AggregateError([], ' 0 errors'); + + var test = makeTest(aggErr); + list([test]); + + var errOut = stdout.join('\n').trim(); + + var expectedFormat = aggErr.name + ': ' + aggErr.message; + + expect(errOut, 'to contain', expectedFormat); + }); + + it('should handle non-Error types properly', function () { + var nonError = {name: 'NotAnError', message: 'This is not an error object'}; + var aggErr = new AggregateError([nonError]); + + var test = makeTest(aggErr); + list([test]); + + assert.strictEqual(aggErr.errors.length, 1, 'Should contain one error'); + assert.strictEqual( + aggErr.errors[0], + nonError, + 'The non-Error object should be preserved in the errors array' + ); }); });