Skip to content

Commit 97ba151

Browse files
fix(kitsu-core): ensure linkedRelationships does not overwrite meta object references (#783)
* Fix: Pick the meta object from actual resource rather from cached resource if it is different * Fix code review comments * Address code review comments * docs: fix jsdoc syntax to use Object instead of object * test(deepEqual): add additional tests to cover uncovered branches * test(linkRelationships): add additional test to cover isDeepEqual path Co-authored-by: wopian <wopian@wopian.me>
1 parent d34e871 commit 97ba151

4 files changed

Lines changed: 480 additions & 1 deletion

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Compare two objects equality
3+
*
4+
* @param {Object} left Object to compare against the right object
5+
* @param {Object} right Object to compare against the left object
6+
* @returns {boolean} Whether the objects are equal
7+
* @example <caption>Deep equality check</caption>
8+
* isDeepEqual({
9+
* firstName: 'John',
10+
* lastName: 'Doe',
11+
* age: 35
12+
* },{
13+
* firstName: 'John',
14+
* lastName: 'Doe',
15+
* age: 35
16+
* }) // true
17+
*/
18+
export const isDeepEqual = (left, right) => {
19+
if (!left || !right) {
20+
return left === right
21+
}
22+
23+
const leftKeys = Object.keys(left)
24+
const rightKeys = Object.keys(right)
25+
26+
if (leftKeys.length !== rightKeys.length) return false
27+
28+
for (const key of leftKeys) {
29+
const leftValue = left[key]
30+
const rightValue = right[key]
31+
32+
const isObjects = isObject(leftValue) && isObject(rightValue)
33+
34+
if ((isObjects && !isDeepEqual(leftValue, rightValue)) ||
35+
(!isObjects && leftValue !== rightValue)
36+
) {
37+
return false
38+
}
39+
}
40+
41+
return true
42+
}
43+
44+
/**
45+
* Check for Object
46+
*
47+
* @param {Object} object Value to check if it is an object
48+
* @returns {boolean} Whether the value is an object
49+
* @private
50+
* @example <caption>Check for object</caption>
51+
* isObject({
52+
* firstName: 'John',
53+
* lastName: 'Doe',
54+
* age: 35
55+
* }) // true
56+
*/
57+
const isObject = (object) => {
58+
return object != null && typeof object === 'object'
59+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { isDeepEqual } from './'
2+
3+
describe('kitsu-core', () => {
4+
describe('isDeepEqual', () => {
5+
const people = {
6+
one: {
7+
firstName: 'John',
8+
lastName: 'Doe',
9+
age: 35
10+
},
11+
two: {
12+
firstName: 'John',
13+
lastName: 'Doe',
14+
age: 35
15+
},
16+
three: {
17+
firstName: 'Akash',
18+
lastName: 'Thakur',
19+
age: 35
20+
},
21+
four: {
22+
firstName: 'Jane',
23+
lastName: 'Doe'
24+
},
25+
five: {
26+
address: {
27+
street: '123 Main St'
28+
}
29+
},
30+
six: {
31+
address: {
32+
street: '123 Main St'
33+
}
34+
},
35+
seven: {
36+
address: {
37+
street: '456 Main St'
38+
}
39+
}
40+
}
41+
42+
it('checks indentical objects are equal', () => {
43+
expect.assertions(1)
44+
expect(isDeepEqual(people.one, people.two)).toBe(true)
45+
})
46+
47+
it('checks different objects are not equal', () => {
48+
expect.assertions(1)
49+
expect(isDeepEqual(people.one, people.three)).toBe(false)
50+
})
51+
52+
it('checks objects have the same number of keys', () => {
53+
expect.assertions(1)
54+
expect(isDeepEqual(people.one, people.four)).toBe(false)
55+
})
56+
57+
it('checks nested objects are equal', () => {
58+
expect.assertions(2)
59+
expect(isDeepEqual(people.five, people.six)).toBe(true)
60+
expect(isDeepEqual(people.five, people.seven)).toBe(false)
61+
})
62+
})
63+
})

packages/kitsu-core/src/linkRelationships/index.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { deattribute } from '../deattribute'
22
import { filterIncludes } from '../filterIncludes'
3+
import { isDeepEqual } from '../deepEqual'
34

45
/**
56
* Core function to link relationships to included data
@@ -67,7 +68,23 @@ function linkObject (data, included, key, previouslyLinked, relationshipCache) {
6768
data[key] = {}
6869
const resource = data.relationships[key].data
6970
const cache = previouslyLinked[`${resource.type}#${resource.id}`]
70-
data[key].data = cache || link(resource, included, previouslyLinked, relationshipCache)
71+
72+
if (cache) {
73+
let resourceCache = null
74+
// Comparing for cache entity meta and resource entity meta object.
75+
if (!isDeepEqual(cache.meta, resource.meta)) {
76+
resourceCache = {
77+
...cache,
78+
meta: resource.meta
79+
}
80+
} else {
81+
resourceCache = cache
82+
}
83+
84+
data[key].data = resourceCache
85+
} else {
86+
data[key].data = link(resource, included, previouslyLinked, relationshipCache)
87+
}
7188

7289
const cacheKey = `${data.type}#${data.id}#${key}`
7390
const relationships = relationshipCache[cacheKey] || data.relationships[key]

0 commit comments

Comments
 (0)