Freeze objects returned by helper methods

This commit is contained in:
Aaron Jensen 2015-08-05 04:29:30 -07:00
parent cc42bbb2a4
commit b1e000b06d
17 changed files with 136 additions and 63 deletions

View File

@ -3,7 +3,8 @@
## [unreleased] ## [unreleased]
* Add `u.if` to conditionally update objects. * Add `u.if` to conditionally update objects.
* Add `u.map` to update all values in an array or object. * Add `u.map` to update all values in an array or object.
* Replace object outright if null or constant givens as `updates`. * Replace object outright if null or constant provided as `updates`.
* Freeze objects returned by helper methods that use `update` like `withDefault`, `map`, `in`, etc. Previously, only `u` did freezing.
## [0.3.1] ## [0.3.1]
* Actually expose `u.in`. * Actually expose `u.in`.

View File

@ -1,5 +1,5 @@
import curry from 'lodash/function/curry';
import update from './update'; import update from './update';
import wrap from './wrap';
function updateIf(predicate, updates, object) { function updateIf(predicate, updates, object) {
const test = typeof predicate === 'function' ? const test = typeof predicate === 'function' ?
@ -13,4 +13,4 @@ function updateIf(predicate, updates, object) {
return update(updates, object); return update(updates, object);
} }
export default curry(updateIf); export default wrap(updateIf);

View File

@ -1,8 +1,12 @@
import curry from 'lodash/function/curry'; import curry from 'lodash/function/curry';
import reject from 'lodash/collection/reject';
import update from './update'; import update from './update';
function updateIn(path, value, object) { function updateIn(path, value, object) {
const parts = Array.isArray(path) ? path : path.split('.'); const parts = Array.isArray(path) ?
path :
reject(path.split('.'), x => !x);
const updates = parts.reduceRight((acc, key) => ({ [key]: acc }), value); const updates = parts.reduceRight((acc, key) => ({ [key]: acc }), value);
return update(updates, object); return update(updates, object);

View File

@ -7,20 +7,14 @@ import reject from './reject';
import withDefault from './withDefault'; import withDefault from './withDefault';
import update from './update'; import update from './update';
import curry from 'lodash/function/curry'; const u = update;
function updateAndFreeze(updates, object) { u.if = _if;
return freeze(update(updates, object)); u.in = _in;
} u.freeze = freeze;
u.map = map;
u.omit = omit;
u.reject = reject;
u.withDefault = withDefault;
const updeep = curry(updateAndFreeze); export default u;
updeep.if = _if;
updeep.in = _in;
updeep.freeze = freeze;
updeep.map = map;
updeep.omit = omit;
updeep.reject = reject;
updeep.withDefault = withDefault;
export default updeep;

View File

@ -1,11 +1,9 @@
import curry from 'lodash/function/curry';
import mapValues from 'lodash/object/mapValues'; import mapValues from 'lodash/object/mapValues';
import update from './update'; import update from './update';
import wrap from './wrap';
function map(iteratee, object) { function map(iteratee, object) {
const updater = typeof iteratee === 'function' ? const updater = update(iteratee);
iteratee :
val => update(iteratee, val);
if (Array.isArray(object)) { if (Array.isArray(object)) {
return object.map(updater); return object.map(updater);
@ -14,4 +12,4 @@ function map(iteratee, object) {
return mapValues(object, updater); return mapValues(object, updater);
} }
export default curry(map); export default wrap(map);

View File

@ -1,4 +1,8 @@
import omit from 'lodash/object/omit'; import _omit from 'lodash/object/omit';
import curry from 'lodash/function/curry'; import wrap from './wrap';
export default curry((predicate, collection) => omit(collection, predicate)); function omit(predicate, collection) {
return _omit(collection, predicate);
}
export default wrap(omit);

View File

@ -1,4 +1,8 @@
import reject from 'lodash/collection/reject'; import _reject from 'lodash/collection/reject';
import curry from 'lodash/function/curry'; import wrap from './wrap';
export default curry((predicate, collection) => reject(collection, predicate)); function reject(predicate, collection) {
return _reject(collection, predicate);
}
export default wrap(reject);

View File

@ -1,6 +1,8 @@
import reduce from 'lodash/collection/reduce'; import reduce from 'lodash/collection/reduce';
import isEmpty from 'lodash/lang/isEmpty'; import isEmpty from 'lodash/lang/isEmpty';
import assign from 'lodash/object/assign'; import assign from 'lodash/object/assign';
import curry from 'lodash/function/curry';
import wrap from './wrap';
function resolveUpdates(updates, object = {}) { function resolveUpdates(updates, object = {}) {
return reduce(updates, (acc, value, key) => { return reduce(updates, (acc, value, key) => {
@ -43,9 +45,9 @@ function updateArray(updates, object) {
* @param {Object|Array} object to update * @param {Object|Array} object to update
* @return {Object|Array} new object with modifications * @return {Object|Array} new object with modifications
*/ */
function update(updates, object) { function update(updates, object, ...args) {
if (typeof updates === 'function') { if (typeof updates === 'function') {
return updates(object); return updates(object, ...args);
} }
if (updates === null || typeof updates !== 'object') { if (updates === null || typeof updates !== 'object') {
@ -65,4 +67,4 @@ function update(updates, object) {
return assign({}, object, resolvedUpdates); return assign({}, object, resolvedUpdates);
} }
export default update; export default wrap(update);

View File

@ -1,11 +1,12 @@
import update from './update'; import update from './update';
import curry from 'lodash/function/curry';
export default function withDefault(defaultValue, updates) { function withDefault(defaultValue, updates, object) {
return (value) => { if (typeof object === 'undefined') {
if (typeof value === 'undefined') {
return update(updates, defaultValue); return update(updates, defaultValue);
} }
return update(updates, value); return update(updates, object);
};
} }
export default curry(withDefault);

9
lib/wrap.js Normal file
View File

@ -0,0 +1,9 @@
import curry from 'lodash/function/curry';
import freeze from './freeze';
export default function wrap(func, length = func.length) {
return curry(
(...args) => freeze(func(...args)),
length
);
}

View File

@ -33,4 +33,9 @@ describe('u.if', () => {
expect(result).to.eql({ a: 3 }); expect(result).to.eql({ a: 3 });
}); });
it('freezes the result', () => {
expect(Object.isFrozen(u.if(true, {}, {}))).to.be.true;
expect(Object.isFrozen(u.if(false, {}, {}))).to.be.true;
});
}); });

View File

@ -32,4 +32,14 @@ describe('u.in', () => {
const result = u.in('a.b')(3)(object); const result = u.in('a.b')(3)(object);
expect(result).to.eql({ a: { b: 3 } }); expect(result).to.eql({ a: { b: 3 } });
}); });
it('replaces the object outright if the path is empty', () => {
const object = {};
const result = u.in('', 3, object);
expect(result).to.equal(3);
});
it('freezes the result', () => {
expect(Object.isFrozen(u.in('a', 0, {}))).to.be.true;
});
}); });

View File

@ -52,4 +52,8 @@ describe('u.map', () => {
b: [0, 1], b: [0, 1],
}); });
}); });
it('freezes the result', () => {
expect(Object.isFrozen(u.map({}, {}))).to.be.true;
});
}); });

14
test/omit-spec.js Normal file
View File

@ -0,0 +1,14 @@
import { expect } from 'chai';
import u from '../lib';
describe('u.omit', () => {
it('can omit a key', () => {
const result = u({ foo: u.omit('bar') }, { foo: { bar: 7 } });
expect(result).to.eql({ foo: {} });
});
it('freezes the result', () => {
expect(Object.isFrozen(u.omit('a', {}))).to.be.true;
});
});

8
test/reject-spec.js Normal file
View File

@ -0,0 +1,8 @@
import { expect } from 'chai';
import u from '../lib';
describe('u.reject', () => {
it('freezes the result', () => {
expect(Object.isFrozen(u.reject('a', []))).to.be.true;
});
});

View File

@ -74,6 +74,13 @@ describe('updeep', () => {
expect(result).to.deep.equal({ foo: 4 }); expect(result).to.deep.equal({ foo: 4 });
}); });
it('passes additional arguments on to updates if it is a function', () => {
const func = (_, x) => x;
const result = u(func, 0, 4);
expect(result).to.equal(4);
});
it('can update if the value is an array', () => { it('can update if the value is an array', () => {
const object = {}; const object = {};
const result = u({ foo: [0, 1] }, object); const result = u({ foo: [0, 1] }, object);
@ -81,15 +88,6 @@ describe('updeep', () => {
expect(result).to.deep.equal({ foo: [0, 1] }); expect(result).to.deep.equal({ foo: [0, 1] });
}); });
it('can use withDefault to default things', () => {
const object = {};
const result = u({
foo: u.withDefault([], { 0: 'bar' }),
}, object);
expect(result).to.eql({ foo: ['bar'] });
});
it('can update when original object is undefined', () => { it('can update when original object is undefined', () => {
const result = u({ foo: [0, 1] }, undefined); const result = u({ foo: [0, 1] }, undefined);
@ -102,12 +100,6 @@ describe('updeep', () => {
expect(result).to.eql(8); expect(result).to.eql(8);
}); });
it('can omit a key', () => {
const result = u({ foo: u.omit('bar') }, { foo: { bar: 7 } });
expect(result).to.eql({ foo: {} });
});
it('deeply freezes the result', () => { it('deeply freezes the result', () => {
const result = u({ foo: { bar: 3 } }, { foo: { bar: 0 } }); const result = u({ foo: { bar: 3 } }, { foo: { bar: 0 } });
@ -118,12 +110,4 @@ describe('updeep', () => {
it('assigns null values', () => { it('assigns null values', () => {
expect(u({isNull: null}, {})).to.eql({isNull: null}); expect(u({isNull: null}, {})).to.eql({isNull: null});
}); });
it('has additional functions', () => {
expect(u.freeze).to.be.a('function');
expect(u.if).to.be.a('function');
expect(u.in).to.be.a('function');
expect(u.omit).to.be.a('function');
expect(u.withDefault).to.be.a('function');
});
}); });

31
test/withDefault-spec.js Normal file
View File

@ -0,0 +1,31 @@
import { expect } from 'chai';
import u from '../lib';
describe('u.withDefault', () => {
it('uses the default as the basis for the update if the object is undefined', () => {
const inc = x => x + 1;
const result = u.withDefault({ a: 0 }, { a: inc }, undefined);
expect(result).to.eql({ a: 1 });
});
it('uses ignores the default if the object is defined', () => {
const inc = x => x + 1;
const result = u.withDefault({ a: 0 }, { a: inc }, { a: 3 });
expect(result).to.eql({ a: 4 });
});
it('can be partially applied', () => {
const object = {};
const result = u({
foo: u.withDefault([], { 0: 'bar' }),
}, object);
expect(result).to.eql({ foo: ['bar'] });
});
it('freezes the result', () => {
expect(Object.isFrozen(u.withDefault({}, 'a')(undefined))).to.be.true;
});
});