From e4b23074c794d97a6b972c6fe9fc9cfbff5f0945 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Tue, 10 Dec 2024 16:19:13 +0100 Subject: [PATCH] assert: show diff when doing partial comparisons --- lib/assert.js | 7 ++- lib/internal/assert/assertion_error.js | 13 +++-- lib/internal/assert/myers_diff.js | 8 +++- test/parallel/test-assert.js | 11 +++++ test/pseudo-tty/test-assert-colors.js | 64 ++++++++++++++++++++++++- test/pseudo-tty/test-assert-no-color.js | 23 +++++++++ 6 files changed, 116 insertions(+), 10 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index ff1f1204f43057..0bd427b14291d0 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -604,10 +604,9 @@ function compareBranch( // Check if all expected keys and values match for (let i = 0; i < keysExpected.length; i++) { const key = keysExpected[i]; - assert( - ReflectHas(actual, key), - new AssertionError({ message: `Expected key ${String(key)} not found in actual object` }), - ); + if (!ReflectHas(actual, key)) { + return false; + } if (!compareBranch(actual[key], expected[key], comparedObjects)) { return false; } diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index d036248e45755e..d654ca5038bbab 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -26,6 +26,7 @@ const { myersDiff, printMyersDiff, printSimpleMyersDiff } = require('internal/as const kReadableOperator = { deepStrictEqual: 'Expected values to be strictly deep-equal:', + partialDeepStrictEqual: 'Expected values to be partially and strictly deep-equal:', strictEqual: 'Expected values to be strictly equal:', strictEqualObject: 'Expected "actual" to be reference-equal to "expected":', deepEqual: 'Expected values to be loosely deep-equal:', @@ -41,6 +42,8 @@ const kReadableOperator = { const kMaxShortStringLength = 12; const kMaxLongStringLength = 512; +const kMethodsWithCustomMessageDiff = ['deepStrictEqual', 'strictEqual', 'partialDeepStrictEqual']; + function copyError(source) { const target = ObjectAssign( { __proto__: ObjectGetPrototypeOf(source) }, @@ -210,9 +213,13 @@ function createErrDiff(actual, expected, operator, customMessage) { const checkCommaDisparity = actual != null && typeof actual === 'object'; const diff = myersDiff(inspectedSplitActual, inspectedSplitExpected, checkCommaDisparity); - const myersDiffMessage = printMyersDiff(diff); + const myersDiffMessage = printMyersDiff(diff, operator); message = myersDiffMessage.message; + if (operator === 'partialDeepStrictEqual') { + header = `${colors.gray}${colors.hasColors ? '' : '+ '}actual${colors.white} ${colors.red}- expected${colors.white}`; + } + if (myersDiffMessage.skipped) { skipped = true; } @@ -255,7 +262,7 @@ class AssertionError extends Error { if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; if (message != null) { - if (operator === 'deepStrictEqual' || operator === 'strictEqual') { + if (kMethodsWithCustomMessageDiff.includes(operator)) { super(createErrDiff(actual, expected, operator, message)); } else { super(String(message)); @@ -275,7 +282,7 @@ class AssertionError extends Error { expected = copyError(expected); } - if (operator === 'deepStrictEqual' || operator === 'strictEqual') { + if (kMethodsWithCustomMessageDiff.includes(operator)) { super(createErrDiff(actual, expected, operator, message)); } else if (operator === 'notDeepStrictEqual' || operator === 'notStrictEqual') { diff --git a/lib/internal/assert/myers_diff.js b/lib/internal/assert/myers_diff.js index 8893ff78d7c2a7..6ea356b4fd06fc 100644 --- a/lib/internal/assert/myers_diff.js +++ b/lib/internal/assert/myers_diff.js @@ -122,7 +122,7 @@ function printSimpleMyersDiff(diff) { return `\n${message}`; } -function printMyersDiff(diff, simple = false) { +function printMyersDiff(diff, operator) { let message = ''; let skipped = false; let nopCount = 0; @@ -148,7 +148,11 @@ function printMyersDiff(diff, simple = false) { } if (type === 'insert') { - message += `${colors.green}+${colors.white} ${value}\n`; + if (operator === 'partialDeepStrictEqual') { + message += `${colors.gray}${colors.hasColors ? ' ' : '+'} ${value}${colors.white}\n`; + } else { + message += `${colors.green}+${colors.white} ${value}\n`; + } } else if (type === 'delete') { message += `${colors.red}-${colors.white} ${value}\n`; } else if (type === 'nop') { diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 26141b04b352b5..ae3df139cd68b7 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -1351,6 +1351,17 @@ test('Additional assert', () => { } ); + assert.throws( + () => { + assert.partialDeepStrictEqual({ a: true }, { a: false }, 'custom message'); + }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'custom message\n+ actual - expected\n\n {\n+ a: true\n- a: false\n }\n' + } + ); + { let threw = false; try { diff --git a/test/pseudo-tty/test-assert-colors.js b/test/pseudo-tty/test-assert-colors.js index d43dd60224a06f..2f215e1e27973d 100644 --- a/test/pseudo-tty/test-assert-colors.js +++ b/test/pseudo-tty/test-assert-colors.js @@ -2,10 +2,14 @@ require('../common'); const assert = require('assert').strict; -assert.throws(() => { +function setup() { process.env.FORCE_COLOR = '1'; delete process.env.NODE_DISABLE_COLORS; delete process.env.NO_COLOR; +} + +assert.throws(() => { + setup(); assert.deepStrictEqual([1, 2, 2, 2, 2], [2, 2, 2, 2, 2]); }, (err) => { const expected = 'Expected values to be strictly deep-equal:\n' + @@ -19,6 +23,64 @@ assert.throws(() => { '\x1B[39m 2,\n' + '\x1B[31m-\x1B[39m 2\n' + '\x1B[39m ]\n'; + assert.strictEqual(err.message, expected); return true; }); + +{ + // TODO(puskin94): remove the emitWarning override once the partialDeepStrictEqual method is not experimental anymore + // Suppress warnings, necessary otherwise the tools/pseudo-tty.py runner will fail + const originalEmitWarning = process.emitWarning; + process.emitWarning = () => {}; + + assert.throws(() => { + setup(); + assert.partialDeepStrictEqual([1, 2, 3, 5], [4, 5]); + }, (err) => { + const expected = 'Expected values to be partially and strictly deep-equal:\n' + + '\x1B[90mactual\x1B[39m \x1B[31m- expected\x1B[39m\n' + + '\n' + + '\x1B[39m [\n' + + '\x1B[90m 1,\x1B[39m\n' + + '\x1B[90m 2,\x1B[39m\n' + + '\x1B[90m 3,\x1B[39m\n' + + '\x1B[31m-\x1B[39m 4,\n' + + '\x1B[39m 5\n' + + '\x1B[39m ]\n'; + + assert.strictEqual(err.message, expected); + return true; + }); + + process.emitWarning = originalEmitWarning; // Restore original process.emitWarning +} + +{ + // TODO(puskin94): remove the emitWarning override once the partialDeepStrictEqual method is not experimental anymore + // Suppress warnings, necessary otherwise the tools/pseudo-tty.py runner will fail + const originalEmitWarning = process.emitWarning; + process.emitWarning = () => {}; + + assert.throws(() => { + setup(); + assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3, d: 5 }, { z: 4, b: 5 }); + }, (err) => { + const expected = 'Expected values to be partially and strictly deep-equal:\n' + + '\x1B[90mactual\x1B[39m \x1B[31m- expected\x1B[39m\n' + + '\n' + + '\x1B[39m {\n' + + '\x1B[90m a: 1,\x1B[39m\n' + + '\x1B[90m b: 2,\x1B[39m\n' + + '\x1B[90m c: 3,\x1B[39m\n' + + '\x1B[90m d: 5\x1B[39m\n' + + '\x1B[31m-\x1B[39m b: 5,\n' + + '\x1B[31m-\x1B[39m z: 4\n' + + '\x1B[39m }\n'; + + assert.strictEqual(err.message, expected); + return true; + }); + + process.emitWarning = originalEmitWarning; // Restore original process.emitWarning +} diff --git a/test/pseudo-tty/test-assert-no-color.js b/test/pseudo-tty/test-assert-no-color.js index d488f4e6bfcbe7..e67e25f145a42e 100644 --- a/test/pseudo-tty/test-assert-no-color.js +++ b/test/pseudo-tty/test-assert-no-color.js @@ -17,3 +17,26 @@ assert.throws( '- foo: \'bar\'\n' + '- }\n', }); + +{ + // TODO(puskin94): remove the emitWarning override once the partialDeepStrictEqual method is not experimental anymore + // Suppress warnings, necessary otherwise the tools/pseudo-tty.py runner will fail + const originalEmitWarning = process.emitWarning; + process.emitWarning = () => {}; + + assert.throws( + () => { + assert.partialDeepStrictEqual({}, { foo: 'bar' }); + }, + { + message: 'Expected values to be partially and strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ {}\n' + + '- {\n' + + "- foo: 'bar'\n" + + '- }\n', + }); + + process.emitWarning = originalEmitWarning; // Restore original process.emitWarning +}