Skip to content

Commit

Permalink
feat: add defu.arrayFn (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
Atinux committed Aug 4, 2020
1 parent e949864 commit df05ed0
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 32 deletions.
47 changes: 41 additions & 6 deletions README.md
Expand Up @@ -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
Expand All @@ -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
Expand Down
15 changes: 12 additions & 3 deletions src/defu.ts
Expand Up @@ -4,6 +4,7 @@ type DefuFn = <T>(...args: T | any) => T
interface Defu {
<T>(...args: T | any): T
fn: DefuFn
arrayFn: DefuFn
extend(merger?: Merger): DefuFn
}

Expand Down Expand Up @@ -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
}
})
Expand Down
68 changes: 45 additions & 23 deletions test/defu.test.ts
Expand Up @@ -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', () => {
Expand All @@ -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)
Expand All @@ -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', () => {
Expand All @@ -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
})
})
})

0 comments on commit df05ed0

Please sign in to comment.