From 5c0855b9dff3b1483a26ccd7df81756c3321aaed Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Thu, 6 Aug 2015 20:48:04 -0700 Subject: [PATCH] Add u.is --- CHANGELOG.md | 3 ++- README.md | 27 ++++++++++++++++++-- lib/in.js | 7 ++--- lib/index.js | 2 ++ lib/is.js | 21 +++++++++++++++ lib/util/splitPath.js | 7 +++++ test/is-spec.js | 59 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 lib/is.js create mode 100644 lib/util/splitPath.js create mode 100644 test/is-spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e514de..e929113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Change Log ## [unreleased] +* Add `u.is` to test predicates in a single path. (https://github.com/substantial/updeep/issues/13) ## [0.4.0] -* Add `u.if` and `u.ifElse` to conditionally update objects. +* Add `u.if` and `u.ifElse` to conditionally update objects. (https://github.com/substantial/updeep/issues/12) * Add `u.map` to update all values in an array or object. * 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. diff --git a/README.md b/README.md index e8520fb..a74553d 100644 --- a/README.md +++ b/README.md @@ -219,8 +219,8 @@ u({ x: u.omit(['b', 'c']) }, { x: { a: 0, b: 0, c: 0 } }); Reject items from an array. See [`_.reject`](https://lodash.com/docs#reject). ```js -function even(i) { return i % 2 === 0 }; -u({ x: u.reject(even) }, { x: [1, 2, 3, 4] }); +function isEven(i) { return i % 2 === 0; } +u({ x: u.reject(isEven) }, { x: [1, 2, 3, 4] }); // => { x: [1, 3] } ``` @@ -235,6 +235,29 @@ u({ x: u.withDefault([], { 0: 3 }) }, {}); See the [tests] for more examples. +### `u.is(path(, predicate)(, object))` + +Returns `true` if the `predicate` matches the `path` applied to the `object`. +If the `predicate` is a function, the result is returned. If not, they are compared with `===`. + +```js +u.is('a.b', 4, { a: { b: 4 } }); +// => true +``` + +```js +function isEven(i) { return i % 2 === 0; } +u.is('a.b', isEven, { a: { b: 4 } }); +// => true +``` + +```js +u({ + person: u.if(u.is('name.first', 'Jen'), u.in('name.last', 'Simpson')) +}, { person: { name: { first: 'Jen', last: 'Matthews' } } }); +// => { person: { name: { first: 'Jen', last: 'Simpson' } } } +``` + ## Install ```sh diff --git a/lib/in.js b/lib/in.js index 3359676..09cc6cc 100644 --- a/lib/in.js +++ b/lib/in.js @@ -1,12 +1,9 @@ import curry from 'lodash/function/curry'; -import reject from 'lodash/collection/reject'; import update from './update'; +import splitPath from './util/splitPath'; function updateIn(path, value, object) { - const parts = Array.isArray(path) ? - path : - reject(path.split('.'), x => !x); - + const parts = splitPath(path); const updates = parts.reduceRight((acc, key) => ({ [key]: acc }), value); return update(updates, object); diff --git a/lib/index.js b/lib/index.js index cd7c5a1..486343a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,6 @@ import freeze from './freeze'; import _in from './in'; +import is from './is'; import ifElse from './ifElse'; import map from './map'; import omit from './omit'; @@ -14,6 +15,7 @@ const u = update; u.if = ifElse(_, _, {}); u.ifElse = ifElse; u.in = _in; +u.is = is; u.freeze = freeze; u.map = map; u.omit = omit; diff --git a/lib/is.js b/lib/is.js new file mode 100644 index 0000000..c5e8db0 --- /dev/null +++ b/lib/is.js @@ -0,0 +1,21 @@ +import splitPath from './util/splitPath'; +import curry from 'lodash/function/curry'; + +function is(path, predicate, object) { + const parts = splitPath(path); + + let rest = object; + let part; + for (part of parts) { + if (typeof rest === 'undefined') return false; + rest = rest[part]; + } + + if (typeof predicate === 'function') { + return predicate(rest); + } + + return predicate === rest; +} + +export default curry(is); diff --git a/lib/util/splitPath.js b/lib/util/splitPath.js new file mode 100644 index 0000000..e3ebf3d --- /dev/null +++ b/lib/util/splitPath.js @@ -0,0 +1,7 @@ +import reject from 'lodash/collection/reject'; + +export default function splitPath(path) { + return Array.isArray(path) ? + path : + reject(path.split('.'), x => !x); +} diff --git a/test/is-spec.js b/test/is-spec.js new file mode 100644 index 0000000..f084066 --- /dev/null +++ b/test/is-spec.js @@ -0,0 +1,59 @@ +import { expect } from 'chai'; +import u from '../lib'; + +describe('u.is', () => { + it('returns true if path matches a value predicate', () => { + const result = u.is('a.b', 4, { a: { b: 4 } }); + expect(result).to.be.true; + }); + + it('returns true if path matches a function predicate', () => { + const isEven = x => x % 2 === 0; + const result = u.is('a.b', isEven, { a: { b: 6 } }); + expect(result).to.be.true; + }); + + it('returns false if path matches a value predicate', () => { + const result = u.is('a.b', 4, { a: { b: 5 } }); + expect(result).to.be.false; + }); + + it('returns false if path matches a function predicate', () => { + const isEven = x => x % 2 === 0; + const result = u.is('a.b', isEven, { a: { b: 7 } }); + expect(result).to.be.false; + }); + + it('returns false if the path does not exist', () => { + const result = u.is('a.b.c.d', 4, { a: { b: {} } }); + expect(result).to.be.false; + }); + + it('can test for undefined', () => { + const result = u.is('a.b.c', undefined, { a: { b: {} } }); + expect(result).to.be.true; + }); + + it('tests the actual object if a blank path is given', () => { + const result = u.is('', 4, 4); + expect(result).to.be.true; + }); + + it('can use arrays as paths', () => { + const result = u.is(['a', 'b'], 4, { a: { b: 4 } }); + expect(result).to.be.true; + }); + + it('can include array indexes in paths', () => { + let result = u.is('a.1.b', 4, { a: [{}, { b: 4 }] }); + expect(result).to.be.true; + + result = u.is(['a', 1, 'b'], 4, { a: [{}, { b: 4 }] }); + expect(result).to.be.true; + }); + + it('can be partially applied', () => { + const result = u.is('a.b')(4)({ a: { b: 4 } }); + expect(result).to.be.true; + }); +});