| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 98f8f4a commit a4f57f0
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2548,6 +2548,96 @@ assert.throws(throwingFirst, /Second$/); | |||
| 2548 | 2548 | Due to the confusing error-prone notation, avoid a string as the second | |
| 2549 | 2549 | argument. | |
| 2550 | 2550 | ||
| 2551 | + ## `assert.partialDeepStrictEqual(actual, expected[, message])` | ||
| 2552 | + | ||
| 2553 | + <!-- YAML | ||
| 2554 | + added: REPLACEME | ||
| 2555 | + --> | ||
| 2556 | + | ||
| 2557 | + > Stability: 1.0 - Early development | ||
| 2558 | + | ||
| 2559 | + * `actual` {any} | ||
| 2560 | + * `expected` {any} | ||
| 2561 | + * `message` {string|Error} | ||
| 2562 | + | ||
| 2563 | + [`assert.partialDeepStrictEqual()`][] Asserts the equivalence between the `actual` and `expected` parameters through a | ||
| 2564 | + deep comparison, ensuring that all properties in the `expected` parameter are | ||
| 2565 | + present in the `actual` parameter with equivalent values, not allowing type coercion. | ||
| 2566 | + The main difference with [`assert.deepStrictEqual()`][] is that [`assert.partialDeepStrictEqual()`][] does not require | ||
| 2567 | + all properties in the `actual` parameter to be present in the `expected` parameter. | ||
| 2568 | + This method should always pass the same test cases as [`assert.deepStrictEqual()`][], behaving as a super set of it. | ||
| 2569 | + | ||
| 2570 | + ```mjs | ||
| 2571 | + import assert from 'node:assert'; | ||
| 2572 | + | ||
| 2573 | + assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); | ||
| 2574 | + // OK | ||
| 2575 | + | ||
| 2576 | + assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } }); | ||
| 2577 | + // OK | ||
| 2578 | + | ||
| 2579 | + assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 }); | ||
| 2580 | + // OK | ||
| 2581 | + | ||
| 2582 | + assert.partialDeepStrictEqual(new Set(['value1', 'value2']), new Set(['value1', 'value2'])); | ||
| 2583 | + // OK | ||
| 2584 | + | ||
| 2585 | + assert.partialDeepStrictEqual(new Map([['key1', 'value1']]), new Map([['key1', 'value1']])); | ||
| 2586 | + // OK | ||
| 2587 | + | ||
| 2588 | + assert.partialDeepStrictEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])); | ||
| 2589 | + // OK | ||
| 2590 | + | ||
| 2591 | + assert.partialDeepStrictEqual(/abc/, /abc/); | ||
| 2592 | + // OK | ||
| 2593 | + | ||
| 2594 | + assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]); | ||
| 2595 | + // OK | ||
| 2596 | + | ||
| 2597 | + assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }])); | ||
| 2598 | + // OK | ||
| 2599 | + | ||
| 2600 | + assert.partialDeepStrictEqual(new Date(0), new Date(0)); | ||
| 2601 | + // OK | ||
| 2602 | + | ||
| 2603 | + assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 }); | ||
| 2604 | + // AssertionError | ||
| 2605 | + | ||
| 2606 | + assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 }); | ||
| 2607 | + // AssertionError | ||
| 2608 | + | ||
| 2609 | + assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } }); | ||
| 2610 | + // AssertionError | ||
| 2611 | + ``` | ||
| 2612 | + | ||
| 2613 | + ```cjs | ||
| 2614 | + const assert = require('node:assert'); | ||
| 2615 | + | ||
| 2616 | + assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 }); | ||
| 2617 | + // OK | ||
| 2618 | + | ||
| 2619 | + assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } }); | ||
| 2620 | + // OK | ||
| 2621 | + | ||
| 2622 | + assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 }); | ||
| 2623 | + // OK | ||
| 2624 | + | ||
| 2625 | + assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]); | ||
| 2626 | + // OK | ||
| 2627 | + | ||
| 2628 | + assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }])); | ||
| 2629 | + // OK | ||
| 2630 | + | ||
| 2631 | + assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 }); | ||
| 2632 | + // AssertionError | ||
| 2633 | + | ||
| 2634 | + assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 }); | ||
| 2635 | + // AssertionError | ||
| 2636 | + | ||
| 2637 | + assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } }); | ||
| 2638 | + // AssertionError | ||
| 2639 | + ``` | ||
| 2640 | + | ||
| 2551 | 2641 | [Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript | |
| 2552 | 2642 | [Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring | |
| 2553 | 2643 | [`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality | |
@@ -2576,6 +2666,7 @@ argument. | |||
| 2576 | 2666 | [`assert.notEqual()`]: #assertnotequalactual-expected-message | |
| 2577 | 2667 | [`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message | |
| 2578 | 2668 | [`assert.ok()`]: #assertokvalue-message | |
| 2669 | + [`assert.partialDeepStrictEqual()`]: #assertpartialdeepstrictequalactual-expected-message | ||
| 2579 | 2670 | [`assert.strictEqual()`]: #assertstrictequalactual-expected-message | |
| 2580 | 2671 | [`assert.throws()`]: #assertthrowsfn-error-message | |
| 2581 | 2672 | [`getColorDepth()`]: tty.md#writestreamgetcolordepthenv | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,22 +21,35 @@ | |||
| 21 | 21 | 'use strict'; | |
| 22 | 22 | ||
| 23 | 23 | const { | |
| 24 | + ArrayFrom, | ||
| 25 | + ArrayIsArray, | ||
| 24 | 26 | ArrayPrototypeIndexOf, | |
| 25 | 27 | ArrayPrototypeJoin, | |
| 26 | 28 | ArrayPrototypePush, | |
| 27 | 29 | ArrayPrototypeSlice, | |
| 28 | 30 | Error, | |
| 31 | + FunctionPrototypeCall, | ||
| 32 | + MapPrototypeDelete, | ||
| 33 | + MapPrototypeGet, | ||
| 34 | + MapPrototypeHas, | ||
| 35 | + MapPrototypeSet, | ||
| 29 | 36 | NumberIsNaN, | |
| 30 | 37 | ObjectAssign, | |
| 31 | 38 | ObjectIs, | |
| 32 | 39 | ObjectKeys, | |
| 33 | 40 | ObjectPrototypeIsPrototypeOf, | |
| 34 | 41 | ReflectApply, | |
| 42 | + ReflectHas, | ||
| 43 | + ReflectOwnKeys, | ||
| 35 | 44 | RegExpPrototypeExec, | |
| 45 | + SafeMap, | ||
| 46 | + SafeSet, | ||
| 47 | + SafeWeakSet, | ||
| 36 | 48 | String, | |
| 37 | 49 | StringPrototypeIndexOf, | |
| 38 | 50 | StringPrototypeSlice, | |
| 39 | 51 | StringPrototypeSplit, | |
| 52 | + SymbolIterator, | ||
| 40 | 53 | } = primordials; | |
| 41 | 54 | ||
| 42 | 55 | const { | |
@@ -50,8 +63,18 @@ const { | |||
| 50 | 63 | } = require('internal/errors'); | |
| 51 | 64 | const AssertionError = require('internal/assert/assertion_error'); | |
| 52 | 65 | const { inspect } = require('internal/util/inspect'); | |
| 53 | - const { isPromise, isRegExp } = require('internal/util/types'); | ||
| 54 | - const { isError, deprecate } = require('internal/util'); | ||
| 66 | + const { Buffer } = require('buffer'); | ||
| 67 | + const { | ||
| 68 | + isKeyObject, | ||
| 69 | + isPromise, | ||
| 70 | + isRegExp, | ||
| 71 | + isMap, | ||
| 72 | + isSet, | ||
| 73 | + isDate, | ||
| 74 | + isWeakSet, | ||
| 75 | + isWeakMap, | ||
| 76 | + } = require('internal/util/types'); | ||
| 77 | + const { isError, deprecate, emitExperimentalWarning } = require('internal/util'); | ||
| 55 | 78 | const { innerOk } = require('internal/assert/utils'); | |
| 56 | 79 | ||
| 57 | 80 | const CallTracker = require('internal/assert/calltracker'); | |
@@ -341,6 +364,191 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) { | |||
| 341 | 364 | } | |
| 342 | 365 | }; | |
| 343 | 366 | ||
| 367 | + function isSpecial(obj) { | ||
| 368 | + return obj == null || typeof obj !== 'object' || isError(obj) || isRegExp(obj) || isDate(obj); | ||
| 369 | + } | ||
| 370 | + | ||
| 371 | + const typesToCallDeepStrictEqualWith = [ | ||
| 372 | + isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, | ||
| 373 | + ]; | ||
| 374 | + | ||
| 375 | + /** | ||
| 376 | + * Compares two objects or values recursively to check if they are equal. | ||
| 377 | + * @param {any} actual - The actual value to compare. | ||
| 378 | + * @param {any} expected - The expected value to compare. | ||
| 379 | + * @param {Set} [comparedObjects=new Set()] - Set to track compared objects for handling circular references. | ||
| 380 | + * @returns {boolean} - Returns `true` if the actual value matches the expected value, otherwise `false`. | ||
| 381 | + * @example | ||
| 382 | + * compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}); // true | ||
| 383 | + */ | ||
| 384 | + function compareBranch( | ||
| 385 | + actual, | ||
| 386 | + expected, | ||
| 387 | + comparedObjects, | ||
| 388 | + ) { | ||
| 389 | + // Check for Map object equality | ||
| 390 | + if (isMap(actual) && isMap(expected)) { | ||
| 391 | + if (actual.size !== expected.size) { | ||
| 392 | + return false; | ||
| 393 | + } | ||
| 394 | + const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual); | ||
| 395 | + | ||
| 396 | + comparedObjects ??= new SafeWeakSet(); | ||
| 397 | + | ||
| 398 | + for (const { 0: key, 1: val } of safeIterator) { | ||
| 399 | + if (!MapPrototypeHas(expected, key)) { | ||
| 400 | + return false; | ||
| 401 | + } | ||
| 402 | + if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) { | ||
| 403 | + return false; | ||
| 404 | + } | ||
| 405 | + } | ||
| 406 | + return true; | ||
| 407 | + } | ||
| 408 | + | ||
| 409 | + for (const type of typesToCallDeepStrictEqualWith) { | ||
| 410 | + if (type(actual) || type(expected)) { | ||
| 411 | + if (isDeepStrictEqual === undefined) lazyLoadComparison(); | ||
| 412 | + return isDeepStrictEqual(actual, expected); | ||
| 413 | + } | ||
| 414 | + } | ||
| 415 | + | ||
| 416 | + // Check for Set object equality | ||
| 417 | + // TODO(aduh95): switch to `SetPrototypeIsSubsetOf` when it's available | ||
| 418 | + if (isSet(actual) && isSet(expected)) { | ||
| 419 | + if (expected.size > actual.size) { | ||
| 420 | + return false; // `expected` can't be a subset if it has more elements | ||
| 421 | + } | ||
| 422 | + | ||
| 423 | + if (isDeepEqual === undefined) lazyLoadComparison(); | ||
| 424 | + | ||
| 425 | + const actualArray = ArrayFrom(actual); | ||
| 426 | + const expectedArray = ArrayFrom(expected); | ||
| 427 | + const usedIndices = new SafeSet(); | ||
| 428 | + | ||
| 429 | + for (let expectedIdx = 0; expectedIdx < expectedArray.length; expectedIdx++) { | ||
| 430 | + const expectedItem = expectedArray[expectedIdx]; | ||
| 431 | + let found = false; | ||
| 432 | + | ||
| 433 | + for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) { | ||
| 434 | + if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) { | ||
| 435 | + usedIndices.add(actualIdx); | ||
| 436 | + found = true; | ||
| 437 | + break; | ||
| 438 | + } | ||
| 439 | + } | ||
| 440 | + | ||
| 441 | + if (!found) { | ||
| 442 | + return false; | ||
| 443 | + } | ||
| 444 | + } | ||
| 445 | + | ||
| 446 | + return true; | ||
| 447 | + } | ||
| 448 | + | ||
| 449 | + // Check if expected array is a subset of actual array | ||
| 450 | + if (ArrayIsArray(actual) && ArrayIsArray(expected)) { | ||
| 451 | + if (expected.length > actual.length) { | ||
| 452 | + return false; | ||
| 453 | + } | ||
| 454 | + | ||
| 455 | + if (isDeepEqual === undefined) lazyLoadComparison(); | ||
| 456 | + | ||
| 457 | + // Create a map to count occurrences of each element in the expected array | ||
| 458 | + const expectedCounts = new SafeMap(); | ||
| 459 | + for (const expectedItem of expected) { | ||
| 460 | + let found = false; | ||
| 461 | + for (const { 0: key, 1: count } of expectedCounts) { | ||
| 462 | + if (isDeepStrictEqual(key, expectedItem)) { | ||
| 463 | + MapPrototypeSet(expectedCounts, key, count + 1); | ||
| 464 | + found = true; | ||
| 465 | + break; | ||
| 466 | + } | ||
| 467 | + } | ||
| 468 | + if (!found) { | ||
| 469 | + MapPrototypeSet(expectedCounts, expectedItem, 1); | ||
| 470 | + } | ||
| 471 | + } | ||
| 472 | + | ||
| 473 | + // Create a map to count occurrences of relevant elements in the actual array | ||
| 474 | + for (const actualItem of actual) { | ||
| 475 | + for (const { 0: key, 1: count } of expectedCounts) { | ||
| 476 | + if (isDeepStrictEqual(key, actualItem)) { | ||
| 477 | + if (count === 1) { | ||
| 478 | + MapPrototypeDelete(expectedCounts, key); | ||
| 479 | + } else { | ||
| 480 | + MapPrototypeSet(expectedCounts, key, count - 1); | ||
| 481 | + } | ||
| 482 | + break; | ||
| 483 | + } | ||
| 484 | + } | ||
| 485 | + } | ||
| 486 | + | ||
| 487 | + return !expectedCounts.size; | ||
| 488 | + } | ||
| 489 | + | ||
| 490 | + // Comparison done when at least one of the values is not an object | ||
| 491 | + if (isSpecial(actual) || isSpecial(expected)) { | ||
| 492 | + if (isDeepEqual === undefined) { | ||
| 493 | + lazyLoadComparison(); | ||
| 494 | + } | ||
| 495 | + return isDeepStrictEqual(actual, expected); | ||
| 496 | + } | ||
| 497 | + | ||
| 498 | + // Use Reflect.ownKeys() instead of Object.keys() to include symbol properties | ||
| 499 | + const keysExpected = ReflectOwnKeys(expected); | ||
| 500 | + | ||
| 501 | + comparedObjects ??= new SafeWeakSet(); | ||
| 502 | + | ||
| 503 | + // Handle circular references | ||
| 504 | + if (comparedObjects.has(actual)) { | ||
| 505 | + return true; | ||
| 506 | + } | ||
| 507 | + comparedObjects.add(actual); | ||
| 508 | + | ||
| 509 | + // Check if all expected keys and values match | ||
| 510 | + for (let i = 0; i < keysExpected.length; i++) { | ||
| 511 | + const key = keysExpected[i]; | ||
| 512 | + assert( | ||
| 513 | + ReflectHas(actual, key), | ||
| 514 | + new AssertionError({ message: `Expected key ${String(key)} not found in actual object` }), | ||
| 515 | + ); | ||
| 516 | + if (!compareBranch(actual[key], expected[key], comparedObjects)) { | ||
| 517 | + return false; | ||
| 518 | + } | ||
| 519 | + } | ||
| 520 | + | ||
| 521 | + return true; | ||
| 522 | + } | ||
| 523 | + | ||
| 524 | + /** | ||
| 525 | + * The strict equivalence assertion test between two objects | ||
| 526 | + * @param {any} actual | ||
| 527 | + * @param {any} expected | ||
| 528 | + * @param {string | Error} [message] | ||
| 529 | + * @returns {void} | ||
| 530 | + */ | ||
| 531 | + assert.partialDeepStrictEqual = function partialDeepStrictEqual( | ||
| 532 | + actual, | ||
| 533 | + expected, | ||
| 534 | + message, | ||
| 535 | + ) { | ||
| 536 | + emitExperimentalWarning('assert.partialDeepStrictEqual'); | ||
| 537 | + if (arguments.length < 2) { | ||
| 538 | + throw new ERR_MISSING_ARGS('actual', 'expected'); | ||
| 539 | + } | ||
| 540 | + | ||
| 541 | + if (!compareBranch(actual, expected)) { | ||
| 542 | + innerFail({ | ||
| 543 | + actual, | ||
| 544 | + expected, | ||
| 545 | + message, | ||
| 546 | + operator: 'partialDeepStrictEqual', | ||
| 547 | + stackStartFn: partialDeepStrictEqual, | ||
| 548 | + }); | ||
| 549 | + } | ||
| 550 | + }; | ||
| 551 | + | ||
| 344 | 552 | class Comparison { | |
| 345 | 553 | constructor(obj, keys, actual) { | |
| 346 | 554 | for (const key of keys) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -119,6 +119,7 @@ function lazyAssertObject(harness) { | |||
| 119 | 119 | 'notDeepStrictEqual', | |
| 120 | 120 | 'notEqual', | |
| 121 | 121 | 'notStrictEqual', | |
| 122 | + 'partialDeepStrictEqual', | ||
| 122 | 123 | 'rejects', | |
| 123 | 124 | 'strictEqual', | |
| 124 | 125 | 'throws', | |
| Back | FazBrowse Home | New Git URL |
0 commit comments