diff --git a/lib/index.js b/lib/index.js index 8552ad9..32d34b6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,2 +1,6 @@ -'use strict'; -export default {}; +const update = require('app/utils/update/update'); +update.omit = require('app/utils/update/omit'); +update.reject = require('app/utils/update/reject'); +update.withDefault = require('app/utils/update/with_default'); + +module.exports = update; diff --git a/lib/omit.js b/lib/omit.js new file mode 100644 index 0000000..5af40e9 --- /dev/null +++ b/lib/omit.js @@ -0,0 +1,4 @@ +const omit = require('lodash/object/omit'); +const curry = require('lodash/function/curry'); + +module.exports = curry((predicate, collection) => omit(collection, predicate)); diff --git a/lib/reject.js b/lib/reject.js new file mode 100644 index 0000000..2b8485e --- /dev/null +++ b/lib/reject.js @@ -0,0 +1,4 @@ +const reject = require('lodash/collection/reject'); +const curry = require('lodash/function/curry'); + +module.exports = curry((predicate, collection) => reject(collection, predicate)); diff --git a/lib/update.js b/lib/update.js new file mode 100644 index 0000000..90643fa --- /dev/null +++ b/lib/update.js @@ -0,0 +1,59 @@ +const reduce = require('lodash/collection/reduce'); +const isEmpty = require('lodash/lang/isEmpty'); +const curry = require('lodash/function/curry'); + +function resolveUpdates(updates, obj = {}) { + return reduce(updates, (acc, value, key) => { + if (!Array.isArray(value) && typeof value === 'object') { + value = update(value, obj[key]); + } else if (typeof value === 'function') { + value = value(obj[key]); + } + + if (obj[key] !== value) { + acc[key] = value; + } + + return acc; + }, {}); +} + +function updateArray(updates, obj) { + const newObj = [...obj]; + + return reduce(updates, (acc, value, index) => { + acc[index] = value; + return acc; + }, newObj); +} + +/** + * Recursively update an object or array. + * + * Can update with values: + * update({ foo: 3 }, { foo: 1, bar: 2 }); + * // => { foo: 3, bar: 2 } + * + * Or with a function: + * update({ foo: x => (x + 1) }, { foo: 2 }); + * // => { foo: 3 } + */ +function update(updates, obj) { + if (typeof updates === 'function') { + return updates(obj); + } + + const resolvedUpdates = resolveUpdates(updates, obj); + + if (isEmpty(resolvedUpdates)) { + return obj; + } + + if (Array.isArray(obj)) { + return updateArray(resolvedUpdates, obj); + } + + return Object.assign({}, obj, resolvedUpdates); +} + +module.exports = curry(update); diff --git a/lib/with_default.js b/lib/with_default.js new file mode 100644 index 0000000..1edacb5 --- /dev/null +++ b/lib/with_default.js @@ -0,0 +1,11 @@ +const update = require('app/utils/update/update'); + +module.exports = function withDefault(defaultValue, updates) { + return (value) => { + if (typeof value === "undefined") { + return update(updates, defaultValue); + } + + return update(updates, value); + }; +}; diff --git a/package.json b/package.json index a5a9569..1c0d2c4 100644 --- a/package.json +++ b/package.json @@ -26,16 +26,20 @@ "bugs": { "url": "https://github.com/aaronjensen/updeep/issues" }, + "peerDependencies": { + "lodash": "^3.10.0" + }, "devDependencies": { + "babel-core": "^5.5.0", + "chai": "^3.2.0", "gulp": "^3.6.0", + "gulp-babel": "^5.1.0", + "gulp-coveralls": "^0.1.0", + "gulp-eslint": "^0.15.0", "gulp-exclude-gitignore": "^1.0.0", "gulp-istanbul": "^0.9.0", - "gulp-eslint": "^0.15.0", "gulp-mocha": "^2.0.0", - "gulp-plumber": "^1.0.0", "gulp-nsp": "^0.4.5", - "gulp-coveralls": "^0.1.0", - "gulp-babel": "^5.1.0", - "babel-core": "^5.5.0" + "gulp-plumber": "^1.0.0" } } diff --git a/test/index.js b/test/index.js index bb5d3ce..b23834c 100644 --- a/test/index.js +++ b/test/index.js @@ -1,9 +1,103 @@ -'use strict'; -import assert from 'assert'; +import { expect } from 'chai'; import updeep from '../lib'; -describe('updeep', function () { - it('should have unit test!', function () { - assert(false, 'we expected this package author to add actual unit tests.'); +console.log("testing"); +describe("updeep", () => { + it("does not change anything if no updates are specified", () => { +console.log("testing2"); + const obj = { foo: 3, bar: [7, 5] }; + const result = updeep({}, obj); + + expect(result).to.equal(obj); + }); + + it("can update with fixed values", () => { + const obj = { foo: 3, bar: [7, 5] }; + const result = updeep({ foo: 4 }, obj); + + expect(result).to.deep.equal({ foo: 4, bar: [7, 5] }); + }); + + it("returns the same instance if an update doesn't make changes", () => { + const obj = { foo: 3 }; + const result = updeep({ foo: 3 }, obj); + + expect(result).to.equal(obj); + }); + + it("can update a nested structure", () => { + const obj = { foo: { bar: 7, bam: 3 }, baz: 32 }; + const result = updeep({ foo: { bar: 8 } }, obj); + + expect(result).to.deep.equal({ foo: { bar: 8, bam: 3 }, baz: 32 }); + }); + + it("can update arrays", () => { + const obj = [1, 2, 3]; + const result = updeep({ 1: 7 }, obj); + + expect(result).to.deep.equal([1, 7, 3]); + }); + + it("can add an element to an array", () => { + const obj = []; + const result = updeep({ 0: 3 }, obj); + + expect(result).to.eql([3]); + }); + + it("can update nested arrays", () => { + const obj = { foo: [1, 2, 3], bar: 9 }; + const result = updeep({ foo: { 1: 7 } }, obj); + + expect(result).to.deep.equal({ foo: [1, 7, 3], bar: 9 }); + }); + + it("can use functions to update values", () => { + const inc = (i) => i + 1; + const obj = { foo: 3, bar: 4, baz: 7 }; + const result = updeep({ foo: inc, bar: inc }, obj); + + expect(result).to.deep.equal({ foo: 4, bar: 5, baz: 7 }); + }); + + it("is curryable", () => { + const inc = (i) => i + 1; + const obj = { foo: 3 }; + const incFoo = updeep({ foo: inc }); + + const result = incFoo(obj); + + expect(result).to.deep.equal({ foo: 4 }); + }); + + it("can update if the value is an array", () => { + const obj = {}; + const result = updeep({ foo: [0, 1] }, obj); + + expect(result).to.deep.equal({ foo: [0, 1] }); + }); + + it("can use withDefault to default things", () => { + const obj = {}; + const result = updeep({ + foo: update.withDefault([], { + 0: 'bar' + }) + }, obj); + + expect(result).to.eql({ foo: ['bar'] }); + }); + + it('can update when original object is undefined', () => { + const result = updeep({ foo: [0, 1] }, undefined); + + expect(result).to.deep.equal({ foo: [0, 1] }); + }); + + it('can take a function as the updater', () => { + const result = updeep(i => i + 1, 7); + + expect(result).to.eql(8); }); });