feat(vanilla): update shallow compare function and tests (#3108)

* feat(vanilla): update shallow compare function and tests

* prefer Object.getPrototypeOf

* docs: update shallow docs

* feat: minor fixes

---------

Co-authored-by: daishi <daishi@axlight.com>
Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
This commit is contained in:
Danilo Britto 2025-05-20 19:46:37 -05:00 committed by GitHub
parent 37878da019
commit a56a3e4bde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 8 deletions

View File

@ -25,6 +25,7 @@ const equal = shallow(a, b)
- [Comparing Maps](#comparing-maps)
- [Troubleshooting](#troubleshooting)
- [Comparing objects returns `false` even if they are identical.](#comparing-objects-returns-false-even-if-they-are-identical)
- [Comparing objects with different prototypes](#comparing-objects-with-different-prototypes)
## Types
@ -224,3 +225,24 @@ In this modified example, `objectLeft` and `objectRight` have the same top-level
primitive values. Since `shallow` function only compares the top-level properties, it will return
`true` because the primitive values (`firstName`, `lastName`, and `age`) are identical in both
objects.
### Comparing objects with different prototypes
The `shallow` function checks whether the two objects have the same prototype. If their prototypes
are referentially different, shallow will return `false`. This comparison is done using:
```ts
Object.getPrototypeOf(a) === Object.getPrototypeOf(b)
```
> [!IMPORTANT]
> Objects created with the object initializer (`{}`) or with `new Object()` inherit from
> `Object.prototype` by default. However, objects created with `Object.create(proto)` inherit from
> the proto you pass in—which may not be `Object.prototype.`
```ts
const a = Object.create({}) // -> prototype is `{}`
const b = {} // -> prototype is `Object.prototype`
shallow(a, b) // -> false
```

View File

@ -57,14 +57,18 @@ export function shallow<T>(valueA: T, valueB: T): boolean {
) {
return false
}
if (!isIterable(valueA) || !isIterable(valueB)) {
return compareEntries(
{ entries: () => Object.entries(valueA) },
{ entries: () => Object.entries(valueB) },
)
if (Object.getPrototypeOf(valueA) !== Object.getPrototypeOf(valueB)) {
return false
}
if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
return compareEntries(valueA, valueB)
if (isIterable(valueA) && isIterable(valueB)) {
if (hasIterableEntries(valueA) && hasIterableEntries(valueB)) {
return compareEntries(valueA, valueB)
}
return compareIterables(valueA, valueB)
}
return compareIterables(valueA, valueB)
// assume plain objects
return compareEntries(
{ entries: () => Object.entries(valueA) },
{ entries: () => Object.entries(valueB) },
)
}

View File

@ -172,6 +172,25 @@ describe('shallow', () => {
})
})
describe('mixed cases', () => {
const obj = { 0: 'foo', 1: 'bar' }
const arr = ['foo', 'bar']
const set = new Set(['foo', 'bar'])
const map = new Map([
[0, 'foo'],
[1, 'bar'],
])
it('compares different data structures', () => {
expect(shallow<unknown>(obj, arr)).toBe(false)
expect(shallow<unknown>(obj, set)).toBe(false)
expect(shallow<unknown>(obj, map)).toBe(false)
expect(shallow<unknown>(arr, set)).toBe(false)
expect(shallow<unknown>(arr, map)).toBe(false)
expect(shallow<unknown>(set, map)).toBe(false)
})
})
describe('generators', () => {
it('pure iterable', () => {
function* gen() {