diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index a31b3b0e3..9fbef613c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -55,7 +55,7 @@ jobs: run: npm ci - name: Build JavaScript (from TypeScript) - run: npm run build-all + run: npm run build && npm run build-dist - name: Run tests with coverage output run: npm run test-coverage diff --git a/package.json b/package.json index 969cc97cc..a1dcb037e 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "postinstall": "reticulate ratsnest", "preinstall": "node ./bootstrap-hoist", "serve-coverage": "python3 -m http.server -d output/lcov-report 8080", - "test": "mocha packages/*/lib/tests/test-*.js", - "test-coverage": "c8 -o output -r lcov -r text mocha packages/*/lib/tests/test-*.js | tee output/summary.txt" + "test": "mocha --reporter ./reporter packages/*/lib/tests/test-*.js", + "test-coverage": "c8 -o output -r lcov -r text mocha --reporter ./reporter packages/*/lib/tests/test-*.js | tee output/summary.txt" }, "version": "0.0.0" } diff --git a/reporter.js b/reporter.js new file mode 100644 index 000000000..cffa99d39 --- /dev/null +++ b/reporter.js @@ -0,0 +1,118 @@ +'use strict'; + +const Mocha = require('mocha'); +const { + EVENT_RUN_BEGIN, + EVENT_RUN_END, + EVENT_TEST_FAIL, + EVENT_TEST_PASS, + EVENT_SUITE_BEGIN, + EVENT_SUITE_END +} = Mocha.Runner.constants; + +function getTime() { return (new Date()).getTime(); } +const KEEP_ALIVE = 20 * 1000; + +// this reporter outputs test results, indenting two spaces per suite +class MyReporter { + constructor(runner) { + this._errors = [ ]; + this._indents = 1; + this._lastLog = getTime(); + this._lastPass = ""; + this._lastPrefix = null; + this._lastPrefixHeader = null; + this._prefixCount = 0; + const stats = runner.stats; + + runner.once(EVENT_RUN_BEGIN, () => { + + }).on(EVENT_SUITE_BEGIN, (suite) => { + if (suite.title.trim()) { + this.log(`Suite: ${ suite.title }`) + } + this.increaseIndent(); + + }).on(EVENT_SUITE_END, (suite) => { + this.flush(true); + this.decreaseIndent(); + if (suite.title.trim()) { this.log(""); } + + }).on(EVENT_TEST_PASS, (test) => { + this.addPass(test.title); + + }).on(EVENT_TEST_FAIL, (test, error) => { + this.flush(); + this._errors.push({ test, error }); + this.log( + ` [ fail(${ this._errors.length }): ${ test.title } - ${error.message} ]` + ); + + }).once(EVENT_RUN_END, () => { + this.flush(true); + + if (this._errors.length) { + this._errors.forEach(({ test, error }, index) => { + console.log("---------------------"); + console.log(`ERROR ${ index + 1 }: ${ test.title }`); + console.log(error); + }); + console.log("====================="); + } + + const { duration, passes, failures } = stats; + const total = passes + failures; + console.log(`Done: ${ passes }/${ total } passed (${ failures } failed)`); + }); + } + + log(line) { + this._lastLog = getTime(); + const indent = Array(this._indents).join(' '); + console.log(`${ indent }${ line }`); + } + + addPass(line) { + const prefix = line.split(":")[0]; + if (prefix === this._lastPrefix) { + this._prefixCount++; + if (getTime() - this._lastLog > KEEP_ALIVE) { + this.flush(false); + this.log(" [ keep-alive: forced output ]") + } + } else { + this.flush(true); + this._lastPrefixHeader = null; + this._lastPrefix = prefix; + this._prefixCount = 1; + } + this._lastLine = line; + } + + flush(reset) { + if (this._lastPrefix != null) { + if (this._prefixCount === 1 && this._lastPrefixHeader == null) { + this.log(this._lastLine); + } else { + if (this._lastPrefixHeader !== this._lastPrefix) { + this.log(`${ this._lastPrefix }:`); + this._lastPrefixHeader = this._lastPrefix; + } + this.log(` - ${ this._prefixCount } tests passed (prefix coalesced)`); + } + } + + if (reset) { + this._lastPrefixHeader = null; + this._lastPrefix = null; + } + + this._prefixCount = 0; + } + + increaseIndent() { this._indents++; } + + decreaseIndent() { this._indents--; } +} + +module.exports = MyReporter;