From b1e000b06dfc212e15c9fe6793d453db5012fd59 Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Wed, 5 Aug 2015 04:29:30 -0700 Subject: [PATCH] Freeze objects returned by helper methods --- CHANGELOG.md | 3 ++- lib/if.js | 4 ++-- lib/in.js | 6 +++++- lib/index.js | 24 +++++++++--------------- lib/map.js | 8 +++----- lib/omit.js | 10 +++++++--- lib/reject.js | 10 +++++++--- lib/update.js | 8 +++++--- lib/withDefault.js | 15 ++++++++------- lib/wrap.js | 9 +++++++++ test/if-spec.js | 5 +++++ test/in-spec.js | 10 ++++++++++ test/map-spec.js | 4 ++++ test/omit-spec.js | 14 ++++++++++++++ test/reject-spec.js | 8 ++++++++ test/updeep-spec.js | 30 +++++++----------------------- test/withDefault-spec.js | 31 +++++++++++++++++++++++++++++++ 17 files changed, 136 insertions(+), 63 deletions(-) create mode 100644 lib/wrap.js create mode 100644 test/omit-spec.js create mode 100644 test/reject-spec.js create mode 100644 test/withDefault-spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aeaa5d..348f756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## [unreleased] * Add `u.if` to conditionally update objects. * 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] * Actually expose `u.in`. diff --git a/lib/if.js b/lib/if.js index 8a565b0..74a6e46 100644 --- a/lib/if.js +++ b/lib/if.js @@ -1,5 +1,5 @@ -import curry from 'lodash/function/curry'; import update from './update'; +import wrap from './wrap'; function updateIf(predicate, updates, object) { const test = typeof predicate === 'function' ? @@ -13,4 +13,4 @@ function updateIf(predicate, updates, object) { return update(updates, object); } -export default curry(updateIf); +export default wrap(updateIf); diff --git a/lib/in.js b/lib/in.js index a72472f..3359676 100644 --- a/lib/in.js +++ b/lib/in.js @@ -1,8 +1,12 @@ import curry from 'lodash/function/curry'; +import reject from 'lodash/collection/reject'; import update from './update'; 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); return update(updates, object); diff --git a/lib/index.js b/lib/index.js index 55a4153..84a2660 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,20 +7,14 @@ import reject from './reject'; import withDefault from './withDefault'; import update from './update'; -import curry from 'lodash/function/curry'; +const u = update; -function updateAndFreeze(updates, object) { - return freeze(update(updates, object)); -} +u.if = _if; +u.in = _in; +u.freeze = freeze; +u.map = map; +u.omit = omit; +u.reject = reject; +u.withDefault = withDefault; -const updeep = curry(updateAndFreeze); - -updeep.if = _if; -updeep.in = _in; -updeep.freeze = freeze; -updeep.map = map; -updeep.omit = omit; -updeep.reject = reject; -updeep.withDefault = withDefault; - -export default updeep; +export default u; diff --git a/lib/map.js b/lib/map.js index 9f816e6..a7eecfe 100644 --- a/lib/map.js +++ b/lib/map.js @@ -1,11 +1,9 @@ -import curry from 'lodash/function/curry'; import mapValues from 'lodash/object/mapValues'; import update from './update'; +import wrap from './wrap'; function map(iteratee, object) { - const updater = typeof iteratee === 'function' ? - iteratee : - val => update(iteratee, val); + const updater = update(iteratee); if (Array.isArray(object)) { return object.map(updater); @@ -14,4 +12,4 @@ function map(iteratee, object) { return mapValues(object, updater); } -export default curry(map); +export default wrap(map); diff --git a/lib/omit.js b/lib/omit.js index 33074e5..b5a9295 100644 --- a/lib/omit.js +++ b/lib/omit.js @@ -1,4 +1,8 @@ -import omit from 'lodash/object/omit'; -import curry from 'lodash/function/curry'; +import _omit from 'lodash/object/omit'; +import wrap from './wrap'; -export default curry((predicate, collection) => omit(collection, predicate)); +function omit(predicate, collection) { + return _omit(collection, predicate); +} + +export default wrap(omit); diff --git a/lib/reject.js b/lib/reject.js index f4e0cf3..50bbb8d 100644 --- a/lib/reject.js +++ b/lib/reject.js @@ -1,4 +1,8 @@ -import reject from 'lodash/collection/reject'; -import curry from 'lodash/function/curry'; +import _reject from 'lodash/collection/reject'; +import wrap from './wrap'; -export default curry((predicate, collection) => reject(collection, predicate)); +function reject(predicate, collection) { + return _reject(collection, predicate); +} + +export default wrap(reject); diff --git a/lib/update.js b/lib/update.js index 36f1308..4b025d3 100644 --- a/lib/update.js +++ b/lib/update.js @@ -1,6 +1,8 @@ import reduce from 'lodash/collection/reduce'; import isEmpty from 'lodash/lang/isEmpty'; import assign from 'lodash/object/assign'; +import curry from 'lodash/function/curry'; +import wrap from './wrap'; function resolveUpdates(updates, object = {}) { return reduce(updates, (acc, value, key) => { @@ -43,9 +45,9 @@ function updateArray(updates, object) { * @param {Object|Array} object to update * @return {Object|Array} new object with modifications */ -function update(updates, object) { +function update(updates, object, ...args) { if (typeof updates === 'function') { - return updates(object); + return updates(object, ...args); } if (updates === null || typeof updates !== 'object') { @@ -65,4 +67,4 @@ function update(updates, object) { return assign({}, object, resolvedUpdates); } -export default update; +export default wrap(update); diff --git a/lib/withDefault.js b/lib/withDefault.js index aa748a2..7ef9a48 100644 --- a/lib/withDefault.js +++ b/lib/withDefault.js @@ -1,11 +1,12 @@ import update from './update'; +import curry from 'lodash/function/curry'; -export default function withDefault(defaultValue, updates) { - return (value) => { - if (typeof value === 'undefined') { - return update(updates, defaultValue); - } +function withDefault(defaultValue, updates, object) { + if (typeof object === 'undefined') { + return update(updates, defaultValue); + } - return update(updates, value); - }; + return update(updates, object); } + +export default curry(withDefault); diff --git a/lib/wrap.js b/lib/wrap.js new file mode 100644 index 0000000..1a81f4f --- /dev/null +++ b/lib/wrap.js @@ -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 + ); +} diff --git a/test/if-spec.js b/test/if-spec.js index 784015d..399383f 100644 --- a/test/if-spec.js +++ b/test/if-spec.js @@ -33,4 +33,9 @@ describe('u.if', () => { 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; + }); }); diff --git a/test/in-spec.js b/test/in-spec.js index 4c9d2be..0e3c0d3 100644 --- a/test/in-spec.js +++ b/test/in-spec.js @@ -32,4 +32,14 @@ describe('u.in', () => { const result = u.in('a.b')(3)(object); 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; + }); }); diff --git a/test/map-spec.js b/test/map-spec.js index 3668bdd..d20d716 100644 --- a/test/map-spec.js +++ b/test/map-spec.js @@ -52,4 +52,8 @@ describe('u.map', () => { b: [0, 1], }); }); + + it('freezes the result', () => { + expect(Object.isFrozen(u.map({}, {}))).to.be.true; + }); }); diff --git a/test/omit-spec.js b/test/omit-spec.js new file mode 100644 index 0000000..175c316 --- /dev/null +++ b/test/omit-spec.js @@ -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; + }); +}); diff --git a/test/reject-spec.js b/test/reject-spec.js new file mode 100644 index 0000000..5d62f16 --- /dev/null +++ b/test/reject-spec.js @@ -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; + }); +}); diff --git a/test/updeep-spec.js b/test/updeep-spec.js index 3ef8306..0423a27 100644 --- a/test/updeep-spec.js +++ b/test/updeep-spec.js @@ -74,6 +74,13 @@ describe('updeep', () => { 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', () => { const object = {}; const result = u({ foo: [0, 1] }, object); @@ -81,15 +88,6 @@ describe('updeep', () => { 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', () => { const result = u({ foo: [0, 1] }, undefined); @@ -102,12 +100,6 @@ describe('updeep', () => { 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', () => { const result = u({ foo: { bar: 3 } }, { foo: { bar: 0 } }); @@ -118,12 +110,4 @@ describe('updeep', () => { it('assigns null values', () => { 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'); - }); }); diff --git a/test/withDefault-spec.js b/test/withDefault-spec.js new file mode 100644 index 0000000..c5c2e1a --- /dev/null +++ b/test/withDefault-spec.js @@ -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; + }); +});