|
1 |
| -// @ts-check |
2 |
| -import { test } from 'uvu'; |
| 1 | +import { suite } from 'uvu'; |
3 | 2 | import * as assert from 'uvu/assert';
|
4 | 3 | import dset from '../dist/dset';
|
5 | 4 |
|
6 |
| -test('dset', () => { |
7 |
| - assert.type(dset, 'function', 'exports a function'); |
| 5 | +const API = suite('API'); |
8 | 6 |
|
9 |
| - let foo = { a:1, b:2 }; |
10 |
| - let out = dset(foo, 'c', 3); // add c |
11 |
| - assert.is(out, undefined, 'does not return output'); |
12 |
| - assert.equal(foo, { a:1, b:2, c:3 }, 'mutates; adds simple key:val'); |
| 7 | +API('should export a function', () => { |
| 8 | + assert.type(dset, 'function'); |
| 9 | +}); |
| 10 | + |
| 11 | +API.run(); |
| 12 | + |
| 13 | +// --- |
| 14 | + |
| 15 | +const usage = suite('usage'); |
| 16 | + |
| 17 | +usage('should not give return value', () => { |
| 18 | + let output = dset({}, 'c', 3); // add c |
| 19 | + assert.is(output, undefined); |
| 20 | +}); |
| 21 | + |
| 22 | +usage('should mutate original object', () => { |
| 23 | + let item = { foo: 1 }; |
| 24 | + dset(item, 'bar', 123); |
| 25 | + assert.ok(item === item); |
| 26 | + assert.equal(item, { |
| 27 | + foo: 1, |
| 28 | + bar: 123 |
| 29 | + }); |
| 30 | +}); |
| 31 | + |
| 32 | +usage.run(); |
| 33 | + |
| 34 | +// --- |
| 35 | + |
| 36 | +const keys = suite('keys'); |
| 37 | + |
| 38 | +keys('should add value to key path :: shallow :: string', () => { |
| 39 | + let input = {}; |
| 40 | + dset(input, 'abc', 123); |
| 41 | + assert.equal(input, { abc: 123 }); |
| 42 | +}); |
| 43 | + |
| 44 | +keys('should add value to key path :: shallow :: array', () => { |
| 45 | + let input = {}; |
| 46 | + dset(input, ['abc'], 123); |
| 47 | + assert.equal(input, { abc: 123 }); |
| 48 | +}); |
| 49 | + |
| 50 | +keys('should add value to key path :: nested :: string', () => { |
| 51 | + let input = {}; |
| 52 | + dset(input, 'a.b.c', 123); |
| 53 | + assert.equal(input, { |
| 54 | + a: { |
| 55 | + b: { |
| 56 | + c: 123 |
| 57 | + } |
| 58 | + } |
| 59 | + }); |
| 60 | +}); |
| 61 | + |
| 62 | +keys('should add value to key path :: nested :: array', () => { |
| 63 | + let input = {}; |
| 64 | + dset(input, ['a', 'b', 'c'], 123); |
| 65 | + assert.equal(input, { |
| 66 | + a: { |
| 67 | + b: { |
| 68 | + c: 123 |
| 69 | + } |
| 70 | + } |
| 71 | + }); |
| 72 | +}); |
| 73 | + |
| 74 | +keys.run(); |
13 | 75 |
|
14 |
| - foo = {}; |
15 |
| - dset(foo, 'a.b.c', 999); // add deep |
16 |
| - assert.equal(foo, { a:{ b:{ c:999 } } }, 'mutates; adds deeply nested key:val'); |
| 76 | +// --- |
17 | 77 |
|
18 |
| - foo = {}; |
19 |
| - dset(foo, ['a', 'b', 'c'], 123); // change via array |
20 |
| - assert.equal(foo, { a:{ b:{ c:123 } } }, 'mutates; changes the value via array-type keys'); |
| 78 | +const arrays = suite('arrays'); |
| 79 | + |
| 80 | +arrays('should create array instead of object via numeric key :: simple', () => { |
| 81 | + let input = { a: 1 }; |
| 82 | + dset(input, 'e.0', 2); |
| 83 | + assert.instance(input.e, Array); |
| 84 | + assert.is(input.e[0], 2); |
| 85 | + assert.equal(input, { |
| 86 | + a: 1, |
| 87 | + e: [2] |
| 88 | + }); |
| 89 | +}); |
| 90 | + |
| 91 | +arrays('should create array instead of object via numeric key :: nested', () => { |
| 92 | + let input = { a: 1 }; |
| 93 | + dset(input, 'e.0.0', 123); |
| 94 | + assert.instance(input.e, Array); |
| 95 | + assert.is(input.e[0][0], 123); |
| 96 | + assert.equal(input, { |
| 97 | + a: 1, |
| 98 | + e: [ [123] ] |
| 99 | + }); |
| 100 | +}); |
21 | 101 |
|
22 |
| - foo = { a:1 }; |
23 |
| - dset(foo, 'e.0.0', 2); // create arrays instead of objects |
24 |
| - assert.is(foo.e[0][0], 2, 'mutates; can create arrays when key is numeric'); |
25 |
| - assert.equal(foo, { a: 1, e:[[2]] }); |
26 |
| - assert.instance(foo.e, Array); |
| 102 | +arrays('should be able to create object inside of array', () => { |
| 103 | + let input = {}; |
| 104 | + dset(input, ['x', '0', 'z'], 123); |
| 105 | + assert.instance(input.x, Array); |
| 106 | + assert.equal(input, { |
| 107 | + x: [{ z:123 }] |
| 108 | + }); |
| 109 | +}); |
27 | 110 |
|
28 |
| - foo = { a:{ b:{ c:123 } } }; |
29 |
| - dset(foo, 'a.b.x.y', 456); // preserve existing structure |
30 |
| - assert.equal(foo, { a:{ b:{ c:123, x:{ y:456 } }} }, 'mutates; writes into/preserves existing object'); |
| 111 | +arrays('should create arrays with hole(s) if needed', () => { |
| 112 | + let input = {}; |
| 113 | + dset(input, ['x', '1', 'z'], 123); |
| 114 | + assert.instance(input.x, Array); |
| 115 | + assert.equal(input, { |
| 116 | + x: [, { z:123 }] |
| 117 | + }); |
| 118 | +}); |
31 | 119 |
|
32 |
| - foo = { a: { b:123 } }; |
33 |
| - dset(foo, 'a.b.c', 'hello'); // preserve non-object value, won't alter |
34 |
| - assert.is(foo.a.b, 123, 'refuses to convert existing non-object value into object'); |
35 |
| - assert.equal(foo, { a: { b:123 }}); |
| 120 | +arrays('should create object from decimal-like key :: array :: zero', () => { |
| 121 | + let input = {}; |
| 122 | + dset(input, ['x', '10.0', 'z'], 123); |
| 123 | + assert.not.instance(input.x, Array); |
| 124 | + assert.equal(input, { |
| 125 | + x: { |
| 126 | + '10.0': { |
| 127 | + z: 123 |
| 128 | + } |
| 129 | + } |
| 130 | + }); |
| 131 | +}); |
36 | 132 |
|
37 |
| - foo = { a:{ b:{ c:123, d:{ e:5 } } } }; |
38 |
| - dset(foo, 'a.b.d.z', [1,2,3,4]); // preserve object tree, with array value |
39 |
| - assert.equal(foo.a.b.d, { e:5, z:[1,2,3,4] }, 'mutates; writes into existing object w/ array value'); |
| 133 | +arrays('should create object from decimal-like key :: array :: nonzero', () => { |
| 134 | + let input = {}; |
| 135 | + dset(input, ['x', '10.2', 'z'], 123); |
| 136 | + assert.not.instance(input.x, Array); |
| 137 | + assert.equal(input, { |
| 138 | + x: { |
| 139 | + '10.2': { |
| 140 | + z: 123 |
| 141 | + } |
| 142 | + } |
| 143 | + }); |
| 144 | +}); |
40 | 145 |
|
41 |
| - foo = { b:123 }; |
42 |
| - assert.not.throws(_ => dset(foo, 'b.c.d.e', 123), 'silently preserves existing non-null value'); |
43 |
| - assert.is(foo.b, 123, 'preserves existing value'); |
| 146 | +arrays.run(); |
| 147 | + |
| 148 | +// --- |
| 149 | + |
| 150 | +const preserves = suite('preserves'); |
| 151 | + |
| 152 | +preserves('should preserve existing object structure', () => { |
| 153 | + let input = { |
| 154 | + a: { |
| 155 | + b: { |
| 156 | + c: 123 |
| 157 | + } |
| 158 | + } |
| 159 | + }; |
| 160 | + |
| 161 | + dset(input, 'a.b.x.y', 456); |
| 162 | + |
| 163 | + assert.equal(input, { |
| 164 | + a: { |
| 165 | + b: { |
| 166 | + c: 123, |
| 167 | + x: { |
| 168 | + y:456 |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + }); |
| 173 | +}); |
44 | 174 |
|
45 |
| - foo = { b:0 }; |
46 |
| - assert.not.throws(_ => dset(foo, 'b.a.s.d', 123), 'silently preserves `0` as existing non-null value'); |
47 |
| - assert.equal(foo, { b:0 }, 'preserves existing object values'); |
| 175 | +preserves('should not convert existing non-object values into object', () => { |
| 176 | + let input = { |
| 177 | + a: { |
| 178 | + b: 123 |
| 179 | + } |
| 180 | + }; |
48 | 181 |
|
49 |
| - foo = {}; |
50 |
| - dset(foo, ['x', 'y', 'z'], 123); |
51 |
| - assert.equal(foo, { x:{ y:{ z:123 } } }); |
| 182 | + let before = JSON.stringify(input); |
| 183 | + dset(input, 'a.b.c', 'hello'); |
52 | 184 |
|
53 |
| - foo = {}; |
54 |
| - dset(foo, ['x', '0', 'z'], 123); |
55 |
| - assert.equal(foo, { x:[{ z:123 }] }); |
56 |
| - assert.instance(foo.x, Array); |
| 185 | + assert.is( |
| 186 | + JSON.stringify(input), |
| 187 | + before |
| 188 | + ); |
| 189 | +}); |
57 | 190 |
|
58 |
| - foo = {}; |
59 |
| - dset(foo, ['x', '1', 'z'], 123); |
60 |
| - assert.equal(foo, { x:[,{ z:123 }] }); |
61 |
| - assert.instance(foo.x, Array); |
| 191 | +preserves('should preserve existing object tree w/ array value', () => { |
| 192 | + let input = { |
| 193 | + a: { |
| 194 | + b: { |
| 195 | + c: 123, |
| 196 | + d: { |
| 197 | + e: 5 |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + }; |
| 202 | + |
| 203 | + dset(input, 'a.b.d.z', [1,2,3,4]); |
| 204 | + |
| 205 | + assert.equal(input.a.b.d, { |
| 206 | + e: 5, |
| 207 | + z: [1,2,3,4] |
| 208 | + }); |
| 209 | +}); |
62 | 210 |
|
63 |
| - foo = {}; |
64 |
| - dset(foo, ['x', '10.0', 'z'], 123); |
65 |
| - assert.equal(foo, { x:{ '10.0':{ z:123 } } }); |
66 |
| - assert.not.instance(foo.x, Array); |
| 211 | +preserves('should not throw when refusing to convert non-object into object', () => { |
| 212 | + try { |
| 213 | + let input = { b:123 }; |
| 214 | + dset(input, 'b.c.d.e', 123); |
| 215 | + assert.is(input.b, 123); |
| 216 | + } catch (err) { |
| 217 | + assert.unreachable('should not have thrown'); |
| 218 | + } |
| 219 | +}); |
67 | 220 |
|
68 |
| - foo = {}; |
69 |
| - dset(foo, ['x', '10.2', 'z'], 123); |
70 |
| - assert.equal(foo, { x:{ '10.2':{ z:123 } } }); |
71 |
| - assert.not.instance(foo.x, Array); |
| 221 | +preserves('should not throw when refusing to convert `0` into object', () => { |
| 222 | + try { |
| 223 | + let input = { b:0 }; |
| 224 | + dset(input, 'b.a.s.d', 123); |
| 225 | + assert.equal(input, { b: 0 }); |
| 226 | + } catch (err) { |
| 227 | + assert.unreachable('should not have thrown'); |
| 228 | + } |
72 | 229 | });
|
73 | 230 |
|
74 |
| -test.run(); |
| 231 | +preserves.run(); |
0 commit comments