From df05ed04088d6e0f0bc1a8cd9603fae46fb59268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Tue, 4 Aug 2020 17:25:19 +0200 Subject: [PATCH] feat: add defu.arrayFn (#21) --- README.md | 47 +++++++++++++++++++++++++++----- src/defu.ts | 15 ++++++++--- test/defu.test.ts | 68 +++++++++++++++++++++++++++++++---------------- 3 files changed, 98 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d39427a..d5a9d00 100644 --- a/README.md +++ b/README.md @@ -62,24 +62,59 @@ ext({ cost: 15 }, { cost: 10 }) // { cost: 25 } ## Function Merger -Using `defu.fn`, if user provided a function, it will be called with default value instead of merging. Mostly useful for array merging. +Using `defu.fn`, if user provided a function, it will be called with default value instead of merging. -**Example:** Filter some items from defaults (array) +I can be useful for default values manipulation. + +**Example:** Filter some items from defaults (array) and add 20 to the count default value. ```js defu.fn({ + ignore: (val) => val.filter(item => item !== 'dist'), + count: (count) => count + 20 + }, { + ignore: ['node_modules','dist], + count: 10 + }) + /* + { + ignore: ['node_modules'], + count: 30 + } + */ +``` + +**Note:** if the default value is not defined, the function defined won't be called and kept as value. + +## Array Function Merger + +`defu.arrayFn` is similar to `defu.fn` but **only applies to array values defined in defaults**. + +**Example:** Filter some items from defaults (array) and add 20 to the count default value. + +```js + +defu.arrayFn({ ignore(val) => val.filter(i => i !== 'dist'), - num: () => 20 + count: () => 20 }, { ignore: [ 'node_modules', 'dist ], - num: 10 - }) // { ignore: ['node_modules'], num: 20 } + count: 10 + }) + /* + { + ignore: ['node_modules'], + count: () => 20 + } + */ ``` +**Note:** the function is called only if the value defined in defaults is an aray. + ### Remarks - `object` and `defaults` are not modified @@ -88,7 +123,7 @@ defu.fn({ - Will concat `array` values (if default property is defined) ```js console.log(defu({ array: ['b', 'c'] }, { array: ['a'] })) -// => { array: [a', 'b', 'c']} +// => { array: ['a', 'b', 'c']} ``` ## License diff --git a/src/defu.ts b/src/defu.ts index 34c4605..8070ecb 100644 --- a/src/defu.ts +++ b/src/defu.ts @@ -4,6 +4,7 @@ type DefuFn = (...args: T | any) => T interface Defu { (...args: T | any): T fn: DefuFn + arrayFn: DefuFn extend(merger?: Merger): DefuFn } @@ -55,9 +56,17 @@ function extend (merger?: Merger): DefuFn { const defu = extend() as Defu // Custom version with function merge support -defu.fn = extend((obj, key, value) => { - if (typeof value === 'function') { - obj[key] = value(obj[key]) +defu.fn = extend((obj, key, currentValue) => { + if (typeof obj[key] !== 'undefined' && typeof currentValue === 'function') { + obj[key] = currentValue(obj[key]) + return true + } +}) + +// Custom version with function merge support only for defined arrays +defu.arrayFn = extend((obj, key, currentValue) => { + if (Array.isArray(obj[key]) && typeof currentValue === 'function') { + obj[key] = currentValue(obj[key]) return true } }) diff --git a/test/defu.test.ts b/test/defu.test.ts index f171c4e..b450075 100644 --- a/test/defu.test.ts +++ b/test/defu.test.ts @@ -15,11 +15,15 @@ describe('defu', () => { }) it('should copy nested values', () => { - expect(defu({ a: { b: 'c' } }, { a: { d: 'e' } })).toEqual({ a: { b: 'c', d: 'e' } }) + expect(defu({ a: { b: 'c' } }, { a: { d: 'e' } })).toEqual({ + a: { b: 'c', d: 'e' }, + }) }) it('should concat array values by default', () => { - expect(defu({ array: ['b', 'c'] }, { array: ['a'] })).toEqual({ array: ['a', 'b', 'c'] }) + expect(defu({ array: ['b', 'c'] }, { array: ['a'] })).toEqual({ + array: ['a', 'b', 'c'], + }) }) it('should handle non object first param', () => { @@ -38,12 +42,14 @@ describe('defu', () => { expect(defu({ a: 1 }, { b: 2, a: 'x' }, { c: 3, a: 'x', b: 'x' })).toEqual({ a: 1, b: 2, - c: 3 + c: 3, }) }) it('should not override Object prototype', () => { - const payload = JSON.parse('{"constructor": {"prototype": {"isAdmin": true}}}') + const payload = JSON.parse( + '{"constructor": {"prototype": {"isAdmin": true}}}' + ) defu({}, payload) defu(payload, {}) defu(payload, payload) @@ -52,7 +58,10 @@ describe('defu', () => { }) it('should ignore non-object arguments', () => { - expect(defu(null, { foo: 1 }, false, 123, { bar: 2 })).toEqual({ foo: 1, bar: 2 }) + expect(defu(null, { foo: 1 }, false, 123, { bar: 2 })).toEqual({ + foo: 1, + bar: 2, + }) }) it('custom merger', () => { @@ -62,28 +71,41 @@ describe('defu', () => { return true } }) - expect(ext({ cost: 15 }, { cost: 10 })) - .toEqual({ cost: 25 }) + expect(ext({ cost: 15 }, { cost: 10 })).toEqual({ cost: 25 }) }) - it('custom merger with array', () => { - const ext = defu.extend((obj, key, currentValue) => { - if (Array.isArray(obj[key]) && typeof currentValue === 'function') { - obj[key] = currentValue(obj[key]) - return true - } - }) - expect(ext({ arr: () => ['c'] }, { arr: ['a', 'b'] })) - .toEqual({ arr: ['c'] }) + it('defu.fn()', () => { const num = () => 20 - expect(ext({ num }, { num: 10 })) - .toEqual({ num }) + expect( + defu.fn( + { + ignore: (val) => val.filter((i) => i !== 'dist'), + num, + ignored: num + }, + { + ignore: ['node_modules', 'dist'], + num: 10 + } + ) + ).toEqual({ + ignore: ['node_modules'], + num: 20, + ignored: num + }) }) - it('fn merger', () => { - expect(defu.fn({ ignore: val => val.filter(i => i !== 'dist') }, { ignore: ['node_modules', 'dist'] })) - .toEqual({ ignore: ['node_modules'] }) - expect(defu.fn({ num: () => 20 }, { num: 10 })) - .toEqual({ num: 20 }) + it('defu.arrayFn()', () => { + const num = () => 20 + expect(defu.arrayFn({ + arr: () => ['c'], + num + }, { + arr: ['a', 'b'], + num: 10 + })).toEqual({ + arr: ['c'], + num + }) }) })