Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support diffing keys named like Object.prototype properties #59

Merged
merged 3 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true,
"jest": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"test": "jest",
"test:coverage": "yarn test -- --coverage",
"test:report": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls",
"test:watch": "yarn test -- --watch"
"test:watch": "yarn test -- --watch",
"posttest": "eslint src/"
},
"author": "Matt Phillips",
"license": "MIT",
Expand All @@ -30,6 +31,7 @@
"babel-preset-es2015": "^6.18.0",
"babel-preset-stage-0": "^6.16.0",
"coveralls": "^3.0.0",
"eslint": "^7.10.0",
"jest": "^23.6.0"
},
"babel": {
Expand Down
8 changes: 4 additions & 4 deletions src/added/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { isEmpty, isObject, properObject } from '../utils';
import { isEmpty, isObject, hasOwnProperty } from '../utils';

const addedDiff = (lhs, rhs) => {

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

const l = properObject(lhs);
const r = properObject(rhs);
const l = lhs;
const r = rhs;

return Object.keys(r).reduce((acc, key) => {
if (l.hasOwnProperty(key)) {
if (hasOwnProperty(l, key)) {
const difference = addedDiff(l[key], r[key]);

if (isObject(difference) && isEmpty(difference)) return acc;
Expand Down
8 changes: 8 additions & 0 deletions src/added/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,13 @@ describe('.addedDiff', () => {
expect(addedDiff(lhs, rhs)).toEqual({ date: new Date('2016') });
});
});

describe('object with non-function hasOwnProperty property', () => {
test('can represent the property in diff despite it being part of Object.prototype', () => {
const lhs = {};
const rhs = { hasOwnProperty: true };
expect(addedDiff(lhs, rhs)).toEqual({ hasOwnProperty: true });
});
});
});
});
14 changes: 7 additions & 7 deletions src/arrayDiff/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { isDate, isEmpty, isObject, properObject } from '../utils';
import { isDate, isEmpty, isObject, hasOwnProperty } from '../utils';

const diff = (lhs, rhs) => {
if (lhs === rhs) return {}; // equal return no diff

if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs

const l = properObject(lhs);
const r = properObject(rhs);
const l = lhs;
const r = rhs;

const deletedValues = Object.keys(l).reduce((acc, key) => {
return r.hasOwnProperty(key) ? acc : { ...acc, [key]: undefined };
return hasOwnProperty(r, key) ? acc : { ...acc, [key]: undefined };
}, {});

if (isDate(l) || isDate(r)) {
Expand All @@ -19,11 +19,11 @@ const diff = (lhs, rhs) => {

if (Array.isArray(r) && Array.isArray(l)) {
const deletedValues = l.reduce((acc, item, index) => {
return r.hasOwnProperty(index) ? acc.concat(item) : acc.concat(undefined);
return hasOwnProperty(r, index) ? acc.concat(item) : acc.concat(undefined);
}, []);

return r.reduce((acc, rightItem, index) => {
if (!deletedValues.hasOwnProperty(index)) {
if (!hasOwnProperty(deletedValues, index)) {
return acc.concat(rightItem);
}

Expand All @@ -40,7 +40,7 @@ const diff = (lhs, rhs) => {
}

return Object.keys(r).reduce((acc, key) => {
if (!l.hasOwnProperty(key)) return { ...acc, [key]: r[key] }; // return added r key
if (!hasOwnProperty(l, key)) return { ...acc, [key]: r[key] }; // return added r key

const difference = diff(l[key], r[key]);

Expand Down
8 changes: 4 additions & 4 deletions src/deleted/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { isEmpty, isObject, properObject } from '../utils';
import { isEmpty, isObject, hasOwnProperty } from '../utils';

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

const l = properObject(lhs);
const r = properObject(rhs);
const l = lhs;
const r = rhs;

return Object.keys(l).reduce((acc, key) => {
if (r.hasOwnProperty(key)) {
if (hasOwnProperty(r, key)) {
const difference = deletedDiff(l[key], r[key]);

if (isObject(difference) && isEmpty(difference)) return acc;
Expand Down
8 changes: 8 additions & 0 deletions src/deleted/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,13 @@ describe('.deletedDiff', () => {
expect(deletedDiff({ date: new Date('2016') }, rhs)).toEqual({ date: undefined });
});
});

describe('object with non-function hasOwnProperty property', () => {
test('can represent the property in diff despite it being part of Object.prototype', () => {
const lhs = { hasOwnProperty: true };
const rhs = {};
expect(deletedDiff(lhs, rhs)).toEqual({ hasOwnProperty: undefined });
});
});
});
});
10 changes: 5 additions & 5 deletions src/diff/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { isDate, isEmptyObject, isObject, properObject } from "../utils";
import { isDate, isEmptyObject, isObject, hasOwnProperty } from '../utils';

const diff = (lhs, rhs) => {
if (lhs === rhs) return {}; // equal return no diff

if (!isObject(lhs) || !isObject(rhs)) return rhs; // return updated rhs

const l = properObject(lhs);
const r = properObject(rhs);
const l = lhs;
const r = rhs;

const deletedValues = Object.keys(l).reduce((acc, key) => {
return r.hasOwnProperty(key) ? acc : { ...acc, [key]: undefined };
return hasOwnProperty(r, key) ? acc : { ...acc, [key]: undefined };
}, {});

if (isDate(l) || isDate(r)) {
Expand All @@ -18,7 +18,7 @@ const diff = (lhs, rhs) => {
}

return Object.keys(r).reduce((acc, key) => {
if (!l.hasOwnProperty(key)) return { ...acc, [key]: r[key] }; // return added r key
if (!hasOwnProperty(l, key)) return { ...acc, [key]: r[key] }; // return added r key

const difference = diff(l[key], r[key]);

Expand Down
4 changes: 2 additions & 2 deletions src/preserveArray.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObject } from './utils';
import { isObject, hasOwnProperty } from './utils';

const getLargerArray = (l, r) => l.length > r.length ? l : r;

Expand All @@ -16,7 +16,7 @@ const preserve = (diff, left, right) => {
return {
...acc,
[key]: array.reduce((acc2, item, index) => {
if (diff[key].hasOwnProperty(index)) {
if (hasOwnProperty(diff[key], index)) {
acc2[index] = preserve(diff[key][index], leftArray[index], rightArray[index]); // diff recurse and check for nested arrays
return acc2;
}
Expand Down
8 changes: 4 additions & 4 deletions src/updated/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { isDate, isEmptyObject, isObject, properObject } from "../utils";
import { isDate, isEmptyObject, isObject, hasOwnProperty } from '../utils';

const updatedDiff = (lhs, rhs) => {
if (lhs === rhs) return {};

if (!isObject(lhs) || !isObject(rhs)) return rhs;

const l = properObject(lhs);
const r = properObject(rhs);
const l = lhs;
const r = rhs;

if (isDate(l) || isDate(r)) {
if (l.valueOf() == r.valueOf()) return {};
return r;
}

return Object.keys(r).reduce((acc, key) => {
if (l.hasOwnProperty(key)) {
if (hasOwnProperty(l, key)) {
const difference = updatedDiff(l[key], r[key]);

// If the difference is empty, and the lhs is an empty object or the rhs is not an empty object
Expand Down
8 changes: 8 additions & 0 deletions src/updated/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,13 @@ describe('.updatedDiff', () => {
expect(updatedDiff(lhs, rhs)).toEqual({ date: new Date('2017') });
});
});

describe('object with non-function hasOwnProperty property', () => {
test('can represent the property in diff despite it being part of Object.prototype', () => {
const lhs = { hasOwnProperty: false };
const rhs = { hasOwnProperty: true };
expect(updatedDiff(lhs, rhs)).toEqual({ hasOwnProperty: true });
});
});
});
});
8 changes: 4 additions & 4 deletions src/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const isDate = (d) => d instanceof Date;
export const isEmpty = (o) => Object.keys(o).length === 0;
export const isObject = (o) => o != null && typeof o === "object";
export const properObject = (o) => (isObject(o) && !o.hasOwnProperty ? { ...o } : o);
export const isDate = d => d instanceof Date;
export const isEmpty = o => Object.keys(o).length === 0;
export const isObject = o => o != null && typeof o === 'object';
export const hasOwnProperty = (o, ...args) => Object.prototype.hasOwnProperty.call(o, ...args)
export const isEmptyObject = (o) => isObject(o) && isEmpty(o);
25 changes: 1 addition & 24 deletions src/utils/index.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isDate, isEmpty, isObject, properObject } from './';
import { isDate, isEmpty, isObject } from './';

describe('utils', () => {

Expand Down Expand Up @@ -70,27 +70,4 @@ describe('utils', () => {
expect(isObject(value)).toBe(false);
});
});

describe('.properObject', () => {
it('returns given object when object has keys and hasOwnProperty function', () => {
const o = { a: 1 };
const a = [1];
expect(properObject(o)).toBe(o);
expect(properObject(a)).toBe(a);
});

it('returns given value when value is not an object', () => {
const o = 'hello';
expect(properObject(o)).toBe(o);
});

it('returns object that has given keys and hasOwnProperty function when given object is created from a null', () => {
const o = Object.create(null);
o.a = 1;
const actual = properObject(o);
expect(actual).toEqual({ a: 1 });
expect(typeof actual.hasOwnProperty === 'function').toBe(true);
expect(actual).not.toBe(o);
});
});
});
Loading