Skip to content

Commit 3fbe355

Browse files
fatso83mroderick
andauthored
fix #251: compare props on the prototype chain (#267)
* fix #251: compare enumerable props on the prototype * Negate test: Arguments with same contents as an array is deeply equal Co-authored-by: Morgan Roderick <[email protected]> --------- Co-authored-by: Morgan Roderick <[email protected]>
1 parent 61cf8ba commit 3fbe355

File tree

2 files changed

+52
-16
lines changed

2 files changed

+52
-16
lines changed

lib/deep-equal.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ var valueToString = require("@sinonjs/commons").valueToString;
44
var className = require("@sinonjs/commons").className;
55
var typeOf = require("@sinonjs/commons").typeOf;
66
var arrayProto = require("@sinonjs/commons").prototypes.array;
7-
var objectProto = require("@sinonjs/commons").prototypes.object;
87
var mapForEach = require("@sinonjs/commons").prototypes.map.forEach;
98

109
var getClass = require("./get-class");
@@ -25,22 +24,38 @@ var every = arrayProto.every;
2524
var push = arrayProto.push;
2625

2726
var getTime = Date.prototype.getTime;
28-
var hasOwnProperty = objectProto.hasOwnProperty;
2927
var indexOf = arrayProto.indexOf;
30-
var keys = Object.keys;
3128
var getOwnPropertySymbols = Object.getOwnPropertySymbols;
3229

30+
/**
31+
* We explicitly want to get props on proto chain
32+
*
33+
* Objects such as URL in the browser do not have any
34+
* enumerable keys of its own. All meaningful props
35+
* to compare are enumerable getters further up the chain
36+
* @param {object} object
37+
* @returns {Array<string>} the list of enumerable keys
38+
*/
39+
function allEnumerableKeysInProtoChain(object) {
40+
const keys = [];
41+
42+
// eslint-disable-next-line
43+
for (const key in object) {
44+
keys.push(key);
45+
}
46+
return keys;
47+
}
48+
3349
/**
3450
* Deep equal comparison. Two values are "deep equal" when:
3551
*
36-
* - They are equal, according to samsam.identical
37-
* - They are both date objects representing the same time
38-
* - They are both arrays containing elements that are all deepEqual
39-
* - They are objects with the same set of properties, and each property
40-
* in ``actual`` is deepEqual to the corresponding property in ``expectation``
52+
* - They are equal, according to samsam.identical
53+
* - They are both date objects representing the same time
54+
* - They are both arrays containing elements that are all deepEqual
55+
* - They are objects with the same set of properties, and each property
56+
* in ``actual`` is deepEqual to the corresponding property in ``expectation``
4157
*
4258
* Supports cyclic objects.
43-
*
4459
* @alias module:samsam.deepEqual
4560
* @param {*} actual The object to examine
4661
* @param {*} expectation The object actual is expected to be equal to
@@ -129,8 +144,8 @@ function deepEqualCyclic(actual, expectation, match) {
129144

130145
var actualClass = getClass(actualObj);
131146
var expectationClass = getClass(expectationObj);
132-
var actualKeys = keys(actualObj);
133-
var expectationKeys = keys(expectationObj);
147+
var actualKeys = allEnumerableKeysInProtoChain(actualObj);
148+
var expectationKeys = allEnumerableKeysInProtoChain(expectationObj);
134149
var actualName = className(actualObj);
135150
var expectationName = className(expectationObj);
136151
var expectationSymbols =
@@ -231,10 +246,6 @@ function deepEqualCyclic(actual, expectation, match) {
231246
}
232247

233248
return every(expectationKeysAndSymbols, function (key) {
234-
if (!hasOwnProperty(actualObj, key)) {
235-
return false;
236-
}
237-
238249
var actualValue = actualObj[key];
239250
var expectationValue = expectationObj[key];
240251
var actualObject = isObject(actualValue);

lib/deep-equal.test.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,31 @@ describe("deepEqual", function () {
196196
assert.isTrue(checkDeep);
197197
});
198198

199+
it("compares enumerable props on the prototype", function () {
200+
class Url {
201+
#value;
202+
constructor(str) {
203+
this.#value = str;
204+
}
205+
get proto() {
206+
return this.#value.split("://")[0];
207+
}
208+
209+
get domain() {
210+
return this.#value.split("://")[1];
211+
}
212+
}
213+
// getters are not enumerable by default, but they are configurable, so let's make them enumerable!
214+
Object.defineProperty(Url.prototype, "proto", { enumerable: true });
215+
Object.defineProperty(Url.prototype, "domain", { enumerable: true });
216+
217+
const u1 = new Url("foo://bar");
218+
const u2 = new Url("bar://baz");
219+
const u3 = new Url("bar://baz");
220+
assert.isFalse(samsam.deepEqual(u1, u2));
221+
assert.isTrue(samsam.deepEqual(u3, u2));
222+
});
223+
199224
it("returns true object without prototype compared to equal object with prototype", function () {
200225
var obj1 = Object.create(null);
201226
obj1.a = 1;
@@ -484,7 +509,7 @@ describe("deepEqual", function () {
484509
[1, 2, {}, []],
485510
gather(1, 2, {}, []),
486511
);
487-
assert.isFalse(checkDeep);
512+
assert.isTrue(checkDeep);
488513
});
489514

490515
it("returns false if array like object to arguments", function () {

0 commit comments

Comments
 (0)