Comparing version 0.3.6-RC.0 to 0.4.0
@@ -9,2 +9,17 @@ # `tap-arc` Changelog | ||
## [0.3.6] - 2023-08-18 | ||
### Fixed | ||
- dependency vulnerabilities in `json5` | ||
### Changed | ||
- update other dependencies | ||
### Notes | ||
- v1 (a rewrite) has been in progress for a while. shifts in the Node TAP ecosystem are settling. | ||
- snapshot tests have been disabled. this approach, while super accurate, is unmaintainable. | ||
## [0.3.5] - 2022-07-11 | ||
@@ -11,0 +26,0 @@ |
290
index.js
#!/usr/bin/env node | ||
import minimist from 'minimist' | ||
import helpText from './src/_help-text.js' | ||
import tapArc from './src/arc-tap.js' | ||
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', | ||
) | ||
// Log test-group name | ||
const RESULT_COMMENTS = [ | ||
'tests ', 'pass ', 'skip', 'todo', 'fail ', 'failed ', 'ok', | ||
] | ||
const alias = { | ||
@@ -21,11 +32,274 @@ help: [ 'h', 'help' ], | ||
if (options.help) { | ||
console.log(helpText) | ||
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`, | ||
) | ||
process.exit() | ||
} | ||
const parser = tapArc(options, (error, result) => { | ||
process.exit(result.ok ? 0 : 1) | ||
// process.exit(result.ok && result.count > 0 ? 0 : 1) | ||
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`) | ||
}) | ||
process.stdin.pipe(parser) | ||
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) |
@@ -5,5 +5,4 @@ { | ||
"author": "tbeseda", | ||
"version": "0.3.6-RC.0", | ||
"version": "0.4.0", | ||
"license": "Apache-2.0", | ||
"type": "module", | ||
"main": "index.js", | ||
@@ -14,4 +13,3 @@ "bin": { | ||
"files": [ | ||
"index.js", | ||
"src/" | ||
"index.js" | ||
], | ||
@@ -23,3 +21,3 @@ "repository": { | ||
"engines": { | ||
"node": ">=16" | ||
"node": ">=14" | ||
}, | ||
@@ -37,2 +35,4 @@ "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,18 +48,20 @@ "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.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" | ||
"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" | ||
}, | ||
"dependencies": { | ||
"chalk": "^5.2.0", | ||
"json5": "^2.2.3", | ||
"minimist": "^1.2.8", | ||
"picocolors": "^1.0.0", | ||
"strip-ansi": "6.0.1", | ||
"tap-parser": "^13.0.0", | ||
"tcompare": "^6.0.0" | ||
"tap-parser": "^11.0.2", | ||
"tcompare": "^5.0.7", | ||
"through2": "^4.0.2" | ||
}, | ||
@@ -69,11 +71,8 @@ "devDependencies": { | ||
"@types/node": "^18.0.03", | ||
"eslint": "^8.39.0", | ||
"tape": "5.6.3" | ||
"eslint": "^8.47.0", | ||
"tape": "5.6.6" | ||
}, | ||
"eslintConfig": { | ||
"extends": "@architect/eslint-config", | ||
"parserOptions": { | ||
"sourceType": "module" | ||
} | ||
"extends": "@architect/eslint-config" | ||
} | ||
} |
# `tap-arc` | ||
> A small (~25kB) [TAP](https://testanything.org/) reporter with spec-like output, streaming, and failure diffing. | ||
> A small (~23kB) [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 | ||
- minimal, maintained dependencies -- can't be shipping React to CI | ||
- streaming in and out | ||
@@ -17,5 +17,5 @@ - helpful diffing for failures | ||
> Compatible with Node.js 14+. | ||
> Compatible with Node.js 12+. | ||
Save `tap-arc` as a development dependency: | ||
For a JavaScript project, save `tap-arc` as a development dependency: | ||
@@ -68,2 +68,4 @@ ```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. | ||
@@ -75,10 +77,12 @@ | ||
### Tests | ||
### Snapshot tests | ||
`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 main library is snapshot tested (`npm test` loads all snapshots to compare to current output). Create snapshots with the `npm run make-snapshots` commands. | ||
Snapshot testing became too arduous to maintain for the sake of aesthetics. | ||
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. | ||
Testing could be improved by unit testing the printer and diff maker. | ||
This is also why `tape` is pinned as a development dependency. Update as needed, but recreate snapshots. | ||
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 | ||
@@ -85,0 +89,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
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
89
25819
7
5
263
No
+ Addedpicocolors@^1.0.0
+ Addedthrough2@^4.0.2
+ Addeddiff@4.0.2(transitive)
+ Addedevents-to-array@1.1.2(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedminipass@3.3.6(transitive)
+ Addedpicocolors@1.1.0(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedtap-parser@11.0.2(transitive)
+ Addedtcompare@5.0.7(transitive)
+ Addedthrough2@4.0.2(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
+ Addedyallist@4.0.0(transitive)
- Removedchalk@^5.2.0
- Removed@base2/pretty-print-object@1.0.1(transitive)
- Removedchalk@5.3.0(transitive)
- Removeddiff@5.2.0(transitive)
- Removedevents-to-array@2.0.3(transitive)
- Removedis-plain-object@5.0.0(transitive)
- Removedjs-tokens@4.0.0(transitive)
- Removedloose-envify@1.4.0(transitive)
- Removedreact@18.3.1(transitive)
- Removedreact-dom@18.3.1(transitive)
- Removedreact-element-to-jsx-string@15.0.0(transitive)
- Removedreact-is@18.1.0(transitive)
- Removedscheduler@0.23.2(transitive)
- Removedtap-parser@13.0.1(transitive)
- Removedtcompare@6.4.6(transitive)
Updatedtap-parser@^11.0.2
Updatedtcompare@^5.0.7