From 468e9577cd787d7607080e84d43864bcf1839f87 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 7 May 2022 13:26:15 -0700 Subject: [PATCH] Include symbol properties in matcher diffs * #1966 --- lib/jasmine-core/jasmine.js | 26 +++++++++----------- spec/core/UtilSpec.js | 36 +++++++++++++++++++++++----- spec/core/matchers/ObjectPathSpec.js | 4 ++++ spec/core/matchers/toEqualSpec.js | 25 +++++++++++++++++++ src/core/matchers/ObjectPath.js | 4 ++-- src/core/matchers/matchersUtil.js | 10 ++++---- src/core/util.js | 12 +++------- 7 files changed, 81 insertions(+), 36 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 84431f6d..616c4961 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -663,15 +663,9 @@ getJasmineRequireObj().util = function(j$) { }; util.objectDifference = function(obj, toRemove) { - var diff = {}; - - for (var key in obj) { - if (util.has(obj, key) && !util.has(toRemove, key)) { - diff[key] = obj[key]; - } - } - - return diff; + return j$.MatchersUtil.keys(obj) + .filter(key => !util.has(toRemove, key)) + .map(key => [key, obj[key]]); }; util.has = function(obj, key) { @@ -5667,11 +5661,13 @@ getJasmineRequireObj().MatchersUtil = function(j$) { ); } - function formatKeyValuePairs(pp, obj) { - var formatted = ''; - for (var key in obj) { - formatted += '\n ' + key + ': ' + pp(obj[key]); + function formatKeyValuePairs(pp, keyValuePairs) { + let formatted = ''; + + for (const [key, value] of keyValuePairs) { + formatted += '\n ' + key.toString() + ': ' + pp(value); } + return formatted; } @@ -5849,8 +5845,8 @@ getJasmineRequireObj().ObjectPath = function(j$) { }; function formatPropertyAccess(prop) { - if (typeof prop === 'number') { - return '[' + prop + ']'; + if (typeof prop === 'number' || typeof prop === 'symbol') { + return '[' + prop.toString() + ']'; } if (isValidIdentifier(prop)) { diff --git a/spec/core/UtilSpec.js b/spec/core/UtilSpec.js index ea933b95..36f4e8ba 100644 --- a/spec/core/UtilSpec.js +++ b/spec/core/UtilSpec.js @@ -196,10 +196,34 @@ describe('util', function() { quux: 7 }; - expect(jasmineUnderTest.util.objectDifference(a, b)).toEqual({ - foo: 3, - baz: 5 - }); + expect(jasmineUnderTest.util.objectDifference(a, b)).toEqual([ + ['foo', 3], + ['baz', 5] + ]); + }); + + it('includes Symbol keys', function() { + const missing = Symbol('missing'); + const both = Symbol('both'); + const symbolDuplicated1 = Symbol('symbolDuplicated'); + const symbolDuplicated2 = Symbol('symbolDuplicated'); + const added = Symbol('added'); + const a = { + [missing]: 1, + [both]: 2, + [symbolDuplicated1]: 3 + }; + + const b = { + [both]: 'anything', + [symbolDuplicated2]: 4, + [added]: 5 + }; + + expect(jasmineUnderTest.util.objectDifference(a, b)).toEqual([ + [missing, 1], + [symbolDuplicated1, 3] + ]); }); it('only looks at own properties of both objects', function() { @@ -214,8 +238,8 @@ describe('util', function() { const b = new Foo(); b.y = 2; - expect(jasmineUnderTest.util.objectDifference(a, b)).toEqual({ x: 1 }); - expect(jasmineUnderTest.util.objectDifference(b, a)).toEqual({ y: 2 }); + expect(jasmineUnderTest.util.objectDifference(a, b)).toEqual([['x', 1]]); + expect(jasmineUnderTest.util.objectDifference(b, a)).toEqual([['y', 2]]); }); }); diff --git a/spec/core/matchers/ObjectPathSpec.js b/spec/core/matchers/ObjectPathSpec.js index b83c61c7..80938f5a 100644 --- a/spec/core/matchers/ObjectPathSpec.js +++ b/spec/core/matchers/ObjectPathSpec.js @@ -25,6 +25,10 @@ describe('ObjectPath', function() { expect(new ObjectPath(['1hello']).toString()).toEqual("$['1hello']"); }); + it('renders symbols with squre bracket notation', function() { + expect(new ObjectPath([Symbol('a')]).toString()).toEqual('$[Symbol(a)]'); + }); + it('renders as the empty string when empty', function() { expect(new ObjectPath().toString()).toEqual(''); }); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index a6ca1ae7..74a20587 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -51,6 +51,15 @@ describe('toEqual', function() { expect(compareEquals(actual, expected).message).toEqual(message); }); + it('reports differences between symbol properties', function() { + const x = Symbol('x'), + actual = { [x]: 1, y: 3 }, + expected = { [x]: 2, y: 3 }, + message = 'Expected $[Symbol(x)] = 1 to equal 2.'; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); + it('reports the difference between nested objects that are not equal', function() { const actual = { x: { y: 1 } }, expected = { x: { y: 2 } }, @@ -75,6 +84,22 @@ describe('toEqual', function() { expect(compareEquals(actual, expected).message).toEqual(message); }); + it('reports missing symbol properties', function() { + const actual = { x: {} }, + expected = { x: { [Symbol('y')]: 1 } }, + message = 'Expected $.x to have properties\n' + ' Symbol(y): 1'; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); + + it('reports extra symbol properties', function() { + const actual = { x: { [Symbol('y')]: 1 } }, + expected = { x: {} }, + message = 'Expected $.x not to have properties\n' + ' Symbol(y): 1'; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); + it('reports extra properties', function() { const actual = { x: { y: 1, z: 2 } }, expected = { x: {} }, diff --git a/src/core/matchers/ObjectPath.js b/src/core/matchers/ObjectPath.js index 2ffc8843..e287af19 100644 --- a/src/core/matchers/ObjectPath.js +++ b/src/core/matchers/ObjectPath.js @@ -24,8 +24,8 @@ getJasmineRequireObj().ObjectPath = function(j$) { }; function formatPropertyAccess(prop) { - if (typeof prop === 'number') { - return '[' + prop + ']'; + if (typeof prop === 'number' || typeof prop === 'symbol') { + return '[' + prop.toString() + ']'; } if (isValidIdentifier(prop)) { diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index e3083322..0bf2a57f 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -610,11 +610,13 @@ getJasmineRequireObj().MatchersUtil = function(j$) { ); } - function formatKeyValuePairs(pp, obj) { - var formatted = ''; - for (var key in obj) { - formatted += '\n ' + key + ': ' + pp(obj[key]); + function formatKeyValuePairs(pp, keyValuePairs) { + let formatted = ''; + + for (const [key, value] of keyValuePairs) { + formatted += '\n ' + key.toString() + ': ' + pp(value); } + return formatted; } diff --git a/src/core/util.js b/src/core/util.js index 7973ff87..f2b7eb7d 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -76,15 +76,9 @@ getJasmineRequireObj().util = function(j$) { }; util.objectDifference = function(obj, toRemove) { - var diff = {}; - - for (var key in obj) { - if (util.has(obj, key) && !util.has(toRemove, key)) { - diff[key] = obj[key]; - } - } - - return diff; + return j$.MatchersUtil.keys(obj) + .filter(key => !util.has(toRemove, key)) + .map(key => [key, obj[key]]); }; util.has = function(obj, key) {