Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(shallow): iterable-like insensitive keys order comparison #2816

Closed
wants to merge 13 commits into from
69 changes: 20 additions & 49 deletions src/vanilla/shallow.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
const isIterable = (obj: object): obj is Iterable<unknown> =>
type IterableLike<T> = Iterable<T> & {
entries: () => IterableIterator<[number, T]>
}

const isIterable = (obj: object): obj is IterableLike<unknown> =>
Symbol.iterator in obj
dbritto-dev marked this conversation as resolved.
Show resolved Hide resolved

const compareMapLike = (
iterableA: Iterable<[unknown, unknown]>,
iterableB: Iterable<[unknown, unknown]>,
) => {
const mapA = iterableA instanceof Map ? iterableA : new Map(iterableA)
const mapB = iterableB instanceof Map ? iterableB : new Map(iterableB)
if (mapA.size !== mapB.size) return false
for (const [key, value] of mapA) {
if (!Object.is(value, mapB.get(key))) {
const toObject = (value: IterableLike<unknown>) =>
Object.fromEntries(value.entries())
dbritto-dev marked this conversation as resolved.
Show resolved Hide resolved

const compareObjects = <T extends object>(objA: T, objB: T) => {
const keysA = Object.keys(objA)
if (keysA.length !== Object.keys(objB).length) return false
for (const keyA of keysA) {
if (
!Object.hasOwn(objB, keyA as string) ||
!Object.is(objA[keyA as keyof T], objB[keyA as keyof T])
) {
return false
}
}
return true
}

export function shallow<T>(objA: T, objB: T): boolean {
if (Object.is(objA, objB)) {
return true
}
if (Object.is(objA, objB)) return true

if (
typeof objA !== 'object' ||
objA === null ||
Expand All @@ -30,42 +35,8 @@ export function shallow<T>(objA: T, objB: T): boolean {
}

if (isIterable(objA) && isIterable(objB)) {
const iteratorA = objA[Symbol.iterator]()
const iteratorB = objB[Symbol.iterator]()
let nextA = iteratorA.next()
let nextB = iteratorB.next()
if (
Array.isArray(nextA.value) &&
Array.isArray(nextB.value) &&
nextA.value.length === 2 &&
nextB.value.length === 2
) {
return compareMapLike(
objA as Iterable<[unknown, unknown]>,
objB as Iterable<[unknown, unknown]>,
)
}
while (!nextA.done && !nextB.done) {
if (!Object.is(nextA.value, nextB.value)) {
return false
}
nextA = iteratorA.next()
nextB = iteratorB.next()
}
return !!nextA.done && !!nextB.done
return compareObjects(toObject(objA), toObject(objB))
}

const keysA = Object.keys(objA)
if (keysA.length !== Object.keys(objB).length) {
return false
}
for (const keyA of keysA) {
if (
!Object.hasOwn(objB, keyA as string) ||
!Object.is(objA[keyA as keyof T], objB[keyA as keyof T])
) {
return false
}
}
return true
return compareObjects(objA, objB)
}
51 changes: 42 additions & 9 deletions tests/vanilla/shallow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,38 +37,71 @@ describe('shallow', () => {
).toBe(false)

expect(shallow([{ foo: 'bar' }], [{ foo: 'bar', asd: 123 }])).toBe(false)

const arr = [1, 2]
expect(shallow([arr, 1], [arr, 1])).toBe(true)
dbritto-dev marked this conversation as resolved.
Show resolved Hide resolved
})

it('compares Maps', () => {
function createMap<T extends object>(obj: T) {
return new Map(Object.entries(obj))
}
expect(
shallow(
new Map<string, unknown>([
['foo', 'bar'],
['asd', 123],
]),
new Map<string, unknown>([
['foo', 'bar'],
['asd', 123],
]),
),
).toBe(true)

expect(
shallow(
createMap({ foo: 'bar', asd: 123 }),
createMap({ foo: 'bar', asd: 123 }),
new Map<string, unknown>([
['foo', 'bar'],
['asd', 123],
]),
new Map<string, unknown>([
['asd', 123],
['foo', 'bar'],
]),
),
).toBe(true)

expect(
shallow(
createMap({ foo: 'bar', asd: 123 }),
createMap({ foo: 'bar', foobar: true }),
new Map<string, unknown>([
['foo', 'bar'],
['asd', 123],
]),
new Map<string, unknown>([
['foo', 'bar'],
['foobar', true],
]),
),
).toBe(false)

expect(
shallow(
createMap({ foo: 'bar', asd: 123 }),
createMap({ foo: 'bar', asd: 123, foobar: true }),
new Map<string, unknown>([
['foo', 'bar'],
['asd', 123],
]),
new Map<string, unknown>([
['foo', 'bar'],
['asd', 123],
['foobar', true],
]),
),
).toBe(false)
})

it('compares Sets', () => {
expect(shallow(new Set(['bar', 123]), new Set(['bar', 123]))).toBe(true)

expect(shallow(new Set(['bar', 123]), new Set([123, 'bar']))).toBe(true)

expect(shallow(new Set(['bar', 123]), new Set(['bar', 2]))).toBe(false)

expect(shallow(new Set(['bar', 123]), new Set(['bar', 123, true]))).toBe(
Expand Down