Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ae6464f

Browse files
Add array diff
1 parent a4a53a4 commit ae6464f

File tree

2 files changed

+241
-0
lines changed

2 files changed

+241
-0
lines changed

‎src/arrayDiff/index.js‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { isDate, isEmpty, isObject, properObject } from '../utils';
2+
3+
const diff = (lhs, rhs) => {
4+
if (lhs === rhs) return {}; // equal return no diff
5+
6+
if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs
7+
8+
const l = properObject(lhs);
9+
const r = properObject(rhs);
10+
11+
const deletedValues = Object.keys(l).reduce((acc, key) => {
12+
return r.hasOwnProperty(key) ? acc : { ...acc, [key]: undefined };
13+
}, {});
14+
15+
if (isDate(l) || isDate(r)) {
16+
if (l.valueOf() == r.valueOf()) return {};
17+
return r;
18+
}
19+
20+
if (Array.isArray(r) && Array.isArray(l)) {
21+
const deletedValues = l.reduce((acc, item, index) => {
22+
return r.hasOwnProperty(index) ? acc.concat(item) : acc.concat(undefined);
23+
}, []);
24+
25+
return r.reduce((acc, rightItem, index) => {
26+
if (!deletedValues.hasOwnProperty(index)) {
27+
return acc.concat(rightItem);
28+
}
29+
30+
const leftItem = l[index];
31+
const difference = diff(rightItem, leftItem);
32+
33+
if (isObject(difference) && isEmpty(difference) && !isDate(difference)) {
34+
delete acc[index];
35+
return acc; // return no diff
36+
}
37+
38+
return acc.slice(0, index).concat(rightItem).concat(acc.slice(index + 1)); // return updated key
39+
}, deletedValues);
40+
}
41+
42+
return Object.keys(r).reduce((acc, key) => {
43+
if (!l.hasOwnProperty(key)) return { ...acc, [key]: r[key] }; // return added r key
44+
45+
const difference = diff(l[key], r[key]);
46+
47+
if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc; // return no diff
48+
49+
return { ...acc, [key]: difference }; // return updated key
50+
}, deletedValues);
51+
};
52+
53+
export default diff;

‎src/arrayDiff/index.test.js‎

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import forEach from 'jest-each';
2+
3+
import diff from './';
4+
5+
describe('.arrayDiff', () => {
6+
7+
describe('base case', () => {
8+
describe('equal', () => {
9+
forEach([
10+
['int', 1],
11+
['string', 'a'],
12+
['boolean', true],
13+
['null', null],
14+
['undefined', undefined],
15+
['object', { a: 1 }],
16+
['array', [1]],
17+
['function', () => ({})],
18+
['date', new Date()],
19+
['date with milliseconds', new Date('2017年01月01日T00:00:00.637Z')],
20+
]).test('returns empty object when given values of type %s are equal', (type, value) => {
21+
expect(diff(value, value)).toEqual({});
22+
});
23+
});
24+
25+
describe('not equal and not object', () => {
26+
forEach([
27+
[1, 2],
28+
['a', 'b'],
29+
[true, false],
30+
['hello', null],
31+
['hello', undefined],
32+
[null, undefined],
33+
[undefined, null],
34+
[null, { a: 1 }],
35+
['872983', { areaCode: '+44', number: '872983' }],
36+
[100, () => ({})],
37+
[() => ({}), 100],
38+
[new Date('2017年01月01日'), new Date('2017年01月02日')],
39+
[new Date('2017年01月01日T00:00:00.636Z'), new Date('2017年01月01日T00:00:00.637Z')],
40+
]).test('returns right hand side value when different to left hand side value (%s, %s)', (lhs, rhs) => {
41+
expect(diff(lhs, rhs)).toEqual(rhs);
42+
});
43+
});
44+
});
45+
46+
describe('recursive case', () => {
47+
describe('object', () => {
48+
test('returns right hand side value when given objects are different', () => {
49+
expect(diff({ a: 1 }, { a: 2 })).toEqual({ a: 2 });
50+
});
51+
52+
test('returns right hand side value when right hand side value is null', () => {
53+
expect(diff({ a: 1 }, { a: null })).toEqual({ a: null });
54+
});
55+
56+
test('returns subset of right hand side value when sibling objects differ', () => {
57+
expect(diff({ a: { b: 1 }, c: 2 }, { a: { b: 1 }, c: 3 })).toEqual({ c: 3 });
58+
});
59+
60+
test('returns subset of right hand side value when nested values differ', () => {
61+
expect(diff({ a: { b: 1, c: 2} }, { a: { b: 1, c: 3 } })).toEqual({ a: { c: 3 } });
62+
});
63+
64+
test('returns subset of right hand side value when nested values differ at multiple paths', () => {
65+
expect(diff({ a: { b: 1 }, c: 2, d: { e: 100 } }, { a: { b: 99 }, c: 3, d: { e: 100 } })).toEqual({ a: { b: 99 }, c: 3 });
66+
});
67+
68+
test('returns subset of right hand side value when a key value has been deleted', () => {
69+
expect(diff({ a: { b: 1 }, c: 2, d: { e: 100 } }, { a: { b: 1 }, c: 2, d: {} })).toEqual({ d: { e: undefined } });
70+
});
71+
72+
test('returns subset of right hand side value when a key value has been added', () => {
73+
expect(diff({ a: 1 }, { a: 1, b: 2 })).toEqual({ b: 2 });
74+
});
75+
76+
test('returns keys as undefined when deleted from right hand side', () => {
77+
expect(diff({ a: 1, b: { c: 2 }}, { a: 1 })).toEqual({ b: undefined });
78+
});
79+
});
80+
81+
describe('arrays', () => {
82+
test('returns right hand side value as object of indices to value when arrays are different', () => {
83+
expect(diff([1], [2])).toEqual([2]);
84+
});
85+
86+
test('returns subset of right hand side array as object of indices to value when arrays differs at multiple indicies', () => {
87+
const expected = [9, 8, 3];
88+
delete expected['2'];
89+
expect(diff([1, 2, 3], [9, 8, 3])).toEqual(expected);
90+
});
91+
92+
test('returns subset of right hand side array as object of indices to value when right hand side array has deletions', () => {
93+
const expected = [1, 3, undefined];
94+
delete expected['0'];
95+
expect(diff([1, 2, 3], [1, 3])).toEqual(expected);
96+
});
97+
98+
test('returns subset of right hand side array as object of indices to value when right hand side array has additions', () => {
99+
const expected = [1, 2, 3, 9];
100+
delete expected['0'];
101+
delete expected['1'];
102+
delete expected['2'];
103+
// expected.forEach(console.log)
104+
expect(diff([1, 2, 3], [1, 2, 3, 9])).toEqual(expected);
105+
});
106+
});
107+
108+
describe('date', () => {
109+
const lhs = new Date('2016');
110+
const rhs = new Date('2017');
111+
112+
test('returns empty object when dates are equal', () => {
113+
expect(diff(new Date('2016'), new Date('2016'))).toEqual({});
114+
});
115+
116+
test('returns right hand side date when updated', () => {
117+
expect(diff({ date: lhs }, { date: rhs })).toEqual({ date: rhs });
118+
expect(diff([lhs], [rhs])).toEqual([rhs]);
119+
});
120+
121+
test('returns undefined when date deleted', () => {
122+
expect(diff({ date: lhs }, {})).toEqual({ date: undefined });
123+
expect(diff([lhs], [])).toEqual([undefined]);
124+
});
125+
126+
test('returns right hand side when date is added', () => {
127+
expect(diff({}, { date: rhs })).toEqual({ date: rhs });
128+
expect(diff([], [rhs])).toEqual([rhs]);
129+
});
130+
});
131+
132+
describe('object create null', () => {
133+
test('returns right hand side value when given objects are different', () => {
134+
const lhs = Object.create(null);
135+
lhs.a = 1;
136+
const rhs = Object.create(null);
137+
rhs.a = 2;
138+
expect(diff(lhs, rhs)).toEqual({ a: 2 });
139+
});
140+
141+
test('returns subset of right hand side value when sibling objects differ', () => {
142+
const lhs = Object.create(null);
143+
lhs.a = { b: 1 };
144+
lhs.c = 2;
145+
const rhs = Object.create(null);
146+
rhs.a = { b: 1 };
147+
rhs.c = 3;
148+
expect(diff(lhs, rhs)).toEqual({ c: 3 });
149+
});
150+
151+
test('returns subset of right hand side value when nested values differ', () => {
152+
const lhs = Object.create(null);
153+
lhs.a = { b: 1, c: 2};
154+
const rhs = Object.create(null);
155+
rhs.a = { b: 1, c: 3 };
156+
expect(diff(lhs, rhs)).toEqual({ a: { c: 3 } });
157+
});
158+
159+
test('returns subset of right hand side value when nested values differ at multiple paths', () => {
160+
const lhs = Object.create(null);
161+
lhs.a = { b: 1 };
162+
lhs.c = 2;
163+
const rhs = Object.create(null);
164+
rhs.a = { b: 99 };
165+
rhs.c = 3;
166+
expect(diff(lhs, rhs)).toEqual({ a: { b: 99 }, c: 3 });
167+
});
168+
169+
test('returns subset of right hand side value when a key value has been deleted', () => {
170+
const lhs = Object.create(null);
171+
lhs.a = { b: 1 };
172+
lhs.c = 2;
173+
const rhs = Object.create(null);
174+
rhs.a = { b: 1 };
175+
expect(diff(lhs, rhs)).toEqual({ c: undefined });
176+
});
177+
178+
test('returns subset of right hand side value when a key value has been added', () => {
179+
const lhs = Object.create(null);
180+
lhs.a = 1;
181+
const rhs = Object.create(null);
182+
rhs.a = 1;
183+
rhs.b = 2;
184+
expect(diff(lhs, rhs)).toEqual({ b: 2 });
185+
});
186+
});
187+
});
188+
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /