Skip to content

Commit 908eb57

Browse files
authored
perf(expect): compare typed arrays in one loop (#11658)
1 parent a3ed42a commit 908eb57

2 files changed

Lines changed: 71 additions & 1 deletion

File tree

‎packages/expect/src/jest-utils.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,18 @@ function hasIterator(object: any) {
407407
return !!(object != null && object[IteratorSymbol])
408408
}
409409

410+
type TypedArray = ArrayBufferView & ArrayLike<unknown>
411+
412+
function isTypedArray(value: unknown): value is TypedArray {
413+
return ArrayBuffer.isView(value)
414+
}
415+
416+
function getNonIndexEntries(typedArray: TypedArray): Array<[string, unknown]> {
417+
return Object.keys(typedArray)
418+
.slice(typedArray.length)
419+
.map((key) => [key, Reflect.get(typedArray, key)])
420+
}
421+
410422
export function iterableEquality(
411423
a: any,
412424
b: any,
@@ -507,6 +519,23 @@ export function iterableEquality(
507519
}
508520
}
509521

522+
if (isTypedArray(a) && isTypedArray(b)) {
523+
if (a.length !== b.length) {
524+
return false
525+
}
526+
for (let i = 0; i < a.length; i++) {
527+
if (!Object.is(a[i], b[i]) && !equals(a[i], b[i], filteredCustomTesters)) {
528+
return false
529+
}
530+
}
531+
if (!equals(getNonIndexEntries(a), getNonIndexEntries(b), filteredCustomTesters)) {
532+
return false
533+
}
534+
aStack.pop()
535+
bStack.pop()
536+
return true
537+
}
538+
510539
const bIterator = b[IteratorSymbol]()
511540

512541
for (const aValue of a) {

‎test/unit/test/jest-expect.test.ts‎

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* oxlint-disable no-sparse-arrays */
22
import nodeAssert, { AssertionError } from 'node:assert'
33
import { stripVTControlCharacters } from 'node:util'
4-
import { generateToBeMessage } from '@vitest/expect'
4+
import { equals, generateToBeMessage, iterableEquality } from '@vitest/expect'
55
import { processError } from '@vitest/utils/error'
66
import { assert, describe, expect, it, vi } from 'vitest'
77

@@ -711,6 +711,47 @@ describe('.toStrictEqual()', () => {
711711
})
712712
})
713713

714+
describe('typed arrays equality', () => {
715+
it('compares length and elements', () => {
716+
expect(Uint8Array.from([1, 2, 3])).toEqual(Uint8Array.from([1, 2, 3]))
717+
expect(Uint8Array.from([1, 2, 3])).not.toEqual(Uint8Array.from([1, 2, 4]))
718+
expect(Uint8Array.from([1, 2])).not.toEqual(Uint8Array.from([1, 2, 3]))
719+
})
720+
721+
it('compares NaN and signed zero like numbers', () => {
722+
expect(Float64Array.from([Number.NaN])).toEqual(Float64Array.from([Number.NaN]))
723+
expect(Float64Array.from([0])).not.toEqual(Float64Array.from([-0]))
724+
})
725+
726+
it('compares own properties that are not indexes', () => {
727+
const create = (props: Record<string, unknown> = {}) =>
728+
Object.assign(Uint8Array.from([1, 2]), props)
729+
730+
expect(create({ label: 'a' })).toEqual(create({ label: 'a' }))
731+
expect(create({ label: 'a' })).not.toEqual(create({ label: 'b' }))
732+
expect(create({ label: 'a' })).not.toEqual(create({ name: 'a' }))
733+
expect(create({ label: 'a' })).not.toEqual(create())
734+
})
735+
736+
it('calls testers only for elements that differ', () => {
737+
const compared: unknown[] = []
738+
const recordNumbers = (a: unknown, b: unknown) => {
739+
if (typeof a === 'number') {
740+
compared.push([a, b])
741+
}
742+
return undefined
743+
}
744+
745+
expect(
746+
equals(Uint8Array.from([1, 2, 3]), Uint8Array.from([1, 2, 4]), [
747+
recordNumbers,
748+
iterableEquality,
749+
]),
750+
).toBe(false)
751+
expect(compared).toEqual([[3, 4]])
752+
})
753+
})
754+
714755
describe('toBeTypeOf()', () => {
715756
it.each([
716757
[1n, 'bigint'],

0 commit comments

Comments
 (0)