Skip to content

Commit

Permalink
Merge pull request #103 from blueflag/feature/replace-equal
Browse files Browse the repository at this point in the history
add: replaceEqual and replaceEqualDeep
  • Loading branch information
dxinteractive committed Mar 3, 2019
2 parents e1fa410 + 03af4a7 commit 763a632
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 0 deletions.
32 changes: 32 additions & 0 deletions packages/unmutable-docs/src/pages/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,38 @@ update(updater: (collection: any) => any) => (collection) => newCollection`,
name: "clone()",
definition: "clone() => (collection) => newCollection",
description: "Returns a clone of `collection` if `collection` is an array or object, or returns the `collection` unchanged if given an Immutable.js `Map` or `List`. Immutable.js data types are inherently immutable so do not need to be explicitly cloned."
},
{
name: "replaceEqual()",
definition: "replaceEqual(otherCollection) => (collection) => newCollection",
description: "If `otherCollection` is deeply equal to `collection`, `otherCollection` is returned, or else `collection` is returned.\n\nThis can be useful if you have a data source that always recreates a data structure, such as `JSON.parse()`, but you want to avoid needlessly passing new instances of unchanged objects and arrays downstream.",
types: ["object", "array", "uc", "imap", "ilist", "irecord"],
example: [
`
let oldData = ['foo','bar','baz'];
let newData = ['foo','bar','baz'];
let data = replaceEqual(oldData)(newData);
// data equals ['foo','bar','baz']
data === oldData
`
]
},
{
name: "replaceEqualDeep()",
definition: "replaceEqualDeep(otherCollection) => (collection) => newCollection",
description: "If `otherCollection` is deeply equal to `collection`, `otherCollection` is returned. If not, each of `collection`s children are compared against `otherCollection`, and if they are deeply equal then that part of `otherCollection` is inserted into the result. This process continues recursively down the data structure.\n\nThis can be useful if you have a data source that always recreates a data structure, such as `JSON.parse()`, but you want to avoid needlessly passing new instances of unchanged objects and arrays downstream.",
types: ["object", "array", "uc", "imap", "ilist", "irecord"],
example: [
`
let oldData = [1,2,[10,20]];
let newData = [3,4,[10,20]];
let data = replaceEqualDeep(oldData)(newData);
// data equals [3,4,[10,20]]
data[2] === oldData[2]
`
]
}
])
},
Expand Down
2 changes: 2 additions & 0 deletions packages/unmutable/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
/reduceRight.js
/remove.js
/rename.js
/replaceEqual.js
/replaceEqualDeep.js
/rest.js
/reverse.js
/rotate.js
Expand Down
2 changes: 2 additions & 0 deletions packages/unmutable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export {default as push} from './lib/push';
export {default as reduce} from './lib/reduce';
export {default as reduceRight} from './lib/reduceRight';
export {default as rename} from './lib/rename';
export {default as replaceEqual} from './lib/replaceEqual';
export {default as replaceEqualDeep} from './lib/replaceEqualDeep';
export {default as rest} from './lib/rest';
export {default as reverse} from './lib/reverse';
export {default as rotate} from './lib/rotate';
Expand Down
2 changes: 2 additions & 0 deletions packages/unmutable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
"reduceRight.js",
"remove.js",
"rename.js",
"replaceEqual.js",
"replaceEqualDeep.js",
"rest.js",
"reverse.js",
"rotate.js",
Expand Down
61 changes: 61 additions & 0 deletions packages/unmutable/src/__test__/replaceEqual-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// @flow
import replaceEqual from '../replaceEqual';
import {fromJS} from 'immutable';

test(`replaceEqual() on object should work`, () => {
let data = {
a: 1,
b: {
c: 2
}
};

let newDataSame = {
a: 1,
b: {
c: 2
}
};

let newDataDifferent = {
a: 1,
b: {
c: 2,
d: undefined
}
};

expect(replaceEqual(data)(newDataSame)).toBe(data);
expect(replaceEqual(data)(newDataSame)).toEqual(data);
expect(replaceEqual(data)(newDataDifferent)).toEqual(newDataDifferent);
// ^ when data is different, dont care if it's not strictly equal, deep equality is all that is guaranteed
});

test(`replaceEqual() on map should work`, () => {
let data = fromJS({
a: 1,
b: {
c: 2
}
});

let newDataSame = fromJS({
a: 1,
b: {
c: 2
}
});

let newDataDifferent = fromJS({
a: 1,
b: {
c: 2,
d: undefined
}
});

expect(replaceEqual(data)(newDataSame)).toBe(data);
expect(replaceEqual(data)(newDataSame)).toEqual(data);
expect(replaceEqual(data)(newDataDifferent)).toEqual(newDataDifferent);
// ^ when data is different, dont care if it's not strictly equal, deep equality is all that is guaranteed
});
82 changes: 82 additions & 0 deletions packages/unmutable/src/__test__/replaceEqualDeep-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// @flow
import replaceEqualDeep from '../replaceEqualDeep';
import {fromJS} from 'immutable';

test(`replaceEqualDeep() on object should work when top level is deeply equal`, () => {
let data = {
a: 1,
b: {
c: 2
}
};

let newDataSame = {
a: 1,
b: {
c: 2
}
};

let newDataDifferent = {
a: 1,
b: {
c: 2,
d: undefined
}
};

expect(replaceEqualDeep(data)(newDataSame)).toBe(data);
expect(replaceEqualDeep(data)(newDataSame)).toEqual(data);
expect(replaceEqualDeep(data)(newDataDifferent)).toEqual(newDataDifferent);
// ^ when data is different, dont care if it's not strictly equal, deep equality is all that is guaranteed
});

test(`replaceEqualDeep() on map should work when top level is deeply equal`, () => {
let data = fromJS({
a: 1,
b: {
c: 2
}
});

let newDataSame = fromJS({
a: 1,
b: {
c: 2
}
});

let newDataDifferent = fromJS({
a: 1,
b: {
c: 2,
d: undefined
}
});

expect(replaceEqualDeep(data)(newDataSame)).toBe(data);
expect(replaceEqualDeep(data)(newDataSame)).toEqual(data);
expect(replaceEqualDeep(data)(newDataDifferent)).toEqual(newDataDifferent);
// ^ when data is different, dont care if it's not strictly equal, deep equality is all that is guaranteed
});

test(`replaceEqualDeep() on object should work when a child is deeply equal`, () => {
let data = {
a: 1,
b: {
c: 2
}
};

let newDataDifferent = {
a: 10,
b: {
c: 2
}
};

expect(replaceEqualDeep(data)(newDataDifferent)).toEqual(newDataDifferent);
expect(replaceEqualDeep(data)(newDataDifferent)).not.toBe(newDataDifferent);
expect(replaceEqualDeep(data)(newDataDifferent).b).toBe(data.b);
expect(replaceEqualDeep(data)(newDataDifferent).b).toEqual(data.b);
});
10 changes: 10 additions & 0 deletions packages/unmutable/src/replaceEqual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow
import prep from './internal/unmutable';
import equals from './equals';

export default prep({
name: 'replaceEqual',
all: (other: any) => (collection: *): * => {
return equals(other)(collection) ? other : collection;
}
});
28 changes: 28 additions & 0 deletions packages/unmutable/src/replaceEqualDeep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @flow
import prep from './internal/unmutable';
import equals from './equals';
import get from './get';
import map from './map';
import pipeWith from './pipeWith';
import isWriteable from './isWriteable';

const replaceEqualDeep = (other: any) => (collection: *): * => {
if(equals(other)(collection)) {
return other;
}
if(isWriteable(collection)) {
return pipeWith(
collection,
map((child, key) => {
let otherChild = get(key)(other);
return replaceEqualDeep(otherChild)(child);
})
);
}
return collection;
};

export default prep({
name: 'replaceEqualDeep',
all: replaceEqualDeep
});

0 comments on commit 763a632

Please sign in to comment.