diff --git a/lib/assert.js b/lib/assert.js index 657ac6225d9833..e77ce6d66f2986 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -73,6 +73,22 @@ function lazyLoadComparison() { isPartialStrictEqual = comparison.isPartialStrictEqual; } +function compareAsymetric(actual, expected) { + const isAsymetric = expected?.type === Symbol.for('blah'); + + if (!isAsymetric && typeof expected === 'object') { + for (const key of ObjectKeys(expected)) { + if (!compareAsymetric(actual[key], expected[key])) { + return false; + } + } + + return true; + } + + return (isAsymetric && expected.match(actual)); +} + let warned = false; // The assert module provides functions that throw @@ -171,7 +187,7 @@ assert.equal = function equal(actual, expected, message) { throw new ERR_MISSING_ARGS('actual', 'expected'); } // eslint-disable-next-line eqeqeq - if (actual != expected && (!NumberIsNaN(actual) || !NumberIsNaN(expected))) { + if (!compareAsymetric(actual, expected) && (actual != expected && (!NumberIsNaN(actual) || !NumberIsNaN(expected)))) { innerFail({ actual, expected, @@ -313,7 +329,7 @@ assert.strictEqual = function strictEqual(actual, expected, message) { if (arguments.length < 2) { throw new ERR_MISSING_ARGS('actual', 'expected'); } - if (!ObjectIs(actual, expected)) { + if (!ObjectIs(actual, expected) || !compareAsymetric(actual, expected)) { innerFail({ actual, expected, @@ -324,6 +340,39 @@ assert.strictEqual = function strictEqual(actual, expected, message) { } }; +assert.asymetric = { + any: function any(sample) { + return { + type: Symbol.for('blah'), + match: function match(other) { + if (sample === String) { + return typeof other === 'string' || other instanceof String; + } + + if (sample === Number) { + return typeof other === 'number' || other instanceof Number; + } + + if (sample === Object) { + return typeof other === 'number' || other instanceof Object; + } + + return other instanceof sample; + }, + getExpectedType() { + if (sample === String) return 'String'; + if (sample === Number) return 'Number'; + if (sample === Object) return 'Object'; + + https://github.com/jestjs/jest/blob/611d1a4ba0008d67b5dcda485177f0813b2b573e/packages/expect/src/asymmetricMatchers.ts#L27 + return sample.toString(); + }, + + toString() { return `Any<${this.getExpectedType()}>`; } + }; + } +}; + /** * The strict non-equivalence assertion tests for any strict inequality. * @param {any} actual diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index d654ca5038bbab..0a82c15f6ee04b 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -181,10 +181,20 @@ function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) { function createErrDiff(actual, expected, operator, customMessage) { operator = checkOperator(actual, expected, operator); + const newExpected = Object.keys(expected).reduce((acc, key) => { + const val = expected[key]; + if (val?.type === Symbol.for('blah')) { + acc[key] = val.toString(); + return acc; + } + acc[key] = expected[key]; + return acc; + }, {}); + let skipped = false; let message = ''; const inspectedActual = inspectValue(actual); - const inspectedExpected = inspectValue(expected); + const inspectedExpected = inspectValue(newExpected); const inspectedSplitActual = StringPrototypeSplit(inspectedActual, '\n'); const inspectedSplitExpected = StringPrototypeSplit(inspectedExpected, '\n'); const showSimpleDiff = isSimpleDiff(actual, inspectedSplitActual, expected, inspectedSplitExpected); @@ -242,6 +252,19 @@ function addEllipsis(string) { return string; } +function transformAsymetric(value) { + let newValue = value; + if (value?.type === Symbol.for('blah')) { + newValue = value.toString(); + } else if (typeof value === 'object') { + for (const key in value) { + newValue[key] = transformAsymetric(value[key]); + } + } + + return newValue; +} + class AssertionError extends Error { constructor(options) { validateObject(options, 'options'); @@ -258,6 +281,8 @@ class AssertionError extends Error { expected, } = options; + expected = transformAsymetric(expected); + const limit = Error.stackTraceLimit; if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index 7eb9c72119eb92..8042c75cf808a5 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -182,6 +182,11 @@ function innerDeepEqual(val1, val2, mode, memos) { return val1 !== 0 || ObjectIs(val1, val2) || mode === kLoose; } + // TODO: prop "type" has to be a symbol itself + if (val2?.type === Symbol.for('blah')) { + return val2.match(val1); + } + // Check more closely if val1 and val2 are equal. if (mode !== kLoose) { if (typeof val1 === 'number') { diff --git a/test/parallel/test-assert-asymetric-matchers.mjs b/test/parallel/test-assert-asymetric-matchers.mjs new file mode 100644 index 00000000000000..7d4618a6714101 --- /dev/null +++ b/test/parallel/test-assert-asymetric-matchers.mjs @@ -0,0 +1,44 @@ +import assert from 'node:assert'; + +assert.equal('Node.js', assert.asymetric.any(String)); +assert.equal(new String('Node.js'), assert.asymetric.any(String)); +assert.equal(123, assert.asymetric.any(Number)); +assert.equal(4.56, assert.asymetric.any(Number)); +assert.equal(new Number(123), assert.asymetric.any(Number)); +assert.equal({name: 'foo'}, { name: assert.asymetric.any(Number) }); +assert.deepStrictEqual({ + a: { + b: { + c: 42, + d: 'edy', + e: { + f: 'foo', + g: 'bar' + } + } + } +}, { + a: { + b: { + c: assert.asymetric.any(Number), + d: assert.asymetric.any(String), + e: assert.asymetric.any(Object) + } + } +}); + +// assert.equal(123, assert.asymetric.any(String)); +// assert.equal('123', assert.asymetric.any(Number)); +// assert.strictEqual({name: 'foo'}, { name: assert.asymetric.any(String) }); +// assert.equal({name: 'bar'}, { name: assert.asymetric.any(Number) }); +// assert.throws(() => { +// assert.deepStrictEqual( +// { +// name: 'foo', +// }, +// { +// name: assert.asymetric.any(Number), +// } +// ); +// }); +