From ab8a9b3f3f4140c1f6e6780a1aec1606ce5238a2 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Mon, 17 Oct 2022 22:18:42 +0100 Subject: [PATCH] Assert: Revert change in how deepEqual treats imposter objects As part of https://github.com/qunitjs/qunit/pull/1700, there was an inintended change to how we compare constructors. Instead of comparing `obj.constructor` of the actual value, we started comparing the constructor property as it exists on the prototype before the constructor function runs, and thus also before any after-the-fact mutation to an object property. Undo that part of the change and add a regression test. Fixes https://github.com/qunitjs/qunit/issues/1706. --- src/equiv.js | 2 +- test/main/deepEqual.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/equiv.js b/src/equiv.js index 7f89b70ee..c67fcf4e6 100644 --- a/src/equiv.js +++ b/src/equiv.js @@ -31,7 +31,7 @@ function getConstructor (obj) { // // Allow objects with no prototype, from Object.create(null), to be equivalent to // plain objects that have Object as their constructor. - return !proto || proto.constructor === null ? Object : proto.constructor; + return !proto || proto.constructor === null ? Object : obj.constructor; } function getRegExpFlags (regexp) { diff --git a/test/main/deepEqual.js b/test/main/deepEqual.js index 689da76fb..8b14a6385 100644 --- a/test/main/deepEqual.js +++ b/test/main/deepEqual.js @@ -1310,6 +1310,41 @@ QUnit.test('Prototypal inheritance', function (assert) { assert.equal(QUnit.equiv(function () {}, function () {}), false); }); +QUnit.test('Prototypal inheritance imposter', function (assert) { + // Bar is a subclass of Foo that very loosely tries to hide itself + // as intermediary prototype by not adding or overriding any methods + // and not adding or overriding any instance properties, except to + // assign obj.constructor as Foo. + // + // This is a regression test for https://github.com/qunitjs/qunit/issues/1706. + // + // We may change this behaviour in a future major version, but for now + // ensure the behaviour does not change unintentionally. + // + // This difference has very low impact, given that any added methods + // or properties will result in non-equality regardless of + // obj.constructor being equal given that equiv() iterates and + // compares all own and inherited properties in a single pass. + // The only observable difference would be `instanceof` (in one + // direction) and possibly presence of non-enumerable properties. + function Foo (id) { + this.id = id; + + // Make subclass Bar pretend to be Foo in terms of obj.constructor, + // thus very loosely hiding that it is an intermediary prototype. + this.constructor = Foo; + } + Foo.prototype.constructor = Foo; + + function Bar (id) { + Foo.call(this, id); + } + Bar.prototype = Object.create(Foo.prototype); + Bar.prototype.constructor = Bar; + + assert.deepEqual(new Foo(4), new Bar(4)); +}); + QUnit.test('Instances', function (assert) { var a1, a2, b1, b2, c1, c2, c3, car, carSame, carDiff, human;