Skip to content
Prev Previous commit
fix: handle reorder and add simple array option
simple array = assume unique & order doesn't matter
  • Loading branch information
Jack Chan committed Oct 12, 2021
commit 2a15a80538c56d2f9cee4cb9ba0907a703e83f1e
16 changes: 9 additions & 7 deletions src/added/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from "lodash"
import { isEmpty, isObject, properObject } from '../utils';

const addedDiff = (lhs, rhs) => {
const addedDiff = (lhs, rhs, simpleArray = true) => {

if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {};

Expand All @@ -11,24 +11,26 @@ const addedDiff = (lhs, rhs) => {
return Object.keys(r).reduce((acc, key) => {
if (l.hasOwnProperty(key)) {
if (Array.isArray(l[key]) && Array.isArray(r[key])) {
//const allKeys = _.merge(l[key], r[key]) ?? []
const newFields = _.uniq(_.difference(r[key], l[key]))
if (newFields.length === 0) {
return acc
}
const newFieldsIndex = _.map(newFields, o => ({
if (simpleArray) {
return { ...acc, [key]: { after: _.uniq(_.difference(r[key], l[key])) } }
}
const newFieldsIndices = _.map(newFields, o => ({
content: o,
index: []
indices: []
}))
for (let i = 0; i < r[key].length; i++) {
const index = _.findIndex(newFields, o => _.isEqual(o, r[key][i]))
if (index !== -1) {
newFieldsIndex[index].index.push(i)
newFieldsIndices[index].indices.push(i)
}
}
return { ...acc, [key]: { after: newFieldsIndex } }
return { ...acc, [key]: { after: newFieldsIndices } }
}
const difference = addedDiff(l[key], r[key]);
const difference = addedDiff(l[key], r[key], simpleArray);

if (isObject(difference) && isEmpty(difference)) return acc;

Expand Down
15 changes: 9 additions & 6 deletions src/deleted/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from "lodash"
import { isEmpty, isObject, properObject } from '../utils';

const deletedDiff = (lhs, rhs) => {
const deletedDiff = (lhs, rhs, simpleArray = true) => {
if (lhs === rhs || !isObject(lhs) || !isObject(rhs)) return {};

const l = properObject(lhs);
Expand All @@ -14,19 +14,22 @@ const deletedDiff = (lhs, rhs) => {
if (oldFields.length === 0) {
return acc
}
const oldFieldsIndex = _.map(oldFields, o => ({
if (simpleArray) {
return { ...acc, [key]: { before: _.uniq(_.difference(l[key], r[key])) } }
}
const oldFieldsIndices = _.map(oldFields, o => ({
content: o,
index: []
indices: []
}))
for (let i = 0; i < l[key].length; i++) {
const index = _.findIndex(oldFields, o => _.isEqual(o, l[key][i]))
if (index !== -1) {
oldFieldsIndex[index].index.push(i)
oldFieldsIndices[index].indices.push(i)
}
}
return { ...acc, [key]: { before: oldFieldsIndex } }
return { ...acc, [key]: { before: oldFieldsIndices } }
}
const difference = deletedDiff(l[key], r[key]);
const difference = deletedDiff(l[key], r[key], simpleArray);

if (isObject(difference) && isEmpty(difference)) return acc;

Expand Down
8 changes: 4 additions & 4 deletions src/detailed/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import addedDiff from '../added';
import deletedDiff from '../deleted';
import updatedDiff from '../updated';

const detailedDiff = (lhs, rhs) => ({
added: addedDiff(lhs, rhs),
deleted: deletedDiff(lhs, rhs),
updated: updatedDiff(lhs, rhs),
const detailedDiff = (lhs, rhs, simpleArray = true) => ({
added: addedDiff(lhs, rhs, simpleArray),
deleted: deletedDiff(lhs, rhs, simpleArray),
updated: updatedDiff(lhs, rhs, simpleArray),
});

export default detailedDiff;
90 changes: 83 additions & 7 deletions src/detailed/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ describe('.detailedDiff', () => {
test.each([
[{}, { a: ["a", "b"] }],
])('returns right hand side value when different to left hand side value (%s, %s)', (lhs, rhs) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { after: ["a", "b"] } }, "deleted": {}, "updated": {} });
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": { a: { after: ["a", "b"] } }, "deleted": {}, "updated": {} });
});
});

Expand All @@ -165,7 +165,7 @@ describe('.detailedDiff', () => {
[{ a: ["a"] }, { a: ["a", "b", "b", "b"] }, [1, 2, 3]],
[{ a: ["a"] }, { a: ["b", "a", "b", "b"] }, [0, 2, 3]],
])('returns inserted array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { after: [{ content: "b", index: indexes }] } }, "deleted": {}, "updated": {} });
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": { a: { after: [{ content: "b", indices: indexes }] } }, "deleted": {}, "updated": {} });
});
});

Expand All @@ -176,15 +176,45 @@ describe('.detailedDiff', () => {
[{ a: ["a", "b", "b", "b"] }, { a: ["a"] }, [1, 2, 3]],
[{ a: ["b", "a", "b", "b"] }, { a: ["a"] }, [0, 2, 3]],
])('returns removed array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": {}, "deleted": { a: { before: [{ content: "b", index: indexes }] } }, "updated": {} });
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": {}, "deleted": { a: { before: [{ content: "b", indices: indexes }] } }, "updated": {} });
});
});

describe('change array field (pull one insert one)', () => {
test.each([
[{ a: ["a", "c"] }, { a: ["a", "b"] }],
])('returns inserted and removed array element of right hand side (%s, %s)', (lhs, rhs) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { after: [{ content: "b", index: [1] }] } }, "deleted": { a: { before: [{ content: "c", index: [1] }] } }, "updated": {} });
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": { a: { after: [{ content: "b", indices: [1] }] } }, "deleted": { a: { before: [{ content: "c", indices: [1] }] } }, "updated": {} });
});
});

describe('add array *element* (simple array)', () => {
test.each([
[{ a: ["a"] }, { a: ["a", "b"] }, [1]],
[{ a: ["a"] }, { a: ["a", "b", "b"] }, [1, 2]],
[{ a: ["a"] }, { a: ["a", "b", "b", "b"] }, [1, 2, 3]],
[{ a: ["a"] }, { a: ["b", "a", "b", "b"] }, [0, 2, 3]],
])('returns inserted array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { after: ["b"] } }, "deleted": {}, "updated": {} });
});
});

describe('remove array element (simple array)', () => {
test.each([
[{ a: ["a", "b"] }, { a: ["a"] }, [1]],
[{ a: ["a", "b", "b"] }, { a: ["a"] }, [1, 2]],
[{ a: ["a", "b", "b", "b"] }, { a: ["a"] }, [1, 2, 3]],
[{ a: ["b", "a", "b", "b"] }, { a: ["a"] }, [0, 2, 3]],
])('returns removed array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": {}, "deleted": { a: { before: ["b"] } }, "updated": {} });
});
});

describe('change array field (pull one insert one) (simple array)', () => {
test.each([
[{ a: ["a", "c"] }, { a: ["a", "b"] }],
])('returns inserted and removed array element of right hand side (%s, %s)', (lhs, rhs) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { after: ["b"] } }, "deleted": { a: { before: ["c"] } }, "updated": {} });
});
});
});
Expand All @@ -198,7 +228,7 @@ describe('.detailedDiff', () => {
[{ a: { a: ["a"] } }, { a: { a: ["a", "b", "b", "b"] } }, [1, 2, 3]],
[{ a: { a: ["a"] } }, { a: { a: ["b", "a", "b", "b"] } }, [0, 2, 3]],
])('returns inserted array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { a: { after: [{ content: "b", index: indexes }] } } }, "deleted": {}, "updated": {} });
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": { a: { a: { after: [{ content: "b", indices: indexes }] } } }, "deleted": {}, "updated": {} });
});
});

Expand All @@ -209,15 +239,61 @@ describe('.detailedDiff', () => {
[{ a: { a: ["a", "b", "b", "b"] } }, { a: { a: ["a"] } }, [1, 2, 3]],
[{ a: { a: ["b", "a", "b", "b"] } }, { a: { a: ["a"] } }, [0, 2, 3]],
])('returns removed array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": {}, "deleted": { a: { a: { before: [{ content: "b", index: indexes }] } } }, "updated": {} });
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": {}, "deleted": { a: { a: { before: [{ content: "b", indices: indexes }] } } }, "updated": {} });
});
});

describe('change array field (pull one insert one) not in root', () => {
test.each([
[{ a: { a: ["a", "c"] } }, { a: { a: ["a", "b"] } }],
])('returns inserted and removed array element of right hand side (%s, %s)', (lhs, rhs) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { a: { after: [{ content: "b", index: [1] }] } } }, "deleted": { a: { a: { before: [{ content: "c", index: [1] }] } } }, "updated": {} });
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": { a: { a: { after: [{ content: "b", indices: [1] }] } } }, "deleted": { a: { a: { before: [{ content: "c", indices: [1] }] } } }, "updated": {} });
});
});

describe('reorder not in root', () => {
test.each([
[{ a: { a: ["a", "b", "c"] } }, { a: { a: ["a", "c", "b"] } }],
])('returns inserted and removed array element of right hand side (%s, %s)', (lhs, rhs) => {
expect(detailedDiff(lhs, rhs, false)).toEqual({ "added": {}, "deleted": {}, "updated": { a: { a: { after: { b: [{ counter: 0, newIndex: 2 }], c: [{ counter: 0, newIndex: 1 }] } } } } });
});
});

describe('add array *element* not in root (simple array)', () => {
test.each([
[{ a: { a: ["a"] } }, { a: { a: ["a", "b"] } }, [1]],
[{ a: { a: ["a"] } }, { a: { a: ["a", "b", "b"] } }, [1, 2]],
[{ a: { a: ["a"] } }, { a: { a: ["a", "b", "b", "b"] } }, [1, 2, 3]],
[{ a: { a: ["a"] } }, { a: { a: ["b", "a", "b", "b"] } }, [0, 2, 3]],
])('returns inserted array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { a: { after: ["b"] } } }, "deleted": {}, "updated": {} });
});
});

describe('remove array element not in root (simple array)', () => {
test.each([
[{ a: { a: ["a", "b"] } }, { a: { a: ["a"] } }, [1]],
[{ a: { a: ["a", "b", "b"] } }, { a: { a: ["a"] } }, [1, 2]],
[{ a: { a: ["a", "b", "b", "b"] } }, { a: { a: ["a"] } }, [1, 2, 3]],
[{ a: { a: ["b", "a", "b", "b"] } }, { a: { a: ["a"] } }, [0, 2, 3]],
])('returns removed array element content and position of right hand side (%s, %s)', (lhs, rhs, indexes) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": {}, "deleted": { a: { a: { before: ["b"] } } }, "updated": {} });
});
});

describe('change array field (pull one insert one) not in root (simple array)', () => {
test.each([
[{ a: { a: ["a", "c"] } }, { a: { a: ["a", "b"] } }],
])('returns inserted and removed array element of right hand side (%s, %s)', (lhs, rhs) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": { a: { a: { after: ["b"] } } }, "deleted": { a: { a: { before: ["c"] } } }, "updated": {} });
});
});

describe('reorder not in root (simple array)', () => {
test.each([
[{ a: { a: ["a", "b", "c"] } }, { a: { a: ["a", "c", "b"] } }],
])('returns inserted and removed array element of right hand side (%s, %s)', (lhs, rhs) => {
expect(detailedDiff(lhs, rhs)).toEqual({ "added": {}, "deleted": {}, "updated": {}});
});
});
});
Expand Down
43 changes: 39 additions & 4 deletions src/updated/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from "lodash"
import { isDate, isEmpty, isObject, properObject } from '../utils';

const updatedDiff = (lhs, rhs) => {
const updatedDiff = (lhs, rhs, simpleArray = true) => {

if (lhs === rhs) return {};

Expand All @@ -17,10 +18,44 @@ const updatedDiff = (lhs, rhs) => {
return Object.keys(r).reduce((acc, key) => {

if (l.hasOwnProperty(key)) {
if (Array.isArray(l[key], r[key])) {
return acc
if (Array.isArray(l[key]) && Array.isArray(r[key])) {
if (_.isEqual(l[key], r[key])) {
return acc
}
const leftArray = _.cloneDeep(l[key])
const rightArray = _.cloneDeep(r[key])
const addedFields = _.uniq(_.difference(r[key], l[key]))
const deletedFields = _.uniq(_.difference(l[key], r[key]))
_.pullAll(leftArray, deletedFields)
_.pullAll(rightArray, addedFields)
if (_.isEqual(leftArray, rightArray) || simpleArray) {
return acc
} else {
const allKeys = _.uniq(rightArray)
const allKeysCounter = _.reduce(allKeys, (acc, curr) => (acc[curr] = 0, acc), {})
const allKeysObject = _.reduce(allKeys, (acc, curr) => (acc[curr] = [], acc), {})
for (let i = 0; i < leftArray.length; i++) {
if (!_.isEqual(leftArray[i], rightArray[i])) {
let index = _.findIndex(r[key], value => _.isEqual(value, rightArray[i]))
for (let j = 0; j < allKeysCounter[rightArray[i]]; j++) {
index = _.findIndex(r[key], value => _.isEqual(value, rightArray[i], index + 1))
}
allKeysObject[rightArray[i]].push({
counter: allKeysCounter[rightArray[i]],
newIndex: index
})
}
allKeysCounter[rightArray[i]] = allKeysCounter[rightArray[i]]++
}
for (const key of Object.keys(allKeysObject)) {
if (_.isEmpty(allKeysObject[key])) {
delete allKeysObject[key]
}
}
return { ...acc, [key]: { after: allKeysObject } }
}
}
const difference = updatedDiff(l[key], r[key]);
const difference = updatedDiff(l[key], r[key], simpleArray);

if (isObject(difference) && isEmpty(difference) && !isDate(difference)) return acc;

Expand Down