@zeix/cause-effect 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/eslint.config.js +35 -0
- package/index.d.ts +7 -7
- package/index.js +1 -1
- package/index.ts +19 -9
- package/package.json +32 -29
- package/{lib → src}/computed.d.ts +3 -3
- package/{lib → src}/computed.ts +87 -65
- package/{lib → src}/effect.ts +27 -19
- package/{lib → src}/scheduler.d.ts +1 -1
- package/{lib → src}/scheduler.ts +30 -35
- package/{lib → src}/signal.d.ts +5 -4
- package/{lib → src}/signal.ts +5 -4
- package/{lib → src}/state.ts +35 -34
- package/{lib → src}/util.d.ts +1 -1
- package/{lib → src}/util.ts +21 -11
- package/test/batch.test.ts +18 -19
- package/test/benchmark.test.ts +36 -36
- package/test/computed.test.ts +43 -42
- package/test/effect.test.ts +18 -21
- package/test/state.test.ts +15 -31
- package/test/util/dependency-graph.ts +147 -145
- package/test/util/framework-types.ts +22 -22
- package/test/util/perf-tests.ts +28 -28
- package/test/util/reactive-framework.ts +11 -12
- /package/{lib → src}/effect.d.ts +0 -0
- /package/{lib → src}/state.d.ts +0 -0
package/test/computed.test.ts
CHANGED
|
@@ -4,12 +4,11 @@ import { state, computed, UNSET, isComputed, isState } from '../index.ts'
|
|
|
4
4
|
/* === Utility Functions === */
|
|
5
5
|
|
|
6
6
|
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
7
|
-
const increment = (n: number) => Number.isFinite(n) ? n + 1 : UNSET
|
|
7
|
+
const increment = (n: number) => (Number.isFinite(n) ? n + 1 : UNSET)
|
|
8
8
|
|
|
9
9
|
/* === Tests === */
|
|
10
10
|
|
|
11
11
|
describe('Computed', function () {
|
|
12
|
-
|
|
13
12
|
test('should identify computed signals with isComputed()', () => {
|
|
14
13
|
const count = state(42)
|
|
15
14
|
const doubled = count.map(v => v * 2)
|
|
@@ -17,24 +16,24 @@ describe('Computed', function () {
|
|
|
17
16
|
expect(isState(doubled)).toBe(false)
|
|
18
17
|
})
|
|
19
18
|
|
|
20
|
-
test('should compute a function', function() {
|
|
19
|
+
test('should compute a function', function () {
|
|
21
20
|
const derived = computed(() => 1 + 2)
|
|
22
21
|
expect(derived.get()).toBe(3)
|
|
23
22
|
})
|
|
24
23
|
|
|
25
|
-
test('should compute function dependent on a signal', function() {
|
|
24
|
+
test('should compute function dependent on a signal', function () {
|
|
26
25
|
const derived = state(42).map(v => ++v)
|
|
27
26
|
expect(derived.get()).toBe(43)
|
|
28
27
|
})
|
|
29
28
|
|
|
30
|
-
test('should compute function dependent on an updated signal', function() {
|
|
29
|
+
test('should compute function dependent on an updated signal', function () {
|
|
31
30
|
const cause = state(42)
|
|
32
31
|
const derived = cause.map(v => ++v)
|
|
33
32
|
cause.set(24)
|
|
34
33
|
expect(derived.get()).toBe(25)
|
|
35
34
|
})
|
|
36
35
|
|
|
37
|
-
test('should compute function dependent on an async signal', async function() {
|
|
36
|
+
test('should compute function dependent on an async signal', async function () {
|
|
38
37
|
const status = state('pending')
|
|
39
38
|
const promised = computed(async () => {
|
|
40
39
|
await wait(100)
|
|
@@ -49,7 +48,7 @@ describe('Computed', function () {
|
|
|
49
48
|
expect(status.get()).toBe('success')
|
|
50
49
|
})
|
|
51
50
|
|
|
52
|
-
test('should handle errors from an async signal gracefully', async function() {
|
|
51
|
+
test('should handle errors from an async signal gracefully', async function () {
|
|
53
52
|
const status = state('pending')
|
|
54
53
|
const error = state('')
|
|
55
54
|
const promised = computed(async () => {
|
|
@@ -66,7 +65,7 @@ describe('Computed', function () {
|
|
|
66
65
|
expect(status.get()).toBe('error')
|
|
67
66
|
})
|
|
68
67
|
|
|
69
|
-
test('should compute async signals in parallel without waterfalls', async function() {
|
|
68
|
+
test('should compute async signals in parallel without waterfalls', async function () {
|
|
70
69
|
const a = computed(async () => {
|
|
71
70
|
await wait(100)
|
|
72
71
|
return 10
|
|
@@ -77,14 +76,14 @@ describe('Computed', function () {
|
|
|
77
76
|
})
|
|
78
77
|
const c = computed({
|
|
79
78
|
signals: [a, b],
|
|
80
|
-
ok: (aValue, bValue) => aValue + bValue
|
|
79
|
+
ok: (aValue, bValue) => aValue + bValue,
|
|
81
80
|
})
|
|
82
81
|
expect(c.get()).toBe(UNSET)
|
|
83
82
|
await wait(110)
|
|
84
83
|
expect(c.get()).toBe(30)
|
|
85
84
|
})
|
|
86
85
|
|
|
87
|
-
test('should compute function dependent on a chain of computed states dependent on a signal', function() {
|
|
86
|
+
test('should compute function dependent on a chain of computed states dependent on a signal', function () {
|
|
88
87
|
const derived = state(42)
|
|
89
88
|
.map(v => ++v)
|
|
90
89
|
.map(v => v * 2)
|
|
@@ -92,7 +91,7 @@ describe('Computed', function () {
|
|
|
92
91
|
expect(derived.get()).toBe(87)
|
|
93
92
|
})
|
|
94
93
|
|
|
95
|
-
test('should compute function dependent on a chain of computed states dependent on an updated signal', function() {
|
|
94
|
+
test('should compute function dependent on a chain of computed states dependent on an updated signal', function () {
|
|
96
95
|
const cause = state(42)
|
|
97
96
|
const derived = cause
|
|
98
97
|
.map(v => ++v)
|
|
@@ -118,7 +117,7 @@ describe('Computed', function () {
|
|
|
118
117
|
expect(count).toBe(2)
|
|
119
118
|
})
|
|
120
119
|
|
|
121
|
-
test('should only update every signal once (diamond graph)', function() {
|
|
120
|
+
test('should only update every signal once (diamond graph)', function () {
|
|
122
121
|
let count = 0
|
|
123
122
|
const x = state('a')
|
|
124
123
|
const a = x.map(v => v)
|
|
@@ -135,7 +134,7 @@ describe('Computed', function () {
|
|
|
135
134
|
expect(count).toBe(2)
|
|
136
135
|
})
|
|
137
136
|
|
|
138
|
-
test('should only update every signal once (diamond graph + tail)', function() {
|
|
137
|
+
test('should only update every signal once (diamond graph + tail)', function () {
|
|
139
138
|
let count = 0
|
|
140
139
|
const x = state('a')
|
|
141
140
|
const a = x.map(v => v)
|
|
@@ -152,7 +151,7 @@ describe('Computed', function () {
|
|
|
152
151
|
expect(count).toBe(2)
|
|
153
152
|
})
|
|
154
153
|
|
|
155
|
-
test('should update multiple times after multiple state changes', function() {
|
|
154
|
+
test('should update multiple times after multiple state changes', function () {
|
|
156
155
|
const a = state(3)
|
|
157
156
|
const b = state(4)
|
|
158
157
|
let count = 0
|
|
@@ -170,19 +169,21 @@ describe('Computed', function () {
|
|
|
170
169
|
|
|
171
170
|
/*
|
|
172
171
|
* Note for the next two tests:
|
|
173
|
-
*
|
|
172
|
+
*
|
|
174
173
|
* Due to the lazy evaluation strategy, unchanged computed signals may propagate
|
|
175
174
|
* change notifications one additional time before stabilizing. This is a
|
|
176
175
|
* one-time performance cost that allows for efficient memoization and
|
|
177
176
|
* error handling in most cases.
|
|
178
177
|
*/
|
|
179
|
-
test('should bail out if result is the same', function() {
|
|
178
|
+
test('should bail out if result is the same', function () {
|
|
180
179
|
let count = 0
|
|
181
180
|
const x = state('a')
|
|
182
|
-
const b = x
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
const b = x
|
|
182
|
+
.map(() => 'foo')
|
|
183
|
+
.map(v => {
|
|
184
|
+
count++
|
|
185
|
+
return v
|
|
186
|
+
})
|
|
186
187
|
expect(b.get()).toBe('foo')
|
|
187
188
|
expect(count).toBe(1)
|
|
188
189
|
x.set('aa')
|
|
@@ -192,11 +193,12 @@ describe('Computed', function () {
|
|
|
192
193
|
expect(count).toBe(2)
|
|
193
194
|
})
|
|
194
195
|
|
|
195
|
-
test('should block if result remains unchanged', function() {
|
|
196
|
+
test('should block if result remains unchanged', function () {
|
|
196
197
|
let count = 0
|
|
197
198
|
const x = state(42)
|
|
198
|
-
const c = x
|
|
199
|
-
.map(v => v
|
|
199
|
+
const c = x
|
|
200
|
+
.map(v => v % 2)
|
|
201
|
+
.map(v => (v ? 'odd' : 'even'))
|
|
200
202
|
.map(v => {
|
|
201
203
|
count++
|
|
202
204
|
return `c: ${v}`
|
|
@@ -210,7 +212,7 @@ describe('Computed', function () {
|
|
|
210
212
|
expect(count).toBe(2)
|
|
211
213
|
})
|
|
212
214
|
|
|
213
|
-
test('should detect and throw error for circular dependencies', function() {
|
|
215
|
+
test('should detect and throw error for circular dependencies', function () {
|
|
214
216
|
const a = state(1)
|
|
215
217
|
const b = computed(() => c.get() + 1)
|
|
216
218
|
const c = computed(() => b.get() + a.get())
|
|
@@ -220,7 +222,7 @@ describe('Computed', function () {
|
|
|
220
222
|
expect(a.get()).toBe(1)
|
|
221
223
|
})
|
|
222
224
|
|
|
223
|
-
test('should propagate error if an error occurred', function() {
|
|
225
|
+
test('should propagate error if an error occurred', function () {
|
|
224
226
|
let okCount = 0
|
|
225
227
|
let errCount = 0
|
|
226
228
|
const x = state(0)
|
|
@@ -230,7 +232,7 @@ describe('Computed', function () {
|
|
|
230
232
|
})
|
|
231
233
|
const c = computed({
|
|
232
234
|
signals: [a],
|
|
233
|
-
ok: v => v ? 'success' : 'failure',
|
|
235
|
+
ok: v => (v ? 'success' : 'failure'),
|
|
234
236
|
err: () => {
|
|
235
237
|
errCount++
|
|
236
238
|
return 'recovered'
|
|
@@ -245,7 +247,7 @@ describe('Computed', function () {
|
|
|
245
247
|
try {
|
|
246
248
|
x.set(1)
|
|
247
249
|
expect(a.get()).toBe(1)
|
|
248
|
-
expect(true).toBe(false)
|
|
250
|
+
expect(true).toBe(false) // This line should not be reached
|
|
249
251
|
} catch (error) {
|
|
250
252
|
expect(error.message).toBe('Calculation error')
|
|
251
253
|
} finally {
|
|
@@ -255,7 +257,7 @@ describe('Computed', function () {
|
|
|
255
257
|
}
|
|
256
258
|
})
|
|
257
259
|
|
|
258
|
-
test('should return a computed signal with .map()', function() {
|
|
260
|
+
test('should return a computed signal with .map()', function () {
|
|
259
261
|
const cause = state(42)
|
|
260
262
|
const derived = cause.map(v => ++v)
|
|
261
263
|
const double = derived.map(v => v * 2)
|
|
@@ -263,7 +265,7 @@ describe('Computed', function () {
|
|
|
263
265
|
expect(double.get()).toBe(86)
|
|
264
266
|
})
|
|
265
267
|
|
|
266
|
-
test('should create an effect that reacts on async computed changes with .tap()', async function() {
|
|
268
|
+
test('should create an effect that reacts on async computed changes with .tap()', async function () {
|
|
267
269
|
const cause = state(42)
|
|
268
270
|
const derived = computed(async () => {
|
|
269
271
|
await wait(100)
|
|
@@ -279,7 +281,7 @@ describe('Computed', function () {
|
|
|
279
281
|
},
|
|
280
282
|
nil: () => {
|
|
281
283
|
nilCount++
|
|
282
|
-
}
|
|
284
|
+
},
|
|
283
285
|
})
|
|
284
286
|
cause.set(43)
|
|
285
287
|
expect(okCount).toBe(0)
|
|
@@ -292,7 +294,7 @@ describe('Computed', function () {
|
|
|
292
294
|
expect(result).toBe(44)
|
|
293
295
|
})
|
|
294
296
|
|
|
295
|
-
test('should handle complex computed signal with error and async dependencies', async function() {
|
|
297
|
+
test('should handle complex computed signal with error and async dependencies', async function () {
|
|
296
298
|
const toggleState = state(true)
|
|
297
299
|
const errorProne = toggleState.map(v => {
|
|
298
300
|
if (v) throw new Error('Intentional error')
|
|
@@ -319,9 +321,9 @@ describe('Computed', function () {
|
|
|
319
321
|
err: () => {
|
|
320
322
|
errCount++
|
|
321
323
|
return -1
|
|
322
|
-
}
|
|
324
|
+
},
|
|
323
325
|
})
|
|
324
|
-
|
|
326
|
+
|
|
325
327
|
/* computed(() => {
|
|
326
328
|
try {
|
|
327
329
|
const x = errorProne.get()
|
|
@@ -338,21 +340,21 @@ describe('Computed', function () {
|
|
|
338
340
|
return -1
|
|
339
341
|
}
|
|
340
342
|
}) */
|
|
341
|
-
|
|
343
|
+
|
|
342
344
|
for (let i = 0; i < 10; i++) {
|
|
343
345
|
toggleState.set(!!(i % 2))
|
|
344
346
|
await wait(10)
|
|
345
347
|
result = complexComputed.get()
|
|
346
348
|
// console.log(`i: ${i}, result: ${result}`)
|
|
347
349
|
}
|
|
348
|
-
|
|
350
|
+
|
|
349
351
|
expect(nilCount).toBeGreaterThanOrEqual(5)
|
|
350
352
|
expect(okCount).toBeGreaterThanOrEqual(2)
|
|
351
353
|
expect(errCount).toBeGreaterThanOrEqual(3)
|
|
352
354
|
expect(okCount + errCount + nilCount).toBe(10)
|
|
353
355
|
})
|
|
354
356
|
|
|
355
|
-
test('should handle signal changes during async computation', async function() {
|
|
357
|
+
test('should handle signal changes during async computation', async function () {
|
|
356
358
|
const source = state(1)
|
|
357
359
|
let computationCount = 0
|
|
358
360
|
const derived = computed(async abort => {
|
|
@@ -373,7 +375,7 @@ describe('Computed', function () {
|
|
|
373
375
|
expect(computationCount).toBe(1)
|
|
374
376
|
})
|
|
375
377
|
|
|
376
|
-
test('should handle multiple rapid changes during async computation', async function() {
|
|
378
|
+
test('should handle multiple rapid changes during async computation', async function () {
|
|
377
379
|
const source = state(1)
|
|
378
380
|
let computationCount = 0
|
|
379
381
|
const derived = computed(async abort => {
|
|
@@ -398,7 +400,7 @@ describe('Computed', function () {
|
|
|
398
400
|
expect(computationCount).toBe(1)
|
|
399
401
|
})
|
|
400
402
|
|
|
401
|
-
test('should handle errors in aborted computations', async function() {
|
|
403
|
+
test('should handle errors in aborted computations', async function () {
|
|
402
404
|
// const startTime = performance.now()
|
|
403
405
|
const source = state(1)
|
|
404
406
|
const derived = computed(async () => {
|
|
@@ -407,7 +409,7 @@ describe('Computed', function () {
|
|
|
407
409
|
if (value === 2) throw new Error('Intentional error')
|
|
408
410
|
return value
|
|
409
411
|
})
|
|
410
|
-
|
|
412
|
+
|
|
411
413
|
/* derived.tap({
|
|
412
414
|
ok: v => {
|
|
413
415
|
console.log(`ok: ${v}, time: ${performance.now() - startTime}ms`)
|
|
@@ -427,11 +429,10 @@ describe('Computed', function () {
|
|
|
427
429
|
source.set(2)
|
|
428
430
|
await wait(110)
|
|
429
431
|
expect(() => derived.get()).toThrow('Intentional error')
|
|
430
|
-
|
|
432
|
+
|
|
431
433
|
// Change to normal state before second computation completes
|
|
432
434
|
source.set(3)
|
|
433
435
|
await wait(100)
|
|
434
436
|
expect(derived.get()).toBe(3)
|
|
435
|
-
|
|
436
437
|
})
|
|
437
|
-
})
|
|
438
|
+
})
|
package/test/effect.test.ts
CHANGED
|
@@ -8,8 +8,7 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
|
8
8
|
/* === Tests === */
|
|
9
9
|
|
|
10
10
|
describe('Effect', function () {
|
|
11
|
-
|
|
12
|
-
test('should be triggered after a state change', function() {
|
|
11
|
+
test('should be triggered after a state change', function () {
|
|
13
12
|
const cause = state('foo')
|
|
14
13
|
let count = 0
|
|
15
14
|
cause.tap(() => {
|
|
@@ -20,7 +19,7 @@ describe('Effect', function () {
|
|
|
20
19
|
expect(count).toBe(2)
|
|
21
20
|
})
|
|
22
21
|
|
|
23
|
-
test('should be triggered after computed async signals resolve without waterfalls', async function() {
|
|
22
|
+
test('should be triggered after computed async signals resolve without waterfalls', async function () {
|
|
24
23
|
const a = computed(async () => {
|
|
25
24
|
await wait(100)
|
|
26
25
|
return 10
|
|
@@ -36,7 +35,7 @@ describe('Effect', function () {
|
|
|
36
35
|
ok: (aValue, bValue) => {
|
|
37
36
|
result = aValue + bValue
|
|
38
37
|
count++
|
|
39
|
-
}
|
|
38
|
+
},
|
|
40
39
|
})
|
|
41
40
|
expect(result).toBe(0)
|
|
42
41
|
expect(count).toBe(0)
|
|
@@ -45,7 +44,7 @@ describe('Effect', function () {
|
|
|
45
44
|
expect(count).toBe(1)
|
|
46
45
|
})
|
|
47
46
|
|
|
48
|
-
test('should be triggered repeatedly after repeated state change', async function() {
|
|
47
|
+
test('should be triggered repeatedly after repeated state change', async function () {
|
|
49
48
|
const cause = state(0)
|
|
50
49
|
let result = 0
|
|
51
50
|
let count = 0
|
|
@@ -56,11 +55,11 @@ describe('Effect', function () {
|
|
|
56
55
|
for (let i = 0; i < 10; i++) {
|
|
57
56
|
cause.set(i)
|
|
58
57
|
expect(result).toBe(i)
|
|
59
|
-
expect(count).toBe(i + 1)
|
|
58
|
+
expect(count).toBe(i + 1) // + 1 for effect initialization
|
|
60
59
|
}
|
|
61
60
|
})
|
|
62
61
|
|
|
63
|
-
test('should handle errors in effects', function() {
|
|
62
|
+
test('should handle errors in effects', function () {
|
|
64
63
|
const a = state(1)
|
|
65
64
|
const b = a.map(v => {
|
|
66
65
|
if (v > 5) throw new Error('Value too high')
|
|
@@ -77,26 +76,26 @@ describe('Effect', function () {
|
|
|
77
76
|
// console.log('Error effect:', error)
|
|
78
77
|
errorCallCount++
|
|
79
78
|
expect(error.message).toBe('Value too high')
|
|
80
|
-
}
|
|
79
|
+
},
|
|
81
80
|
})
|
|
82
|
-
|
|
81
|
+
|
|
83
82
|
// Normal case
|
|
84
83
|
a.set(2)
|
|
85
84
|
expect(normalCallCount).toBe(2)
|
|
86
85
|
expect(errorCallCount).toBe(0)
|
|
87
|
-
|
|
86
|
+
|
|
88
87
|
// Error case
|
|
89
88
|
a.set(6)
|
|
90
89
|
expect(normalCallCount).toBe(2)
|
|
91
90
|
expect(errorCallCount).toBe(1)
|
|
92
|
-
|
|
91
|
+
|
|
93
92
|
// Back to normal
|
|
94
93
|
a.set(3)
|
|
95
94
|
expect(normalCallCount).toBe(3)
|
|
96
95
|
expect(errorCallCount).toBe(1)
|
|
97
96
|
})
|
|
98
97
|
|
|
99
|
-
test('should handle UNSET values in effects', async function() {
|
|
98
|
+
test('should handle UNSET values in effects', async function () {
|
|
100
99
|
const a = computed(async () => {
|
|
101
100
|
await wait(100)
|
|
102
101
|
return 42
|
|
@@ -110,7 +109,7 @@ describe('Effect', function () {
|
|
|
110
109
|
},
|
|
111
110
|
nil: () => {
|
|
112
111
|
nilCount++
|
|
113
|
-
}
|
|
112
|
+
},
|
|
114
113
|
})
|
|
115
114
|
|
|
116
115
|
expect(normalCallCount).toBe(0)
|
|
@@ -142,14 +141,12 @@ describe('Effect', function () {
|
|
|
142
141
|
a.set(6)
|
|
143
142
|
|
|
144
143
|
// Check if console.error was called with the error
|
|
145
|
-
expect(mockConsoleError).toHaveBeenCalledWith(
|
|
146
|
-
expect.any(Error)
|
|
147
|
-
)
|
|
144
|
+
expect(mockConsoleError).toHaveBeenCalledWith(expect.any(Error))
|
|
148
145
|
|
|
149
146
|
// Check the error message
|
|
150
|
-
const error = (mockConsoleError as ReturnType<typeof mock>).mock
|
|
147
|
+
const error = (mockConsoleError as ReturnType<typeof mock>).mock
|
|
148
|
+
.calls[0][0] as Error
|
|
151
149
|
expect(error.message).toBe('Value too high')
|
|
152
|
-
|
|
153
150
|
} finally {
|
|
154
151
|
// Restore the original console.error
|
|
155
152
|
console.error = originalConsoleError
|
|
@@ -176,7 +173,7 @@ describe('Effect', function () {
|
|
|
176
173
|
let okCount = 0
|
|
177
174
|
let errCount = 0
|
|
178
175
|
const count = state(0)
|
|
179
|
-
|
|
176
|
+
|
|
180
177
|
count.tap({
|
|
181
178
|
ok: () => {
|
|
182
179
|
okCount++
|
|
@@ -187,9 +184,9 @@ describe('Effect', function () {
|
|
|
187
184
|
errCount++
|
|
188
185
|
expect(e).toBeInstanceOf(Error)
|
|
189
186
|
expect(e.message).toBe('Circular dependency in effect detected')
|
|
190
|
-
}
|
|
187
|
+
},
|
|
191
188
|
})
|
|
192
|
-
|
|
189
|
+
|
|
193
190
|
// Verify that the count was changed only once due to the circular dependency error
|
|
194
191
|
expect(count.get()).toBe(1)
|
|
195
192
|
expect(okCount).toBe(1)
|
package/test/state.test.ts
CHANGED
|
@@ -8,9 +8,8 @@ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
|
8
8
|
/* === Tests === */
|
|
9
9
|
|
|
10
10
|
describe('State', function () {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
test("isState identifies state signals", () => {
|
|
11
|
+
describe('State type guard', () => {
|
|
12
|
+
test('isState identifies state signals', () => {
|
|
14
13
|
const count = state(42)
|
|
15
14
|
expect(isState(count)).toBe(true)
|
|
16
15
|
expect(isComputed(count)).toBe(false)
|
|
@@ -18,7 +17,6 @@ describe('State', function () {
|
|
|
18
17
|
})
|
|
19
18
|
|
|
20
19
|
describe('Boolean cause', function () {
|
|
21
|
-
|
|
22
20
|
test('should be boolean', function () {
|
|
23
21
|
const cause = state(false)
|
|
24
22
|
expect(typeof cause.get()).toBe('boolean')
|
|
@@ -42,14 +40,12 @@ describe('State', function () {
|
|
|
42
40
|
|
|
43
41
|
test('should toggle initial value with .set(v => !v)', function () {
|
|
44
42
|
const cause = state(false)
|
|
45
|
-
cause.update(
|
|
43
|
+
cause.update(v => !v)
|
|
46
44
|
expect(cause.get()).toBe(true)
|
|
47
45
|
})
|
|
48
|
-
|
|
49
46
|
})
|
|
50
47
|
|
|
51
48
|
describe('Number cause', function () {
|
|
52
|
-
|
|
53
49
|
test('should be number', function () {
|
|
54
50
|
const cause = state(0)
|
|
55
51
|
expect(typeof cause.get()).toBe('number')
|
|
@@ -71,11 +67,9 @@ describe('State', function () {
|
|
|
71
67
|
cause.update(v => ++v)
|
|
72
68
|
expect(cause.get()).toBe(1)
|
|
73
69
|
})
|
|
74
|
-
|
|
75
70
|
})
|
|
76
71
|
|
|
77
72
|
describe('String cause', function () {
|
|
78
|
-
|
|
79
73
|
test('should be string', function () {
|
|
80
74
|
const cause = state('foo')
|
|
81
75
|
expect(typeof cause.get()).toBe('string')
|
|
@@ -94,14 +88,12 @@ describe('State', function () {
|
|
|
94
88
|
|
|
95
89
|
test('should upper case value with .set(v => v.toUpperCase())', function () {
|
|
96
90
|
const cause = state('foo')
|
|
97
|
-
cause.update(v => v ? v.toUpperCase() : '')
|
|
98
|
-
expect(cause.get()).toBe(
|
|
91
|
+
cause.update(v => (v ? v.toUpperCase() : ''))
|
|
92
|
+
expect(cause.get()).toBe('FOO')
|
|
99
93
|
})
|
|
100
|
-
|
|
101
94
|
})
|
|
102
95
|
|
|
103
96
|
describe('Array cause', function () {
|
|
104
|
-
|
|
105
97
|
test('should be array', function () {
|
|
106
98
|
const cause = state([1, 2, 3])
|
|
107
99
|
expect(Array.isArray(cause.get())).toBe(true)
|
|
@@ -121,21 +113,19 @@ describe('State', function () {
|
|
|
121
113
|
test('should reflect current value of array after modification', function () {
|
|
122
114
|
const array = [1, 2, 3]
|
|
123
115
|
const cause = state(array)
|
|
124
|
-
array.push(4)
|
|
116
|
+
array.push(4) // don't do this! the result will be correct, but we can't trigger effects
|
|
125
117
|
expect(cause.get()).toEqual([1, 2, 3, 4])
|
|
126
118
|
})
|
|
127
119
|
|
|
128
120
|
test('should set new value with .set([...array, 4])', function () {
|
|
129
121
|
const array = [1, 2, 3]
|
|
130
122
|
const cause = state(array)
|
|
131
|
-
cause.set([...array, 4])
|
|
123
|
+
cause.set([...array, 4]) // use destructuring instead!
|
|
132
124
|
expect(cause.get()).toEqual([1, 2, 3, 4])
|
|
133
125
|
})
|
|
134
|
-
|
|
135
126
|
})
|
|
136
127
|
|
|
137
128
|
describe('Object cause', function () {
|
|
138
|
-
|
|
139
129
|
test('should be object', function () {
|
|
140
130
|
const cause = state({ a: 'a', b: 1 })
|
|
141
131
|
expect(typeof cause.get()).toBe('object')
|
|
@@ -156,29 +146,27 @@ describe('State', function () {
|
|
|
156
146
|
const obj = { a: 'a', b: 1 }
|
|
157
147
|
const cause = state<Record<string, any>>(obj)
|
|
158
148
|
// @ts-expect-error
|
|
159
|
-
obj.c = true
|
|
149
|
+
obj.c = true // don't do this! the result will be correct, but we can't trigger effects
|
|
160
150
|
expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
|
|
161
151
|
})
|
|
162
152
|
|
|
163
153
|
test('should set new value with .set({...obj, c: true})', function () {
|
|
164
154
|
const obj = { a: 'a', b: 1 }
|
|
165
155
|
const cause = state<Record<string, any>>(obj)
|
|
166
|
-
cause.set({...obj, c: true})
|
|
156
|
+
cause.set({ ...obj, c: true }) // use destructuring instead!
|
|
167
157
|
expect(cause.get()).toEqual({ a: 'a', b: 1, c: true })
|
|
168
158
|
})
|
|
169
|
-
|
|
170
159
|
})
|
|
171
160
|
|
|
172
161
|
describe('Map method', function () {
|
|
173
|
-
|
|
174
|
-
test('should return a computed signal', function() {
|
|
162
|
+
test('should return a computed signal', function () {
|
|
175
163
|
const cause = state(42)
|
|
176
164
|
const double = cause.map(v => v * 2)
|
|
177
165
|
expect(isComputed(double)).toBe(true)
|
|
178
166
|
expect(double.get()).toBe(84)
|
|
179
167
|
})
|
|
180
168
|
|
|
181
|
-
test('should return a computed signal for an async function', async function() {
|
|
169
|
+
test('should return a computed signal for an async function', async function () {
|
|
182
170
|
const cause = state(42)
|
|
183
171
|
const asyncDouble = cause.map(async value => {
|
|
184
172
|
await wait(100)
|
|
@@ -189,12 +177,10 @@ describe('State', function () {
|
|
|
189
177
|
await wait(110)
|
|
190
178
|
expect(asyncDouble.get()).toBe(84)
|
|
191
179
|
})
|
|
192
|
-
|
|
193
180
|
})
|
|
194
181
|
|
|
195
182
|
describe('Tap method', function () {
|
|
196
|
-
|
|
197
|
-
test('should create an effect that reacts on signal changes', function() {
|
|
183
|
+
test('should create an effect that reacts on signal changes', function () {
|
|
198
184
|
const cause = state(42)
|
|
199
185
|
let okCount = 0
|
|
200
186
|
let nilCount = 0
|
|
@@ -206,10 +192,10 @@ describe('State', function () {
|
|
|
206
192
|
},
|
|
207
193
|
nil: () => {
|
|
208
194
|
nilCount++
|
|
209
|
-
}
|
|
195
|
+
},
|
|
210
196
|
})
|
|
211
197
|
cause.set(43)
|
|
212
|
-
expect(okCount).toBe(2)
|
|
198
|
+
expect(okCount).toBe(2) // + 1 for effect initialization
|
|
213
199
|
expect(nilCount).toBe(0)
|
|
214
200
|
expect(result).toBe(43)
|
|
215
201
|
|
|
@@ -217,7 +203,5 @@ describe('State', function () {
|
|
|
217
203
|
expect(okCount).toBe(2)
|
|
218
204
|
expect(nilCount).toBe(1)
|
|
219
205
|
})
|
|
220
|
-
|
|
221
206
|
})
|
|
222
|
-
|
|
223
|
-
});
|
|
207
|
+
})
|