Comparing version 0.3.5 to 0.3.6-RC.0
290
index.js
#!/usr/bin/env node | ||
import minimist from 'minimist' | ||
const JSON5 = require('json5') | ||
const minimist = require('minimist') | ||
const Parser = require('tap-parser') | ||
const stripAnsi = require('strip-ansi') | ||
const through = require('through2') | ||
const { strict } = require('tcompare') | ||
const { blue, bold, dim, green, italic, red, underline, yellow } = require( | ||
'picocolors', | ||
) | ||
import helpText from './src/_help-text.js' | ||
import tapArc from './src/arc-tap.js' | ||
// Log test-group name | ||
const RESULT_COMMENTS = [ | ||
'tests ', 'pass ', 'skip', 'todo', 'fail ', 'failed ', 'ok', | ||
] | ||
const alias = { | ||
@@ -32,274 +21,11 @@ help: [ 'h', 'help' ], | ||
if (options.help) { | ||
console.log( | ||
` | ||
Usage: | ||
tap-arc <options> | ||
Parses TAP data from stdin, and outputs a "spec-like" formatted result. | ||
Options: | ||
-v | --verbose | ||
Output full stack trace | ||
-p | --pessimistic | --bail | ||
Immediately exit upon encountering a failure | ||
example: tap-arc -p | ||
--no-color | ||
Output without ANSI escape sequences for colors | ||
example: tap-arc --no-color`, | ||
) | ||
console.log(helpText) | ||
process.exit() | ||
} | ||
const parser = new Parser({ bail: options.pessimistic }) | ||
const tapArc = through() | ||
const cwd = process.cwd() | ||
const start = Date.now() | ||
const OKAY = green('✔') | ||
const FAIL = red('✖') | ||
function pad (count = 1, char = ' ') { | ||
return dim(char).repeat(count) | ||
} | ||
function makeDiff (lhs, rhs) { | ||
const msg = [] | ||
let isJson = true | ||
let pLhs = lhs | ||
let pRhs = rhs | ||
try { | ||
pLhs = JSON5.parse(lhs) | ||
pRhs = JSON5.parse(rhs) | ||
} | ||
catch (e) { | ||
isJson = false | ||
} | ||
if (isJson) { | ||
lhs = pLhs | ||
rhs = pRhs | ||
} | ||
const compared = strict(lhs, rhs, { | ||
includeEnumerable: true, | ||
includeGetters: true, | ||
pretty: true, | ||
sort: true, | ||
}) | ||
if (!compared.match) { | ||
// remove leading header lines | ||
let diff = compared.diff.split('\n') | ||
diff = diff.slice(2, diff.length - 1) | ||
for (const line of diff) { | ||
const char0 = line.charAt(0) | ||
if (char0 === '-') { | ||
msg.push(red(line)) | ||
} | ||
else if (char0 === '+') { | ||
msg.push(green(line)) | ||
} | ||
else if (char0 === '@') { | ||
msg.push(italic(dim(line))) | ||
} | ||
else { | ||
msg.push(line) | ||
} | ||
} | ||
} | ||
else { | ||
msg.push(`${red('Expected')} did not match ${green('actual')}.`) | ||
} | ||
return msg | ||
} | ||
function print (msg) { | ||
tapArc.push(options.color ? msg : stripAnsi(msg)) | ||
} | ||
function prettyMs (start) { | ||
const ms = Date.now() - start | ||
return ms < 1000 ? `${ms} ms` : `${ms / 1000} s` | ||
} | ||
parser.on('pass', (pass) => { | ||
print(`${pad(2)}${OKAY} ${dim(pass.name)}\n`) | ||
const parser = tapArc(options, (error, result) => { | ||
process.exit(result.ok ? 0 : 1) | ||
// process.exit(result.ok && result.count > 0 ? 0 : 1) | ||
}) | ||
parser.on('skip', (skip) => { | ||
print(`${pad(2)}${dim(`SKIP ${skip.name}`)}\n`) | ||
}) | ||
parser.on('extra', (extra) => { | ||
const stripped = stripAnsi(extra).trim() | ||
const justAnsi = stripped.length === 0 && extra.length > 0 | ||
if (!justAnsi) { | ||
print(`${pad(2)}${extra}`) | ||
} | ||
}) | ||
parser.on('comment', (comment) => { | ||
if (!RESULT_COMMENTS.some((c) => comment.startsWith(c, 2))) { | ||
print(`\n${pad()}${underline(comment.trimEnd().replace(/^(# )/, ''))}\n`) | ||
} | ||
}) | ||
parser.on('todo', (todo) => { | ||
if (todo.ok) { | ||
print(`${pad(2)}${yellow('TODO')} ${dim(todo.name)}\n`) | ||
} | ||
else { | ||
print(`${pad(2)}${red('TODO')} ${dim(todo.name)}\n`) | ||
} | ||
}) | ||
parser.on('fail', (fail) => { | ||
print(`${pad(2)}${FAIL} ${dim(`${fail.id})`)} ${red(fail.name)}\n`) | ||
if (fail.diag) { | ||
const { actual, at, expected, operator, stack } = fail.diag | ||
let msg = [] // individual lines of output | ||
if ([ 'equal', 'deepEqual' ].includes(operator)) { | ||
if (typeof expected === 'string' && typeof actual === 'string') { | ||
msg = [ ...msg, ...makeDiff(actual, expected) ] | ||
} | ||
else if (typeof expected === 'object' && typeof actual === 'object') { | ||
// probably an array | ||
msg = [ ...msg, ...makeDiff(actual, expected) ] | ||
} | ||
else if (typeof expected === 'number' || typeof actual === 'number') { | ||
msg.push(`Expected ${red(expected)} but got ${green(actual)}`) | ||
} | ||
else { | ||
// mixed types | ||
msg.push(`operator: ${operator}`) | ||
msg.push(`expected: ${red(`- ${expected}`)} <${typeof expected}>`) | ||
msg.push(`actual: ${green(`+ ${actual}`)} <${typeof actual}>`) | ||
} | ||
} | ||
else if ([ 'notEqual', 'notDeepEqual' ].includes(operator)) { | ||
msg.push('Expected values to differ') | ||
} | ||
else if (operator === 'ok') { | ||
msg.push(`Expected ${blue('truthy')} but got ${green(actual)}`) | ||
} | ||
else if (operator === 'match') { | ||
msg.push(`Expected "${actual}" to match ${blue(expected)}`) | ||
} | ||
else if (operator === 'doesNotMatch') { | ||
msg.push(`Expected "${actual}" to not match ${blue(expected)}`) | ||
} | ||
else if ( | ||
operator === 'throws' && | ||
actual && | ||
actual !== 'undefined' && | ||
expected && | ||
expected !== 'undefined' | ||
) { | ||
// this combination is throws with expected/assertion | ||
msg.push( | ||
`Expected ${red(expected)} to match "${green( | ||
actual.message || actual, | ||
)}"`, | ||
) | ||
} | ||
else if (operator === 'throws' && actual && actual !== 'undefined') { | ||
// this combination is ~doesNotThrow | ||
msg.push(`Expected to not throw, received "${green(actual)}"`) | ||
} | ||
else if (operator === 'throws') { | ||
msg.push('Expected to throw') | ||
} | ||
else if (operator === 'error') { | ||
msg.push(`Expected error to be ${blue('falsy')}`) | ||
} | ||
else if (expected && !actual) { | ||
msg.push(`Expected ${red(operator)} but got nothing`) | ||
} | ||
else if (actual && !expected) { | ||
msg.push(`Expected ${blue('falsy')} but got ${green(actual)}`) | ||
} | ||
else if (expected && actual) { | ||
msg.push(`Expected ${red(expected)} but got ${green(actual)}`) | ||
} | ||
else if (operator === 'fail') { | ||
msg.push('Explicit fail') | ||
} | ||
else if (!expected && !actual) { | ||
msg.push(`operator: ${yellow(operator)}`) | ||
} | ||
else { | ||
// unlikely | ||
msg.push(`operator: ${yellow(operator)}`) | ||
msg.push(`expected: ${green(expected)}`) | ||
msg.push(`actual: ${red(actual)}`) | ||
} | ||
if (at) { | ||
msg.push(`${dim(`At: ${at.replace(cwd, '')}`)}`) | ||
} | ||
if (options.verbose && stack) { | ||
msg.push('') | ||
stack.split('\n').forEach((s) => { | ||
msg.push(dim(s.trim().replace(cwd, ''))) | ||
}) | ||
} | ||
msg.push('') | ||
// final formatting, each entry must be a single line | ||
msg = msg.map((line) => `${pad(3)}${line}\n`) | ||
print(msg.join('')) | ||
} | ||
}) | ||
parser.on('complete', (result) => { | ||
if (!result.ok) { | ||
let failureSummary = '\n' | ||
failureSummary += `${pad()}${red('Failed tests:')}` | ||
failureSummary += ` There ${result.fail > 1 ? 'were' : 'was'} ` | ||
failureSummary += red(result.fail) | ||
failureSummary += ` failure${result.fail > 1 ? 's' : ''}\n\n` | ||
print(failureSummary) | ||
for (const fail of result.failures) { | ||
print(`${pad(2)}${FAIL} ${dim(`${fail.id})`)} ${fail.name}\n`) | ||
} | ||
} | ||
print(`\n${pad()}total: ${result.count}\n`) | ||
if (result.pass > 0) { | ||
print(green(`${pad()}passing: ${result.pass}\n`)) | ||
} | ||
if (result.fail > 0) { | ||
print(red(`${pad()}failing: ${result.fail}\n`)) | ||
} | ||
if (result.skip > 0) { | ||
print(`${pad()}skipped: ${result.skip}\n`) | ||
} | ||
if (result.todo > 0) { | ||
print(`${pad()}todo: ${result.todo}\n`) | ||
} | ||
if (result.bailout) { | ||
print(`${pad()}${bold(underline(red('BAILED!')))}\n`) | ||
} | ||
tapArc.end(`${dim(`${pad()}${prettyMs(start)}`)}\n\n`) | ||
process.exit(result.ok && result.count > 0 ? 0 : 1) | ||
}) | ||
process.stdin | ||
.pipe(parser) | ||
.pipe(tapArc) | ||
.pipe(process.stdout) | ||
process.stdin.pipe(parser) |
@@ -5,4 +5,5 @@ { | ||
"author": "tbeseda", | ||
"version": "0.3.5", | ||
"version": "0.3.6-RC.0", | ||
"license": "Apache-2.0", | ||
"type": "module", | ||
"main": "index.js", | ||
@@ -13,3 +14,4 @@ "bin": { | ||
"files": [ | ||
"index.js" | ||
"index.js", | ||
"src/" | ||
], | ||
@@ -21,3 +23,3 @@ "repository": { | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=16" | ||
}, | ||
@@ -35,4 +37,2 @@ "keywords": [ | ||
"lint:check": "eslint .", | ||
"make-snapshots": "node scripts/make-snapshots.js", | ||
"slow": "npm run --silent tape:slow-pass | ./index.js", | ||
"tap-arc:diff:-v": "npm run --silent tape:diff | ./index.js -v", | ||
@@ -48,30 +48,31 @@ "tap-arc:diff": "npm run --silent tape:diff | ./index.js", | ||
"tap-arc:throws": "npm run --silent tape:throws | ./index.js", | ||
"tape:diff": "tape test/create-diff-tap.js", | ||
"tape:empty": "tape test/create-empty-tap.js", | ||
"tape:mixed": "tape test/create-mixed-tap.js", | ||
"tape:upstream-error": "tape test/create-upstream-error-tap.js", | ||
"tape:passing": "tape test/create-passing-tap.js", | ||
"tape:simple": "tape test/create-simple-tap.js", | ||
"tape:slow-pass": "tape test/create-slow-passing-tap.js", | ||
"tape:throws": "tape test/create-throws-tap.js", | ||
"test": "npm run lint:check && tape test/index.js | ./index.js" | ||
"tape:diff": "tape test/create-diff-tap.cjs", | ||
"tape:empty": "tape test/create-empty-tap.cjs", | ||
"tape:mixed": "tape test/create-mixed-tap.cjs", | ||
"tape:upstream-error": "tape test/create-upstream-error-tap.cjs", | ||
"tape:passing": "tape test/create-passing-tap.cjs", | ||
"tape:simple": "tape test/create-simple-tap.cjs", | ||
"tape:throws": "tape test/create-throws-tap.cjs", | ||
"test": "npm run lint:check && tape test/index.cjs | ./index.js" | ||
}, | ||
"dependencies": { | ||
"json5": "^2.2.1", | ||
"minimist": "^1.2.6", | ||
"picocolors": "^1.0.0", | ||
"chalk": "^5.2.0", | ||
"json5": "^2.2.3", | ||
"minimist": "^1.2.8", | ||
"strip-ansi": "6.0.1", | ||
"tap-parser": "^11.0.1", | ||
"tcompare": "^5.0.7", | ||
"through2": "^4.0.2" | ||
"tap-parser": "^13.0.0", | ||
"tcompare": "^6.0.0" | ||
}, | ||
"devDependencies": { | ||
"@architect/eslint-config": "^2.0.1", | ||
"@architect/eslint-config": "^2.1.1", | ||
"@types/node": "^18.0.03", | ||
"eslint": "^8.19.0", | ||
"tape": "5.5.3" | ||
"eslint": "^8.39.0", | ||
"tape": "5.6.3" | ||
}, | ||
"eslintConfig": { | ||
"extends": "@architect/eslint-config" | ||
"extends": "@architect/eslint-config", | ||
"parserOptions": { | ||
"sourceType": "module" | ||
} | ||
} | ||
} |
# `tap-arc` | ||
> A small (~23kB) [TAP](https://testanything.org/) reporter with spec-like output, streaming, and failure diffing. | ||
> A small (~25kB) [TAP](https://testanything.org/) reporter with spec-like output, streaming, and failure diffing. | ||
@@ -8,3 +8,3 @@ ## Objectives | ||
- minimal, informative spec-like output for all assertions | ||
- minimal, maintained dependencies -- can't be shipping React to CI | ||
- minimal, maintained dependencies | ||
- streaming in and out | ||
@@ -17,5 +17,5 @@ - helpful diffing for failures | ||
> Compatible with Node.js 12+. | ||
> Compatible with Node.js 14+. | ||
For a JavaScript project, save `tap-arc` as a development dependency: | ||
Save `tap-arc` as a development dependency: | ||
@@ -68,4 +68,2 @@ ```sh | ||
The entirety of the reporter lives in `./index.js`. | ||
When building `tap-arc`, it's helpful to try various TAP outputs. See `package.json` `"scripts"` for useful "tap-arc:*" commands to test passing and failing TAP. | ||
@@ -77,12 +75,10 @@ | ||
### Snapshot tests | ||
### Tests | ||
The main library is snapshot tested (`npm test` loads all snapshots to compare to current output). Create snapshots with the `npm run make-snapshots` commands. | ||
`tap-arc` is tested to output the correct exit code based on your test suite's TAP output. In the process, the boundaries of tap-arc's process are also tested by creating and parsing several types of TAP output. | ||
The snapshots are versioned by Node.js' major version, ie. `node14` and `node16`. But snapshots may vary between minor and patch versions of Node. (Line numbers of Node internals shift, causing changes in stack traces.) GitHub's Actions are set to use the latest Node.js 14.x and 16.x, so when testing and creating snapshots locally, do the same. | ||
Snapshot testing became too arduous to maintain for the sake of aesthetics. | ||
This is also why `tape` is pinned as a development dependency. Update as needed, but recreate snapshots. | ||
Testing could be improved by unit testing the printer and diff maker. | ||
Request: please exclude updated snapshots from commits if the _only_ change is to the duration line. This variance is accounted for in the tests. | ||
## Credit & Inspiration | ||
@@ -89,0 +85,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
26489
6
9
318
Yes
85
1
+ Addedchalk@^5.2.0
+ Added@base2/pretty-print-object@1.0.1(transitive)
+ Addedchalk@5.3.0(transitive)
+ Addeddiff@5.2.0(transitive)
+ Addedevents-to-array@2.0.3(transitive)
+ Addedis-plain-object@5.0.0(transitive)
+ Addedjs-tokens@4.0.0(transitive)
+ Addedloose-envify@1.4.0(transitive)
+ Addedreact@18.3.1(transitive)
+ Addedreact-dom@18.3.1(transitive)
+ Addedreact-element-to-jsx-string@15.0.0(transitive)
+ Addedreact-is@18.1.0(transitive)
+ Addedscheduler@0.23.2(transitive)
+ Addedtap-parser@13.0.1(transitive)
+ Addedtcompare@6.4.6(transitive)
- Removedpicocolors@^1.0.0
- Removedthrough2@^4.0.2
- Removeddiff@4.0.2(transitive)
- Removedevents-to-array@1.1.2(transitive)
- Removedinherits@2.0.4(transitive)
- Removedminipass@3.3.6(transitive)
- Removedpicocolors@1.1.0(transitive)
- Removedreadable-stream@3.6.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedstring_decoder@1.3.0(transitive)
- Removedtap-parser@11.0.2(transitive)
- Removedtcompare@5.0.7(transitive)
- Removedthrough2@4.0.2(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedyallist@4.0.0(transitive)
Updatedjson5@^2.2.3
Updatedminimist@^1.2.8
Updatedtap-parser@^13.0.0
Updatedtcompare@^6.0.0