From ae64a17da27b95fb6928df64d3f38f790c4d3f61 Mon Sep 17 00:00:00 2001 From: Edy Silva Date: Fri, 21 Mar 2025 16:49:26 -0300 Subject: [PATCH 1/2] wip on asymetric matchers --- lib/assert.js | 69 ++++++++++++++++++- lib/internal/assert/assertion_error.js | 12 +++- lib/internal/util/comparisons.js | 5 ++ .../test-assert-asymetric-matchers.mjs | 41 +++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-assert-asymetric-matchers.mjs diff --git a/lib/assert.js b/lib/assert.js index 657ac6225d9833..c89451d6c47e49 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -73,6 +73,10 @@ function lazyLoadComparison() { isPartialStrictEqual = comparison.isPartialStrictEqual; } +function compareAsymetric(actual, expected) { + return (expected?.type === Symbol.for('blah') && expected.match(actual)); +} + let warned = false; // The assert module provides functions that throw @@ -313,7 +317,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 +328,69 @@ 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()}>`; } + }; + }, + typeof: function t(sample) { + return { + type: Symbol.for('blah'), + match: function match(other) { + if (sample === String) { + return typeof other === 'string'; + } + + if (sample === Number) { + return typeof other === 'number'; + } + + if (sample === Object) { + return typeof other === '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 `typeof<${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..1e96cfa57ffd5f 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); 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..3d5688aea6b636 --- /dev/null +++ b/test/parallel/test-assert-asymetric-matchers.mjs @@ -0,0 +1,41 @@ +import assert from 'node:assert'; + +assert.equal('Node.js', assert.asymetric.any(String)); +// assert.strictEqual('Node.js', assert.asymetric.any(String)); + +// assert.throws(() => { +// assert.deepStrictEqual( +// { +// name: 'foo', +// }, +// { +// name: assert.asymetric.any(Number), +// } +// ); +// }); + +// assert.deepStrictEqual({ +// a: { +// b: { +// c: 42 +// } +// } +// }, { +// a: { +// b: { +// c: assert.asymetric.any(Number) +// } +// } +// }); + +// assert.deepStrictEqual({ +// a: { +// b: { +// c: 42 +// } +// } +// }, { +// a: { +// b: assert.asymetric.any(Object) +// } +// }); From 3b35067ed52bcb4fd2f66446f70a67b69ae63034 Mon Sep 17 00:00:00 2001 From: Edy Silva Date: Fri, 21 Mar 2025 21:03:59 -0300 Subject: [PATCH 2/2] put this on hold --- lib/assert.js | 46 +++++----------- lib/internal/assert/assertion_error.js | 15 +++++ .../test-assert-asymetric-matchers.mjs | 55 ++++++++++--------- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index c89451d6c47e49..e77ce6d66f2986 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -74,7 +74,19 @@ function lazyLoadComparison() { } function compareAsymetric(actual, expected) { - return (expected?.type === Symbol.for('blah') && expected.match(actual)); + 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; @@ -175,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, @@ -358,36 +370,6 @@ assert.asymetric = { toString() { return `Any<${this.getExpectedType()}>`; } }; - }, - typeof: function t(sample) { - return { - type: Symbol.for('blah'), - match: function match(other) { - if (sample === String) { - return typeof other === 'string'; - } - - if (sample === Number) { - return typeof other === 'number'; - } - - if (sample === Object) { - return typeof other === '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 `typeof<${this.getExpectedType()}>`; } - }; } }; diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index 1e96cfa57ffd5f..0a82c15f6ee04b 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -252,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'); @@ -268,6 +281,8 @@ class AssertionError extends Error { expected, } = options; + expected = transformAsymetric(expected); + const limit = Error.stackTraceLimit; if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; diff --git a/test/parallel/test-assert-asymetric-matchers.mjs b/test/parallel/test-assert-asymetric-matchers.mjs index 3d5688aea6b636..7d4618a6714101 100644 --- a/test/parallel/test-assert-asymetric-matchers.mjs +++ b/test/parallel/test-assert-asymetric-matchers.mjs @@ -1,8 +1,36 @@ import assert from 'node:assert'; assert.equal('Node.js', assert.asymetric.any(String)); -// assert.strictEqual('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( // { @@ -14,28 +42,3 @@ assert.equal('Node.js', assert.asymetric.any(String)); // ); // }); -// assert.deepStrictEqual({ -// a: { -// b: { -// c: 42 -// } -// } -// }, { -// a: { -// b: { -// c: assert.asymetric.any(Number) -// } -// } -// }); - -// assert.deepStrictEqual({ -// a: { -// b: { -// c: 42 -// } -// } -// }, { -// a: { -// b: assert.asymetric.any(Object) -// } -// });