Skip to content

Commit 8e66dbd

Browse files
committed
fix(differ): let respect values, not only keys
1 parent 858b4ca commit 8e66dbd

File tree

3 files changed

+109
-21
lines changed

3 files changed

+109
-21
lines changed

src/differ.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,77 @@
11
import Differ from './differ';
22

3+
describe('object diff', () => {
4+
it('preserve key order', () => {
5+
const l = { a: 1, b: 2, c: 3 };
6+
const r = { c: 3, b: 2, d: 4, a: 1 };
7+
const d = new Differ();
8+
const result = d.diff(l, r);
9+
expect(result[0]).toEqual([
10+
{ lineNumber: 1, level: 0, type: 'equal', text: '{' },
11+
{ lineNumber: 2, level: 1, type: 'equal', text: '"a": 1', comma: true },
12+
{ lineNumber: 3, level: 1, type: 'equal', text: '"b": 2', comma: true },
13+
{ lineNumber: 4, level: 1, type: 'equal', text: '"c": 3' },
14+
{ level: 1, type: 'equal', text: '' },
15+
{ lineNumber: 5, level: 0, type: 'equal', text: '}' },
16+
]);
17+
expect(result[1]).toEqual([
18+
{ lineNumber: 1, level: 0, type: 'equal', text: '{' },
19+
{ lineNumber: 2, level: 1, type: 'equal', text: '"a": 1', comma: true },
20+
{ lineNumber: 3, level: 1, type: 'equal', text: '"b": 2', comma: true },
21+
{ lineNumber: 4, level: 1, type: 'equal', text: '"c": 3', comma: true },
22+
{ lineNumber: 5, level: 1, type: 'add', text: '"d": 4' },
23+
{ lineNumber: 6, level: 0, type: 'equal', text: '}' },
24+
]);
25+
});
26+
});
27+
28+
describe('object array diff', () => {
29+
it('recursive equal', () => {
30+
const l = [
31+
{ id: '1', x: 'a' },
32+
{ id: '2', x: 'b' },
33+
];
34+
const r = [
35+
{ id: '2', x: 'b' },
36+
{ id: '1', x: 'a' },
37+
];
38+
const d = new Differ({ recursiveEqual: true, arrayDiffMethod: 'lcs' });
39+
const result = d.diff(l, r);
40+
expect(result[0]).toEqual([
41+
{ lineNumber: 1, level: 0, type: 'equal', text: '[' },
42+
{ level: 1, type: 'equal', text: '' },
43+
{ level: 1, type: 'equal', text: '' },
44+
{ level: 1, type: 'equal', text: '' },
45+
{ level: 1, type: 'equal', text: '' },
46+
{ lineNumber: 2, level: 1, type: 'equal', text: '{' },
47+
{ lineNumber: 3, level: 2, type: 'equal', text: '"id": "1"', comma: true },
48+
{ lineNumber: 4, level: 2, type: 'equal', text: '"x": "a"' },
49+
{ lineNumber: 5, level: 1, type: 'equal', text: '}', comma: true },
50+
{ lineNumber: 6, level: 1, type: 'remove', text: '{' },
51+
{ lineNumber: 7, level: 2, type: 'remove', text: '"id": "2"', comma: true },
52+
{ lineNumber: 8, level: 2, type: 'remove', text: '"x": "b"' },
53+
{ lineNumber: 9, level: 1, type: 'remove', text: '}' },
54+
{ lineNumber: 10, level: 0, type: 'equal', text: ']' },
55+
]);
56+
expect(result[1]).toEqual([
57+
{ lineNumber: 1, level: 0, type: 'equal', text: '[' },
58+
{ lineNumber: 2, level: 1, type: 'add', text: '{' },
59+
{ lineNumber: 3, level: 2, type: 'add', text: '"id": "2"', comma: true },
60+
{ lineNumber: 4, level: 2, type: 'add', text: '"x": "b"' },
61+
{ lineNumber: 5, level: 1, type: 'add', text: '}', comma: true },
62+
{ lineNumber: 6, level: 1, type: 'equal', text: '{' },
63+
{ lineNumber: 7, level: 2, type: 'equal', text: '"id": "1"', comma: true },
64+
{ lineNumber: 8, level: 2, type: 'equal', text: '"x": "a"' },
65+
{ lineNumber: 9, level: 1, type: 'equal', text: '}' },
66+
{ level: 1, type: 'equal', text: '' },
67+
{ level: 1, type: 'equal', text: '' },
68+
{ level: 1, type: 'equal', text: '' },
69+
{ level: 1, type: 'equal', text: '' },
70+
{ lineNumber: 10, level: 0, type: 'equal', text: ']' },
71+
]);
72+
});
73+
});
74+
375
describe('2-dimensional array diff', () => {
476
it('normal diff', () => {
577
const l = [[1, 2, 3, 4], [5, 6], [9]];
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import shallowSimilarity from './shallow-similarity';
2+
3+
describe('Utility function: shallowSimilarity', () => {
4+
it('should return 1 if both values are the same', () => {
5+
expect(shallowSimilarity('2', '2')).toBe(1);
6+
});
7+
8+
it('should return 0 if either value is null', () => {
9+
expect(shallowSimilarity(null, '2')).toBe(0);
10+
expect(shallowSimilarity('2', null)).toBe(0);
11+
});
12+
13+
it('should return 0 if either value is not an object', () => {
14+
expect(shallowSimilarity('2', 2)).toBe(0);
15+
});
16+
17+
it('should return 0 if both values are objects but have no common keys', () => {
18+
expect(shallowSimilarity({ a: 1 }, { b: 2 })).toBe(0);
19+
});
20+
21+
it('should return the correct value if both values are objects and have common keys', () => {
22+
expect(shallowSimilarity({ a: 1 }, { a: 1 })).toBe(1);
23+
expect(shallowSimilarity({ a: 1, b: 2 }, { a: 1, c: 3 })).toBe(0.5);
24+
expect(shallowSimilarity({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(1);
25+
});
26+
});

src/utils/shallow-similarity.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,19 @@ const shallowSimilarity = (left: any, right: any): number => {
88
if (typeof left !== 'object' || typeof right !== 'object') {
99
return 0;
1010
}
11-
const leftKeys = Object.keys(left);
12-
const rightKeys = Object.keys(right);
13-
const leftKeysLength = leftKeys.length;
14-
const rightKeysLength = rightKeys.length;
15-
if (leftKeysLength === 0 || rightKeysLength === 0) {
16-
return 0;
17-
}
18-
const leftKeysSet = new Set(leftKeys);
19-
const rightKeysSet = new Set(rightKeys);
20-
const intersection = new Set([...leftKeysSet].filter(x => rightKeysSet.has(x)));
21-
if (intersection.size === 0) {
22-
return 0;
23-
}
24-
if (
25-
intersection.size === 1 &&
26-
(leftKeysLength === 1 || rightKeysLength === 1) &&
27-
left[leftKeys[0]] !== right[rightKeys[0]]
28-
) {
29-
return 0;
11+
let intersection = 0;
12+
for (const key in left) {
13+
if (
14+
Object.prototype.hasOwnProperty.call(left, key) &&
15+
Object.prototype.hasOwnProperty.call(right, key) &&
16+
left[key] === right[key]
17+
) {
18+
intersection++;
19+
}
3020
}
3121
return Math.max(
32-
intersection.size / leftKeysLength,
33-
intersection.size / rightKeysLength,
22+
intersection / Object.keys(left).length,
23+
intersection / Object.keys(right).length,
3424
);
3525
};
3626

0 commit comments

Comments
 (0)