0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-21 13:09:21 +01:00

assert: make assertion_error use Myers diff algorithm

Fixes: https://github.com/nodejs/node/issues/51733

Co-Authored-By: Pietro Marchini <pietro.marchini94@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/54862
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Giovanni Bucci 2024-10-17 18:02:54 +02:00 committed by GitHub
parent d458b933ed
commit 4d6d7d644b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 738 additions and 431 deletions

View File

@ -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,174 @@ 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;
}
}
if (indicatorIdx !== -1) {
message += `\n${StringPrototypeRepeat(' ', indicatorIdx + 2)}^`;
}
}
// 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}`;
return { message };
}
function getSimpleDiff(originalActual, actual, originalExpected, expected) {
const stringsLen = actual.length + expected.length;
if (stringsLen <= kMaxShortStringLength && (originalActual !== 0 || originalExpected !== 0)) {
return { message: `${actual} !== ${expected}`, header: '' };
}
const isStringComparison = typeof originalActual === 'string' && typeof originalExpected === 'string';
// colored myers diff
if (isStringComparison && colors.hasColors) {
return getColoredMyersDiff(actual, expected);
}
return getStackedDiff(actual, expected);
}
function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) {
if (inspectedActual.length > 1 || inspectedExpected.length > 1) {
return false;
}
return typeof actual !== 'object' || actual === null || typeof expected !== 'object' || expected === null;
}
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 {
other = a;
message = ArrayPrototypeJoin(inspectedSplitActual, '\n');
}
ArrayPrototypePop(actualLines);
ArrayPrototypePop(expectedLines);
if (actualLines.length === 0 || expectedLines.length === 0)
break;
a = actualLines[actualLines.length - 1];
b = expectedLines[expectedLines.length - 1];
}
header = '';
} else {
const checkCommaDisparity = actual != null && typeof actual === 'object';
const diff = myersDiff(inspectedSplitActual, inspectedSplitExpected, checkCommaDisparity);
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');
const myersDiffMessage = printMyersDiff(diff);
message = myersDiffMessage.message;
// 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 `${kReadableOperator.notIdentical}\n\n` +
`${ArrayPrototypeJoin(actualLines, '\n')}\n`;
}
// 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 = '';
}
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;
}
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
} 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++;
}
}
}
// 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}`;
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 +210,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 +257,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 +299,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') {

View File

@ -0,0 +1,167 @@
'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 nopCount = 0;
for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) {
const { type, value } = diff[diffIdx];
const previousType = (diffIdx < (diff.length - 1)) ? diff[diffIdx + 1].type : null;
const typeChanged = previousType && (type !== previousType);
if (typeChanged && previousType === 'nop') {
// Avoid grouping if only one line would have been grouped otherwise
if (nopCount === kNopLinesToCollapse + 1) {
message += `${colors.white} ${diff[diffIdx + 1].value}\n`;
} else if (nopCount === kNopLinesToCollapse + 2) {
message += `${colors.white} ${diff[diffIdx + 2].value}\n`;
message += `${colors.white} ${diff[diffIdx + 1].value}\n`;
} if (nopCount >= (kNopLinesToCollapse + 3)) {
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++;
}
}
message = message.trimEnd();
return { message: `\n${message}`, skipped };
}
module.exports = { myersDiff, printMyersDiff, printSimpleMyersDiff };

View File

@ -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'
}
);
}

View File

@ -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'
}
);
});
@ -1347,6 +1374,107 @@ test('Comparing two different WeakSet instances', () => {
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' +
'...\n' +
' c: [\n' +
'+ 1,\n' +
'+ 2,\n' +
' 3,\n' +
'- 4,\n' +
'- 5\n' +
' ]\n' +
' }\n' +
' ]\n'
}
);
});
// check URL
{
const a = new URL('http://foo');

View File

@ -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,
'<ref *1> {\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' +
'+ <ref *1> {\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,117 @@ 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');
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' +
'...\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, 1, 1, 0, 1, 1, 1, 1],
[1, 1, 1, 0, 1, 1, 1]),
{ 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]),
{ message });
message = 'Expected values to be strictly deep-equal:\n' +
'+ actual - expected\n' +
'\n' +
' [\n' +
' 1,\n' +
' 2,\n' +
' 3,\n' +
' 4,\n' +
' 5,\n' +
'+ 6,\n' +
'- 9,\n' +
' 7\n' +
' ]\n';
message = [
start,
actExp,
'',
' [',
' 1,',
'+ 2,',
'- 1,',
' 1,',
' 1,',
' 0,',
'+ 1,',
' 1',
' ]',
].join('\n');
assert.throws(
() => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 9, 7]),
{ message }
);
message = 'Expected values to be strictly deep-equal:\n' +
'+ actual - expected\n' +
'\n' +
' [\n' +
' 1,\n' +
' 2,\n' +
' 3,\n' +
' 4,\n' +
' 5,\n' +
' 6,\n' +
'+ 7,\n' +
'- 9,\n' +
' 8\n' +
' ]\n';
assert.throws(
() => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 9, 8]),
{ message }
);
message = 'Expected values to be strictly deep-equal:\n' +
'+ actual - expected\n' +
'... Skipped lines\n' +
'\n' +
' [\n' +
' 1,\n' +
' 2,\n' +
' 3,\n' +
' 4,\n' +
'...\n' +
' 7,\n' +
'+ 8,\n' +
'- 0,\n' +
' 9\n' +
' ]\n';
assert.throws(
() => assert.deepStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 0, 9]),
{ message }
);
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 +756,7 @@ test('Test strict assert', () => {
'+ 2,',
'+ 1',
'+ ]',
'- undefined',
'- undefined\n',
].join('\n');
strict.throws(
() => strict.deepEqual([1, 2, 1], undefined),
@ -673,22 +770,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 +803,7 @@ test('Test strict assert', () => {
'- {\n' +
'- [Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)],\n' +
"- loop: 'forever'\n" +
'- }'
'- }\n'
});
// notDeepEqual tests
@ -823,7 +923,7 @@ test('Additional asserts', () => {
'+ actual - expected\n' +
'\n' +
"+ 'string'\n" +
'- false'
'- false\n'
}
);
@ -839,7 +939,7 @@ test('Additional asserts', () => {
'+ actual - expected\n' +
'\n' +
"+ 'string'\n" +
'- false'
'- false\n'
}
);
@ -855,7 +955,7 @@ test('Additional asserts', () => {
'+ actual - expected\n' +
'\n' +
"+ 'string'\n" +
'- false'
'- false\n'
}
);
/* eslint-enable @stylistic/js/indent */
@ -1008,7 +1108,7 @@ test('Throws accepts objects', () => {
'- foo: undefined,\n' +
" message: 'Wrong value',\n" +
" name: 'TypeError'\n" +
' }'
' }\n'
}
);
@ -1026,7 +1126,7 @@ test('Throws accepts objects', () => {
'- foo: undefined,\n' +
" message: 'Wrong value',\n" +
" name: 'TypeError'\n" +
' }'
' }\n'
}
);
@ -1060,7 +1160,7 @@ test('Throws accepts objects', () => {
" message: 'e',\n" +
"+ name: 'TypeError'\n" +
"- name: 'Error'\n" +
' }'
' }\n'
}
);
assert.throws(
@ -1074,7 +1174,7 @@ test('Throws accepts objects', () => {
"+ message: 'foo',\n" +
"- message: '',\n" +
" name: 'Error'\n" +
' }'
' }\n'
}
);
@ -1142,7 +1242,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 +1269,7 @@ test('Additional assert', () => {
"+ message: 'foobar',\n" +
'- message: /fooa/,\n' +
" name: 'TypeError'\n" +
' }'
' }\n'
}
);
@ -1190,7 +1290,7 @@ test('Additional assert', () => {
'+ null\n' +
'- {\n' +
"- message: 'foo'\n" +
'- }'
'- }\n'
}
);
@ -1204,7 +1304,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 +1317,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 +1359,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 +1590,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', () => {

View File

@ -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;
});

View File

@ -15,5 +15,5 @@ assert.throws(
'+ {}\n' +
'- {\n' +
'- foo: \'bar\'\n' +
'- }',
'- }\n',
});

View File

@ -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('aaaa', 'aaaaa'); },
(err) => err.message.includes('^'),
() => { 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('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',
},
);