assert: improve default error messages
This improves the error messages for: - assert.notDeepStrictEqual - assert.deepStrictEqual - assert.notStrictEqual - assert.strictEqual Those will now always use the same error message as used in the strict mode. PR-URL: https://github.com/nodejs/node/pull/19467 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
f2d112c6b7
commit
bfe54df812
@ -54,7 +54,6 @@ const meta = [
|
|||||||
|
|
||||||
const escapeFn = (str) => meta[str.charCodeAt(0)];
|
const escapeFn = (str) => meta[str.charCodeAt(0)];
|
||||||
|
|
||||||
const ERR_DIFF_DEACTIVATED = 0;
|
|
||||||
const ERR_DIFF_NOT_EQUAL = 1;
|
const ERR_DIFF_NOT_EQUAL = 1;
|
||||||
const ERR_DIFF_EQUAL = 2;
|
const ERR_DIFF_EQUAL = 2;
|
||||||
|
|
||||||
@ -323,7 +322,7 @@ assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
|
|||||||
message,
|
message,
|
||||||
operator: 'deepStrictEqual',
|
operator: 'deepStrictEqual',
|
||||||
stackStartFn: deepStrictEqual,
|
stackStartFn: deepStrictEqual,
|
||||||
errorDiff: this === strict ? ERR_DIFF_EQUAL : ERR_DIFF_DEACTIVATED
|
errorDiff: ERR_DIFF_EQUAL
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -337,7 +336,7 @@ function notDeepStrictEqual(actual, expected, message) {
|
|||||||
message,
|
message,
|
||||||
operator: 'notDeepStrictEqual',
|
operator: 'notDeepStrictEqual',
|
||||||
stackStartFn: notDeepStrictEqual,
|
stackStartFn: notDeepStrictEqual,
|
||||||
errorDiff: this === strict ? ERR_DIFF_NOT_EQUAL : ERR_DIFF_DEACTIVATED
|
errorDiff: ERR_DIFF_NOT_EQUAL
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,7 +349,7 @@ assert.strictEqual = function strictEqual(actual, expected, message) {
|
|||||||
message,
|
message,
|
||||||
operator: 'strictEqual',
|
operator: 'strictEqual',
|
||||||
stackStartFn: strictEqual,
|
stackStartFn: strictEqual,
|
||||||
errorDiff: this === strict ? ERR_DIFF_EQUAL : ERR_DIFF_DEACTIVATED
|
errorDiff: ERR_DIFF_EQUAL
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -363,7 +362,7 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
|
|||||||
message,
|
message,
|
||||||
operator: 'notStrictEqual',
|
operator: 'notStrictEqual',
|
||||||
stackStartFn: notStrictEqual,
|
stackStartFn: notStrictEqual,
|
||||||
errorDiff: this === strict ? ERR_DIFF_NOT_EQUAL : ERR_DIFF_DEACTIVATED
|
errorDiff: ERR_DIFF_NOT_EQUAL
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,13 @@ let green = '';
|
|||||||
let red = '';
|
let red = '';
|
||||||
let white = '';
|
let white = '';
|
||||||
|
|
||||||
|
const READABLE_OPERATOR = {
|
||||||
|
deepStrictEqual: 'Input A expected to strictly deep-equal input B',
|
||||||
|
notDeepStrictEqual: 'Input A expected to strictly not deep-equal input B',
|
||||||
|
strictEqual: 'Input A expected to strictly equal input B',
|
||||||
|
notStrictEqual: 'Input A expected to strictly not equal input B'
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
errmap,
|
errmap,
|
||||||
UV_EAI_MEMORY,
|
UV_EAI_MEMORY,
|
||||||
@ -40,10 +47,34 @@ function lazyInternalUtil() {
|
|||||||
return internalUtil;
|
return internalUtil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyError(source) {
|
||||||
|
const keys = Object.keys(source);
|
||||||
|
const target = Object.create(Object.getPrototypeOf(source));
|
||||||
|
for (const key of keys) {
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
Object.defineProperty(target, 'message', { value: source.message });
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
function inspectValue(val) {
|
function inspectValue(val) {
|
||||||
|
// The util.inspect default values could be changed. This makes sure the
|
||||||
|
// error messages contain the necessary information nevertheless.
|
||||||
return util.inspect(
|
return util.inspect(
|
||||||
val,
|
val,
|
||||||
{ compact: false, customInspect: false }
|
{
|
||||||
|
compact: false,
|
||||||
|
customInspect: false,
|
||||||
|
depth: 1000,
|
||||||
|
maxArrayLength: Infinity,
|
||||||
|
// Assert compares only enumerable properties (with a few exceptions).
|
||||||
|
showHidden: false,
|
||||||
|
// Having a long line as error is better than wrapping the line for
|
||||||
|
// comparison.
|
||||||
|
breakLength: Infinity,
|
||||||
|
// Assert does not detect proxies currently.
|
||||||
|
showProxy: false
|
||||||
|
}
|
||||||
).split('\n');
|
).split('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,8 +257,8 @@ function createErrDiff(actual, expected, operator) {
|
|||||||
if (util === undefined) util = require('util');
|
if (util === undefined) util = require('util');
|
||||||
const actualLines = inspectValue(actual);
|
const actualLines = inspectValue(actual);
|
||||||
const expectedLines = inspectValue(expected);
|
const expectedLines = inspectValue(expected);
|
||||||
const msg = `Input A expected to ${operator} input B:\n` +
|
const msg = READABLE_OPERATOR[operator] +
|
||||||
`${green}+ expected${white} ${red}- actual${white}`;
|
`:\n${green}+ expected${white} ${red}- actual${white}`;
|
||||||
const skippedMsg = ' ... Lines skipped';
|
const skippedMsg = ' ... Lines skipped';
|
||||||
|
|
||||||
// Remove all ending lines that match (this optimizes the output for
|
// Remove all ending lines that match (this optimizes the output for
|
||||||
@ -259,6 +290,7 @@ function createErrDiff(actual, expected, operator) {
|
|||||||
|
|
||||||
const maxLines = Math.max(actualLines.length, expectedLines.length);
|
const maxLines = Math.max(actualLines.length, expectedLines.length);
|
||||||
var printedLines = 0;
|
var printedLines = 0;
|
||||||
|
var identical = 0;
|
||||||
for (i = 0; i < maxLines; i++) {
|
for (i = 0; i < maxLines; i++) {
|
||||||
// Only extra expected lines exist
|
// Only extra expected lines exist
|
||||||
const cur = i - lastPos;
|
const cur = i - lastPos;
|
||||||
@ -318,12 +350,38 @@ function createErrDiff(actual, expected, operator) {
|
|||||||
res += `\n ${actualLines[i]}`;
|
res += `\n ${actualLines[i]}`;
|
||||||
printedLines++;
|
printedLines++;
|
||||||
}
|
}
|
||||||
|
identical++;
|
||||||
}
|
}
|
||||||
// Inspected object to big (Show ~20 rows max)
|
// Inspected object to big (Show ~20 rows max)
|
||||||
if (printedLines > 20 && i < maxLines - 2) {
|
if (printedLines > 20 && i < maxLines - 2) {
|
||||||
return `${msg}${skippedMsg}\n${res}\n...${other}\n...`;
|
return `${msg}${skippedMsg}\n${res}\n...${other}\n...`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strict equal with identical objects that are not identical by reference.
|
||||||
|
if (identical === maxLines) {
|
||||||
|
let base = 'Input object identical but not reference equal:';
|
||||||
|
|
||||||
|
if (operator !== 'strictEqual') {
|
||||||
|
// This code path should not be possible to reach.
|
||||||
|
// The output is identical but it is not clear why.
|
||||||
|
base = 'Input objects not identical:';
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to get the result again. The lines were all removed before.
|
||||||
|
const actualLines = inspectValue(actual);
|
||||||
|
|
||||||
|
// Only remove lines in case it makes sense to collapse those.
|
||||||
|
// TODO: Accept env to always show the full error.
|
||||||
|
if (actualLines.length > 30) {
|
||||||
|
actualLines[26] = '...';
|
||||||
|
while (actualLines.length > 27) {
|
||||||
|
actualLines.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${base}\n\n ${actualLines.join('\n ')}\n`;
|
||||||
|
}
|
||||||
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
|
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,13 +416,15 @@ class AssertionError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (util === undefined) util = require('util');
|
if (util === undefined) util = require('util');
|
||||||
|
// Prevent the error stack from being visible by duplicating the error
|
||||||
|
// in a very close way to the original in case both sides are actually
|
||||||
|
// instances of Error.
|
||||||
if (typeof actual === 'object' && actual !== null &&
|
if (typeof actual === 'object' && actual !== null &&
|
||||||
'stack' in actual && actual instanceof Error) {
|
typeof expected === 'object' && expected !== null &&
|
||||||
actual = `${actual.name}: ${actual.message}`;
|
'stack' in actual && actual instanceof Error &&
|
||||||
}
|
'stack' in expected && expected instanceof Error) {
|
||||||
if (typeof expected === 'object' && expected !== null &&
|
actual = copyError(actual);
|
||||||
'stack' in expected && expected instanceof Error) {
|
expected = copyError(expected);
|
||||||
expected = `${expected.name}: ${expected.message}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorDiff === 0) {
|
if (errorDiff === 0) {
|
||||||
@ -379,15 +439,23 @@ class AssertionError extends Error {
|
|||||||
// In case the objects are equal but the operator requires unequal, show
|
// In case the objects are equal but the operator requires unequal, show
|
||||||
// the first object and say A equals B
|
// the first object and say A equals B
|
||||||
const res = inspectValue(actual);
|
const res = inspectValue(actual);
|
||||||
|
const base = `Identical input passed to ${operator}:`;
|
||||||
|
|
||||||
if (res.length > 20) {
|
// Only remove lines in case it makes sense to collapse those.
|
||||||
res[19] = '...';
|
// TODO: Accept env to always show the full error.
|
||||||
while (res.length > 20) {
|
if (res.length > 30) {
|
||||||
|
res[26] = '...';
|
||||||
|
while (res.length > 27) {
|
||||||
res.pop();
|
res.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Only print a single object.
|
|
||||||
super(`Identical input passed to ${operator}:\n${res.join('\n')}`);
|
// Only print a single input.
|
||||||
|
if (res.length === 1) {
|
||||||
|
super(`${base} ${res[0]}`);
|
||||||
|
} else {
|
||||||
|
super(`${base}\n\n ${res.join('\n ')}\n`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
super(createErrDiff(actual, expected, operator));
|
super(createErrDiff(actual, expected, operator));
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ assert.js:*
|
|||||||
throw new AssertionError(obj);
|
throw new AssertionError(obj);
|
||||||
^
|
^
|
||||||
|
|
||||||
AssertionError [ERR_ASSERTION]: Input A expected to deepStrictEqual input B:
|
AssertionError [ERR_ASSERTION]: Input A expected to strictly deep-equal input B:
|
||||||
+ expected - actual
|
+ expected - actual
|
||||||
|
|
||||||
- Comparison {}
|
- Comparison {}
|
||||||
|
@ -3,7 +3,11 @@ assert.js:*
|
|||||||
throw new AssertionError(obj);
|
throw new AssertionError(obj);
|
||||||
^
|
^
|
||||||
|
|
||||||
AssertionError [ERR_ASSERTION]: 1 strictEqual 2
|
AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
|
||||||
|
+ expected - actual
|
||||||
|
|
||||||
|
- 1
|
||||||
|
+ 2
|
||||||
at Object.<anonymous> (*test*message*error_exit.js:*:*)
|
at Object.<anonymous> (*test*message*error_exit.js:*:*)
|
||||||
at Module._compile (internal/modules/cjs/loader.js:*:*)
|
at Module._compile (internal/modules/cjs/loader.js:*:*)
|
||||||
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*)
|
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*:*)
|
||||||
|
@ -1,24 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const common = require('../common');
|
require('../common');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const util = require('util');
|
|
||||||
|
|
||||||
// Template tag function turning an error message into a RegExp
|
|
||||||
// for assert.throws()
|
|
||||||
function re(literals, ...values) {
|
|
||||||
let result = literals[0];
|
|
||||||
const escapeRE = /[\\^$.*+?()[\]{}|=!<>:-]/g;
|
|
||||||
for (const [i, value] of values.entries()) {
|
|
||||||
const str = util.inspect(value);
|
|
||||||
// Need to escape special characters.
|
|
||||||
result += str.replace(escapeRE, '\\$&');
|
|
||||||
result += literals[i + 1];
|
|
||||||
}
|
|
||||||
return common.expectsError({
|
|
||||||
code: 'ERR_ASSERTION',
|
|
||||||
message: new RegExp(`^${result}$`)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn off no-restricted-properties because we are testing deepEqual!
|
// Turn off no-restricted-properties because we are testing deepEqual!
|
||||||
/* eslint-disable no-restricted-properties */
|
/* eslint-disable no-restricted-properties */
|
||||||
@ -35,10 +17,20 @@ function re(literals, ...values) {
|
|||||||
|
|
||||||
// For deepStrictEqual we check the runtime type,
|
// For deepStrictEqual we check the runtime type,
|
||||||
// then reveal the fakeness of the fake date
|
// then reveal the fakeness of the fake date
|
||||||
assert.throws(() => assert.deepStrictEqual(date, fake),
|
assert.throws(
|
||||||
re`${date} deepStrictEqual Date {}`);
|
() => assert.deepStrictEqual(date, fake),
|
||||||
assert.throws(() => assert.deepStrictEqual(fake, date),
|
{
|
||||||
re`Date {} deepStrictEqual ${date}`);
|
message: 'Input A expected to strictly deep-equal input B:\n' +
|
||||||
|
'+ expected - actual\n\n- 2016-01-01T00:00:00.000Z\n+ Date {}'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => assert.deepStrictEqual(fake, date),
|
||||||
|
{
|
||||||
|
message: 'Input A expected to strictly deep-equal input B:\n' +
|
||||||
|
'+ expected - actual\n\n- Date {}\n+ 2016-01-01T00:00:00.000Z'
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // At the moment global has its own type tag
|
{ // At the moment global has its own type tag
|
||||||
|
@ -4,6 +4,8 @@ const common = require('../common');
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const { AssertionError } = assert;
|
const { AssertionError } = assert;
|
||||||
|
const defaultMsgStart = 'Input A expected to strictly deep-equal input B:\n' +
|
||||||
|
'+ expected - actual';
|
||||||
|
|
||||||
// Template tag function turning an error message into a RegExp
|
// Template tag function turning an error message into a RegExp
|
||||||
// for assert.throws()
|
// for assert.throws()
|
||||||
@ -32,24 +34,45 @@ function re(literals, ...values) {
|
|||||||
const arr = new Uint8Array([120, 121, 122, 10]);
|
const arr = new Uint8Array([120, 121, 122, 10]);
|
||||||
const buf = Buffer.from(arr);
|
const buf = Buffer.from(arr);
|
||||||
// They have different [[Prototype]]
|
// They have different [[Prototype]]
|
||||||
assert.throws(() => assert.deepStrictEqual(arr, buf),
|
assert.throws(
|
||||||
re`${arr} deepStrictEqual ${buf}`);
|
() => assert.deepStrictEqual(arr, buf),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
message: `${defaultMsgStart} ... Lines skipped\n\n` +
|
||||||
|
'- Uint8Array [\n' +
|
||||||
|
'+ Buffer [Uint8Array] [\n 120,\n...\n 10\n ]'
|
||||||
|
}
|
||||||
|
);
|
||||||
assert.deepEqual(arr, buf);
|
assert.deepEqual(arr, buf);
|
||||||
|
|
||||||
{
|
{
|
||||||
const buf2 = Buffer.from(arr);
|
const buf2 = Buffer.from(arr);
|
||||||
buf2.prop = 1;
|
buf2.prop = 1;
|
||||||
|
|
||||||
assert.throws(() => assert.deepStrictEqual(buf2, buf),
|
assert.throws(
|
||||||
re`${buf2} deepStrictEqual ${buf}`);
|
() => assert.deepStrictEqual(buf2, buf),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
message: `${defaultMsgStart}\n\n` +
|
||||||
|
' Buffer [Uint8Array] [\n 120,\n 121,\n 122,\n' +
|
||||||
|
'- 10,\n- prop: 1\n+ 10\n ]'
|
||||||
|
}
|
||||||
|
);
|
||||||
assert.deepEqual(buf2, buf);
|
assert.deepEqual(buf2, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const arr2 = new Uint8Array([120, 121, 122, 10]);
|
const arr2 = new Uint8Array([120, 121, 122, 10]);
|
||||||
arr2.prop = 5;
|
arr2.prop = 5;
|
||||||
assert.throws(() => assert.deepStrictEqual(arr, arr2),
|
assert.throws(
|
||||||
re`${arr} deepStrictEqual ${arr2}`);
|
() => assert.deepStrictEqual(arr, arr2),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
message: `${defaultMsgStart}\n\n` +
|
||||||
|
' Uint8Array [\n 120,\n 121,\n 122,\n' +
|
||||||
|
'- 10\n+ 10,\n+ prop: 5\n ]'
|
||||||
|
}
|
||||||
|
);
|
||||||
assert.deepEqual(arr, arr2);
|
assert.deepEqual(arr, arr2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,10 +91,24 @@ const date2 = new MyDate('2016');
|
|||||||
// but deepStrictEqual checks own properties
|
// but deepStrictEqual checks own properties
|
||||||
assert.deepEqual(date, date2);
|
assert.deepEqual(date, date2);
|
||||||
assert.deepEqual(date2, date);
|
assert.deepEqual(date2, date);
|
||||||
assert.throws(() => assert.deepStrictEqual(date, date2),
|
assert.throws(
|
||||||
re`${date} deepStrictEqual ${date2}`);
|
() => assert.deepStrictEqual(date, date2),
|
||||||
assert.throws(() => assert.deepStrictEqual(date2, date),
|
{
|
||||||
re`${date2} deepStrictEqual ${date}`);
|
code: 'ERR_ASSERTION',
|
||||||
|
message: `${defaultMsgStart}\n\n` +
|
||||||
|
'- 2016-01-01T00:00:00.000Z\n+ 2016-01-01T00:00:00.000Z {\n' +
|
||||||
|
"+ '0': '1'\n+ }"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => assert.deepStrictEqual(date2, date),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
message: `${defaultMsgStart}\n\n` +
|
||||||
|
'- 2016-01-01T00:00:00.000Z {\n' +
|
||||||
|
"- '0': '1'\n- }\n+ 2016-01-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
class MyRegExp extends RegExp {
|
class MyRegExp extends RegExp {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
@ -86,8 +123,14 @@ const re2 = new MyRegExp('test');
|
|||||||
// deepEqual returns true as long as the regexp-specific properties
|
// deepEqual returns true as long as the regexp-specific properties
|
||||||
// are the same, but deepStrictEqual checks all properties
|
// are the same, but deepStrictEqual checks all properties
|
||||||
assert.deepEqual(re1, re2);
|
assert.deepEqual(re1, re2);
|
||||||
assert.throws(() => assert.deepStrictEqual(re1, re2),
|
assert.throws(
|
||||||
re`${re1} deepStrictEqual ${re2}`);
|
() => assert.deepStrictEqual(re1, re2),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
message: `${defaultMsgStart}\n\n` +
|
||||||
|
"- /test/\n+ /test/ {\n+ '0': '1'\n+ }"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// For these weird cases, deepEqual should pass (at least for now),
|
// For these weird cases, deepEqual should pass (at least for now),
|
||||||
// but deepStrictEqual should throw.
|
// but deepStrictEqual should throw.
|
||||||
@ -115,8 +158,10 @@ assert.throws(() => assert.deepStrictEqual(re1, re2),
|
|||||||
for (const b of similar) {
|
for (const b of similar) {
|
||||||
if (a !== b) {
|
if (a !== b) {
|
||||||
assert.deepEqual(a, b);
|
assert.deepEqual(a, b);
|
||||||
assert.throws(() => assert.deepStrictEqual(a, b),
|
assert.throws(
|
||||||
re`${a} deepStrictEqual ${b}`);
|
() => assert.deepStrictEqual(a, b),
|
||||||
|
{ code: 'ERR_ASSERTION' }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,22 +184,30 @@ function assertDeepAndStrictEqual(a, b) {
|
|||||||
|
|
||||||
function assertNotDeepOrStrict(a, b, err) {
|
function assertNotDeepOrStrict(a, b, err) {
|
||||||
assert.throws(() => assert.deepEqual(a, b), err || re`${a} deepEqual ${b}`);
|
assert.throws(() => assert.deepEqual(a, b), err || re`${a} deepEqual ${b}`);
|
||||||
assert.throws(() => assert.deepStrictEqual(a, b), err ||
|
assert.throws(
|
||||||
re`${a} deepStrictEqual ${b}`);
|
() => assert.deepStrictEqual(a, b),
|
||||||
|
err || { code: 'ERR_ASSERTION' }
|
||||||
|
);
|
||||||
|
|
||||||
assert.throws(() => assert.deepEqual(b, a), err || re`${b} deepEqual ${a}`);
|
assert.throws(() => assert.deepEqual(b, a), err || re`${b} deepEqual ${a}`);
|
||||||
assert.throws(() => assert.deepStrictEqual(b, a), err ||
|
assert.throws(
|
||||||
re`${b} deepStrictEqual ${a}`);
|
() => assert.deepStrictEqual(b, a),
|
||||||
|
err || { code: 'ERR_ASSERTION' }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertOnlyDeepEqual(a, b, err) {
|
function assertOnlyDeepEqual(a, b, err) {
|
||||||
assert.deepEqual(a, b);
|
assert.deepEqual(a, b);
|
||||||
assert.throws(() => assert.deepStrictEqual(a, b), err ||
|
assert.throws(
|
||||||
re`${a} deepStrictEqual ${b}`);
|
() => assert.deepStrictEqual(a, b),
|
||||||
|
err || { code: 'ERR_ASSERTION' }
|
||||||
|
);
|
||||||
|
|
||||||
assert.deepEqual(b, a);
|
assert.deepEqual(b, a);
|
||||||
assert.throws(() => assert.deepStrictEqual(b, a), err ||
|
assert.throws(
|
||||||
re`${b} deepStrictEqual ${a}`);
|
() => assert.deepStrictEqual(b, a),
|
||||||
|
err || { code: 'ERR_ASSERTION' }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// es6 Maps and Sets
|
// es6 Maps and Sets
|
||||||
@ -352,7 +405,8 @@ assertOnlyDeepEqual(
|
|||||||
assertDeepAndStrictEqual(b, c);
|
assertDeepAndStrictEqual(b, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GH-7178. Ensure reflexivity of deepEqual with `arguments` objects.
|
// https://github.com/nodejs/node-v0.x-archive/pull/7178
|
||||||
|
// Ensure reflexivity of deepEqual with `arguments` objects.
|
||||||
{
|
{
|
||||||
const args = (function() { return arguments; })();
|
const args = (function() { return arguments; })();
|
||||||
assertNotDeepOrStrict([], args);
|
assertNotDeepOrStrict([], args);
|
||||||
@ -434,8 +488,14 @@ assertOnlyDeepEqual(
|
|||||||
const map1 = new Map([[1, 1]]);
|
const map1 = new Map([[1, 1]]);
|
||||||
const map2 = new Map([[1, '1']]);
|
const map2 = new Map([[1, '1']]);
|
||||||
assert.deepEqual(map1, map2);
|
assert.deepEqual(map1, map2);
|
||||||
assert.throws(() => assert.deepStrictEqual(map1, map2),
|
assert.throws(
|
||||||
re`${map1} deepStrictEqual ${map2}`);
|
() => assert.deepStrictEqual(map1, map2),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
message: `${defaultMsgStart}\n\n` +
|
||||||
|
" Map {\n- 1 => 1\n+ 1 => '1'\n }"
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -682,8 +742,11 @@ assert.throws(
|
|||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assert.notDeepStrictEqual(new Date(2000, 3, 14), new Date(2000, 3, 14)),
|
() => assert.notDeepStrictEqual(new Date(2000, 3, 14), new Date(2000, 3, 14)),
|
||||||
AssertionError,
|
{
|
||||||
'notDeepStrictEqual(new Date(2000, 3, 14), new Date(2000, 3, 14))'
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
|
message: 'Identical input passed to notDeepStrictEqual: ' +
|
||||||
|
util.inspect(new Date(2000, 3, 14))
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.notDeepStrictEqual(new Date(), new Date(2000, 3, 14));
|
assert.notDeepStrictEqual(new Date(), new Date(2000, 3, 14));
|
||||||
@ -698,35 +761,35 @@ assert.throws(
|
|||||||
{
|
{
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
message: '/ab/ deepStrictEqual /a/'
|
message: `${defaultMsgStart}\n\n- /ab/\n+ /a/`
|
||||||
});
|
});
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assert.deepStrictEqual(/a/g, /a/),
|
() => assert.deepStrictEqual(/a/g, /a/),
|
||||||
{
|
{
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
message: '/a/g deepStrictEqual /a/'
|
message: `${defaultMsgStart}\n\n- /a/g\n+ /a/`
|
||||||
});
|
});
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assert.deepStrictEqual(/a/i, /a/),
|
() => assert.deepStrictEqual(/a/i, /a/),
|
||||||
{
|
{
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
message: '/a/i deepStrictEqual /a/'
|
message: `${defaultMsgStart}\n\n- /a/i\n+ /a/`
|
||||||
});
|
});
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assert.deepStrictEqual(/a/m, /a/),
|
() => assert.deepStrictEqual(/a/m, /a/),
|
||||||
{
|
{
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
message: '/a/m deepStrictEqual /a/'
|
message: `${defaultMsgStart}\n\n- /a/m\n+ /a/`
|
||||||
});
|
});
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assert.deepStrictEqual(/a/igm, /a/im),
|
() => assert.deepStrictEqual(/a/igm, /a/im),
|
||||||
{
|
{
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
message: '/a/gim deepStrictEqual /a/im'
|
message: `${defaultMsgStart}\n\n- /a/gim\n+ /a/im`
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -735,17 +798,15 @@ assert.throws(
|
|||||||
assert.deepStrictEqual(re1, /a/);
|
assert.deepStrictEqual(re1, /a/);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.throws(() => assert.deepStrictEqual(4, '4'),
|
assert.throws(
|
||||||
AssertionError,
|
() => assert.deepStrictEqual(4, '4'),
|
||||||
'deepStrictEqual(4, \'4\')');
|
{ message: `${defaultMsgStart}\n\n- 4\n+ '4'` }
|
||||||
|
);
|
||||||
|
|
||||||
assert.throws(() => assert.deepStrictEqual(true, 1),
|
assert.throws(
|
||||||
AssertionError,
|
() => assert.deepStrictEqual(true, 1),
|
||||||
'deepStrictEqual(true, 1)');
|
{ message: `${defaultMsgStart}\n\n- true\n+ 1` }
|
||||||
|
);
|
||||||
assert.throws(() => assert.deepStrictEqual(4, '5'),
|
|
||||||
AssertionError,
|
|
||||||
'deepStrictEqual(4, \'5\')');
|
|
||||||
|
|
||||||
// Having the same number of owned properties && the same set of keys.
|
// Having the same number of owned properties && the same set of keys.
|
||||||
assert.deepStrictEqual({ a: 4 }, { a: 4 });
|
assert.deepStrictEqual({ a: 4 }, { a: 4 });
|
||||||
@ -754,20 +815,22 @@ assert.throws(() => assert.deepStrictEqual([4], ['4']),
|
|||||||
{
|
{
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
message: "[ 4 ] deepStrictEqual [ '4' ]"
|
message: `${defaultMsgStart}\n\n [\n- 4\n+ '4'\n ]`
|
||||||
});
|
|
||||||
assert.throws(() => assert.deepStrictEqual({ a: 4 }, { a: 4, b: true }),
|
|
||||||
{
|
|
||||||
code: 'ERR_ASSERTION',
|
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
|
||||||
message: '{ a: 4 } deepStrictEqual { a: 4, b: true }'
|
|
||||||
});
|
|
||||||
assert.throws(() => assert.deepStrictEqual(['a'], { 0: 'a' }),
|
|
||||||
{
|
|
||||||
code: 'ERR_ASSERTION',
|
|
||||||
name: 'AssertionError [ERR_ASSERTION]',
|
|
||||||
message: "[ 'a' ] deepStrictEqual { '0': 'a' }"
|
|
||||||
});
|
});
|
||||||
|
assert.throws(
|
||||||
|
() => assert.deepStrictEqual({ a: 4 }, { a: 4, b: true }),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
|
message: `${defaultMsgStart}\n\n {\n- a: 4\n+ a: 4,\n+ b: true\n }`
|
||||||
|
});
|
||||||
|
assert.throws(
|
||||||
|
() => assert.deepStrictEqual(['a'], { 0: 'a' }),
|
||||||
|
{
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
|
message: `${defaultMsgStart}\n\n- [\n- 'a'\n- ]\n+ {\n+ '0': 'a'\n+ }`
|
||||||
|
});
|
||||||
|
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
@ -826,3 +889,35 @@ assert.throws(() => assert.deepStrictEqual(new String('a'), { 0: 'a' }),
|
|||||||
assert.throws(() => assert.deepStrictEqual(new Number(1), {}), AssertionError);
|
assert.throws(() => assert.deepStrictEqual(new Number(1), {}), AssertionError);
|
||||||
assert.throws(() => assert.deepStrictEqual(new Boolean(true), {}),
|
assert.throws(() => assert.deepStrictEqual(new Boolean(true), {}),
|
||||||
AssertionError);
|
AssertionError);
|
||||||
|
|
||||||
|
// Check extra properties on errors.
|
||||||
|
{
|
||||||
|
const a = new TypeError('foo');
|
||||||
|
const b = new TypeError('foo');
|
||||||
|
a.foo = 'bar';
|
||||||
|
b.foo = 'baz';
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => assert.deepStrictEqual(a, b),
|
||||||
|
{
|
||||||
|
message: `${defaultMsgStart}\n\n` +
|
||||||
|
' [TypeError: foo] {\n- foo: \'bar\'\n+ foo: \'baz\'\n }'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check proxies.
|
||||||
|
{
|
||||||
|
// TODO(BridgeAR): Check if it would not be better to detect proxies instead
|
||||||
|
// of just using the proxy value.
|
||||||
|
const arrProxy = new Proxy([1, 2], {});
|
||||||
|
assert.deepStrictEqual(arrProxy, [1, 2]);
|
||||||
|
const tmp = util.inspect.defaultOptions;
|
||||||
|
util.inspect.defaultOptions = { showProxy: true };
|
||||||
|
assert.throws(
|
||||||
|
() => assert.deepStrictEqual(arrProxy, [1, 2, 3]),
|
||||||
|
{ message: `${defaultMsgStart}\n\n` +
|
||||||
|
' [\n 1,\n- 2\n+ 2,\n+ 3\n ]' }
|
||||||
|
);
|
||||||
|
util.inspect.defaultOptions = tmp;
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ const { writeFileSync, unlinkSync } = require('fs');
|
|||||||
const { inspect } = require('util');
|
const { inspect } = require('util');
|
||||||
const a = assert;
|
const a = assert;
|
||||||
|
|
||||||
const start = 'Input A expected to deepStrictEqual input B:';
|
const start = 'Input A expected to strictly deep-equal input B:';
|
||||||
const actExp = '+ expected - actual';
|
const actExp = '+ expected - actual';
|
||||||
|
|
||||||
assert.ok(a.AssertionError.prototype instanceof Error,
|
assert.ok(a.AssertionError.prototype instanceof Error,
|
||||||
@ -67,8 +67,21 @@ assert.throws(() => a.strictEqual(2, '2'),
|
|||||||
assert.throws(() => a.strictEqual(null, undefined),
|
assert.throws(() => a.strictEqual(null, undefined),
|
||||||
a.AssertionError, 'strictEqual(null, undefined)');
|
a.AssertionError, 'strictEqual(null, undefined)');
|
||||||
|
|
||||||
assert.throws(() => a.notStrictEqual(2, 2),
|
assert.throws(
|
||||||
a.AssertionError, 'notStrictEqual(2, 2)');
|
() => a.notStrictEqual(2, 2),
|
||||||
|
{
|
||||||
|
message: 'Identical input passed to notStrictEqual: 2',
|
||||||
|
name: 'AssertionError [ERR_ASSERTION]'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => a.notStrictEqual('a '.repeat(30), 'a '.repeat(30)),
|
||||||
|
{
|
||||||
|
message: `Identical input passed to notStrictEqual: '${'a '.repeat(30)}'`,
|
||||||
|
name: 'AssertionError [ERR_ASSERTION]'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
a.notStrictEqual(2, '2');
|
a.notStrictEqual(2, '2');
|
||||||
|
|
||||||
@ -245,8 +258,11 @@ function testAssertionMessage(actual, expected) {
|
|||||||
try {
|
try {
|
||||||
assert.strictEqual(actual, '');
|
assert.strictEqual(actual, '');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert.strictEqual(e.message,
|
assert.strictEqual(
|
||||||
[expected, 'strictEqual', '\'\''].join(' '));
|
e.message,
|
||||||
|
'Input A expected to strictly equal input B:\n+ expected - actual\n\n' +
|
||||||
|
`- ${expected}\n+ ''`
|
||||||
|
);
|
||||||
assert.ok(e.generatedMessage, 'Message not marked as generated');
|
assert.ok(e.generatedMessage, 'Message not marked as generated');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,29 +279,34 @@ testAssertionMessage(-Infinity, '-Infinity');
|
|||||||
testAssertionMessage('', '""');
|
testAssertionMessage('', '""');
|
||||||
testAssertionMessage('foo', '\'foo\'');
|
testAssertionMessage('foo', '\'foo\'');
|
||||||
testAssertionMessage([], '[]');
|
testAssertionMessage([], '[]');
|
||||||
testAssertionMessage([1, 2, 3], '[ 1, 2, 3 ]');
|
testAssertionMessage([1, 2, 3], '[\n- 1,\n- 2,\n- 3\n- ]');
|
||||||
testAssertionMessage(/a/, '/a/');
|
testAssertionMessage(/a/, '/a/');
|
||||||
testAssertionMessage(/abc/gim, '/abc/gim');
|
testAssertionMessage(/abc/gim, '/abc/gim');
|
||||||
testAssertionMessage(function f() {}, '[Function: f]');
|
testAssertionMessage(function f() {}, '[Function: f]');
|
||||||
testAssertionMessage(function() {}, '[Function]');
|
testAssertionMessage(function() {}, '[Function]');
|
||||||
testAssertionMessage({}, '{}');
|
testAssertionMessage({}, '{}');
|
||||||
testAssertionMessage(circular, '{ y: 1, x: [Circular] }');
|
testAssertionMessage(circular, '{\n- y: 1,\n- x: [Circular]\n- }');
|
||||||
testAssertionMessage({ a: undefined, b: null }, '{ a: undefined, b: null }');
|
testAssertionMessage({ a: undefined, b: null },
|
||||||
|
'{\n- a: undefined,\n- b: null\n- }');
|
||||||
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
|
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
|
||||||
'{ a: NaN, b: Infinity, c: -Infinity }');
|
'{\n- a: NaN,\n- b: Infinity,\n- c: -Infinity\n- }');
|
||||||
|
|
||||||
// https://github.com/nodejs/node-v0.x-archive/issues/5292
|
// https://github.com/nodejs/node-v0.x-archive/issues/5292
|
||||||
try {
|
try {
|
||||||
assert.strictEqual(1, 2);
|
assert.strictEqual(1, 2);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert.strictEqual(e.message.split('\n')[0], '1 strictEqual 2');
|
assert.strictEqual(
|
||||||
|
e.message,
|
||||||
|
'Input A expected to strictly equal input B:\n' +
|
||||||
|
'+ expected - actual\n\n- 1\n+ 2'
|
||||||
|
);
|
||||||
assert.ok(e.generatedMessage, 'Message not marked as generated');
|
assert.ok(e.generatedMessage, 'Message not marked as generated');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assert.strictEqual(1, 2, 'oh no');
|
assert.strictEqual(1, 2, 'oh no');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert.strictEqual(e.message.split('\n')[0], 'oh no');
|
assert.strictEqual(e.message, 'oh no');
|
||||||
assert.strictEqual(e.generatedMessage, false,
|
assert.strictEqual(e.generatedMessage, false,
|
||||||
'Message incorrectly marked as generated');
|
'Message incorrectly marked as generated');
|
||||||
}
|
}
|
||||||
@ -361,10 +382,11 @@ assert.throws(() => { throw new Error(); }, (err) => err instanceof Error);
|
|||||||
// Long values should be truncated for display.
|
// Long values should be truncated for display.
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
assert.strictEqual('A'.repeat(1000), '');
|
assert.strictEqual('A'.repeat(1000), '');
|
||||||
}, common.expectsError({
|
}, {
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
message: /^'A{124}\.\.\. strictEqual ''$/
|
message: 'Input A expected to strictly equal input B:\n' +
|
||||||
}));
|
`+ expected - actual\n\n- '${'A'.repeat(1000)}'\n+ ''`
|
||||||
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
// Bad args to AssertionError constructor should throw TypeError.
|
// Bad args to AssertionError constructor should throw TypeError.
|
||||||
@ -381,12 +403,13 @@ assert.throws(() => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
common.expectsError(
|
assert.throws(
|
||||||
() => assert.strictEqual(new Error('foo'), new Error('foobar')),
|
() => assert.strictEqual(new Error('foo'), new Error('foobar')),
|
||||||
{
|
{
|
||||||
code: 'ERR_ASSERTION',
|
code: 'ERR_ASSERTION',
|
||||||
type: assert.AssertionError,
|
name: 'AssertionError [ERR_ASSERTION]',
|
||||||
message: /^'Error: foo' strictEqual 'Error: foobar'$/
|
message: 'Input A expected to strictly equal input B:\n' +
|
||||||
|
'+ expected - actual\n\n- [Error: foo]\n+ [Error: foobar]'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -576,14 +599,15 @@ common.expectsError(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// notDeepEqual tests
|
// notDeepEqual tests
|
||||||
message = 'Identical input passed to notDeepStrictEqual:\n[\n 1\n]';
|
message = 'Identical input passed to notDeepStrictEqual:\n\n' +
|
||||||
|
' [\n 1\n ]\n';
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assert.notDeepEqual([1], [1]),
|
() => assert.notDeepEqual([1], [1]),
|
||||||
{ message });
|
{ message });
|
||||||
|
|
||||||
message = 'Identical input passed to notDeepStrictEqual:' +
|
message = 'Identical input passed to notDeepStrictEqual:' +
|
||||||
`\n[${'\n 1,'.repeat(18)}\n...`;
|
`\n\n [${'\n 1,'.repeat(25)}\n ...\n`;
|
||||||
const data = Array(21).fill(1);
|
const data = Array(31).fill(1);
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assert.notDeepEqual(data, data),
|
() => assert.notDeepEqual(data, data),
|
||||||
{ message });
|
{ message });
|
||||||
@ -873,3 +897,21 @@ assert.throws(
|
|||||||
// Should not throw.
|
// Should not throw.
|
||||||
// eslint-disable-next-line no-restricted-syntax, no-throw-literal
|
// eslint-disable-next-line no-restricted-syntax, no-throw-literal
|
||||||
assert.throws(() => { throw null; }, 'foo');
|
assert.throws(() => { throw null; }, 'foo');
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => assert.strictEqual([], []),
|
||||||
|
{
|
||||||
|
message: 'Input object identical but not reference equal:\n\n []\n'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
const args = (function() { return arguments; })('a');
|
||||||
|
assert.throws(
|
||||||
|
() => assert.strictEqual(args, { 0: 'a' }),
|
||||||
|
{
|
||||||
|
message: 'Input A expected to strictly equal input B:\n+ expected' +
|
||||||
|
" - actual\n\n- [Arguments] {\n+ {\n '0': 'a'\n }"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ try {
|
|||||||
process.env.COLORTERM = '1';
|
process.env.COLORTERM = '1';
|
||||||
assert.deepStrictEqual([1, 2], [2, 2]);
|
assert.deepStrictEqual([1, 2], [2, 2]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const expected = 'Input A expected to deepStrictEqual input B:\n' +
|
const expected = 'Input A expected to strictly deep-equal input B:\n' +
|
||||||
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m\n\n' +
|
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m\n\n' +
|
||||||
' [\n' +
|
' [\n' +
|
||||||
'\u001b[31m-\u001b[39m 1,\n' +
|
'\u001b[31m-\u001b[39m 1,\n' +
|
||||||
|
Loading…
x
Reference in New Issue
Block a user