generated from voxpelli/node-module-template
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
479 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
{ | ||
"$schema": "https://unpkg.com/knip@2/schema.json", | ||
"ignoreDependencies": ["@types/mocha", "mocha"] | ||
"entry": [ | ||
"index.js", | ||
"index.d.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,63 @@ | ||
# Node Module Template | ||
# @voxpelli/node-test-pretty-reporter | ||
|
||
A GitHub template repo for node modules | ||
Reporter for [node:test](https://nodejs.org/api/test.html#custom-reporters) that supports colorful diffs etc | ||
|
||
<!-- | ||
[![npm version](https://img.shields.io/npm/v/buffered-async-iterable.svg?style=flat)](https://www.npmjs.com/package/buffered-async-iterable) | ||
[![npm downloads](https://img.shields.io/npm/dm/buffered-async-iterable.svg?style=flat)](https://www.npmjs.com/package/buffered-async-iterable) | ||
--> | ||
[![npm version](https://img.shields.io/npm/v/@voxpelli/node-test-pretty-reporter.svg?style=flat)](https://www.npmjs.com/package/@voxpelli/node-test-pretty-reporter) | ||
[![npm downloads](https://img.shields.io/npm/dm/@voxpelli/node-test-pretty-reporter.svg?style=flat)](https://www.npmjs.com/package/@voxpelli/node-test-pretty-reporter) | ||
[![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg)](https://github.com/voxpelli/eslint-config) | ||
[![Module type: ESM](https://img.shields.io/badge/module%20type-esm-brightgreen)](https://github.com/voxpelli/badges-cjs-esm) | ||
[![Types in JS](https://img.shields.io/badge/types_in_js-yes-brightgreen)](https://github.com/voxpelli/types-in-js) | ||
[![Follow @voxpelli@mastodon.social](https://img.shields.io/mastodon/follow/109247025527949675?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@voxpelli) | ||
|
||
## Usage | ||
|
||
### Simple | ||
|
||
```javascript | ||
import { something } from '@voxpelli/node-module-template'; | ||
```sh | ||
npm install -D @voxpelli/node-test-pretty-reporter | ||
``` | ||
|
||
// Use that something | ||
```sh | ||
node --test --test-reporter=@voxpelli/node-test-pretty-reporter | ||
``` | ||
|
||
## API | ||
## Why another test reporter? | ||
|
||
This one is similar to the built-in `spec` reporter but differs in some ways which I personally prefer. | ||
|
||
### Rendering diffs from assertions | ||
|
||
Outputs colored diffs when a test is failed with an `Error` that has `expected` and `actual` properties (respecting a `showDiff` property set to `false`). | ||
|
||
Diff is generated by [`jest-diff`](https://www.npmjs.com/package/jest-diff) (no other part of `jest` is used in this reporter). | ||
|
||
Assertion libraries that outputs compatible errors: | ||
* [`node:assert`](https://nodejs.org/api/assert.html#assert) | ||
* [`chai`](https://www.chaijs.com/) | ||
|
||
### `something(input, { configParam }) => Promise<output>` | ||
As with other changes, this makes the reporter on par [with Mocha](https://mochajs.org/#diffs). | ||
|
||
### Output styling | ||
|
||
The output styling aligns more with Mocha's [`spec` reporter](https://mochajs.org/#spec): | ||
|
||
* Errors are presented at the end instead of in the list of tests | ||
* Less visually intense, eg. no `▶` in front of names and only failed tests gets colored | ||
* Durations are only reported if considered slow (using same default [as Mocha](https://mochajs.org/#test-duration): 75ms) | ||
* No redundant mentioning of a test suite after the suite has completed – opts for a clean tree from top to bottom instead | ||
|
||
### _Planned:_ Outputting the full `cause` chain of an `Error` | ||
|
||
Originally [a PR of mine to Mocha](https://github.com/mochajs/mocha/pull/4829), my plan was to output the full `cause` chain in this reporter instead, but seems like the full `cause` chain is not available to the reporters, probably due to the reporter and the tests living in different processes. Will investigate further, follow in [issue #2](https://github.com/voxpelli/node-test-pretty-reporter/issues/2). | ||
|
||
Takes a value (`input`), does something configured by the config (`configParam`) and returns the processed value asyncly(`output`) | ||
|
||
## Similar modules | ||
|
||
* [`example`](https://example.com/) – is similar in this way | ||
I have not tested any of these myself yet so can't say if they work well or not, but adding here for reference. | ||
|
||
* [`MoLow/reporters`](https://github.com/MoLow/reporters) – many custom reporters for `node:test` | ||
* [`nearform/node-test-github-reporter`](https://github.com/nearform/node-test-github-reporter) – another custom report for `node:test`, this one from [@nearform](https://github.com/nearform) and geared towards GitHub Actions | ||
|
||
## See also | ||
|
||
* [Announcement blog post](#) | ||
* [Announcement tweet](#) | ||
* [`node:test`](https://nodejs.org/api/test.html) – the full documentation for the `node:test` module that shipped in Node.js 18 | ||
* [`nodejs/node-core-test`](https://github.com/nodejs/node-core-test) – a userland port of `node:test` making it available in Node.js 14 and later (this reporter has not been tested with this userland port) | ||
* [`@matteo.collina/tspl`](https://github.com/mcollina/tspl) – test planner for `node:test` and `node:assert` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,143 @@ | ||
// TODO: Replace with proper types or remove file | ||
import {} from 'node:path'; | ||
export interface TestsStreamEvents { | ||
'test:diagnostic': TestDiagnosticData | ||
'test:fail': TestFail | ||
'test:pass': TestPass | ||
'test:plan': TestPlan | ||
'test:start': TestFileEvent | ||
'test:coverage': TestCoverage | ||
// Not exposed to custom reporter? | ||
'test:dequeue': TestFileEvent | ||
'test:enqueue': TestFileEvent | ||
'test:stderr': TestStdout | ||
'test:stdout': TestStdout | ||
'test:watch:drained': TestEmptyEvent | ||
|
||
} | ||
|
||
export type TestsStreamEventPayloads = { | ||
[type in keyof TestsStreamEvents]: { | ||
type: type, | ||
data: TestsStreamEvents[type], | ||
} | ||
}[keyof TestsStreamEvents]; | ||
|
||
|
||
// *** Event payloads not yet in @types/node *** | ||
|
||
export interface TestEventBasic { | ||
nesting: number; | ||
} | ||
|
||
export interface TestFileEventBasic extends TestEventBasic { | ||
file: string|undefined, | ||
} | ||
|
||
export interface TestFileEvent extends TestFileEventBasic { | ||
name: string, | ||
} | ||
|
||
export interface TestPlan extends TestFileEventBasic { | ||
count: number; | ||
} | ||
|
||
export interface TestDiagnosticData extends TestFileEventBasic { | ||
message: string; | ||
} | ||
|
||
|
||
export interface TestStdout { | ||
file: string, | ||
message: string, | ||
} | ||
|
||
export interface TestEmptyEvent {} | ||
|
||
// *** test:coverage event *** | ||
|
||
export interface TestCoverageSummary { | ||
totalLineCount: number | ||
totalBranchCount: number | ||
totalFunctionCount: number | ||
coveredLineCount: number | ||
coveredBranchCount: number | ||
coveredFunctionCount: number | ||
coveredLinePercent: number | ||
coveredBranchPercent: number | ||
coveredFunctionPercent: number | ||
} | ||
|
||
export interface TestCoverageSummaryFile extends TestCoverageSummary { | ||
path: string | ||
uncoveredLineNumbers: number[] | ||
} | ||
|
||
export interface TestCoverage extends TestEventBasic { | ||
summary: { | ||
files: TestCoverageSummaryFile[] | ||
totals: TestCoverageSummary | ||
workingDirectory: string | ||
} | ||
} | ||
|
||
// *** Somewhat of a copy and paste from @types/node *** | ||
|
||
// TODO: Have @types/node export these instead of copy and pasting them here | ||
|
||
export interface TestFail extends TestFileEvent { | ||
/** | ||
* Additional execution metadata. | ||
*/ | ||
details: { | ||
/** | ||
* The duration of the test in milliseconds. | ||
*/ | ||
duration_ms: number; | ||
|
||
/** | ||
* The error thrown by the test. | ||
*/ | ||
error: Error; | ||
}; | ||
|
||
/** | ||
* The ordinal number of the test. | ||
*/ | ||
testNumber: number; | ||
|
||
/** | ||
* Present if `context.todo` is called. | ||
*/ | ||
todo?: string | boolean; | ||
|
||
/** | ||
* Present if `context.skip` is called. | ||
*/ | ||
skip?: string | boolean; | ||
} | ||
|
||
export interface TestPass extends TestFileEvent { | ||
/** | ||
* Additional execution metadata. | ||
*/ | ||
details: { | ||
/** | ||
* The duration of the test in milliseconds. | ||
*/ | ||
duration_ms: number; | ||
}; | ||
|
||
/** | ||
* The ordinal number of the test. | ||
*/ | ||
testNumber: number; | ||
|
||
/** | ||
* Present if `context.todo` is called. | ||
*/ | ||
todo?: string | boolean; | ||
|
||
/** | ||
* Present if `context.skip` is called. | ||
*/ | ||
skip?: string | boolean; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { diff } from 'jest-diff'; | ||
|
||
/** | ||
* @typedef DiffableError | ||
* @property {unknown} expected | ||
* @property {unknown} actual | ||
* @property {boolean|undefined} [showDiff] | ||
*/ | ||
|
||
/** | ||
* @template {Error} T | ||
* @param {T} input | ||
* @returns {input is (DiffableError & T)} | ||
*/ | ||
export function errIsDiffable (input) { | ||
if (!('expected' in input)) return false; | ||
if (!('actual' in input)) return false; | ||
if (input.expected === undefined) return false; | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* @param {DiffableError} input | ||
* @returns {string|undefined} | ||
*/ | ||
export function generateErrDiff ({ actual, expected }) { | ||
return diff(actual, expected) || undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { errIsDiffable, generateErrDiff } from './diff.js'; | ||
import { getErrorAndCauses } from './utils.js'; | ||
|
||
/** | ||
* @param {import('markdown-or-chalk').MarkdownOrChalk} format | ||
* @param {Error} err | ||
* @returns {string} | ||
*/ | ||
export function formatErrorAndCauses (format, err) { | ||
if ('code' in err && err.code === 'ERR_TEST_FAILURE' && err.cause instanceof Error) { | ||
err = err.cause; | ||
} | ||
|
||
return getErrorAndCauses(err) | ||
.map(value => formatError(format, value)) | ||
.join('\n\ncaused by:\n\n'); | ||
} | ||
|
||
/** | ||
* @param {import('markdown-or-chalk').MarkdownOrChalk} format | ||
* @param {Error} err | ||
* @returns {string} | ||
*/ | ||
function formatError (format, err) { | ||
let message = err.message; | ||
let stack = (err.stack || '').replaceAll(/^\s+/gm, ''); | ||
|
||
const index = stack.indexOf(message); | ||
|
||
if (index > -1) { | ||
const splitIndex = index + message.length; | ||
|
||
message = stack.slice(0, splitIndex); | ||
stack = stack.slice(splitIndex + 1); | ||
} | ||
|
||
if (format.chalk) { | ||
message = format.chalk.red(message); | ||
stack = format.chalk.gray(stack); | ||
} | ||
|
||
const diff = errIsDiffable(err) && err.showDiff !== false | ||
? generateErrDiff(err) | ||
: undefined; | ||
|
||
return ( | ||
diff | ||
? [message, diff, stack] | ||
: [message, stack] | ||
).join('\n\n'); | ||
} |
Oops, something went wrong.