| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -227,11 +227,20 @@ The `Assert` class allows creating independent assertion instances with custom o | |||
| 227 | 227 | ||
| 228 | 228 | ### `new assert.Assert([options])` | |
| 229 | 229 | ||
| 230 | + <!-- YAML | ||
| 231 | + changes: | ||
| 232 | + - version: REPLACEME | ||
| 233 | + pr-url: https://github.com/nodejs/node/pull/59762 | ||
| 234 | + description: Added `skipPrototype` option. | ||
| 235 | + --> | ||
| 236 | + | ||
| 230 | 237 | * `options` {Object} | |
| 231 | 238 | * `diff` {string} If set to `'full'`, shows the full diff in assertion errors. Defaults to `'simple'`. | |
| 232 | 239 | Accepted values: `'simple'`, `'full'`. | |
| 233 | 240 | * `strict` {boolean} If set to `true`, non-strict methods behave like their | |
| 234 | 241 | corresponding strict methods. Defaults to `true`. | |
| 242 | + * `skipPrototype` {boolean} If set to `true`, skips prototype and constructor | ||
| 243 | + comparison in deep equality checks. Defaults to `false`. | ||
| 235 | 244 | ||
| 236 | 245 | Creates a new assertion instance. The `diff` option controls the verbosity of diffs in assertion error messages. | |
| 237 | 246 | ||
@@ -243,7 +252,8 @@ assertInstance.deepStrictEqual({ a: 1 }, { a: 2 }); | |||
| 243 | 252 | ``` | |
| 244 | 253 | ||
| 245 | 254 | **Important**: When destructuring assertion methods from an `Assert` instance, | |
| 246 | - the methods lose their connection to the instance's configuration options (such as `diff` and `strict` settings). | ||
| 255 | + the methods lose their connection to the instance's configuration options (such | ||
| 256 | + as `diff`, `strict`, and `skipPrototype` settings). | ||
| 247 | 257 | The destructured methods will fall back to default behavior instead. | |
| 248 | 258 | ||
| 249 | 259 | ```js | |
@@ -257,6 +267,33 @@ const { strictEqual } = myAssert; | |||
| 257 | 267 | strictEqual({ a: 1 }, { b: { c: 1 } }); | |
| 258 | 268 | ``` | |
| 259 | 269 | ||
| 270 | + The `skipPrototype` option affects all deep equality methods: | ||
| 271 | + | ||
| 272 | + ```js | ||
| 273 | + class Foo { | ||
| 274 | + constructor(a) { | ||
| 275 | + this.a = a; | ||
| 276 | + } | ||
| 277 | + } | ||
| 278 | + | ||
| 279 | + class Bar { | ||
| 280 | + constructor(a) { | ||
| 281 | + this.a = a; | ||
| 282 | + } | ||
| 283 | + } | ||
| 284 | + | ||
| 285 | + const foo = new Foo(1); | ||
| 286 | + const bar = new Bar(1); | ||
| 287 | + | ||
| 288 | + // Default behavior - fails due to different constructors | ||
| 289 | + const assert1 = new Assert(); | ||
| 290 | + assert1.deepStrictEqual(foo, bar); // AssertionError | ||
| 291 | + | ||
| 292 | + // Skip prototype comparison - passes if properties are equal | ||
| 293 | + const assert2 = new Assert({ skipPrototype: true }); | ||
| 294 | + assert2.deepStrictEqual(foo, bar); // OK | ||
| 295 | + ``` | ||
| 296 | + | ||
| 260 | 297 | When destructured, methods lose access to the instance's `this` context and revert to default assertion behavior | |
| 261 | 298 | (diff: 'simple', non-strict mode). | |
| 262 | 299 | To maintain custom options when using destructured methods, avoid | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1551,19 +1551,56 @@ inspect.defaultOptions.maxArrayLength = null; | |||
| 1551 | 1551 | console.log(arr); // logs the full array | |
| 1552 | 1552 | ``` | |
| 1553 | 1553 | ||
| 1554 | - ## `util.isDeepStrictEqual(val1, val2)` | ||
| 1554 | + ## `util.isDeepStrictEqual(val1, val2[, options])` | ||
| 1555 | 1555 | ||
| 1556 | 1556 | <!-- YAML | |
| 1557 | 1557 | added: v9.0.0 | |
| 1558 | + changes: | ||
| 1559 | + - version: REPLACEME | ||
| 1560 | + pr-url: https://github.com/nodejs/node/pull/59762 | ||
| 1561 | + description: Added `options` parameter to allow skipping prototype comparison. | ||
| 1558 | 1562 | --> | |
| 1559 | 1563 | ||
| 1560 | 1564 | * `val1` {any} | |
| 1561 | 1565 | * `val2` {any} | |
| 1566 | + * `skipPrototype` {boolean} If `true`, prototype and constructor | ||
| 1567 | + comparison is skipped during deep strict equality check. **Default:** `false`. | ||
| 1562 | 1568 | * Returns: {boolean} | |
| 1563 | 1569 | ||
| 1564 | 1570 | Returns `true` if there is deep strict equality between `val1` and `val2`. | |
| 1565 | 1571 | Otherwise, returns `false`. | |
| 1566 | 1572 | ||
| 1573 | + By default, deep strict equality includes comparison of object prototypes and | ||
| 1574 | + constructors. When `skipPrototype` is `true`, objects with | ||
| 1575 | + different prototypes or constructors can still be considered equal if their | ||
| 1576 | + enumerable properties are deeply strictly equal. | ||
| 1577 | + | ||
| 1578 | + ```js | ||
| 1579 | + const util = require('node:util'); | ||
| 1580 | + | ||
| 1581 | + class Foo { | ||
| 1582 | + constructor(a) { | ||
| 1583 | + this.a = a; | ||
| 1584 | + } | ||
| 1585 | + } | ||
| 1586 | + | ||
| 1587 | + class Bar { | ||
| 1588 | + constructor(a) { | ||
| 1589 | + this.a = a; | ||
| 1590 | + } | ||
| 1591 | + } | ||
| 1592 | + | ||
| 1593 | + const foo = new Foo(1); | ||
| 1594 | + const bar = new Bar(1); | ||
| 1595 | + | ||
| 1596 | + // Different constructors, same properties | ||
| 1597 | + console.log(util.isDeepStrictEqual(foo, bar)); | ||
| 1598 | + // false | ||
| 1599 | + | ||
| 1600 | + console.log(util.isDeepStrictEqual(foo, bar, true)); | ||
| 1601 | + // true | ||
| 1602 | + ``` | ||
| 1603 | + | ||
| 1567 | 1604 | See [`assert.deepStrictEqual()`][] for more information about deep strict | |
| 1568 | 1605 | equality. | |
| 1569 | 1606 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -96,6 +96,8 @@ const NO_EXCEPTION_SENTINEL = {}; | |||
| 96 | 96 | * @property {'full'|'simple'} [diff='simple'] - If set to 'full', shows the full diff in assertion errors. | |
| 97 | 97 | * @property {boolean} [strict=true] - If set to true, non-strict methods behave like their corresponding | |
| 98 | 98 | * strict methods. | |
| 99 | + * @property {boolean} [skipPrototype=false] - If set to true, skips comparing prototypes | ||
| 100 | + * in deep equality checks. | ||
| 99 | 101 | */ | |
| 100 | 102 | ||
| 101 | 103 | /** | |
@@ -108,7 +110,7 @@ function Assert(options) { | |||
| 108 | 110 | throw new ERR_CONSTRUCT_CALL_REQUIRED('Assert'); | |
| 109 | 111 | } | |
| 110 | 112 | ||
| 111 | - options = ObjectAssign({ __proto__: null, strict: true }, options); | ||
| 113 | + options = ObjectAssign({ __proto__: null, strict: true, skipPrototype: false }, options); | ||
| 112 | 114 | ||
| 113 | 115 | const allowedDiffs = ['simple', 'full']; | |
| 114 | 116 | if (options.diff !== undefined) { | |
@@ -336,7 +338,7 @@ Assert.prototype.deepStrictEqual = function deepStrictEqual(actual, expected, me | |||
| 336 | 338 | throw new ERR_MISSING_ARGS('actual', 'expected'); | |
| 337 | 339 | } | |
| 338 | 340 | if (isDeepEqual === undefined) lazyLoadComparison(); | |
| 339 | - if (!isDeepStrictEqual(actual, expected)) { | ||
| 341 | + if (!isDeepStrictEqual(actual, expected, this?.[kOptions]?.skipPrototype)) { | ||
| 340 | 342 | innerFail({ | |
| 341 | 343 | actual, | |
| 342 | 344 | expected, | |
@@ -362,7 +364,7 @@ function notDeepStrictEqual(actual, expected, message) { | |||
| 362 | 364 | throw new ERR_MISSING_ARGS('actual', 'expected'); | |
| 363 | 365 | } | |
| 364 | 366 | if (isDeepEqual === undefined) lazyLoadComparison(); | |
| 365 | - if (isDeepStrictEqual(actual, expected)) { | ||
| 367 | + if (isDeepStrictEqual(actual, expected, this?.[kOptions]?.skipPrototype)) { | ||
| 366 | 368 | innerFail({ | |
| 367 | 369 | actual, | |
| 368 | 370 | expected, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -126,9 +126,10 @@ const { | |||
| 126 | 126 | getOwnNonIndexProperties, | |
| 127 | 127 | } = internalBinding('util'); | |
| 128 | 128 | ||
| 129 | - const kStrict = 1; | ||
| 129 | + const kStrict = 2; | ||
| 130 | + const kStrictWithoutPrototypes = 3; | ||
| 130 | 131 | const kLoose = 0; | |
| 131 | - const kPartial = 2; | ||
| 132 | + const kPartial = 1; | ||
| 132 | 133 | ||
| 133 | 134 | const kNoIterator = 0; | |
| 134 | 135 | const kIsArray = 1; | |
@@ -452,7 +453,7 @@ function keyCheck(val1, val2, mode, memos, iterationType, keys2) { | |||
| 452 | 453 | } | |
| 453 | 454 | } else if (keys2.length !== (keys1 = ObjectKeys(val1)).length) { | |
| 454 | 455 | return false; | |
| 455 | - } else if (mode === kStrict) { | ||
| 456 | + } else if (mode === kStrict || mode === kStrictWithoutPrototypes) { | ||
| 456 | 457 | const symbolKeysA = getOwnSymbols(val1); | |
| 457 | 458 | if (symbolKeysA.length !== 0) { | |
| 458 | 459 | let count = 0; | |
@@ -1021,7 +1022,10 @@ module.exports = { | |||
| 1021 | 1022 | isDeepEqual(val1, val2) { | |
| 1022 | 1023 | return detectCycles(val1, val2, kLoose); | |
| 1023 | 1024 | }, | |
| 1024 | - isDeepStrictEqual(val1, val2) { | ||
| 1025 | + isDeepStrictEqual(val1, val2, skipPrototype) { | ||
| 1026 | + if (skipPrototype) { | ||
| 1027 | + return detectCycles(val1, val2, kStrictWithoutPrototypes); | ||
| 1028 | + } | ||
| 1025 | 1029 | return detectCycles(val1, val2, kStrict); | |
| 1026 | 1030 | }, | |
| 1027 | 1031 | isPartialStrictEqual(val1, val2) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -487,12 +487,11 @@ module.exports = { | |||
| 487 | 487 | isArray: deprecate(ArrayIsArray, | |
| 488 | 488 | 'The `util.isArray` API is deprecated. Please use `Array.isArray()` instead.', | |
| 489 | 489 | 'DEP0044'), | |
| 490 | - isDeepStrictEqual(a, b) { | ||
| 490 | + isDeepStrictEqual(a, b, skipPrototype) { | ||
| 491 | 491 | if (internalDeepEqual === undefined) { | |
| 492 | - internalDeepEqual = require('internal/util/comparisons') | ||
| 493 | - .isDeepStrictEqual; | ||
| 492 | + internalDeepEqual = require('internal/util/comparisons').isDeepStrictEqual; | ||
| 494 | 493 | } | |
| 495 | - return internalDeepEqual(a, b); | ||
| 494 | + return internalDeepEqual(a, b, skipPrototype); | ||
| 496 | 495 | }, | |
| 497 | 496 | promisify, | |
| 498 | 497 | stripVTControlCharacters, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -478,3 +478,163 @@ test('Assert class non strict with simple diff', () => { | |||
| 478 | 478 | ); | |
| 479 | 479 | } | |
| 480 | 480 | }); | |
| 481 | + | ||
| 482 | + // Shared setup for skipPrototype tests | ||
| 483 | + { | ||
| 484 | + const message = 'Expected values to be strictly deep-equal:\n' + | ||
| 485 | + '+ actual - expected\n' + | ||
| 486 | + '\n' + | ||
| 487 | + ' [\n' + | ||
| 488 | + ' 1,\n' + | ||
| 489 | + ' 2,\n' + | ||
| 490 | + ' 3,\n' + | ||
| 491 | + ' 4,\n' + | ||
| 492 | + ' 5,\n' + | ||
| 493 | + '+ 6,\n' + | ||
| 494 | + '- 9,\n' + | ||
| 495 | + ' 7\n' + | ||
| 496 | + ' ]\n'; | ||
| 497 | + | ||
| 498 | + function CoolClass(name) { this.name = name; } | ||
| 499 | + | ||
| 500 | + function AwesomeClass(name) { this.name = name; } | ||
| 501 | + | ||
| 502 | + class Modern { constructor(value) { this.value = value; } } | ||
| 503 | + class Legacy { constructor(value) { this.value = value; } } | ||
| 504 | + | ||
| 505 | + const cool = new CoolClass('Assert is inspiring'); | ||
| 506 | + const awesome = new AwesomeClass('Assert is inspiring'); | ||
| 507 | + const modern = new Modern(42); | ||
| 508 | + const legacy = new Legacy(42); | ||
| 509 | + | ||
| 510 | + test('Assert class strict with skipPrototype', () => { | ||
| 511 | + const assertInstance = new Assert({ skipPrototype: true }); | ||
| 512 | + | ||
| 513 | + assert.throws( | ||
| 514 | + () => assertInstance.deepEqual([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 9, 7]), | ||
| 515 | + { message } | ||
| 516 | + ); | ||
| 517 | + | ||
| 518 | + assertInstance.deepEqual(cool, awesome); | ||
| 519 | + assertInstance.deepStrictEqual(cool, awesome); | ||
| 520 | + assertInstance.deepEqual(modern, legacy); | ||
| 521 | + assertInstance.deepStrictEqual(modern, legacy); | ||
| 522 | + | ||
| 523 | + const cool2 = new CoolClass('Soooo coooool'); | ||
| 524 | + assert.throws( | ||
| 525 | + () => assertInstance.deepStrictEqual(cool, cool2), | ||
| 526 | + { code: 'ERR_ASSERTION' } | ||
| 527 | + ); | ||
| 528 | + | ||
| 529 | + const nested1 = { obj: new CoolClass('test'), arr: [1, 2, 3] }; | ||
| 530 | + const nested2 = { obj: new AwesomeClass('test'), arr: [1, 2, 3] }; | ||
| 531 | + assertInstance.deepStrictEqual(nested1, nested2); | ||
| 532 | + | ||
| 533 | + const arr = new Uint8Array([1, 2, 3]); | ||
| 534 | + const buf = Buffer.from([1, 2, 3]); | ||
| 535 | + assertInstance.deepStrictEqual(arr, buf); | ||
| 536 | + }); | ||
| 537 | + | ||
| 538 | + test('Assert class non strict with skipPrototype', () => { | ||
| 539 | + const assertInstance = new Assert({ strict: false, skipPrototype: true }); | ||
| 540 | + | ||
| 541 | + assert.throws( | ||
| 542 | + () => assertInstance.deepStrictEqual([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 9, 7]), | ||
| 543 | + { message } | ||
| 544 | + ); | ||
| 545 | + | ||
| 546 | + assertInstance.deepStrictEqual(cool, awesome); | ||
| 547 | + assertInstance.deepStrictEqual(modern, legacy); | ||
| 548 | + }); | ||
| 549 | + | ||
| 550 | + test('Assert class skipPrototype with complex objects', () => { | ||
| 551 | + const assertInstance = new Assert({ skipPrototype: true }); | ||
| 552 | + | ||
| 553 | + function ComplexAwesomeClass(name, age) { | ||
| 554 | + this.name = name; | ||
| 555 | + this.age = age; | ||
| 556 | + this.settings = { | ||
| 557 | + theme: 'dark', | ||
| 558 | + lang: 'en' | ||
| 559 | + }; | ||
| 560 | + } | ||
| 561 | + | ||
| 562 | + function ComplexCoolClass(name, age) { | ||
| 563 | + this.name = name; | ||
| 564 | + this.age = age; | ||
| 565 | + this.settings = { | ||
| 566 | + theme: 'dark', | ||
| 567 | + lang: 'en' | ||
| 568 | + }; | ||
| 569 | + } | ||
| 570 | + | ||
| 571 | + const awesome1 = new ComplexAwesomeClass('Foo', 30); | ||
| 572 | + const cool1 = new ComplexCoolClass('Foo', 30); | ||
| 573 | + | ||
| 574 | + assertInstance.deepStrictEqual(awesome1, cool1); | ||
| 575 | + | ||
| 576 | + const cool2 = new ComplexCoolClass('Foo', 30); | ||
| 577 | + cool2.settings.theme = 'light'; | ||
| 578 | + | ||
| 579 | + assert.throws( | ||
| 580 | + () => assertInstance.deepStrictEqual(awesome1, cool2), | ||
| 581 | + { code: 'ERR_ASSERTION' } | ||
| 582 | + ); | ||
| 583 | + }); | ||
| 584 | + | ||
| 585 | + test('Assert class skipPrototype with arrays and special objects', () => { | ||
| 586 | + const assertInstance = new Assert({ skipPrototype: true }); | ||
| 587 | + | ||
| 588 | + const arr1 = [1, 2, 3]; | ||
| 589 | + const arr2 = new Array(1, 2, 3); | ||
| 590 | + assertInstance.deepStrictEqual(arr1, arr2); | ||
| 591 | + | ||
| 592 | + const date1 = new Date('2023-01-01'); | ||
| 593 | + const date2 = new Date('2023-01-01'); | ||
| 594 | + assertInstance.deepStrictEqual(date1, date2); | ||
| 595 | + | ||
| 596 | + const regex1 = /test/g; | ||
| 597 | + const regex2 = new RegExp('test', 'g'); | ||
| 598 | + assertInstance.deepStrictEqual(regex1, regex2); | ||
| 599 | + | ||
| 600 | + const date3 = new Date('2023-01-02'); | ||
| 601 | + assert.throws( | ||
| 602 | + () => assertInstance.deepStrictEqual(date1, date3), | ||
| 603 | + { code: 'ERR_ASSERTION' } | ||
| 604 | + ); | ||
| 605 | + }); | ||
| 606 | + | ||
| 607 | + test('Assert class skipPrototype with notDeepStrictEqual', () => { | ||
| 608 | + const assertInstance = new Assert({ skipPrototype: true }); | ||
| 609 | + | ||
| 610 | + assert.throws( | ||
| 611 | + () => assertInstance.notDeepStrictEqual(cool, awesome), | ||
| 612 | + { code: 'ERR_ASSERTION' } | ||
| 613 | + ); | ||
| 614 | + | ||
| 615 | + const notAwesome = new AwesomeClass('Not so awesome'); | ||
| 616 | + assertInstance.notDeepStrictEqual(cool, notAwesome); | ||
| 617 | + | ||
| 618 | + const defaultAssertInstance = new Assert({ skipPrototype: false }); | ||
| 619 | + defaultAssertInstance.notDeepStrictEqual(cool, awesome); | ||
| 620 | + }); | ||
| 621 | + | ||
| 622 | + test('Assert class skipPrototype with mixed types', () => { | ||
| 623 | + const assertInstance = new Assert({ skipPrototype: true }); | ||
| 624 | + | ||
| 625 | + const obj1 = { value: 42, nested: { prop: 'test' } }; | ||
| 626 | + | ||
| 627 | + function CustomObj(value, nested) { | ||
| 628 | + this.value = value; | ||
| 629 | + this.nested = nested; | ||
| 630 | + } | ||
| 631 | + | ||
| 632 | + const obj2 = new CustomObj(42, { prop: 'test' }); | ||
| 633 | + assertInstance.deepStrictEqual(obj1, obj2); | ||
| 634 | + | ||
| 635 | + assert.throws( | ||
| 636 | + () => assertInstance.deepStrictEqual({ num: 42 }, { num: '42' }), | ||
| 637 | + { code: 'ERR_ASSERTION' } | ||
| 638 | + ); | ||
| 639 | + }); | ||
| 640 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments