Repository: jestjs/jest (TypeScript)

Issue:
[Bug]: Jest 28 fails on Immutable.List().filter toEqual comparison
### Version

28.0.1

### Steps to reproduce

1. Install latest `immutable` - (e.g. `yarn add immutable@^4.0.0`).
2. Create the following test file:
    
    ```js
    const { List } = require("immutable");
    const numbers = List([1, 2]);
    
    describe("test", () => {
      it("fails in Jest 28.0.1, passes in Jest 27.5.1", () => {
        const twos = numbers.filter((num) => num === 2);
        expect(twos).toEqual(List([2]));
      });
    });
    ```
3. Run the file in Jest 28.0.1.

### Expected behavior

I expect the test to pass, as it does in Jest 27.5.1

### Actual behavior

 The test fails with the following error:
    
    ```
     FAIL  ./example.test.js
      test
        ✕ fails in Jest 28.0.1, passes in Jest 27.5.1 (6 ms)
    
      ● test › fails in Jest 28.0.1, passes in Jest 27.5.1
    
        expect(received).toEqual(expected) // deep equality
    
        Expected: Immutable.List [2]
        Received: serializes to the same string
    
           5 | 	it("fails in Jest 28.0.1, passes in Jest 27.5.1", () => {
           6 | 		const evens = numbers.filter((num) => num === 2);
        >  7 | 		expect(evens).toEqual(List([2]));
             | 		              ^
           8 | 	});
           9 | });
          10 |
    
          at Object.toEqual (example.test.js:7:17)
    ```


### Additional context

I see the same issue with `immutable@^3` as well.

Other deep equality comparisons with ImmutableJS objects that you'd expect to pass, do pass in Jest 28:

```js
const { List, Map } = require("immutable");

it("passes1", () => {
    expect(List([2])).toEqual(List([2]));
});

it("passes2", () => {
    expect(Map({ hello: "world" })).toEqual(Map({ hello: "world" }));
});

it("passes3", () => {
    expect(List([Map({ hello: "world" }), Map({ foo: "bar" })])).toEqual(
        List([Map({ hello: "world" }), Map({ foo: "bar" })])
    );
});
```

The only reliable one I can reproduce is when a `List` gets `filter`'d.



### Environment

```shell
System:
    OS: macOS 10.15.7
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
  Binaries:
    Node: 16.14.2 - ~/.nvm/versions/node/v16.14.2/bin/node
    Yarn: 1.22.18 - ~/.nvm/versions/node/v16.14.2/bin/yarn
    npm: 8.5.0 - ~/.nvm/versions/node/v16.14.2/bin/npm
  npmPackages:
    jest: ^27 => 27.5.1
```



Hints:
Bisecting, this was introduced in #8359.

@romellem would you be able to provide a PR fixing this? 🙂 
I can definitely take a look. Thanks for tracking down the offending commit, I'll do some digging and see what [filter][1] is doing with `entries` compared a regular `List`.

Any ideas that are coming to mind?

[1]: https://immutable-js.com/docs/v4.0.0/List/#filter()
Had some time to dig in, the problem is `.filter` (maybe other Collection methods too?) sets an `ownerID` on the `_tail` property whereas a direct `List()` does not. Since Lists are iterable, this path gets hit and eventually gets flagged as a difference.

<table><tr><td><code>List([1]).filter(v => v === 1)</code></td><td><code>List([1])</code></td></tr>
<tr><td><img width="300" src="https://user-images.githubusercontent.com/8504000/165560503-dea2919b-5356-4403-98d8-7bd9db815cc5.png" alt="filtered"></td><td>

<img width="300" src="https://user-images.githubusercontent.com/8504000/165560520-bc14d590-08d3-4fec-8ead-95557ac4b9a4.png" alt="direct"></td></tr></table>

I see there are checks for [`isImmutableUnorderedSet()`](https://github.com/facebook/jest/blob/27eb77e86f388ee88fd3badb7ce6e872d82405ad/packages/expect-utils/src/utils.ts#L183) and [`isImmutableUnorderedKeyed()`](https://github.com/facebook/jest/blob/27eb77e86f388ee88fd3badb7ce6e872d82405ad/packages/expect-utils/src/utils.ts#L205). Could a fix for this be to check if the object `isList` via its [`'@@__IMMUTABLE_LIST__@@'`](https://github.com/immutable-js/immutable-js/blob/f2dec5aa72c8ce0d893fa1b0eebe67f254ee2034/src/predicates/isList.js#L1) key, and check its iterable values similar to what happens with a `Set`?


That sounds reasonable to me!

Patch:
diff --git a/packages/expect-utils/package.json b/packages/expect-utils/package.json
--- a/packages/expect-utils/package.json
+++ b/packages/expect-utils/package.json
@@ -20,6 +20,7 @@
     "jest-get-type": "^28.0.2"
   },
   "devDependencies": {
+    "immutable": "^4.0.0",
     "jest-matcher-utils": "^28.0.2"
   },
   "engines": {
diff --git a/packages/expect-utils/src/jasmineUtils.ts b/packages/expect-utils/src/jasmineUtils.ts
--- a/packages/expect-utils/src/jasmineUtils.ts
+++ b/packages/expect-utils/src/jasmineUtils.ts
@@ -238,9 +238,10 @@ function isDomNode(obj: any): boolean {
   );
 }
 
-// SENTINEL constants are from https://github.com/facebook/immutable-js
+// SENTINEL constants are from https://github.com/immutable-js/immutable-js/tree/main/src/predicates
 const IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@';
 const IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@';
+const IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@';
 const IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@';
 
 export function isImmutableUnorderedKeyed(maybeKeyed: any) {
@@ -258,3 +259,10 @@ export function isImmutableUnorderedSet(maybeSet: any) {
     !maybeSet[IS_ORDERED_SENTINEL]
   );
 }
+
+export function isImmutableList(maybeList: any) {
+  return !!(
+    maybeList &&
+    maybeList[IS_LIST_SENTINEL]
+  );
+}
diff --git a/packages/expect-utils/src/utils.ts b/packages/expect-utils/src/utils.ts
--- a/packages/expect-utils/src/utils.ts
+++ b/packages/expect-utils/src/utils.ts
@@ -10,6 +10,7 @@ import {isPrimitive} from 'jest-get-type';
 import {
   equals,
   isA,
+  isImmutableList,
   isImmutableUnorderedKeyed,
   isImmutableUnorderedSet,
 } from './jasmineUtils';
@@ -254,10 +255,12 @@ export const iterableEquality = (
     return false;
   }
 
-  const aEntries = Object.entries(a);
-  const bEntries = Object.entries(b);
-  if (!equals(aEntries, bEntries)) {
-    return false;
+  if (!isImmutableList(a)) {
+    const aEntries = Object.entries(a);
+    const bEntries = Object.entries(b);
+    if (!equals(aEntries, bEntries)) {
+      return false;
+    }
   }
 
   // Remove the first value from the stack of traversed values.


Test patch:
diff --git a/packages/expect-utils/src/__tests__/utils.test.ts b/packages/expect-utils/src/__tests__/utils.test.ts
--- a/packages/expect-utils/src/__tests__/utils.test.ts
+++ b/packages/expect-utils/src/__tests__/utils.test.ts
@@ -6,6 +6,7 @@
  *
  */
 
+import {List} from 'immutable';
 import {stringify} from 'jest-matcher-utils';
 import {
   arrayBufferEquality,
@@ -517,6 +518,13 @@ describe('iterableEquality', () => {
 
     expect(iterableEquality(a, b)).toBe(true);
   });
+
+  test('returns true when given Immutable Lists without an OwnerID', () => {
+    const a = List([1, 2, 3]);
+    const b = a.filter(v => v > 0);
+
+    expect(iterableEquality(a, b)).toBe(true);
+  });
 });
 
 describe('arrayBufferEquality', () => {