Skip to content

Commit

Permalink
Fix set/update return type, stronger tests (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
Siegrift committed Jun 5, 2019
1 parent 5ab4049 commit c3eeee0
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 99 deletions.
20 changes: 10 additions & 10 deletions docs/globals.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ <h3>Primitive</h3>
<section class="tsd-panel tsd-member tsd-kind-type-alias tsd-has-type-parameter">
<a name="set1" class="tsd-anchor"></a>
<h3>Set1</h3>
<div class="tsd-signature tsd-kind-icon">Set1<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Pick</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">Exclude</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">keyof T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">K1</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol"> &amp; </span><span class="tsd-signature-type">{ [KK1 in K1]-?: Required&lt;Pick&lt;T, KK1&gt;&gt;; }[K1]</span></div>
<div class="tsd-signature tsd-kind-icon">Set1<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Set1&lt;T, K1&gt;</span></div>
<aside class="tsd-sources">
<ul>
<li>Defined in types.ts:37</li>
Expand All @@ -187,40 +187,40 @@ <h3>Set1</h3>
<section class="tsd-panel tsd-member tsd-kind-type-alias tsd-has-type-parameter">
<a name="set2" class="tsd-anchor"></a>
<h3>Set2</h3>
<div class="tsd-signature tsd-kind-icon">Set2<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Pick</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">Exclude</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">keyof T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">K1</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol"> &amp; </span><span class="tsd-signature-type">{ [KK1 in K1]-?: Required&lt;{ [key in K1]: Set1&lt;Exclude&lt;T[K1], null | undefined&gt;, K2&gt;; }&gt;; }[K1]</span></div>
<div class="tsd-signature tsd-kind-icon">Set2<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Set2&lt;T, K1, K2&gt;</span></div>
<aside class="tsd-sources">
<ul>
<li>Defined in types.ts:40</li>
<li>Defined in types.ts:42</li>
</ul>
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-type-alias tsd-has-type-parameter">
<a name="set3" class="tsd-anchor"></a>
<h3>Set3</h3>
<div class="tsd-signature tsd-kind-icon">Set3<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Pick</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">Exclude</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">keyof T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">K1</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol"> &amp; </span><span class="tsd-signature-type">{ [KK1 in K1]-?: Required&lt;{ [key in K1]: Set2&lt;Exclude&lt;T[K1], null | undefined&gt;, K2, K3&gt;; }&gt;; }[K1]</span></div>
<div class="tsd-signature tsd-kind-icon">Set3<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Set3&lt;T, K1, K2, K3&gt;</span></div>
<aside class="tsd-sources">
<ul>
<li>Defined in types.ts:46</li>
<li>Defined in types.ts:51</li>
</ul>
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-type-alias tsd-has-type-parameter">
<a name="set4" class="tsd-anchor"></a>
<h3>Set4</h3>
<div class="tsd-signature tsd-kind-icon">Set4<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Pick</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">Exclude</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">keyof T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">K1</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol"> &amp; </span><span class="tsd-signature-type">{ [KK1 in K1]-?: Required&lt;{ [key in K1]: Set3&lt;Exclude&lt;T[K1], null | undefined&gt;, K2, K3, K4&gt;; }&gt;; }[K1]</span></div>
<div class="tsd-signature tsd-kind-icon">Set4<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Set4&lt;T, K1, K2, K3, K4&gt;</span></div>
<aside class="tsd-sources">
<ul>
<li>Defined in types.ts:54</li>
<li>Defined in types.ts:61</li>
</ul>
</aside>
</section>
<section class="tsd-panel tsd-member tsd-kind-type-alias tsd-has-type-parameter">
<a name="set5" class="tsd-anchor"></a>
<h3>Set5</h3>
<div class="tsd-signature tsd-kind-icon">Set5<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Pick</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">Exclude</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">keyof T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">K1</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol"> &amp; </span><span class="tsd-signature-type">{ [KK1 in K1]-?: Required&lt;{ [key in K1]: Set4&lt;Exclude&lt;T[K1], null | undefined&gt;, K2, K3, K4, K5&gt;; }&gt;; }[K1]</span></div>
<div class="tsd-signature tsd-kind-icon">Set5<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Set5&lt;T, K1, K2, K3, K4, K5&gt;</span></div>
<aside class="tsd-sources">
<ul>
<li>Defined in types.ts:63</li>
<li>Defined in types.ts:74</li>
</ul>
</aside>
</section>
Expand Down Expand Up @@ -317,7 +317,7 @@ <h4>Type declaration</h4>
<section class="tsd-panel tsd-member tsd-kind-type-alias tsd-has-type-parameter">
<a name="without" class="tsd-anchor"></a>
<h3>Without</h3>
<div class="tsd-signature tsd-kind-icon">Without<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Pick</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">Exclude</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">keyof T</span><span class="tsd-signature-symbol">, </span><span class="tsd-signature-type">K</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span></div>
<div class="tsd-signature tsd-kind-icon">Without<span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">Without&lt;T, K&gt;</span></div>
<aside class="tsd-sources">
<ul>
<li>Defined in types.ts:19</li>
Expand Down
6 changes: 5 additions & 1 deletion src/test/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ export interface User {
key: string
}

export interface Dict {
[key: string]: number | string
}

export interface State {
users: User[]
dict: { [key: string]: number | string }
dict: Dict
optional?: { a: number }
a: { b: { c: { d: { e: string } } } }
}
Expand Down
53 changes: 36 additions & 17 deletions src/test/get.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get } from '../lib/get'
import { State } from './common'
import { Dict, State, User } from './common'

describe('get', () => {
let state: State
Expand All @@ -26,43 +26,62 @@ describe('get', () => {

describe('get the nested value in object', () => {
test('in array', () => {
expect(get(state, ['users', 0])).toEqual({ id: 156, key: 'asd' })
expect(get(state, ['users', 0, 'id'])).toEqual(156)
const user: User = get(state, ['users', 0])
expect(user).toEqual({ id: 156, key: 'asd' })

const id: number = get(state, ['users', 0, 'id'])
expect(id).toEqual(156)
})

test('in dictionary', () => {
expect(get(state, ['dict'])).toEqual({
const dict: Dict = get(state, ['dict'])
expect(dict).toEqual({
someId: 'hello',
someOther: 132,
})
expect(get(state, ['dict', 'someId'])).toBe('hello')

const id: string | number = get(state, ['dict', 'someId'])
expect(id).toBe('hello')
})

test('get up to 5 levels', () => {
expect(get(state, ['a'])).toEqual({ b: { c: { d: { e: '123' } } } })
expect(get(state, ['a', 'b'])).toEqual({ c: { d: { e: '123' } } })
expect(get(state, ['a', 'b', 'c'])).toEqual({ d: { e: '123' } })
expect(get(state, ['a', 'b', 'c', 'd'])).toEqual({ e: '123' })
expect(get(state, ['a', 'b', 'c', 'd', 'e'])).toEqual('123')
const get1: { b: { c: { d: { e: string } } } } = get(state, ['a'])
expect(get1).toEqual({ b: { c: { d: { e: '123' } } } })

const get2: { c: { d: { e: string } } } = get(state, ['a', 'b'])
expect(get2).toEqual({ c: { d: { e: '123' } } })

const get3: { d: { e: string } } = get(state, ['a', 'b', 'c'])
expect(get3).toEqual({ d: { e: '123' } })

const get4: { e: string } = get(state, ['a', 'b', 'c', 'd'])
expect(get4).toEqual({ e: '123' })

const get5: string = get(state, ['a', 'b', 'c', 'd', 'e'])
expect(get5).toEqual('123')
})
})

test('if object or intermediate path is undefined, returns undefined', () => {
expect(get(state, ['users', 10, 'id'])).toBe(undefined)
expect(get(state, ['dict', 'badId'])).toBe(undefined)
// type is correct because array type is NOT optional, but returns undefined for non-existent keys
const badIndex: number = get(state, ['users', 10, 'id'])
expect(badIndex).toBe(undefined)

// again correct, because dictionary type is required
const badId = get(state, ['dict', 'badId'])
expect(badId).toBe(undefined)
})

test('accepts default value', () => {
expect(get(state, ['optional'], { a: 10 })).toEqual({ a: 10 })
const opt: { a: number } | undefined = get(state, ['optional'], { a: 10 })
expect(opt).toEqual({ a: 10 })
})

test('returns nested value (as non optional) when default value is passed', () => {
const user = get(state, ['users', 10], { id: -1, key: 'default' })
// this line verifies that user is of type 'User' and not 'User | undefined'
const userId = user.id
const user: User = get(state, ['users', 10], { id: -1, key: 'default' })

expect(user).toEqual({ id: -1, key: 'default' })
expect(userId).toBe(-1)
expect(user.id).toBe(-1)
})

test('falsy last value has more priority than defaultValue', () => {
Expand Down
45 changes: 31 additions & 14 deletions src/test/set.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ describe('set', () => {

describe('set or override the nested value in object', () => {
test('in array', () => {
expect(set(state, ['users', 0], { id: 777, key: 'new' })).toEqual({
state = set(state, ['users', 0], { id: 777, key: 'new' })

expect(state).toEqual({
users: [{ id: 777, key: 'new' }],
dict: {
someId: 'hello',
Expand All @@ -26,7 +28,9 @@ describe('set', () => {
})

test('in dictionary', () => {
expect(set(state, ['dict'], { newKey: 777 })).toEqual({
state = set(state, ['dict'], { newKey: 777 })

expect(state).toEqual({
users: [{ id: 56, key: 'key' }],
dict: {
newKey: 777,
Expand All @@ -38,15 +42,22 @@ describe('set', () => {
test('set up to 5 levels', () => {
const obj = { a: state.a }
const expected = { a: { b: { c: { d: { e: '123' } } } } }
let setObj: Pick<State, 'a'> = obj

setObj = set(obj, ['a'], { b: { c: { d: { e: '123' } } } })
expect(setObj).toEqual(expected)

setObj = set(obj, ['a', 'b'], { c: { d: { e: '123' } } })
expect(setObj).toEqual(expected)

setObj = set(obj, ['a', 'b', 'c'], { d: { e: '123' } })
expect(setObj).toEqual(expected)

expect(set(obj, ['a'], { b: { c: { d: { e: '123' } } } })).toEqual(
expected,
)
expect(set(obj, ['a', 'b'], { c: { d: { e: '123' } } })).toEqual(
expected,
)
expect(set(obj, ['a', 'b', 'c'], { d: { e: '123' } })).toEqual(expected)
expect(set(obj, ['a', 'b', 'c', 'd'], { e: '123' })).toEqual(expected)
setObj = set(obj, ['a', 'b', 'c', 'd'], { e: '123' })
expect(setObj).toEqual(expected)

setObj = set(obj, ['a', 'b', 'c', 'd', 'e'], '123')
expect(setObj).toEqual(expected)
})
})

Expand All @@ -65,7 +76,9 @@ describe('set', () => {
})

test('create new index in array', () => {
expect(set(state, ['users', 3, 'id'], 777)).toEqual({
state = set(state, ['users', 3, 'id'], 777)

expect(state).toEqual({
users: [{ id: 56, key: 'key' }, undefined, undefined, { id: 777 }],
dict: {
someId: 'hello',
Expand All @@ -75,7 +88,9 @@ describe('set', () => {
})

test('create new property in dictionary', () => {
expect(set(state, ['dict', 'newKey'], 777)).toEqual({
state = set(state, ['dict', 'newKey'], 777)

expect(state).toEqual({
users: [{ id: 56, key: 'key' }],
dict: {
someId: 'hello',
Expand All @@ -95,9 +110,11 @@ describe('set', () => {
type A = string[]
type D = { [key: string]: A }
type T = { req: { opt?: D | null } }
const obj: T = { req: { opt: null } }
let obj: T = { req: { opt: null } }

obj = set(obj, ['req', 'opt', 'key', 1], 'str')

expect(set(obj, ['req', 'opt', 'key', 1], 'str')).toEqual({
expect(obj).toEqual({
req: { opt: { key: [undefined, 'str'] } },
})
})
Expand Down
62 changes: 41 additions & 21 deletions src/test/unset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ describe('unset', () => {

describe('(immutably) remove value in source object', () => {
test('in array', () => {
expect(unset(state, ['users', 0])).toEqual({
state = unset(state, ['users', 0])

expect(state).toEqual({
users: [],
dict: {
someId: 'hello',
Expand All @@ -26,22 +28,38 @@ describe('unset', () => {
})

test('in dictionary', () => {
expect(unset(state, ['dict'])).toEqual({
state = unset(state, ['dict', 'someId'])

expect(state).toEqual({
users: [{ id: 56, key: 'key' }],
a: { b: { c: { d: { e: '123' } } } },
dict: {},
})
})

test('unset up to 5 levels', () => {
const obj = { a: state.a }

expect(unset(obj, ['a'])).toEqual({})
expect(unset(obj, ['a', 'b'])).toEqual({ a: {} })
expect(unset(obj, ['a', 'b', 'c'])).toEqual({ a: { b: {} } })
expect(unset(obj, ['a', 'b', 'c', 'd'])).toEqual({ a: { b: { c: {} } } })
expect(unset(obj, ['a', 'b', 'c', 'd', 'e'])).toEqual({
a: { b: { c: { d: {} } } },
})
const obj1: {} = unset(obj, ['a'])
expect(obj1).toEqual({})

const obj2: { a: {} } = unset(obj, ['a', 'b'])
expect(obj2).toEqual({ a: {} })

const obj3: { a: { b: {} } } = unset(obj, ['a', 'b', 'c'])
expect(obj3).toEqual({ a: { b: {} } })

const obj4: { a: { b: { c: {} } } } = unset(obj, ['a', 'b', 'c', 'd'])
expect(obj4).toEqual({ a: { b: { c: {} } } })

const obj5: { a: { b: { c: { d: {} } } } } = unset(obj, [
'a',
'b',
'c',
'd',
'e',
])
expect(obj5).toEqual({ a: { b: { c: { d: {} } } } })
})
})

Expand All @@ -52,23 +70,25 @@ describe('unset', () => {
})

test("if path doesn't exist, returns object", () => {
expect(unset(null as any, ['key'])).toEqual(null)
expect(unset(undefined as any, ['key'])).toEqual(undefined)

expect(unset(state, ['users', 3, 'id'])).toEqual({
let unsetState = state
const originalState = {
users: [{ id: 56, key: 'key' }],
dict: {
someId: 'hello',
},
a: { b: { c: { d: { e: '123' } } } },
})
}

expect(unset(state, ['dict', 'newKey'])).toEqual({
users: [{ id: 56, key: 'key' }],
dict: {
someId: 'hello',
},
a: { b: { c: { d: { e: '123' } } } },
})
expect(unset(null as any, ['key'])).toEqual(null)
expect(unset(undefined as any, ['key'])).toEqual(undefined)

unsetState = unset(state, ['users', 3, 'id'])
expect(unsetState).toEqual(originalState)

unsetState = unset(state, ['dict', 'newKey'])
expect(unsetState).toEqual(originalState)

unsetState = unset(state, ['optional'])
expect(unsetState).toEqual(originalState)
})
})

0 comments on commit c3eeee0

Please sign in to comment.