Skip to content

Commit

Permalink
fix(differ): let respect values, not only keys
Browse files Browse the repository at this point in the history
  • Loading branch information
RexSkz committed Dec 10, 2024
1 parent 858b4ca commit 8e66dbd
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 21 deletions.
72 changes: 72 additions & 0 deletions src/differ.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
import Differ from './differ';

describe('object diff', () => {
it('preserve key order', () => {
const l = { a: 1, b: 2, c: 3 };
const r = { c: 3, b: 2, d: 4, a: 1 };
const d = new Differ();
const result = d.diff(l, r);
expect(result[0]).toEqual([
{ lineNumber: 1, level: 0, type: 'equal', text: '{' },
{ lineNumber: 2, level: 1, type: 'equal', text: '"a": 1', comma: true },
{ lineNumber: 3, level: 1, type: 'equal', text: '"b": 2', comma: true },
{ lineNumber: 4, level: 1, type: 'equal', text: '"c": 3' },
{ level: 1, type: 'equal', text: '' },
{ lineNumber: 5, level: 0, type: 'equal', text: '}' },
]);
expect(result[1]).toEqual([
{ lineNumber: 1, level: 0, type: 'equal', text: '{' },
{ lineNumber: 2, level: 1, type: 'equal', text: '"a": 1', comma: true },
{ lineNumber: 3, level: 1, type: 'equal', text: '"b": 2', comma: true },
{ lineNumber: 4, level: 1, type: 'equal', text: '"c": 3', comma: true },
{ lineNumber: 5, level: 1, type: 'add', text: '"d": 4' },
{ lineNumber: 6, level: 0, type: 'equal', text: '}' },
]);
});
});

describe('object array diff', () => {
it('recursive equal', () => {
const l = [
{ id: '1', x: 'a' },
{ id: '2', x: 'b' },
];
const r = [
{ id: '2', x: 'b' },
{ id: '1', x: 'a' },
];
const d = new Differ({ recursiveEqual: true, arrayDiffMethod: 'lcs' });
const result = d.diff(l, r);
expect(result[0]).toEqual([
{ lineNumber: 1, level: 0, type: 'equal', text: '[' },
{ level: 1, type: 'equal', text: '' },
{ level: 1, type: 'equal', text: '' },
{ level: 1, type: 'equal', text: '' },
{ level: 1, type: 'equal', text: '' },
{ lineNumber: 2, level: 1, type: 'equal', text: '{' },
{ lineNumber: 3, level: 2, type: 'equal', text: '"id": "1"', comma: true },
{ lineNumber: 4, level: 2, type: 'equal', text: '"x": "a"' },
{ lineNumber: 5, level: 1, type: 'equal', text: '}', comma: true },
{ lineNumber: 6, level: 1, type: 'remove', text: '{' },
{ lineNumber: 7, level: 2, type: 'remove', text: '"id": "2"', comma: true },
{ lineNumber: 8, level: 2, type: 'remove', text: '"x": "b"' },
{ lineNumber: 9, level: 1, type: 'remove', text: '}' },
{ lineNumber: 10, level: 0, type: 'equal', text: ']' },
]);
expect(result[1]).toEqual([
{ lineNumber: 1, level: 0, type: 'equal', text: '[' },
{ lineNumber: 2, level: 1, type: 'add', text: '{' },
{ lineNumber: 3, level: 2, type: 'add', text: '"id": "2"', comma: true },
{ lineNumber: 4, level: 2, type: 'add', text: '"x": "b"' },
{ lineNumber: 5, level: 1, type: 'add', text: '}', comma: true },
{ lineNumber: 6, level: 1, type: 'equal', text: '{' },
{ lineNumber: 7, level: 2, type: 'equal', text: '"id": "1"', comma: true },
{ lineNumber: 8, level: 2, type: 'equal', text: '"x": "a"' },
{ lineNumber: 9, level: 1, type: 'equal', text: '}' },
{ level: 1, type: 'equal', text: '' },
{ level: 1, type: 'equal', text: '' },
{ level: 1, type: 'equal', text: '' },
{ level: 1, type: 'equal', text: '' },
{ lineNumber: 10, level: 0, type: 'equal', text: ']' },
]);
});
});

describe('2-dimensional array diff', () => {
it('normal diff', () => {
const l = [[1, 2, 3, 4], [5, 6], [9]];
Expand Down
26 changes: 26 additions & 0 deletions src/utils/shallow-similarity.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import shallowSimilarity from './shallow-similarity';

describe('Utility function: shallowSimilarity', () => {
it('should return 1 if both values are the same', () => {
expect(shallowSimilarity('2', '2')).toBe(1);
});

it('should return 0 if either value is null', () => {
expect(shallowSimilarity(null, '2')).toBe(0);
expect(shallowSimilarity('2', null)).toBe(0);
});

it('should return 0 if either value is not an object', () => {
expect(shallowSimilarity('2', 2)).toBe(0);
});

it('should return 0 if both values are objects but have no common keys', () => {
expect(shallowSimilarity({ a: 1 }, { b: 2 })).toBe(0);
});

it('should return the correct value if both values are objects and have common keys', () => {
expect(shallowSimilarity({ a: 1 }, { a: 1 })).toBe(1);
expect(shallowSimilarity({ a: 1, b: 2 }, { a: 1, c: 3 })).toBe(0.5);
expect(shallowSimilarity({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(1);
});
});
32 changes: 11 additions & 21 deletions src/utils/shallow-similarity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,19 @@ const shallowSimilarity = (left: any, right: any): number => {
if (typeof left !== 'object' || typeof right !== 'object') {
return 0;
}
const leftKeys = Object.keys(left);
const rightKeys = Object.keys(right);
const leftKeysLength = leftKeys.length;
const rightKeysLength = rightKeys.length;
if (leftKeysLength === 0 || rightKeysLength === 0) {
return 0;
}
const leftKeysSet = new Set(leftKeys);
const rightKeysSet = new Set(rightKeys);
const intersection = new Set([...leftKeysSet].filter(x => rightKeysSet.has(x)));
if (intersection.size === 0) {
return 0;
}
if (
intersection.size === 1 &&
(leftKeysLength === 1 || rightKeysLength === 1) &&
left[leftKeys[0]] !== right[rightKeys[0]]
) {
return 0;
let intersection = 0;
for (const key in left) {
if (
Object.prototype.hasOwnProperty.call(left, key) &&
Object.prototype.hasOwnProperty.call(right, key) &&
left[key] === right[key]
) {
intersection++;
}
}
return Math.max(
intersection.size / leftKeysLength,
intersection.size / rightKeysLength,
intersection / Object.keys(left).length,
intersection / Object.keys(right).length,
);
};

Expand Down

0 comments on commit 8e66dbd

Please sign in to comment.