Skip to content

Commit 314df33

Browse files
authored
fix(expect-utils): iterableEquality should not throw on exotic iterables (#15952)
1 parent 9b1171f commit 314df33

4 files changed

Lines changed: 45 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
### Fixes
2121

22-
- `[expect-utils]` Fix `toStrictEqual` failing on `structuredClone` results due to cross-realm constructor mismatch ([#14011](https://github.com/jestjs/jest/issues/14011))
22+
- `[expect-utils]` Fix `toStrictEqual` failing on `structuredClone` results due to cross-realm constructor mismatch ([#15959](https://github.com/jestjs/jest/pull/15959))
23+
- `[@jest/expect-utils]` Prevent `toMatchObject`/subset matching from throwing when encountering exotic iterables ([#15952](https://github.com/jestjs/jest/pull/15952))
2324
- `[fake-timers]` Convert `Date` to milliseconds before passing to `@sinonjs/fake-timers` ([#16029](https://github.com/jestjs/jest/pull/16029))
2425
- `[jest-circus]` Prevent crash when `asyncError` is undefined for non-Error throws ([#16003](https://github.com/jestjs/jest/pull/16003))
2526
- `[jest-circus, jest-jasmine2]` Include `Error.cause` in JSON `failureMessages` output ([#15949](https://github.com/jestjs/jest/issues/15949))

packages/expect-utils/src/__tests__/utils.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,18 @@ describe('iterableEquality', () => {
653653

654654
expect(iterableEquality(a, b)).toBe(false);
655655
});
656+
657+
test('does not throw when iterating an object with a TypedArray iterator', () => {
658+
const badIterable = {
659+
[Symbol.iterator]: Uint8Array.prototype[Symbol.iterator],
660+
};
661+
662+
expect(() => iterableEquality(badIterable, badIterable)).not.toThrow();
663+
// Returns undefined so equals() can fall through to Object.is / property checks.
664+
expect(iterableEquality(badIterable, badIterable)).toBeUndefined();
665+
// Same reference must still be considered equal via equals().
666+
expect(equals(badIterable, badIterable, [iterableEquality])).toBe(true);
667+
});
656668
});
657669

658670
describe('typeEquality', () => {

packages/expect-utils/src/utils.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,30 @@ export const iterableEquality = (
303303
}
304304
}
305305

306-
const bIterator = b[IteratorSymbol]();
306+
let aIterator: Iterator<unknown>;
307+
let bIterator: Iterator<unknown>;
308+
try {
309+
aIterator = a[IteratorSymbol]();
310+
bIterator = b[IteratorSymbol]();
311+
} catch {
312+
// If the iterator factory itself throws (e.g. a TypedArray method used as
313+
// [Symbol.iterator] on a plain object), we cannot compare as iterables.
314+
// Return undefined so equals() falls through to Object.is / property checks.
315+
aStack.pop();
316+
bStack.pop();
317+
return undefined;
318+
}
307319

308-
for (const aValue of a) {
309-
const nextB = bIterator.next();
310-
if (nextB.done || !equals(aValue, nextB.value, filteredCustomTesters)) {
320+
let aStep = aIterator.next();
321+
while (!aStep.done) {
322+
const bStep = bIterator.next();
323+
if (
324+
bStep.done ||
325+
!equals(aStep.value, bStep.value, filteredCustomTesters)
326+
) {
311327
return false;
312328
}
329+
aStep = aIterator.next();
313330
}
314331
if (!bIterator.next().done) {
315332
return false;

packages/expect/src/__tests__/matchers.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2389,4 +2389,14 @@ describe('toMatchObject()', () => {
23892389
[sym]: true,
23902390
});
23912391
});
2392+
2393+
test('does not throw on exotic iterables (e.g. TypedArray iterator on plain object)', () => {
2394+
const badIterable = {
2395+
[Symbol.iterator]: Uint8Array.prototype[Symbol.iterator],
2396+
};
2397+
expect(() =>
2398+
jestExpect([badIterable]).toMatchObject([badIterable]),
2399+
).not.toThrow();
2400+
jestExpect([badIterable]).toMatchObject([badIterable]);
2401+
});
23922402
});

0 commit comments

Comments
 (0)