diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 7777fb1b0..890c3094a 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -407,6 +407,18 @@ function hasIterator(object: any) { return !!(object != null && object[IteratorSymbol]) } +type TypedArray = ArrayBufferView & ArrayLike + +function isTypedArray(value: unknown): value is TypedArray { + return ArrayBuffer.isView(value) +} + +function getNonIndexEntries(typedArray: TypedArray): Array<[string, unknown]> { + return Object.keys(typedArray) + .slice(typedArray.length) + .map((key) => [key, Reflect.get(typedArray, key)]) +} + export function iterableEquality( a: any, b: any, @@ -507,6 +519,23 @@ export function iterableEquality( } } + if (isTypedArray(a) && isTypedArray(b)) { + if (a.length !== b.length) { + return false + } + for (let i = 0; i < a.length; i++) { + if (!Object.is(a[i], b[i]) && !equals(a[i], b[i], filteredCustomTesters)) { + return false + } + } + if (!equals(getNonIndexEntries(a), getNonIndexEntries(b), filteredCustomTesters)) { + return false + } + aStack.pop() + bStack.pop() + return true + } + const bIterator = b[IteratorSymbol]() for (const aValue of a) { diff --git a/test/unit/test/jest-expect.test.ts b/test/unit/test/jest-expect.test.ts index 36151bc24..0e283b516 100644 --- a/test/unit/test/jest-expect.test.ts +++ b/test/unit/test/jest-expect.test.ts @@ -1,7 +1,7 @@ /* oxlint-disable no-sparse-arrays */ import nodeAssert, { AssertionError } from 'node:assert' import { stripVTControlCharacters } from 'node:util' -import { generateToBeMessage } from '@vitest/expect' +import { equals, generateToBeMessage, iterableEquality } from '@vitest/expect' import { processError } from '@vitest/utils/error' import { assert, describe, expect, it, vi } from 'vitest' @@ -711,6 +711,47 @@ describe('.toStrictEqual()', () => { }) }) +describe('typed arrays equality', () => { + it('compares length and elements', () => { + expect(Uint8Array.from([1, 2, 3])).toEqual(Uint8Array.from([1, 2, 3])) + expect(Uint8Array.from([1, 2, 3])).not.toEqual(Uint8Array.from([1, 2, 4])) + expect(Uint8Array.from([1, 2])).not.toEqual(Uint8Array.from([1, 2, 3])) + }) + + it('compares NaN and signed zero like numbers', () => { + expect(Float64Array.from([Number.NaN])).toEqual(Float64Array.from([Number.NaN])) + expect(Float64Array.from([0])).not.toEqual(Float64Array.from([-0])) + }) + + it('compares own properties that are not indexes', () => { + const create = (props: Record = {}) => + Object.assign(Uint8Array.from([1, 2]), props) + + expect(create({ label: 'a' })).toEqual(create({ label: 'a' })) + expect(create({ label: 'a' })).not.toEqual(create({ label: 'b' })) + expect(create({ label: 'a' })).not.toEqual(create({ name: 'a' })) + expect(create({ label: 'a' })).not.toEqual(create()) + }) + + it('calls testers only for elements that differ', () => { + const compared: unknown[] = [] + const recordNumbers = (a: unknown, b: unknown) => { + if (typeof a === 'number') { + compared.push([a, b]) + } + return undefined + } + + expect( + equals(Uint8Array.from([1, 2, 3]), Uint8Array.from([1, 2, 4]), [ + recordNumbers, + iterableEquality, + ]), + ).toBe(false) + expect(compared).toEqual([[3, 4]]) + }) +}) + describe('toBeTypeOf()', () => { it.each([ [1n, 'bigint'],