From 278e209dd6e46bb43cb3758ac1d195b6dd5b15e6 Mon Sep 17 00:00:00 2001 From: David Zukowski Date: Mon, 23 Jan 2017 15:10:30 -0600 Subject: [PATCH] feat(transform): add function --- src/bundles/index.js | 3 +- src/transform.js | 47 ++++++++++++++++ tests/transform.spec.js | 121 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/transform.js create mode 100644 tests/transform.spec.js diff --git a/src/bundles/index.js b/src/bundles/index.js index 40e19e3..445f273 100644 --- a/src/bundles/index.js +++ b/src/bundles/index.js @@ -97,13 +97,14 @@ export { default as tail } from '../tail' export { default as take } from '../take' export { default as takeUntil } from '../takeUntil' export { default as takeWhile } from '../takeWhile' +export { default as tap } from '../tap' export { default as test } from '../test' export { default as times } from '../times' export { default as toLower } from '../toLower' export { default as toUpper } from '../toUpper' -export { default as tap } from '../tap' export { default as toPairs } from '../toPairs' export { default as trace } from '../trace' +export { default as transform } from '../transform' export { default as trim } from '../trim' export { default as type } from '../type' export { default as unless } from '../unless' diff --git a/src/transform.js b/src/transform.js new file mode 100644 index 0000000..641f87f --- /dev/null +++ b/src/transform.js @@ -0,0 +1,47 @@ +import _curry2 from './internal/_curry2' +import _hasOwn from './internal/_hasOwn' + +/** + * @name transform + * @signature String k, Any v => {k: (v -> v)} -> {k:v} -> {k:v} + * @since v0.18.0 + * @description + * Recursively applies property-based transforms to the target object's own + * properties. Transforms defined for non-existent properties are ignored, + * and properties without a corresponding transform are passed through + * unmodified. + * @example + * transform( + * { name: toUpper, details: { location: toUpper } }, + * { + * name: 'joe', + * details: { + * age: 20, + * location: 'usa' + * } + * } + * ) // => { name: 'JOE', details: { age: 40, location: 'USA' } } + */ +export default _curry2(function transform (transforms, obj) { + var res = {} + , prop + + for (prop in obj) { + if (_hasOwn.call(obj, prop) && _hasOwn.call(transforms, prop)) { + if (typeof transforms[prop] === 'object') { + res[prop] = transform(transforms[prop], obj[prop]) + } else if (typeof transforms[prop] === 'function') { + res[prop] = transforms[prop](obj[prop]) + } else { + throw new Error( + 'Invalid transformation supplied under the key "' + prop + '". ' + + 'Transformation must be either a function or object, but was ' + + '"' + typeof transforms[prop] + '".' + ) + } + } else { + res[prop] = obj[prop] + } + } + return res +}) diff --git a/tests/transform.spec.js b/tests/transform.spec.js new file mode 100644 index 0000000..2683208 --- /dev/null +++ b/tests/transform.spec.js @@ -0,0 +1,121 @@ +const test = require('ava') + , sinon = require('sinon') + , { + filter + , gte + , head + , toUpper + , transform + } = require('../dist/redash') + +test('properly reports its arity (is binary)', (t) => { + t.is(transform.length, 2) +}) + +test('is curried', (t) => { + t.is(typeof transform({}), 'function') +}) + +test('shallowly transforms based on a spec', (t) => { + const spec = { + name: x => x.toUpperCase() + , friends: () => [] + } + t.deepEqual(transform(spec, { + name: 'joe', friends: [1, 2, 3] + }), { + name: 'JOE' + , friends: [] + }) +}) + +test('only runs the transform if the property exists', (t) => { + const spy = sinon.spy() + + transform({ foo: spy }, { name: 'joe' }) + t.is(spy.callCount, 0) + + transform({ name: spy }, { name: 'joe' }) + t.is(spy.callCount, 1) +}) + +test('passes through properties without a transform', (t) => { + const spec = { + name: x => x.toUpperCase() + } + t.deepEqual(transform(spec, { + name: 'joe' + , friends: [1, 2, 3] + }), { + name: 'JOE' + , friends: [1, 2, 3] + }) +}) + +test('transforms recursively', (t) => { + /* eslint-disable */ + const spec = { + info: { + things: head + , data: { + name: toUpper + , nums: filter(gte(10)) + } + } + } + /* eslint-enasble */ + + t.deepEqual(transform(spec, { + foo: 'bar', + info: { + things: [1, 2, 3, 4] + , bar: 'baz' + , data: { + name: 'joe' + , nums: [7, 8, 9, 10, 11, 12] + } + } + }), { + foo: 'bar', + info: { + things: 1 + , bar: 'baz' + , data: { + name: 'JOE' + , nums: [10, 11, 12] + } + } + }) +}) + +test('transforms undefined/null properties', (t) => { + t.deepEqual(transform({ + foo: () => true + }, { + foo: null + }), { + foo: true + }) +}) + +test('does not transform inherited properties', (t) => { + function A () {} + A.prototype.bar = () => {} + const obj = new A() + obj.foo = 'foo' + + t.deepEqual(transform({ + bar: () => 'BAR' + , foo: () => 'FOO' + }, obj), { + bar: A.prototype.bar + , foo: 'FOO' + }) +}) + +test('throws for invalid transforms', (t) => { + t.throws( + () => transform({ bar: 'baz' }, { bar: 'bar' }), + /Invalid transformation/ + ) +})