Repository: unexpectedjs/unexpected
Pull request #855 (2023-07-10 22:13:36)

Don't require `array-like` subtypes to have the same constructor in order to be equal
Updating `jsdom` in `unexpected-dom` is [causing trouble](https://github.com/unexpectedjs/unexpected-dom/pull/464) because `NodeList` instances from different windows [have different constructors](https://github.com/jsdom/jsdom/issues/3575) in jsdom 16 and above.

Seems like the easiest way to get unexpected and unexpected-dom to DTRT in that kind of scenario is to relax this requirement a bit and only require the objects to have the same `array-like` subtype.


Patch:
diff --git a/lib/types.js b/lib/types.js
index 01b5fbc4..0a4e1fc1 100644
--- a/lib/types.js
+++ b/lib/types.js
@@ -453,50 +453,49 @@ module.exports = function (expect) {
     equal(a, b, equal) {
       if (a === b) {
         return true;
-      } else if (a.constructor === b.constructor && a.length === b.length) {
-        let i;
+      } else if (a.length !== b.length) {
+        return false;
+      } else if (expect.findTypeOf(a) !== expect.findTypeOf(b)) {
+        return false;
+      }
 
-        // compare numerically indexed elements
-        for (i = 0; i < a.length; i += 1) {
-          if (!equal(this.valueForKey(a, i), this.valueForKey(b, i))) {
-            return false;
-          }
+      let i;
+
+      // compare numerically indexed elements
+      for (i = 0; i < a.length; i += 1) {
+        if (!equal(this.valueForKey(a, i), this.valueForKey(b, i))) {
+          return false;
         }
+      }
 
-        // compare non-numerical keys if enabled for the type
-        if (!this.numericalPropertiesOnly) {
-          const aKeys = this.getKeysNonNumerical(a).filter((key) => {
-            // include keys whose value is not undefined
-            return typeof this.valueForKey(a, key) !== 'undefined';
-          });
-          const bKeys = this.getKeysNonNumerical(b).filter((key) => {
-            // include keys whose value is not undefined on either LHS or RHS
-            return (
-              typeof this.valueForKey(b, key) !== 'undefined' ||
-              typeof this.valueForKey(a, key) !== 'undefined'
-            );
-          });
+      // compare non-numerical keys if enabled for the type
+      if (!this.numericalPropertiesOnly) {
+        const aKeys = this.getKeysNonNumerical(a).filter((key) => {
+          // include keys whose value is not undefined
+          return typeof this.valueForKey(a, key) !== 'undefined';
+        });
+        const bKeys = this.getKeysNonNumerical(b).filter((key) => {
+          // include keys whose value is not undefined on either LHS or RHS
+          return (
+            typeof this.valueForKey(b, key) !== 'undefined' ||
+            typeof this.valueForKey(a, key) !== 'undefined'
+          );
+        });
 
-          if (aKeys.length !== bKeys.length) {
-            return false;
-          }
+        if (aKeys.length !== bKeys.length) {
+          return false;
+        }
 
-          for (i = 0; i < aKeys.length; i += 1) {
-            if (
-              !equal(
-                this.valueForKey(a, aKeys[i]),
-                this.valueForKey(b, bKeys[i])
-              )
-            ) {
-              return false;
-            }
+        for (i = 0; i < aKeys.length; i += 1) {
+          if (
+            !equal(this.valueForKey(a, aKeys[i]), this.valueForKey(b, bKeys[i]))
+          ) {
+            return false;
           }
         }
-
-        return true;
-      } else {
-        return false;
       }
+
+      return true;
     },
     prefix(output) {
       return output.text('[');
@@ -593,7 +592,7 @@ module.exports = function (expect) {
         return output;
       }
 
-      if (actual.constructor !== expected.constructor) {
+      if (expect.findTypeOf(actual) !== expect.findTypeOf(expected)) {
         return this.baseType.diff(actual, expected, output);
       }
 


Test patch:
diff --git a/test/types/array-like-type.spec.js b/test/types/array-like-type.spec.js
index 8ce345d5..132d5edf 100644
--- a/test/types/array-like-type.spec.js
+++ b/test/types/array-like-type.spec.js
@@ -144,6 +144,62 @@ describe('array-like type', () => {
         }
       );
     }
+
+    describe('when comparing instances of different classes that are identified by the same array-like subtype', () => {
+      clonedExpect.addType({
+        name: 'subtypeOfSimpleArrayLike',
+        base: 'array-like',
+        identify(value) {
+          return value && value._subtypeOfSimpleArrayLike;
+        },
+        numericalPropertiesOnly: true,
+      });
+
+      class MyArray extends Array {
+        constructor(value) {
+          super(value);
+          this._subtypeOfSimpleArrayLike = true;
+        }
+      }
+
+      class MyOtherArray extends Array {
+        constructor(value) {
+          super(value);
+          this._subtypeOfSimpleArrayLike = true;
+        }
+      }
+
+      it('should succeed when the elements match', () => {
+        const a = new MyArray(1);
+        a.length = 1;
+        a[0] = 123;
+        const b = new MyOtherArray(1);
+        b.length = 1;
+        b[0] = 123;
+
+        clonedExpect(a, 'to equal', b);
+        clonedExpect(b, 'to equal', a);
+      });
+
+      it('should fail with a diff when the elements do not match', () => {
+        const a = new MyArray(1);
+        a.length = 1;
+        a[0] = 123;
+        const b = new MyOtherArray(1);
+        b.length = 1;
+        b[0] = 456;
+
+        expect(
+          () => clonedExpect(a, 'to equal', b),
+          'to throw',
+          'expected [ 123 ] to equal [ 456 ]\n' +
+            '\n' +
+            '[\n' +
+            '  123 // should equal 456\n' +
+            ']'
+        );
+      });
+    });
   });
 
   describe('with a subtype that disables indentation', () => {