Comparing version 1.0.2 to 1.1.0-RC0
@@ -8,2 +8,3 @@ #!/usr/bin/env node | ||
help: [ 'h', 'help' ], | ||
showDiff: [ 'diff' ], | ||
pessimistic: [ 'p', 'pessimistic', 'bail' ], | ||
@@ -17,2 +18,3 @@ verbose: [ 'v', 'verbose' ], | ||
help: false, | ||
showDiff: true, | ||
pessimistic: false, | ||
@@ -36,2 +38,3 @@ failBadCount: false, | ||
parser.on('end', () => { | ||
// @ts-ignore | ||
const { results } = parser._writable | ||
@@ -38,0 +41,0 @@ |
@@ -5,3 +5,3 @@ { | ||
"author": "tbeseda", | ||
"version": "1.0.2", | ||
"version": "1.1.0-RC0", | ||
"license": "Apache-2.0", | ||
@@ -57,2 +57,3 @@ "type": "module", | ||
"chalk": "^5.3.0", | ||
"diff": "^5.1.0", | ||
"duplexer3": "^1.0.0", | ||
@@ -62,9 +63,8 @@ "json5": "^2.2.3", | ||
"strip-ansi": "^7.1.0", | ||
"tap-parser": "^15.2.0", | ||
"tcompare": "^6.4.1" | ||
"tap-parser": "^15.2.0" | ||
}, | ||
"devDependencies": { | ||
"@architect/eslint-config": "^2.1.2", | ||
"@types/node": "^16.18.53", | ||
"eslint": "^8.50.0", | ||
"@types/node": "^16.18.55", | ||
"eslint": "^8.51.0", | ||
"tap-min": "^3.0.0", | ||
@@ -71,0 +71,0 @@ "tape": "5.7.0" |
@@ -1,8 +0,6 @@ | ||
// eslint-disable-next-line import/no-unresolved | ||
import { strict } from 'tcompare' // what's going on with @tapjs packages? | ||
import JSON5 from 'json5' | ||
import * as Diff from 'diff' | ||
/** | ||
* Create a function to create a diff as a set of strings | ||
* @param {object} params | ||
* @param {object} params printer | ||
* @param {function} params.actual | ||
@@ -13,66 +11,93 @@ * @param {function} params.expected | ||
*/ | ||
export default function createMakeDiff ({ actual, expected, dim }){ | ||
export default function ({ actual, expected }){ | ||
/** | ||
* Create a diff as a set of strings | ||
* @param {string} a | ||
* @param {string} e | ||
* @returns [string] | ||
* @param {string} a actual | ||
* @param {string} e expected | ||
* @returns {string[]} output lines | ||
*/ | ||
return function (a, e) { | ||
const msg = [] | ||
let isJson = true | ||
let actualJson = a | ||
let expectedJson = e | ||
function diffLine (a, e) { | ||
const diff = Diff.diffWordsWithSpace(a, e) | ||
const output = [] | ||
try { | ||
actualJson = JSON5.parse(a) | ||
expectedJson = JSON5.parse(e) | ||
for (const d of diff) { | ||
if (d.added) output.push(`${expected(d.value)}`) | ||
else if (d.removed) output.push(`${actual(d.value)}`) | ||
else output.push(d.value) | ||
} | ||
catch (e) { | ||
isJson = false | ||
} | ||
if (isJson) { | ||
a = actualJson | ||
e = expectedJson | ||
return output.join('').split('\n') | ||
} | ||
/** | ||
* @param {string} a actual | ||
* @param {string} e expected | ||
* @returns {string[]} output lines | ||
*/ | ||
function diffMultiLine (a, e) { | ||
const diff = Diff.diffLines(a, e) | ||
const output = [] | ||
for (const d of diff) { | ||
if (d.added) output.push(`${expected(d.value)}`) | ||
else if (d.removed) output.push(`${actual(d.value)}`) | ||
else output.push(d.value) | ||
} | ||
const compared = strict(a, e, { | ||
includeEnumerable: true, | ||
includeGetters: true, | ||
sort: true, | ||
}) | ||
return output.join('').split('\n') | ||
} | ||
if (compared.match) { | ||
msg.push(`${expected('Expected')} did not match ${actual('actual')}.`) | ||
/** | ||
* @param {any[]} a actual | ||
* @param {any[]} e expected | ||
* @returns {string[]} output lines | ||
*/ | ||
function diffArray (a, e) { | ||
const output = [] | ||
const diff = Diff.diffArrays(a, e) | ||
for (const d of diff) { | ||
if (d.added) d.value.forEach(v => output.push(` ${expected(JSON.stringify(v))},`)) | ||
else if (d.removed) d.value.forEach(v => output.push(` ${actual(JSON.stringify(v))},`)) | ||
else d.value.forEach(v => output.push(` ${JSON.stringify(v)},`)) | ||
} | ||
else { | ||
// remove leading header lines | ||
let diff = compared.diff.split('\n') | ||
diff = diff.slice(2, diff.length - 1) | ||
for (const line of diff) { | ||
switch (line.charAt(0)) { | ||
case '-': { | ||
msg.push(expected(line)) | ||
break | ||
} | ||
case '+': { | ||
msg.push(actual(line)) | ||
break | ||
} | ||
case '@': { | ||
msg.push(dim(line)) | ||
break | ||
} | ||
default:{ | ||
msg.push(line) | ||
break | ||
} | ||
} | ||
} | ||
output.unshift('Array [') | ||
output.push(']') | ||
return output | ||
} | ||
/** | ||
* @param {object} a actual | ||
* @param {object} e expected | ||
* @returns {string[]} output lines | ||
*/ | ||
function diffObject (a, e) { | ||
const output = [] | ||
const diff = Diff.diffJson(a, e) | ||
for (const d of diff) { | ||
if (d.added) output.push(`${expected(d.value)}`) | ||
else if (d.removed) output.push(`${actual(d.value)}`) | ||
else output.push(d.value) | ||
} | ||
return msg | ||
output[0] = `Object ${output[0]}` | ||
return output.join('').split('\n') | ||
} | ||
/** | ||
* @param {string} a actual | ||
* @param {string} e expected | ||
* @returns {string[]} output lines | ||
*/ | ||
function diffString (a, e) { | ||
const aLines = a.split('\n') | ||
const eLines = e.split('\n') | ||
if (aLines.length > 1 || eLines.length > 1) return diffMultiLine(a, e) | ||
else return diffLine(a, e) | ||
} | ||
return { diffArray, diffLine, diffObject, diffString } | ||
} |
@@ -21,4 +21,2 @@ import { Chalk } from 'chalk' | ||
green, | ||
italic, | ||
magenta, | ||
red, | ||
@@ -52,3 +50,2 @@ yellow, | ||
}, | ||
diffOptions: { actual, expected, dim: italic.dim }, | ||
pad, | ||
@@ -62,3 +59,2 @@ prettyMs, | ||
good: green, | ||
highlight: magenta, | ||
strong: bold, | ||
@@ -65,0 +61,0 @@ title: bold.underline, |
@@ -1,13 +0,14 @@ | ||
// eslint-disable-next-line import/no-unresolved | ||
import { Parser } from 'tap-parser' // what's going on with @tapjs packages? | ||
import { Parser } from 'tap-parser' | ||
import { PassThrough } from 'stream' | ||
import duplexer from 'duplexer3' // TODO: write a custom, simpler duplexer | ||
import JSON5 from 'json5' | ||
import stripAnsi from 'strip-ansi' | ||
import createMakeDiff from './_make-diff.js' | ||
import differ from './_make-diff.js' | ||
import createPrinter from './_printer.js' | ||
const { parse } = JSON5 | ||
const reservedCommentPrefixes = [ 'tests ', 'pass ', 'skip', 'todo', 'fail ', 'failed ', 'ok', 'test count' ] | ||
export default function createParser (options) { | ||
const { debug, pessimistic, verbose } = options | ||
const { debug, pessimistic, showDiff, verbose } = options | ||
const output = new PassThrough() | ||
@@ -17,5 +18,12 @@ const parser = new Parser({ bail: pessimistic }) | ||
const _ = createPrinter(options) | ||
const { diffOptions, prettyMs, pad } = _ | ||
const makeDiff = createMakeDiff(diffOptions) | ||
const { prettyMs, pad } = _ | ||
const { diffArray, diffObject, diffString } = differ(_) | ||
/** | ||
* Print a line to the output stream | ||
* @param {string} str | ||
* @param {number} p padding | ||
* @param {number} n newlines | ||
* @returns void | ||
*/ | ||
function P (str, p = 0, n = 1) { | ||
@@ -69,103 +77,101 @@ output.write(`${pad(p)}${str}${'\n'.repeat(n)}`) | ||
if (test.diag) { | ||
const { actual, at, expected, operator, stack } = test.diag | ||
const I = 4 | ||
const { at, operator, stack } = test.diag | ||
let { actual, expected } = test.diag | ||
const indent = 4 | ||
if (operator === 'equal' || operator === 'deepEqual') { | ||
if (typeof expected === 'string' && typeof actual === 'string') { | ||
for (const line of makeDiff(actual, expected)) { | ||
P(line, I) | ||
} | ||
} | ||
else if (typeof expected === 'object' && typeof actual === 'object') { | ||
// probably an array | ||
for (const line of makeDiff(actual, expected)) { | ||
P(line, I) | ||
} | ||
} | ||
else if (typeof expected === 'number' || typeof actual === 'number') { | ||
P(`Expected ${_.expected(expected)} but got ${_.actual(actual)}`, I) | ||
} | ||
else { | ||
// mixed types | ||
P(`operator: ${operator}`, I) | ||
P(`expected: ${_.expected(`- ${expected}`)} <${typeof expected}>`, I) | ||
P(`actual: ${_.actual(`+ ${actual}`)} <${typeof actual}>`, I) | ||
} | ||
function printBoth (e, a) { | ||
P(`Actual: ${JSON.stringify(a)}`, indent) | ||
P(`Expected: ${JSON.stringify(e)}`, indent) | ||
} | ||
else if (operator === 'throws' && actual && actual !== 'undefined' && expected && expected !== 'undefined') { | ||
// this combination is throws with expected/assertion | ||
P(`Expected ${_.expected(expected)} to match "${_.actual(actual.message || actual)}"`, I) | ||
if (actual === 'undefined') actual = undefined | ||
if (expected === 'undefined') expected = undefined | ||
if (actual && expected && actual === expected) { // shallow test output; can't be diffed | ||
P(`${_.expected('Expected')} did not match ${_.actual('actual')}.`, indent) | ||
P(_.dim('TAP output cannot be diffed'), indent) | ||
P(actual, indent) | ||
} | ||
else if (operator === 'equal' || operator === 'deepEqual') { | ||
// try parsing for JS types | ||
if (typeof actual === 'string') | ||
try { actual = parse(actual) } | ||
catch (_e) { _e } | ||
if (typeof expected === 'string') | ||
try { expected = parse(expected) } | ||
catch (_e) { _e } | ||
switch (operator) { | ||
case 'equal': { | ||
break | ||
} | ||
case 'deepEqual': { | ||
break | ||
} | ||
case 'notEqual': { | ||
P('Expected values to differ', I) | ||
break | ||
} | ||
case 'notDeepEqual': { | ||
P('Expected values to differ', I) | ||
break | ||
} | ||
case 'ok': { | ||
P(`Expected ${_.highlight('truthy')} but got ${_.actual(actual)}`, I) | ||
break | ||
} | ||
case 'match': { | ||
P(`Expected "${actual}" to match ${_.highlight(expected)}`, I) | ||
break | ||
} | ||
case 'doesNotMatch': { | ||
P(`Expected "${actual}" to not match ${_.highlight(expected)}`, I) | ||
break | ||
} | ||
case 'throws': { | ||
if (actual && actual !== 'undefined') { | ||
// this combination is ~doesNotThrow | ||
P(`Expected to not throw, received "${_.actual(actual)}"`, I) | ||
const aType = typeof actual | ||
const eType = typeof expected | ||
let sharedType = aType === eType ? `${aType}` : null | ||
if (sharedType === 'object') { | ||
if (Array.isArray(actual) && Array.isArray(expected)) sharedType = 'array' | ||
else if (Array.isArray(actual) && !Array.isArray(expected)) sharedType = null | ||
else if (!Array.isArray(actual) && Array.isArray(expected)) sharedType = null | ||
} | ||
else { | ||
P('Expected to throw', I) | ||
} | ||
break | ||
} | ||
case 'error': { | ||
P(`Expected error to be ${_.highlight('falsy')}`, I) | ||
break | ||
} | ||
case 'fail': { | ||
P('Explicit fail', I) | ||
break | ||
} | ||
default: { | ||
if (expected && !actual) P(`Expected ${_.expected(operator)} but got nothing`, I) | ||
else if (actual && !expected) P(`Expected ${_.highlight('falsy')} but got ${_.actual(actual)}`, I) | ||
if (expected && actual) P(`Expected ${_.expected(expected)} but got ${_.actual(actual)}`, I) | ||
else if (expected || actual) { | ||
// unlikely | ||
P(`operator: ${operator}`, I) | ||
P(`expected: ${_.expected(expected)}`, I) | ||
P(`actual: ${_.actual(actual)}`, I) | ||
if (sharedType) { | ||
if ([ 'number', 'bigint', 'boolean', 'symbol', 'function', 'undefined' ].includes(sharedType)) | ||
P(`Expected ${_.expected(expected)} but got ${_.actual(actual)}`, indent) | ||
else if (showDiff && sharedType === 'array') | ||
diffArray(actual, expected).forEach(line => P(line, indent)) | ||
else if (showDiff && sharedType === 'object') | ||
diffObject(actual, expected).forEach(line => P(line, indent)) | ||
else if (showDiff && sharedType === 'string') | ||
diffString(actual, expected).forEach(line => P(line, indent)) | ||
else | ||
printBoth(expected, actual) | ||
} | ||
else { | ||
P(`operator: ${operator}`, I) | ||
else { // mixed types | ||
printBoth(expected, actual) | ||
} | ||
break | ||
} | ||
else { | ||
switch (operator) { | ||
case 'notEqual': | ||
P('Expected values to differ', indent) | ||
break | ||
case 'notDeepEqual': | ||
P('Expected values to differ', indent) | ||
break | ||
case 'ok': | ||
P(`Expected ${_.expected('truthy')} but got ${_.actual(actual)}`, indent) | ||
break | ||
case 'match': | ||
P(`Expected "${_.actual(actual)}" to match ${_.expected(expected)}`, indent) | ||
break | ||
case 'doesNotMatch': | ||
P(`Expected "${_.actual(actual)}" to not match ${_.expected(expected)}`, indent) | ||
break | ||
case 'throws': | ||
if ( | ||
actual | ||
&& typeof actual !== 'undefined' | ||
&& expected | ||
&& typeof expected !== 'undefined' | ||
) // this weird combination is throws with expected/assertion | ||
P(`Expected ${_.expected(expected)} to match "${_.actual(actual.message || actual)}"`, indent) | ||
else if (actual && typeof actual !== 'undefined') // this combination is usually "doesNotThrow" | ||
P(`Expected to not throw, received "${_.actual(actual)}"`, indent) | ||
else | ||
P('Expected to throw', indent) | ||
break | ||
case 'error': | ||
P(`Expected error to be ${_.expected('falsy')}`, indent) | ||
break | ||
case 'fail': | ||
P('Explicit fail', indent) | ||
break | ||
default: | ||
printBoth(expected, actual) | ||
break | ||
} | ||
} | ||
if (at) P(_.dim(`At: ${at.replace(cwd, '')}`), I) | ||
if (at) P(_.dim(`At: ${at.replace(cwd, '')}`), indent) | ||
if (verbose && stack) { | ||
if (stack && verbose) | ||
stack.split('\n').forEach((s) => { | ||
P(_.dim(s.trim().replace(cwd, '')), I) | ||
P(_.dim(s.trim().replace(cwd, '')), indent) | ||
}) | ||
} | ||
} | ||
@@ -177,3 +183,3 @@ }) | ||
const tapFailures = result.failures.filter((f) => f.tapError) | ||
for (const tapFailure of tapFailures){ | ||
for (const tapFailure of tapFailures) { | ||
const { tapError } = tapFailure | ||
@@ -180,0 +186,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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
31306
417
2
+ Addeddiff@^5.1.0
- Removedtcompare@^6.4.1
- Removed@base2/pretty-print-object@1.0.1(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)
- Removedtcompare@6.4.6(transitive)