diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index f30621a0..98795db5 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -393,6 +393,13 @@ describe("matchersUtil", function() { expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); }); + it("passes when comparing identical sets with different insertion order", function() { + jasmine.getEnv().requireFunctioningSets(); + var setA = new Set([3, 6]); + var setB = new Set([6, 3]); + expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(true); + }); + it("fails for sets with different elements", function() { jasmine.getEnv().requireFunctioningSets(); var setA = new Set([6, 3, 5]); @@ -407,13 +414,6 @@ describe("matchersUtil", function() { expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(false); }); - it("fails for sets with different insertion order", function() { - jasmine.getEnv().requireFunctioningSets(); - var setA = new Set([3, 6]); - var setB = new Set([6, 3]); - expect(jasmineUnderTest.matchersUtil.equals(setA, setB)).toBe(false); - }); - it("passes when comparing two empty maps", function() { jasmine.getEnv().requireFunctioningMaps(); expect(jasmineUnderTest.matchersUtil.equals(new Map(), new Map())).toBe(true); @@ -427,6 +427,13 @@ describe("matchersUtil", function() { expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(true); }); + it("passes when comparing identical maps with different insertion order", function() { + jasmine.getEnv().requireFunctioningMaps(); + var mapA = new Map([['a', 3], [6, 1]]); + var mapB = new Map([[6, 1], ['a', 3]]); + expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(true); + }); + it("fails for maps with different elements", function() { jasmine.getEnv().requireFunctioningMaps(); var mapA = new Map([[6, 3], [5, 1]]); @@ -441,13 +448,6 @@ describe("matchersUtil", function() { expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(false); }); - it("fails for maps with different insertion order", function() { - jasmine.getEnv().requireFunctioningMaps(); - var mapA = new Map([['a', 3], [6, 1]]); - var mapB = new Map([[6, 1], ['a', 3]]); - expect(jasmineUnderTest.matchersUtil.equals(mapA, mapB)).toBe(false); - }); - describe("when running in an environment with array polyfills", function() { // IE 8 doesn't support `definePropery` on non-DOM nodes if (jasmine.getEnv().ieVersion < 9) { return; } diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 0cf455cd..fc4726cb 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -359,8 +359,9 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); - it("does not report deep mismatches within Sets", function() { - // TODO: implement deep comparison of set elements + // == Sets == + + it("reports mismatches between Sets", function() { jasmine.getEnv().requireFunctioningSets(); var actual = new Set([1]), @@ -370,6 +371,16 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); + it("reports deep mismatches within Sets", function() { + jasmine.getEnv().requireFunctioningSets(); + + var actual = new Set([{x: 1}]), + expected = new Set([{x: 2}]), + message = 'Expected Set( Object({ x: 1 }) ) to equal Set( Object({ x: 2 }) ).'; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); + it("reports mismatches between Sets nested in objects", function() { jasmine.getEnv().requireFunctioningSets(); @@ -390,13 +401,46 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); - it("does not report deep mismatches within Maps", function() { - // TODO: implement deep comparison of Map elements + it("reports mismatches between Sets where actual is missing a value from expected", function() { + jasmine.getEnv().requireFunctioningSets(); + + // Use 'duplicate' object in actual so sizes match + var actual = new Set([{x: 1}, {x: 1}]), + expected = new Set([{x: 1}, {x: 2}]), + message = 'Expected Set( Object({ x: 1 }), Object({ x: 1 }) ) to equal Set( Object({ x: 1 }), Object({ x: 2 }) ).'; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); + + it("reports mismatches between Sets where actual has a value missing from expected", function() { + jasmine.getEnv().requireFunctioningSets(); + + // Use 'duplicate' object in expected so sizes match + var actual = new Set([{x: 1}, {x: 2}]), + expected = new Set([{x: 1}, {x: 1}]), + message = 'Expected Set( Object({ x: 1 }), Object({ x: 2 }) ) to equal Set( Object({ x: 1 }), Object({ x: 1 }) ).'; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); + + // == Maps == + + it("does not report mismatches between deep equal Maps", function() { + jasmine.getEnv().requireFunctioningSets(); + + // values are the same but with different object identity + var actual = new Map([['a', {x: 1}]]), + expected = new Map([['a', {x: 1}]]); + + expect(compareEquals(actual, expected).pass).toBe(true); + }); + + it("reports deep mismatches within Maps", function() { jasmine.getEnv().requireFunctioningMaps(); - var actual = new Map([['a', 1]]), - expected = new Map([['a', 2]]), - message = "Expected Map( [ 'a', 1 ] ) to equal Map( [ 'a', 2 ] )."; + var actual = new Map([['a', {x: 1}]]), + expected = new Map([['a', {x: 2}]]), + message = "Expected Map( [ 'a', Object({ x: 1 }) ] ) to equal Map( [ 'a', Object({ x: 2 }) ] )."; expect(compareEquals(actual, expected).message).toEqual(message); }); @@ -405,8 +449,8 @@ describe("toEqual", function() { jasmine.getEnv().requireFunctioningMaps(); var actual = {Maps: [new Map([['a', 1]])]}, - expected = {Maps: [new Map([['a', 2], ['b', 1]])]}, - message = "Expected $.Maps[0] = Map( [ 'a', 1 ] ) to equal Map( [ 'a', 2 ], [ 'b', 1 ] )."; + expected = {Maps: [new Map([['a', 2]])]}, + message = "Expected $.Maps[0] = Map( [ 'a', 1 ] ) to equal Map( [ 'a', 2 ] )."; expect(compareEquals(actual, expected).message).toEqual(message); }); @@ -415,11 +459,40 @@ describe("toEqual", function() { jasmine.getEnv().requireFunctioningMaps(); var actual = new Map([['a', 1]]), - expected = new Map([['a', 2]]), - message = "Expected Map( [ 'a', 1 ] ) to equal Map( [ 'a', 2 ] )."; + expected = new Map([['a', 2], ['b', 1]]), + message = "Expected Map( [ 'a', 1 ] ) to equal Map( [ 'a', 2 ], [ 'b', 1 ] )."; expect(compareEquals(actual, expected).message).toEqual(message); - }); + }); + + it("reports mismatches between Maps with equal values but differing keys", function() { + jasmine.getEnv().requireFunctioningMaps(); + + var actual = new Map([['a', 1]]), + expected = new Map([['b', 1]]), + message = "Expected Map( [ 'a', 1 ] ) to equal Map( [ 'b', 1 ] )."; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); + + it("does not report mismatches between Maps with keys with same object identity", function() { + jasmine.getEnv().requireFunctioningMaps(); + var key = {x: 1}, + actual = new Map([[key, 2]]), + expected = new Map([[key, 2]]); + + expect(compareEquals(actual, expected).pass).toBe(true); + }); + + it("reports mismatches between Maps with identical keys with different object identity", function() { + jasmine.getEnv().requireFunctioningMaps(); + + var actual = new Map([[{x: 1}, 2]]), + expected = new Map([[{x: 1}, 2]]), + message = "Expected Map( [ Object({ x: 1 }), 2 ] ) to equal Map( [ Object({ x: 1 }), 2 ] )."; + + expect(compareEquals(actual, expected).message).toEqual(message); + }); function isNotRunningInBrowser() { return typeof document === 'undefined' diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index 1ed2ee34..054975e7 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -239,21 +239,66 @@ getJasmineRequireObj().matchersUtil = function(j$) { if (!result) { return false; } - } else if (className == '[object Set]' || className == '[object Map]') { + } else if (className == '[object Map]') { if (a.size != b.size) { diffBuilder.record(a, b); return false; } - var iterA = a.entries(), iterB = b.entries(); - var valA, valB; - do { - valA = iterA.next(); - valB = iterB.next(); - if (!eq(valA.value, valB.value, aStack, bStack, customTesters, j$.NullDiffBuilder())) { - diffBuilder.record(a, b); - return false; + + // For both sets of keys, check they map to equal values in both maps + var mapKeys = [a.keys(), b.keys()]; + var mapIter, mapKeyIt, mapKey; + for (i = 0; result && i < mapKeys.length; i++) { + mapIter = mapKeys[i]; + mapKeyIt = mapIter.next(); + while (result && !mapKeyIt.done) { + mapKey = mapKeyIt.value; + result = eq(a.get(mapKey), b.get(mapKey), aStack, bStack, customTesters, j$.NullDiffBuilder()); + mapKeyIt = mapIter.next(); } - } while (!valA.done && !valB.done); + } + + if (!result) { + diffBuilder.record(a, b); + return false; + } + } else if (className == '[object Set]') { + if (a.size != b.size) { + diffBuilder.record(a, b); + return false; + } + + // For both sets, check they are all contained in the other set + var setPairs = [[a, b], [b, a]]; + var baseIter, baseValueIt, baseValue; + var otherSet, otherIter, otherValueIt, otherValue, found; + for (i = 0; result && i < setPairs.length; i++) { + baseIter = setPairs[i][0].values(); + otherSet = setPairs[i][1]; + // For each value in the base set... + baseValueIt = baseIter.next(); + while (result && !baseValueIt.done) { + baseValue = baseValueIt.value; + // ... test that it is present in the other set + otherIter = otherSet.values(); + otherValueIt = otherIter.next(); + // Optimisation: start looking for value by object identity + found = otherSet.has(baseValue); + // If not found, compare by value equality + while (!found && !otherValueIt.done) { + otherValue = otherValueIt.value; + found = eq(baseValue, otherValue, aStack, bStack, customTesters, j$.NullDiffBuilder()); + otherValueIt = otherIter.next(); + } + result = result && found; + baseValueIt = baseIter.next(); + } + } + + if (!result) { + diffBuilder.record(a, b); + return false; + } } else { // Objects with different constructors are not equivalent, but `Object`s