diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index 2f222a3d85ebf9..29de2ff9427b57 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -3,29 +3,23 @@ const { ArrayPrototypeJoin, ArrayPrototypePop, + ArrayPrototypeSlice, Error, ErrorCaptureStackTrace, - MathMax, ObjectAssign, ObjectDefineProperty, ObjectGetPrototypeOf, String, - StringPrototypeEndsWith, StringPrototypeRepeat, StringPrototypeSlice, StringPrototypeSplit, } = primordials; const { inspect } = require('internal/util/inspect'); -const { - removeColors, -} = require('internal/util'); const colors = require('internal/util/colors'); -const { - validateObject, -} = require('internal/validators'); +const { validateObject } = require('internal/validators'); const { isErrorStackTraceLimitWritable } = require('internal/errors'); - +const { myersDiff, printMyersDiff, printSimpleMyersDiff } = require('internal/assert/myers_diff'); const kReadableOperator = { deepStrictEqual: 'Expected values to be strictly deep-equal:', @@ -41,272 +35,175 @@ const kReadableOperator = { notDeepEqualUnequal: 'Expected values not to be loosely deep-equal:', }; -// Comparing short primitives should just show === / !== instead of using the -// diff. -const kMaxShortLength = 12; +const kMaxShortStringLength = 12; +const kMaxLongStringLength = 512; function copyError(source) { - const target = ObjectAssign({ __proto__: ObjectGetPrototypeOf(source) }, source); - ObjectDefineProperty(target, 'message', { __proto__: null, value: source.message }); + const target = ObjectAssign( + { __proto__: ObjectGetPrototypeOf(source) }, + source, + ); + ObjectDefineProperty(target, 'message', { + __proto__: null, + value: source.message, + }); return target; } function inspectValue(val) { // The util.inspect default values could be changed. This makes sure the // error messages contain the necessary information nevertheless. - return inspect( - val, - { - compact: false, - customInspect: false, - depth: 1000, - maxArrayLength: Infinity, - // Assert compares only enumerable properties (with a few exceptions). - showHidden: false, - // Assert does not detect proxies currently. - showProxy: false, - sorted: true, - // Inspect getters as we also check them when comparing entries. - getters: true, - }, - ); + return inspect(val, { + compact: false, + customInspect: false, + depth: 1000, + maxArrayLength: Infinity, + // Assert compares only enumerable properties (with a few exceptions). + showHidden: false, + // Assert does not detect proxies currently. + showProxy: false, + sorted: true, + // Inspect getters as we also check them when comparing entries. + getters: true, + }); } function getErrorMessage(operator, message) { return message || kReadableOperator[operator]; } -function createErrDiff(actual, expected, operator, message = '') { - let other = ''; - let res = ''; - let end = ''; - let skipped = false; - const actualInspected = inspectValue(actual); - const actualLines = StringPrototypeSplit(actualInspected, '\n'); - const expectedLines = StringPrototypeSplit(inspectValue(expected), '\n'); - - let i = 0; - let indicator = ''; - +function checkOperator(actual, expected, operator) { // In case both values are objects or functions explicitly mark them as not // reference equal for the `strictEqual` operator. - if (operator === 'strictEqual' && - ((typeof actual === 'object' && actual !== null && - typeof expected === 'object' && expected !== null) || - (typeof actual === 'function' && typeof expected === 'function'))) { + if ( + operator === 'strictEqual' && + ((typeof actual === 'object' && + actual !== null && + typeof expected === 'object' && + expected !== null) || + (typeof actual === 'function' && typeof expected === 'function')) + ) { operator = 'strictEqualObject'; } - // If "actual" and "expected" fit on a single line and they are not strictly - // equal, check further special handling. - if (actualLines.length === 1 && expectedLines.length === 1 && - actualLines[0] !== expectedLines[0]) { - // Check for the visible length using the `removeColors()` function, if - // appropriate. - const c = inspect.defaultOptions.colors; - const actualRaw = c ? removeColors(actualLines[0]) : actualLines[0]; - const expectedRaw = c ? removeColors(expectedLines[0]) : expectedLines[0]; - const inputLength = actualRaw.length + expectedRaw.length; - // If the character length of "actual" and "expected" together is less than - // kMaxShortLength and if neither is an object and at least one of them is - // not `zero`, use the strict equal comparison to visualize the output. - if (inputLength <= kMaxShortLength) { - if ((typeof actual !== 'object' || actual === null) && - (typeof expected !== 'object' || expected === null) && - (actual !== 0 || expected !== 0)) { // -0 === +0 - return `${getErrorMessage(operator, message)}\n\n` + - `${actualLines[0]} !== ${expectedLines[0]}\n`; - } - } else if (operator !== 'strictEqualObject') { - // If the stderr is a tty and the input length is lower than the current - // columns per line, add a mismatch indicator below the output. If it is - // not a tty, use a default value of 80 characters. - const maxLength = process.stderr.isTTY ? process.stderr.columns : 80; - if (inputLength < maxLength) { - while (actualRaw[i] === expectedRaw[i]) { - i++; - } - // Ignore the first characters. - if (i > 2) { - // Add position indicator for the first mismatch in case it is a - // single line and the input length is less than the column length. - indicator = `\n ${StringPrototypeRepeat(' ', i)}^`; - i = 0; + return operator; +} + +function getColoredMyersDiff(actual, expected) { + const header = `${colors.green}actual${colors.white} ${colors.red}expected${colors.white}`; + const skipped = false; + + const diff = myersDiff(StringPrototypeSplit(actual, ''), StringPrototypeSplit(expected, '')); + let message = printSimpleMyersDiff(diff); + + if (skipped) { + message += '...'; + } + + return { message, header, skipped }; +} + +function getStackedDiff(actual, expected) { + const isStringComparison = typeof actual === 'string' && typeof expected === 'string'; + + let message = `\n${colors.green}+${colors.white} ${actual}\n${colors.red}- ${colors.white}${expected}`; + const stringsLen = actual.length + expected.length; + const maxTerminalLength = process.stderr.isTTY ? process.stderr.columns : 80; + const showIndicator = isStringComparison && (stringsLen <= maxTerminalLength); + + if (showIndicator) { + let indicatorIdx = -1; + + for (let i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) { + // Skip the indicator for the first 2 characters because the diff is immediately apparent + // It is 3 instead of 2 to account for the quotes + if (i >= 3) { + indicatorIdx = i; + break; } + break; } } - } - // Remove all ending lines that match (this optimizes the output for - // readability by reducing the number of total changed lines). - let a = actualLines[actualLines.length - 1]; - let b = expectedLines[expectedLines.length - 1]; - while (a === b) { - if (i++ < 3) { - end = `\n ${a}${end}`; - } else { - other = a; + if (indicatorIdx !== -1) { + message += `\n${StringPrototypeRepeat(' ', indicatorIdx + 2)}^`; } - ArrayPrototypePop(actualLines); - ArrayPrototypePop(expectedLines); - if (actualLines.length === 0 || expectedLines.length === 0) - break; - a = actualLines[actualLines.length - 1]; - b = expectedLines[expectedLines.length - 1]; } - const maxLines = MathMax(actualLines.length, expectedLines.length); - // Strict equal with identical objects that are not identical by reference. - // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() }) - if (maxLines === 0) { - // We have to get the result again. The lines were all removed before. - const actualLines = StringPrototypeSplit(actualInspected, '\n'); - - // Only remove lines in case it makes sense to collapse those. - // TODO: Accept env to always show the full error. - if (actualLines.length > 50) { - actualLines[46] = `${colors.blue}...${colors.white}`; - while (actualLines.length > 47) { - ArrayPrototypePop(actualLines); - } - } + return { message }; +} - return `${kReadableOperator.notIdentical}\n\n` + - `${ArrayPrototypeJoin(actualLines, '\n')}\n`; +function getSimpleDiff(originalActual, actual, originalExpected, expected) { + const stringsLen = actual.length + expected.length; + if (stringsLen <= kMaxShortStringLength && (originalActual !== 0 || originalExpected !== 0)) { + return { message: `${actual} !== ${expected}`, header: '' }; } - // There were at least five identical lines at the end. Mark a couple of - // skipped. - if (i >= 5) { - end = `\n${colors.blue}...${colors.white}${end}`; - skipped = true; - } - if (other !== '') { - end = `\n ${other}${end}`; - other = ''; + const isStringComparison = typeof originalActual === 'string' && typeof originalExpected === 'string'; + // colored myers diff + if (isStringComparison && colors.hasColors) { + return getColoredMyersDiff(actual, expected); } - let printedLines = 0; - let identical = 0; - const msg = `${getErrorMessage(operator, message)}\n${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`; - const skippedMsg = ` ${colors.blue}...${colors.white} Lines skipped`; - - let lines = actualLines; - let plusMinus = `${colors.green}+${colors.white}`; - let maxLength = expectedLines.length; - if (actualLines.length < maxLines) { - lines = expectedLines; - plusMinus = `${colors.red}-${colors.white}`; - maxLength = actualLines.length; + return getStackedDiff(actual, expected); +} + +function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) { + if (inspectedActual.length > 1 || inspectedExpected.length > 1) { + return false; } - for (i = 0; i < maxLines; i++) { - if (maxLength < i + 1) { - // If more than two former lines are identical, print them. Collapse them - // in case more than five lines were identical. - if (identical > 2) { - if (identical > 3) { - if (identical > 4) { - if (identical === 5) { - res += `\n ${lines[i - 3]}`; - printedLines++; - } else { - res += `\n${colors.blue}...${colors.white}`; - skipped = true; - } - } - res += `\n ${lines[i - 2]}`; - printedLines++; - } - res += `\n ${lines[i - 1]}`; - printedLines++; - } - // No identical lines before. - identical = 0; - // Add the expected line to the cache. - if (lines === actualLines) { - res += `\n${plusMinus} ${lines[i]}`; - } else { - other += `\n${plusMinus} ${lines[i]}`; - } - printedLines++; - // Only extra actual lines exist - // Lines diverge + return typeof actual !== 'object' || typeof expected !== 'object'; +} + +function createErrDiff(actual, expected, operator, customMessage) { + operator = checkOperator(actual, expected, operator); + + let skipped = false; + let message = ''; + const inspectedActual = inspectValue(actual); + const inspectedExpected = inspectValue(expected); + const inspectedSplitActual = StringPrototypeSplit(inspectedActual, '\n'); + const inspectedSplitExpected = StringPrototypeSplit(inspectedExpected, '\n'); + const showSimpleDiff = isSimpleDiff(actual, inspectedSplitActual, expected, inspectedSplitExpected); + let header = `${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`; + + if (showSimpleDiff) { + const simpleDiff = getSimpleDiff(actual, inspectedSplitActual[0], expected, inspectedSplitExpected[0]); + message = simpleDiff.message; + if (typeof simpleDiff.header !== 'undefined') { + header = simpleDiff.header; + } + if (simpleDiff.skipped) { + skipped = true; + } + } else if (inspectedActual === inspectedExpected) { + // Handles the case where the objects are structurally the same but different references + operator = 'notIdentical'; + if (inspectedSplitActual.length > 50) { + message = `${ArrayPrototypeJoin(ArrayPrototypeSlice(inspectedSplitActual, 0, 50), '\n')}\n...}`; + skipped = true; } else { - const expectedLine = expectedLines[i]; - let actualLine = actualLines[i]; - // If the lines diverge, specifically check for lines that only diverge by - // a trailing comma. In that case it is actually identical and we should - // mark it as such. - let divergingLines = - actualLine !== expectedLine && - (!StringPrototypeEndsWith(actualLine, ',') || - StringPrototypeSlice(actualLine, 0, -1) !== expectedLine); - // If the expected line has a trailing comma but is otherwise identical, - // add a comma at the end of the actual line. Otherwise the output could - // look weird as in: - // - // [ - // 1 // No comma at the end! - // + 2 - // ] - // - if (divergingLines && - StringPrototypeEndsWith(expectedLine, ',') && - StringPrototypeSlice(expectedLine, 0, -1) === actualLine) { - divergingLines = false; - actualLine += ','; - } - if (divergingLines) { - // If more than two former lines are identical, print them. Collapse - // them in case more than five lines were identical. - if (identical > 2) { - if (identical > 3) { - if (identical > 4) { - if (identical === 5) { - res += `\n ${actualLines[i - 3]}`; - printedLines++; - } else { - res += `\n${colors.blue}...${colors.white}`; - skipped = true; - } - } - res += `\n ${actualLines[i - 2]}`; - printedLines++; - } - res += `\n ${actualLines[i - 1]}`; - printedLines++; - } - // No identical lines before. - identical = 0; - // Add the actual line to the result and cache the expected diverging - // line so consecutive diverging lines show up as +++--- and not +-+-+-. - res += `\n${colors.green}+${colors.white} ${actualLine}`; - other += `\n${colors.red}-${colors.white} ${expectedLine}`; - printedLines += 2; - // Lines are identical - } else { - // Add all cached information to the result before adding other things - // and reset the cache. - res += other; - other = ''; - identical++; - // The very first identical line since the last diverging line is be - // added to the result. - if (identical <= 2) { - res += `\n ${actualLine}`; - printedLines++; - } - } + message = ArrayPrototypeJoin(inspectedSplitActual, '\n'); } - // Inspected object to big (Show ~50 rows max) - if (printedLines > 50 && i < maxLines - 2) { - return `${msg}${skippedMsg}\n${res}\n${colors.blue}...${colors.white}${other}\n` + - `${colors.blue}...${colors.white}`; + header = ''; + } else { + const checkCommaDisparity = actual != null && typeof actual === 'object'; + const diff = myersDiff(inspectedSplitActual, inspectedSplitExpected, checkCommaDisparity); + + const myersDiffMessage = printMyersDiff(diff); + message = myersDiffMessage.message; + + if (myersDiffMessage.skipped) { + skipped = true; } } - return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}${indicator}`; + const headerMessage = `${getErrorMessage(operator, customMessage)}\n${header}`; + const skippedMessage = skipped ? '\n... Skipped lines' : ''; + + return `${headerMessage}${skippedMessage}\n${message}\n`; } function addEllipsis(string) { @@ -314,8 +211,8 @@ function addEllipsis(string) { if (lines.length > 10) { lines.length = 10; return `${ArrayPrototypeJoin(lines, '\n')}\n...`; - } else if (string.length > 512) { - return `${StringPrototypeSlice(string, 512)}...`; + } else if (string.length > kMaxLongStringLength) { + return `${StringPrototypeSlice(string, kMaxLongStringLength)}...`; } return string; } @@ -361,7 +258,7 @@ class AssertionError extends Error { } if (operator === 'deepStrictEqual' || operator === 'strictEqual') { - super(createErrDiff(actual, expected, operator)); + super(createErrDiff(actual, expected, operator, message)); } else if (operator === 'notDeepStrictEqual' || operator === 'notStrictEqual') { // In case the objects are equal but the operator requires unequal, show @@ -403,10 +300,10 @@ class AssertionError extends Error { } super(res); } else { - if (res.length > 512) { + if (res.length > kMaxLongStringLength) { res = `${StringPrototypeSlice(res, 0, 509)}...`; } - if (other.length > 512) { + if (other.length > kMaxLongStringLength) { other = `${StringPrototypeSlice(other, 0, 509)}...`; } if (operator === 'deepEqual') { diff --git a/lib/internal/assert/myers_diff.js b/lib/internal/assert/myers_diff.js new file mode 100644 index 00000000000000..7c1061f3d51535 --- /dev/null +++ b/lib/internal/assert/myers_diff.js @@ -0,0 +1,163 @@ +'use strict'; + +const { + Array, + ArrayPrototypeFill, + ArrayPrototypePush, + ArrayPrototypeSlice, + StringPrototypeEndsWith, +} = primordials; + +const colors = require('internal/util/colors'); + +const kNopLinesToCollapse = 5; + +function areLinesEqual(actual, expected, checkCommaDisparity) { + if (actual === expected) { + return true; + } + if (checkCommaDisparity) { + return `${actual},` === expected || actual === `${expected},`; + } + return false; +} + +function myersDiff(actual, expected, checkCommaDisparity = false) { + const actualLength = actual.length; + const expectedLength = expected.length; + const max = actualLength + expectedLength; + const v = ArrayPrototypeFill(Array(2 * max + 1), 0); + + const trace = []; + + for (let diffLevel = 0; diffLevel <= max; diffLevel++) { + const newTrace = ArrayPrototypeSlice(v); + ArrayPrototypePush(trace, newTrace); + + for (let diagonalIndex = -diffLevel; diagonalIndex <= diffLevel; diagonalIndex += 2) { + let x; + if (diagonalIndex === -diffLevel || + (diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max])) { + x = v[diagonalIndex + 1 + max]; + } else { + x = v[diagonalIndex - 1 + max] + 1; + } + + let y = x - diagonalIndex; + + while (x < actualLength && y < expectedLength && areLinesEqual(actual[x], expected[y], checkCommaDisparity)) { + x++; + y++; + } + + v[diagonalIndex + max] = x; + + if (x >= actualLength && y >= expectedLength) { + return backtrack(trace, actual, expected, checkCommaDisparity); + } + } + } +} + +function backtrack(trace, actual, expected, checkCommaDisparity) { + const actualLength = actual.length; + const expectedLength = expected.length; + const max = actualLength + expectedLength; + + let x = actualLength; + let y = expectedLength; + const result = []; + + for (let diffLevel = trace.length - 1; diffLevel >= 0; diffLevel--) { + const v = trace[diffLevel]; + const diagonalIndex = x - y; + let prevDiagonalIndex; + + if (diagonalIndex === -diffLevel || + (diagonalIndex !== diffLevel && v[diagonalIndex - 1 + max] < v[diagonalIndex + 1 + max])) { + prevDiagonalIndex = diagonalIndex + 1; + } else { + prevDiagonalIndex = diagonalIndex - 1; + } + + const prevX = v[prevDiagonalIndex + max]; + const prevY = prevX - prevDiagonalIndex; + + while (x > prevX && y > prevY) { + const value = !checkCommaDisparity || + StringPrototypeEndsWith(actual[x - 1], ',') ? actual[x - 1] : expected[y - 1]; + ArrayPrototypePush(result, { __proto__: null, type: 'nop', value }); + x--; + y--; + } + + if (diffLevel > 0) { + if (x > prevX) { + ArrayPrototypePush(result, { __proto__: null, type: 'insert', value: actual[x - 1] }); + x--; + } else { + ArrayPrototypePush(result, { __proto__: null, type: 'delete', value: expected[y - 1] }); + y--; + } + } + } + + return result; +} + +function printSimpleMyersDiff(diff) { + let message = ''; + + for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { + const { type, value } = diff[diffIdx]; + if (type === 'insert') { + message += `${colors.green}${value}${colors.white}`; + } else if (type === 'delete') { + message += `${colors.red}${value}${colors.white}`; + } else { + message += `${colors.white}${value}${colors.white}`; + } + } + + return `\n${message}`; +} + +function printMyersDiff(diff, simple = false) { + let message = ''; + let skipped = false; + let previousType = 'null'; + let nopCount = 0; + + for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) { + const { type, value } = diff[diffIdx]; + const typeChanged = previousType && (type !== previousType); + + if (typeChanged && previousType === 'nop') { + if (nopCount > kNopLinesToCollapse) { + message += `${colors.blue}...${colors.white}\n`; + message += `${colors.white} ${diff[diffIdx + 1].value}\n`; + skipped = true; + } + nopCount = 0; + } + + if (type === 'insert') { + message += `${colors.green}+${colors.white} ${value}\n`; + } else if (type === 'delete') { + message += `${colors.red}-${colors.white} ${value}\n`; + } else if (type === 'nop') { + if (nopCount <= kNopLinesToCollapse) { + message += `${colors.white} ${value}\n`; + } + nopCount++; + } + + previousType = type; + } + + message = message.trimEnd(); + + return { message: `\n${message}`, skipped }; +} + +module.exports = { myersDiff, printMyersDiff, printSimpleMyersDiff }; diff --git a/test/parallel/test-assert-checktag.js b/test/parallel/test-assert-checktag.js index 7ea12b89d75127..7587939436f40d 100644 --- a/test/parallel/test-assert-checktag.js +++ b/test/parallel/test-assert-checktag.js @@ -29,14 +29,20 @@ test('', { skip: !hasCrypto }, () => { () => assert.deepStrictEqual(date, fake), { message: 'Expected values to be strictly deep-equal:\n' + - '+ actual - expected\n\n+ 2016-01-01T00:00:00.000Z\n- Date {}' + '+ actual - expected\n' + + '\n' + + '+ 2016-01-01T00:00:00.000Z\n' + + '- Date {}\n' } ); assert.throws( () => assert.deepStrictEqual(fake, date), { message: 'Expected values to be strictly deep-equal:\n' + - '+ actual - expected\n\n+ Date {}\n- 2016-01-01T00:00:00.000Z' + '+ actual - expected\n' + + '\n' + + '+ Date {}\n' + + '- 2016-01-01T00:00:00.000Z\n' } ); } diff --git a/test/parallel/test-assert-deep.js b/test/parallel/test-assert-deep.js index a8bc5d3cf4e815..da275d47ffbfea 100644 --- a/test/parallel/test-assert-deep.js +++ b/test/parallel/test-assert-deep.js @@ -70,9 +70,16 @@ test('deepEqual', () => { () => assert.deepStrictEqual(arr, buf), { code: 'ERR_ASSERTION', - message: `${defaultMsgStartFull} ... Lines skipped\n\n` + - '+ Uint8Array(4) [\n' + - '- Buffer(4) [Uint8Array] [\n 120,\n...\n 122,\n 10\n ]' + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ Uint8Array(4) [\n' + + '- Buffer(4) [Uint8Array] [\n' + + ' 120,\n' + + ' 121,\n' + + ' 122,\n' + + ' 10\n' + + ' ]\n' } ); assert.deepEqual(arr, buf); @@ -92,7 +99,7 @@ test('deepEqual', () => { ' 122,\n' + ' 10,\n' + '+ prop: 1\n' + - ' ]' + ' ]\n' } ); assert.notDeepEqual(buf2, buf); @@ -112,7 +119,7 @@ test('deepEqual', () => { ' 122,\n' + ' 10,\n' + '- prop: 5\n' + - ' ]' + ' ]\n' } ); assert.notDeepEqual(arr, arr2); @@ -127,7 +134,7 @@ test('date', () => { code: 'ERR_ASSERTION', message: `${defaultMsgStartFull}\n\n` + '+ 2016-01-01T00:00:00.000Z\n- MyDate 2016-01-01T00:00:00.000Z' + - " {\n- '0': '1'\n- }" + " {\n- '0': '1'\n- }\n" } ); assert.throws( @@ -136,7 +143,7 @@ test('date', () => { code: 'ERR_ASSERTION', message: `${defaultMsgStartFull}\n\n` + '+ MyDate 2016-01-01T00:00:00.000Z {\n' + - "+ '0': '1'\n+ }\n- 2016-01-01T00:00:00.000Z" + "+ '0': '1'\n+ }\n- 2016-01-01T00:00:00.000Z\n" } ); }); @@ -151,7 +158,7 @@ test('regexp', () => { { code: 'ERR_ASSERTION', message: `${defaultMsgStartFull}\n\n` + - "+ /test/\n- MyRegExp /test/ {\n- '0': '1'\n- }" + "+ /test/\n- MyRegExp /test/ {\n- '0': '1'\n- }\n" } ); }); @@ -474,7 +481,7 @@ test('es6 Maps and Sets', () => { { code: 'ERR_ASSERTION', message: `${defaultMsgStartFull}\n\n` + - " Map(1) {\n+ 1 => 1\n- 1 => '1'\n }" + " Map(1) {\n+ 1 => 1\n- 1 => '1'\n }\n" } ); } @@ -846,35 +853,55 @@ test('Additional tests', () => { { code: 'ERR_ASSERTION', name: 'AssertionError', - message: `${defaultMsgStartFull}\n\n+ /ab/\n- /a/` + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ /ab/\n' + + '- /a/\n' }); assert.throws( () => assert.deepStrictEqual(/a/g, /a/), { code: 'ERR_ASSERTION', name: 'AssertionError', - message: `${defaultMsgStartFull}\n\n+ /a/g\n- /a/` + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ /a/g\n' + + '- /a/\n' }); assert.throws( () => assert.deepStrictEqual(/a/i, /a/), { code: 'ERR_ASSERTION', name: 'AssertionError', - message: `${defaultMsgStartFull}\n\n+ /a/i\n- /a/` + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ /a/i\n' + + '- /a/\n' }); assert.throws( () => assert.deepStrictEqual(/a/m, /a/), { code: 'ERR_ASSERTION', name: 'AssertionError', - message: `${defaultMsgStartFull}\n\n+ /a/m\n- /a/` + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ /a/m\n' + + '- /a/\n' }); assert.throws( () => assert.deepStrictEqual(/aa/igm, /aa/im), { code: 'ERR_ASSERTION', name: 'AssertionError', - message: `${defaultMsgStartFull}\n\n+ /aa/gim\n- /aa/im\n ^` + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ /aa/gim\n' + + '- /aa/im\n' }); { @@ -909,7 +936,7 @@ test('Having the same number of owned properties && the same set of keys', () => { code: 'ERR_ASSERTION', name: 'AssertionError', - message: `${defaultMsgStartFull}\n\n [\n+ 4\n- '4'\n ]` + message: `${defaultMsgStartFull}\n\n [\n+ 4\n- '4'\n ]\n` }); assert.throws( () => assert.deepStrictEqual({ a: 4 }, { a: 4, b: true }), @@ -917,7 +944,7 @@ test('Having the same number of owned properties && the same set of keys', () => code: 'ERR_ASSERTION', name: 'AssertionError', message: `${defaultMsgStartFull}\n\n ` + - '{\n a: 4,\n- b: true\n }' + '{\n a: 4,\n- b: true\n }\n' }); assert.throws( () => assert.deepStrictEqual(['a'], { 0: 'a' }), @@ -925,7 +952,7 @@ test('Having the same number of owned properties && the same set of keys', () => code: 'ERR_ASSERTION', name: 'AssertionError', message: `${defaultMsgStartFull}\n\n` + - "+ [\n+ 'a'\n+ ]\n- {\n- '0': 'a'\n- }" + "+ [\n+ 'a'\n+ ]\n- {\n- '0': 'a'\n- }\n" }); }); @@ -964,25 +991,25 @@ test('Check extra properties on errors', () => { () => assert.deepStrictEqual(a, b), { operator: 'throws', - message: `${defaultMsgStartFull}\n\n` + - ' [TypeError: foo] {\n+ foo: \'bar\'\n- foo: \'baz\'\n }', + message: '', } ), { message: 'Expected values to be strictly deep-equal:\n' + - '+ actual - expected ... Lines skipped\n' + + '+ actual - expected\n' + '\n' + ' Comparison {\n' + - " message: 'Expected values to be strictly deep-equal:\\n' +\n" + - '...\n' + - " ' [TypeError: foo] {\\n' +\n" + - " \"+ foo: 'bar'\\n\" +\n" + - "+ \"- foo: 'baz.'\\n\" +\n" + - "- \"- foo: 'baz'\\n\" +\n" + - " ' }',\n" + + "+ message: 'Expected values to be strictly deep-equal:\\n' +\n" + + "+ '+ actual - expected\\n' +\n" + + "+ '\\n' +\n" + + "+ ' [TypeError: foo] {\\n' +\n" + + `+ "+ foo: 'bar'\\n" +\n` + + `+ "- foo: 'baz.'\\n" +\n` + + "+ ' }\\n',\n" + "+ operator: 'deepStrictEqual'\n" + + "- message: '',\n" + "- operator: 'throws'\n" + - ' }' + ' }\n' } ); }); @@ -995,7 +1022,7 @@ test('Check proxies', () => { assert.throws( () => assert.deepStrictEqual(arrProxy, [1, 2, 3]), { message: `${defaultMsgStartFull}\n\n` + - ' [\n 1,\n 2,\n- 3\n ]' } + ' [\n 1,\n 2,\n- 3\n ]\n' } ); util.inspect.defaultOptions = tmp; @@ -1052,7 +1079,7 @@ test('Basic array out of bounds check', () => { ' 1,\n' + ' 2,\n' + '+ 3\n' + - ' ]' + ' ]\n' } ); }); @@ -1346,3 +1373,105 @@ test('Comparing two different WeakSet instances', () => { const weakSet2 = new WeakSet(); assertNotDeepOrStrict(weakSet1, weakSet2); }); + +test('Comparing two arrays nested inside object, with overlapping elements', () => { + const actual = { a: { b: [1, 2, 3] } }; + const expected = { a: { b: [3, 4, 5] } }; + + assert.throws( + () => assert.deepStrictEqual(actual, expected), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' {\n' + + ' a: {\n' + + ' b: [\n' + + '+ 1,\n' + + '+ 2,\n' + + ' 3,\n' + + '- 4,\n' + + '- 5\n' + + ' ]\n' + + ' }\n' + + ' }\n' + } + ); +}); + +test('Comparing two arrays nested inside object, with overlapping elements, swapping keys', () => { + const actual = { a: { b: [1, 2, 3], c: 2 } }; + const expected = { a: { b: 1, c: [3, 4, 5] } }; + + assert.throws( + () => assert.deepStrictEqual(actual, expected), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' {\n' + + ' a: {\n' + + '+ b: [\n' + + '+ 1,\n' + + '+ 2,\n' + + '- b: 1,\n' + + '- c: [\n' + + ' 3,\n' + + '- 4,\n' + + '- 5\n' + + ' ],\n' + + '+ c: 2\n' + + ' }\n' + + ' }\n' + } + ); +}); + +test('Detects differences in deeply nested arrays instead of seeing a new object', () => { + const actual = [ + { a: 1 }, + 2, + 3, + 4, + { c: [1, 2, 3] }, + ]; + const expected = [ + { a: 1 }, + 2, + 3, + 4, + { c: [3, 4, 5] }, + ]; + + assert.throws( + () => assert.deepStrictEqual(actual, expected), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '... Skipped lines\n' + + '\n' + + ' [\n' + + ' {\n' + + ' a: 1\n' + + ' },\n' + + ' 2,\n' + + ' 3,\n' + + '...\n' + + ' c: [\n' + + '+ 1,\n' + + '+ 2,\n' + + ' 3,\n' + + '- 4,\n' + + '- 5\n' + + ' ]\n' + + ' }\n' + + ' ]\n' + } + ); +}); diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 0197ebf543a3cb..186932136bc138 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -30,8 +30,9 @@ const vm = require('vm'); // Disable colored output to prevent color codes from breaking assertion // message comparisons. This should only be an issue when process.stdout // is a TTY. -if (process.stdout.isTTY) +if (process.stdout.isTTY) { process.env.NODE_DISABLE_COLORS = '1'; +} const strictEqualMessageStart = 'Expected values to be strictly equal:\n'; const start = 'Expected values to be strictly deep-equal:'; @@ -277,8 +278,10 @@ test('assert.throws()', () => { code: 'ERR_ASSERTION', name: 'AssertionError', message: 'Expected "actual" to be reference-equal to "expected":\n' + - '+ actual - expected\n\n' + - '+ [Error: foo]\n- [Error: foobar]' + '+ actual - expected\n' + + '\n' + + '+ [Error: foo]\n' + + '- [Error: foobar]\n' } ); }); @@ -342,15 +345,21 @@ test('Test assertion messages', () => { () => assert.strictEqual(actual, ''), { generatedMessage: true, - message: msg || strictEqualMessageStart + - `+ actual - expected\n\n+ ${expected}\n- ''` + message: msg || `Expected values to be strictly equal:\n\n${expected} !== ''\n` } ); } + function testLongAssertionMessage(actual, expected) { + testAssertionMessage(actual, expected, 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + `+ ${expected}\n` + + "- ''\n"); + } + function testShortAssertionMessage(actual, expected) { - testAssertionMessage(actual, expected, strictEqualMessageStart + - `\n${inspect(actual)} !== ''\n`); + testAssertionMessage(actual, expected, strictEqualMessageStart + `\n${inspect(actual)} !== ''\n`); } testShortAssertionMessage(null, 'null'); @@ -359,31 +368,86 @@ test('Test assertion messages', () => { testShortAssertionMessage(100, '100'); testShortAssertionMessage(NaN, 'NaN'); testShortAssertionMessage(Infinity, 'Infinity'); - testShortAssertionMessage('a', '"a"'); + testShortAssertionMessage('a', '\'a\''); testShortAssertionMessage('foo', '\'foo\''); testShortAssertionMessage(0, '0'); testShortAssertionMessage(Symbol(), 'Symbol()'); testShortAssertionMessage(undefined, 'undefined'); testShortAssertionMessage(-Infinity, '-Infinity'); - testAssertionMessage([], '[]'); + testShortAssertionMessage([], '[]'); + testShortAssertionMessage({}, '{}'); testAssertionMessage(/a/, '/a/'); testAssertionMessage(/abc/gim, '/abc/gim'); - testAssertionMessage({}, '{}'); - testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]'); - testAssertionMessage(function f() {}, '[Function: f]'); - testAssertionMessage(function() {}, '[Function (anonymous)]'); - testAssertionMessage(circular, - ' {\n+ x: [Circular *1],\n+ y: 1\n+ }'); - testAssertionMessage({ a: undefined, b: null }, - '{\n+ a: undefined,\n+ b: null\n+ }'); - testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity }, - '{\n+ a: NaN,\n+ b: Infinity,\n+ c: -Infinity\n+ }'); + testLongAssertionMessage(function f() {}, '[Function: f]'); + testLongAssertionMessage(function() {}, '[Function (anonymous)]'); + + assert.throws( + () => assert.strictEqual([1, 2, 3], ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ [\n' + + '+ 1,\n' + + '+ 2,\n' + + '+ 3\n' + + '+ ]\n' + + "- ''\n", + generatedMessage: true + } + ); + + assert.throws( + () => assert.strictEqual(circular, ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ {\n' + + '+ x: [Circular *1],\n' + + '+ y: 1\n' + + '+ }\n' + + "- ''\n", + generatedMessage: true + } + ); + + assert.throws( + () => assert.strictEqual({ a: undefined, b: null }, ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ {\n' + + '+ a: undefined,\n' + + '+ b: null\n' + + '+ }\n' + + "- ''\n", + generatedMessage: true + } + ); + + assert.throws( + () => assert.strictEqual({ a: NaN, b: Infinity, c: -Infinity }, ''), + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + '+ {\n' + + '+ a: NaN,\n' + + '+ b: Infinity,\n' + + '+ c: -Infinity\n' + + '+ }\n' + + "- ''\n", + generatedMessage: true + } + ); // https://github.com/nodejs/node-v0.x-archive/issues/5292 assert.throws( () => assert.strictEqual(1, 2), { - message: `${strictEqualMessageStart}\n1 !== 2\n`, + message: 'Expected values to be strictly equal:\n\n1 !== 2\n', generatedMessage: true } ); @@ -472,7 +536,7 @@ test('Long values should be truncated for display', () => { assert.strictEqual(err.code, 'ERR_ASSERTION'); assert.strictEqual(err.message, `${strictEqualMessageStart}+ actual - expected\n\n` + - `+ '${'A'.repeat(1000)}'\n- ''`); + `+ '${'A'.repeat(1000)}'\n- ''\n`); assert.strictEqual(err.actual.length, 1000); assert.ok(inspect(err).includes(`actual: '${'A'.repeat(488)}...'`)); return true; @@ -485,7 +549,7 @@ test('Output that extends beyond 10 lines should also be truncated for display', assert.strictEqual(multilineString, ''); }, (err) => { assert.strictEqual(err.code, 'ERR_ASSERTION'); - assert.strictEqual(err.message.split('\n').length, 19); + assert.strictEqual(err.message.split('\n').length, 20); assert.strictEqual(err.actual.split('\n').length, 16); assert.ok(inspect(err).includes( "actual: 'fhqwhgads\\n' +\n" + @@ -566,84 +630,58 @@ test('Test strict assert', () => { Error.stackTraceLimit = tmpLimit; // Test error diffs. - let message = [ - start, - `${actExp} ... Lines skipped`, - '', - ' [', - ' [', - ' [', - ' 1,', - ' 2,', - '+ 3', - "- '3'", - ' ]', - '...', - ' 4,', - ' 5', - ' ]'].join('\n'); + let message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + ' [\n' + + ' [\n' + + ' 1,\n' + + ' 2,\n' + + '+ 3\n' + + "- '3'\n" + + ' ]\n' + + ' ],\n' + + ' 4,\n' + + ' 5\n' + + ' ]\n'; strict.throws( () => strict.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]), { message }); - message = [ - start, - `${actExp} ... Lines skipped`, - '', - ' [', - ' 1,', - '...', - ' 1,', - ' 0,', - '- 1,', - ' 1,', - '...', - ' 1,', - ' 1', - ' ]', - ].join('\n'); - strict.throws( - () => strict.deepEqual( - [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]), - { message }); - - message = [ - start, - `${actExp} ... Lines skipped`, - '', - ' [', - ' 1,', - '...', - ' 1,', - ' 0,', - '+ 1,', - ' 1,', - ' 1,', - ' 1', - ' ]', - ].join('\n'); + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '... Skipped lines\n' + + '\n' + + ' [\n' + + ' 1,\n' + + ' 1,\n' + + ' 1,\n' + + ' 0,\n' + + ' 1,\n' + + '...\n' + + ' 1,\n' + + '+ 1\n' + + ' ]\n'; strict.throws( () => strict.deepEqual( - [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]), + [1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1]), { message }); - message = [ - start, - actExp, - '', - ' [', - ' 1,', - '+ 2,', - '- 1,', - ' 1,', - ' 1,', - ' 0,', - '+ 1,', - ' 1', - ' ]', - ].join('\n'); + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + ' 1,\n' + + '+ 2,\n' + + ' 1,\n' + + ' 1,\n' + + '- 1,\n' + + ' 0,\n' + + ' 1,\n' + + '+ 1\n' + + ' ]\n'; strict.throws( () => strict.deepEqual( [1, 2, 1, 1, 0, 1, 1], @@ -659,7 +697,7 @@ test('Test strict assert', () => { '+ 2,', '+ 1', '+ ]', - '- undefined', + '- undefined\n', ].join('\n'); strict.throws( () => strict.deepEqual([1, 2, 1], undefined), @@ -673,22 +711,25 @@ test('Test strict assert', () => { '+ 1,', ' 2,', ' 1', - ' ]', + ' ]\n', ].join('\n'); strict.throws( () => strict.deepEqual([1, 2, 1], [2, 1]), { message }); - message = `${start}\n` + - `${actExp} ... Lines skipped\n` + - '\n' + - ' [\n' + - '+ 1,\n'.repeat(25) + - '...\n' + - '- 2,\n'.repeat(25) + - '...'; + message = 'Expected values to be strictly deep-equal:\n' + + '+ actual - expected\n' + + '\n' + + ' [\n' + + '+ 1,\n'.repeat(10) + + '+ 3\n' + + '- 2,\n'.repeat(11) + + '- 4,\n' + + '- 4,\n' + + '- 4\n' + + ' ]\n'; strict.throws( - () => strict.deepEqual(Array(28).fill(1), Array(28).fill(2)), + () => strict.deepEqual([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4]), { message }); const obj1 = {}; @@ -703,7 +744,7 @@ test('Test strict assert', () => { '- {\n' + '- [Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)],\n' + "- loop: 'forever'\n" + - '- }' + '- }\n' }); // notDeepEqual tests @@ -823,7 +864,7 @@ test('Additional asserts', () => { '+ actual - expected\n' + '\n' + "+ 'string'\n" + - '- false' + '- false\n' } ); @@ -839,7 +880,7 @@ test('Additional asserts', () => { '+ actual - expected\n' + '\n' + "+ 'string'\n" + - '- false' + '- false\n' } ); @@ -855,7 +896,7 @@ test('Additional asserts', () => { '+ actual - expected\n' + '\n' + "+ 'string'\n" + - '- false' + '- false\n' } ); /* eslint-enable @stylistic/js/indent */ @@ -1008,7 +1049,7 @@ test('Throws accepts objects', () => { '- foo: undefined,\n' + " message: 'Wrong value',\n" + " name: 'TypeError'\n" + - ' }' + ' }\n' } ); @@ -1026,7 +1067,7 @@ test('Throws accepts objects', () => { '- foo: undefined,\n' + " message: 'Wrong value',\n" + " name: 'TypeError'\n" + - ' }' + ' }\n' } ); @@ -1060,7 +1101,7 @@ test('Throws accepts objects', () => { " message: 'e',\n" + "+ name: 'TypeError'\n" + "- name: 'Error'\n" + - ' }' + ' }\n' } ); assert.throws( @@ -1074,7 +1115,7 @@ test('Throws accepts objects', () => { "+ message: 'foo',\n" + "- message: '',\n" + " name: 'Error'\n" + - ' }' + ' }\n' } ); @@ -1142,7 +1183,7 @@ test('Additional assert', () => { { message: 'Expected "actual" to be reference-equal to "expected":\n' + '+ actual - expected\n\n' + - "+ [Arguments] {\n- {\n '0': 'a'\n }" + "+ [Arguments] {\n- {\n '0': 'a'\n }\n" } ); } @@ -1169,7 +1210,7 @@ test('Additional assert', () => { "+ message: 'foobar',\n" + '- message: /fooa/,\n' + " name: 'TypeError'\n" + - ' }' + ' }\n' } ); @@ -1190,7 +1231,7 @@ test('Additional assert', () => { '+ null\n' + '- {\n' + "- message: 'foo'\n" + - '- }' + '- }\n' } ); @@ -1204,7 +1245,7 @@ test('Additional assert', () => { ), { actual, - message: "message\n+ actual - expected\n\n+ 'foobar'\n- {\n- message: 'foobar'\n- }", + message: "message\n+ actual - expected\n\n+ 'foobar'\n- {\n- message: 'foobar'\n- }\n", operator: 'throws', generatedMessage: false } @@ -1217,11 +1258,12 @@ test('Additional assert', () => { { code: 'ERR_ASSERTION', name: 'AssertionError', - message: strictEqualMessageStart + - '+ actual - expected\n\n' + - "+ 'test test'\n" + - "- 'test foobar'\n" + - ' ^' + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + "+ 'test test'\n" + + "- 'test foobar'\n" + + ' ^\n', } ); @@ -1258,7 +1300,7 @@ test('Additional assert', () => { { code: 'ERR_ASSERTION', name: 'AssertionError', - message: 'custom message\n+ actual - expected\n\n {\n+ a: true\n- a: false\n }' + message: 'custom message\n+ actual - expected\n\n {\n+ a: true\n- a: false\n }\n' } ); @@ -1489,19 +1531,6 @@ test('Additional assert', () => { ); assert.doesNotMatch('I will pass', /different$/); } - - { - const tempColor = inspect.defaultOptions.colors; - assert.throws(() => { - inspect.defaultOptions.colors = true; - // Guarantee the position indicator is placed correctly. - assert.strictEqual(111554n, 11111115); - }, (err) => { - assert.strictEqual(inspect(err).split('\n')[5], ' ^'); - inspect.defaultOptions.colors = tempColor; - return true; - }); - } }); test('assert/strict exists', () => { diff --git a/test/pseudo-tty/test-assert-colors.js b/test/pseudo-tty/test-assert-colors.js index 9d5a923aa0f8e7..d43dd60224a06f 100644 --- a/test/pseudo-tty/test-assert-colors.js +++ b/test/pseudo-tty/test-assert-colors.js @@ -9,16 +9,16 @@ assert.throws(() => { assert.deepStrictEqual([1, 2, 2, 2, 2], [2, 2, 2, 2, 2]); }, (err) => { const expected = 'Expected values to be strictly deep-equal:\n' + - '\u001b[32m+ actual\u001b[39m \u001b[31m- expected\u001b[39m' + - ' \u001b[34m...\u001b[39m Lines skipped\n\n' + - ' [\n' + - '\u001b[32m+\u001b[39m 1,\n' + - '\u001b[31m-\u001b[39m 2,\n' + - ' 2,\n' + - '\u001b[34m...\u001b[39m\n' + - ' 2,\n' + - ' 2\n' + - ' ]'; + '\x1B[32m+ actual\x1B[39m \x1B[31m- expected\x1B[39m\n' + + '\n' + + '\x1B[39m [\n' + + '\x1B[32m+\x1B[39m 1,\n' + + '\x1B[39m 2,\n' + + '\x1B[39m 2,\n' + + '\x1B[39m 2,\n' + + '\x1B[39m 2,\n' + + '\x1B[31m-\x1B[39m 2\n' + + '\x1B[39m ]\n'; assert.strictEqual(err.message, expected); return true; }); diff --git a/test/pseudo-tty/test-assert-no-color.js b/test/pseudo-tty/test-assert-no-color.js index 698d29ab8dbceb..d488f4e6bfcbe7 100644 --- a/test/pseudo-tty/test-assert-no-color.js +++ b/test/pseudo-tty/test-assert-no-color.js @@ -15,5 +15,5 @@ assert.throws( '+ {}\n' + '- {\n' + '- foo: \'bar\'\n' + - '- }', + '- }\n', }); diff --git a/test/pseudo-tty/test-assert-position-indicator.js b/test/pseudo-tty/test-assert-position-indicator.js index 9909fee2b4ae72..22d0cbab5e376e 100644 --- a/test/pseudo-tty/test-assert-position-indicator.js +++ b/test/pseudo-tty/test-assert-position-indicator.js @@ -2,17 +2,39 @@ require('../common'); const assert = require('assert'); -process.env.NODE_DISABLE_COLORS = true; +process.env.NODE_DISABLE_COLORS = '1'; + +// Does not show the indicator when the terminal is too narrow process.stderr.columns = 20; -// Confirm that there is no position indicator. assert.throws( - () => { assert.strictEqual('a'.repeat(30), 'a'.repeat(31)); }, + () => { assert.strictEqual('123456789ABCDEFGHI', '1!3!5!7!9!BC!!!GHI'); }, (err) => !err.message.includes('^'), ); -// Confirm that there is a position indicator. +// Does not show the indicator because the first difference is in the first 2 chars +process.stderr.columns = 80; +assert.throws( + () => { assert.strictEqual('123456789ABCDEFGHI', '1!3!5!7!9!BC!!!GHI'); }, + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + "+ '123456789ABCDEFGHI'\n" + + "- '1!3!5!7!9!BC!!!GHI'\n", + }, +); + +// Show the indicator because the first difference is in the 3 char +process.stderr.columns = 80; assert.throws( - () => { assert.strictEqual('aaaa', 'aaaaa'); }, - (err) => err.message.includes('^'), + () => { assert.strictEqual('123456789ABCDEFGHI', '12!!5!7!9!BC!!!GHI'); }, + { + message: 'Expected values to be strictly equal:\n' + + '+ actual - expected\n' + + '\n' + + "+ '123456789ABCDEFGHI'\n" + + "- '12!!5!7!9!BC!!!GHI'\n" + + ' ^\n', + }, );