@zeix/cause-effect 0.14.2 → 0.15.1
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 +256 -35
- package/index.d.ts +31 -6
- package/index.dev.js +383 -47
- package/index.js +1 -1
- package/index.ts +33 -4
- package/package.json +2 -2
- package/src/computed.ts +15 -6
- package/src/diff.ts +148 -0
- package/src/effect.ts +57 -50
- package/src/match.ts +52 -0
- package/src/resolve.ts +48 -0
- package/src/signal.ts +60 -14
- package/src/state.ts +4 -3
- package/src/store.ts +324 -0
- package/src/util.ts +54 -4
- package/test/batch.test.ts +23 -19
- package/test/benchmark.test.ts +8 -8
- package/test/computed.test.ts +15 -11
- package/test/diff.test.ts +638 -0
- package/test/effect.test.ts +656 -48
- package/test/match.test.ts +378 -0
- package/test/resolve.test.ts +156 -0
- package/test/signal.test.ts +451 -0
- package/test/store.test.ts +746 -0
- package/tsconfig.json +9 -10
- package/types/index.d.ts +15 -0
- package/types/src/diff.d.ts +30 -0
- package/types/src/effect.d.ts +16 -0
- package/types/src/match.d.ts +21 -0
- package/types/src/resolve.d.ts +29 -0
- package/types/src/signal.d.ts +44 -0
- package/{src → types/src}/state.d.ts +1 -1
- package/types/src/store.d.ts +62 -0
- package/types/src/util.d.ts +14 -0
- package/types/test-new-effect.d.ts +1 -0
- package/src/effect.d.ts +0 -17
- package/src/signal.d.ts +0 -26
- package/src/util.d.ts +0 -7
- /package/{src → types/src}/computed.d.ts +0 -0
- /package/{src → types/src}/scheduler.d.ts +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { computed, match, resolve, state, UNSET } from '../'
|
|
3
|
+
|
|
4
|
+
/* === Tests === */
|
|
5
|
+
|
|
6
|
+
describe('Match Function', () => {
|
|
7
|
+
test('should call ok handler for successful resolution', () => {
|
|
8
|
+
const a = state(10)
|
|
9
|
+
const b = state('hello')
|
|
10
|
+
let okCalled = false
|
|
11
|
+
let okValues: { a: number; b: string } | null = null
|
|
12
|
+
|
|
13
|
+
match(resolve({ a, b }), {
|
|
14
|
+
ok: values => {
|
|
15
|
+
okCalled = true
|
|
16
|
+
okValues = values
|
|
17
|
+
},
|
|
18
|
+
err: () => {
|
|
19
|
+
throw new Error('Should not be called')
|
|
20
|
+
},
|
|
21
|
+
nil: () => {
|
|
22
|
+
throw new Error('Should not be called')
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
expect(okCalled).toBe(true)
|
|
27
|
+
expect(okValues).toBeTruthy()
|
|
28
|
+
expect((okValues as unknown as { a: number; b: string }).a).toBe(10)
|
|
29
|
+
expect((okValues as unknown as { a: number; b: string }).b).toBe(
|
|
30
|
+
'hello',
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('should call nil handler for pending signals', () => {
|
|
35
|
+
const a = state(10)
|
|
36
|
+
const b = state(UNSET)
|
|
37
|
+
let nilCalled = false
|
|
38
|
+
|
|
39
|
+
match(resolve({ a, b }), {
|
|
40
|
+
ok: () => {
|
|
41
|
+
throw new Error('Should not be called')
|
|
42
|
+
},
|
|
43
|
+
err: () => {
|
|
44
|
+
throw new Error('Should not be called')
|
|
45
|
+
},
|
|
46
|
+
nil: () => {
|
|
47
|
+
nilCalled = true
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
expect(nilCalled).toBe(true)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('should call error handler for error signals', () => {
|
|
55
|
+
const a = state(10)
|
|
56
|
+
const b = computed(() => {
|
|
57
|
+
throw new Error('Test error')
|
|
58
|
+
})
|
|
59
|
+
let errCalled = false
|
|
60
|
+
let errValue: Error | null = null
|
|
61
|
+
|
|
62
|
+
match(resolve({ a, b }), {
|
|
63
|
+
ok: () => {
|
|
64
|
+
throw new Error('Should not be called')
|
|
65
|
+
},
|
|
66
|
+
err: errors => {
|
|
67
|
+
errCalled = true
|
|
68
|
+
errValue = errors[0]
|
|
69
|
+
},
|
|
70
|
+
nil: () => {
|
|
71
|
+
throw new Error('Should not be called')
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
expect(errCalled).toBe(true)
|
|
76
|
+
expect(errValue).toBeTruthy()
|
|
77
|
+
expect((errValue as unknown as Error).message).toBe('Test error')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('should handle missing handlers gracefully', () => {
|
|
81
|
+
const a = state(10)
|
|
82
|
+
const result = resolve({ a })
|
|
83
|
+
|
|
84
|
+
// Should not throw even with no handlers
|
|
85
|
+
expect(() => {
|
|
86
|
+
match(result, {})
|
|
87
|
+
}).not.toThrow()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('should return void always', () => {
|
|
91
|
+
const a = state(42)
|
|
92
|
+
|
|
93
|
+
const returnValue = match(resolve({ a }), {
|
|
94
|
+
ok: () => {
|
|
95
|
+
// Even if we try to return something, match should return void
|
|
96
|
+
return 'something'
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
expect(returnValue).toBeUndefined()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('should handle handler errors by calling error handler', () => {
|
|
104
|
+
const a = state(10)
|
|
105
|
+
let handlerErrorCalled = false
|
|
106
|
+
let handlerError: Error | null = null
|
|
107
|
+
|
|
108
|
+
match(resolve({ a }), {
|
|
109
|
+
ok: () => {
|
|
110
|
+
throw new Error('Handler error')
|
|
111
|
+
},
|
|
112
|
+
err: errors => {
|
|
113
|
+
handlerErrorCalled = true
|
|
114
|
+
handlerError = errors[errors.length - 1] // Last error should be the handler error
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
expect(handlerErrorCalled).toBe(true)
|
|
119
|
+
expect(handlerError).toBeTruthy()
|
|
120
|
+
expect((handlerError as unknown as Error).message).toBe('Handler error')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('should rethrow handler errors if no error handler available', () => {
|
|
124
|
+
const a = state(10)
|
|
125
|
+
|
|
126
|
+
expect(() => {
|
|
127
|
+
match(resolve({ a }), {
|
|
128
|
+
ok: () => {
|
|
129
|
+
throw new Error('Handler error')
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
}).toThrow('Handler error')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('should combine existing errors with handler errors', () => {
|
|
136
|
+
const a = computed(() => {
|
|
137
|
+
throw new Error('Signal error')
|
|
138
|
+
})
|
|
139
|
+
let allErrors: readonly Error[] | null = null
|
|
140
|
+
|
|
141
|
+
match(resolve({ a }), {
|
|
142
|
+
err: errors => {
|
|
143
|
+
// First call with signal error
|
|
144
|
+
if (errors.length === 1) {
|
|
145
|
+
throw new Error('Handler error')
|
|
146
|
+
}
|
|
147
|
+
// Second call with both errors
|
|
148
|
+
allErrors = errors
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
expect(allErrors).toBeTruthy()
|
|
153
|
+
expect((allErrors as unknown as readonly Error[]).length).toBe(2)
|
|
154
|
+
expect((allErrors as unknown as readonly Error[])[0].message).toBe(
|
|
155
|
+
'Signal error',
|
|
156
|
+
)
|
|
157
|
+
expect((allErrors as unknown as readonly Error[])[1].message).toBe(
|
|
158
|
+
'Handler error',
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('should work with complex type inference', () => {
|
|
163
|
+
const user = state({ id: 1, name: 'Alice' })
|
|
164
|
+
const posts = state([{ id: 1, title: 'Hello' }])
|
|
165
|
+
const settings = state({ theme: 'dark' })
|
|
166
|
+
|
|
167
|
+
let typeTestPassed = false
|
|
168
|
+
|
|
169
|
+
match(resolve({ user, posts, settings }), {
|
|
170
|
+
ok: values => {
|
|
171
|
+
// TypeScript should infer these types perfectly
|
|
172
|
+
const userId: number = values.user.id
|
|
173
|
+
const userName: string = values.user.name
|
|
174
|
+
const firstPost = values.posts[0]
|
|
175
|
+
const postTitle: string = firstPost.title
|
|
176
|
+
const theme: string = values.settings.theme
|
|
177
|
+
|
|
178
|
+
expect(userId).toBe(1)
|
|
179
|
+
expect(userName).toBe('Alice')
|
|
180
|
+
expect(postTitle).toBe('Hello')
|
|
181
|
+
expect(theme).toBe('dark')
|
|
182
|
+
typeTestPassed = true
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
expect(typeTestPassed).toBe(true)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('should handle side effects only pattern', () => {
|
|
190
|
+
const count = state(5)
|
|
191
|
+
const name = state('test')
|
|
192
|
+
let sideEffectExecuted = false
|
|
193
|
+
let capturedData = ''
|
|
194
|
+
|
|
195
|
+
match(resolve({ count, name }), {
|
|
196
|
+
ok: values => {
|
|
197
|
+
// Pure side effect - no return value expected
|
|
198
|
+
sideEffectExecuted = true
|
|
199
|
+
capturedData = `${values.name}: ${values.count}`
|
|
200
|
+
// Even if we try to return something, it should be ignored
|
|
201
|
+
return 'ignored'
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
expect(sideEffectExecuted).toBe(true)
|
|
206
|
+
expect(capturedData).toBe('test: 5')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('should handle multiple error types correctly', () => {
|
|
210
|
+
const error1 = computed(() => {
|
|
211
|
+
throw new Error('First error')
|
|
212
|
+
})
|
|
213
|
+
const error2 = computed(() => {
|
|
214
|
+
throw new Error('Second error')
|
|
215
|
+
})
|
|
216
|
+
let errorMessages: string[] = []
|
|
217
|
+
|
|
218
|
+
match(resolve({ error1, error2 }), {
|
|
219
|
+
err: errors => {
|
|
220
|
+
errorMessages = errors.map(e => e.message)
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
expect(errorMessages).toHaveLength(2)
|
|
225
|
+
expect(errorMessages).toContain('First error')
|
|
226
|
+
expect(errorMessages).toContain('Second error')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('should work with async computed signals', async () => {
|
|
230
|
+
const wait = (ms: number) =>
|
|
231
|
+
new Promise(resolve => setTimeout(resolve, ms))
|
|
232
|
+
|
|
233
|
+
const asyncSignal = computed(async () => {
|
|
234
|
+
await wait(10)
|
|
235
|
+
return 'async result'
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// Initially should be pending
|
|
239
|
+
let pendingCalled = false
|
|
240
|
+
let okCalled = false
|
|
241
|
+
let finalValue = ''
|
|
242
|
+
|
|
243
|
+
let result = resolve({ asyncSignal })
|
|
244
|
+
match(result, {
|
|
245
|
+
ok: values => {
|
|
246
|
+
okCalled = true
|
|
247
|
+
finalValue = values.asyncSignal
|
|
248
|
+
},
|
|
249
|
+
nil: () => {
|
|
250
|
+
pendingCalled = true
|
|
251
|
+
},
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
expect(pendingCalled).toBe(true)
|
|
255
|
+
expect(okCalled).toBe(false)
|
|
256
|
+
|
|
257
|
+
// Wait for resolution
|
|
258
|
+
await wait(20)
|
|
259
|
+
|
|
260
|
+
result = resolve({ asyncSignal })
|
|
261
|
+
match(result, {
|
|
262
|
+
ok: values => {
|
|
263
|
+
okCalled = true
|
|
264
|
+
finalValue = values.asyncSignal
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
expect(okCalled).toBe(true)
|
|
269
|
+
expect(finalValue).toBe('async result')
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
test('should maintain referential transparency', () => {
|
|
273
|
+
const a = state(42)
|
|
274
|
+
const result = resolve({ a })
|
|
275
|
+
let callCount = 0
|
|
276
|
+
|
|
277
|
+
// Calling match multiple times with same result should be consistent
|
|
278
|
+
match(result, {
|
|
279
|
+
ok: () => {
|
|
280
|
+
callCount++
|
|
281
|
+
},
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
match(result, {
|
|
285
|
+
ok: () => {
|
|
286
|
+
callCount++
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
expect(callCount).toBe(2)
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
describe('Match Function Integration', () => {
|
|
295
|
+
test('should work seamlessly with resolve', () => {
|
|
296
|
+
const data = state({ id: 1, value: 'test' })
|
|
297
|
+
let processed = false
|
|
298
|
+
let processedValue = ''
|
|
299
|
+
|
|
300
|
+
match(resolve({ data }), {
|
|
301
|
+
ok: values => {
|
|
302
|
+
processed = true
|
|
303
|
+
processedValue = values.data.value
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
expect(processed).toBe(true)
|
|
308
|
+
expect(processedValue).toBe('test')
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('should handle real-world scenario with mixed states', async () => {
|
|
312
|
+
const wait = (ms: number) =>
|
|
313
|
+
new Promise(resolve => setTimeout(resolve, ms))
|
|
314
|
+
|
|
315
|
+
const syncData = state('available')
|
|
316
|
+
const asyncData = computed(async () => {
|
|
317
|
+
await wait(10)
|
|
318
|
+
return 'loaded'
|
|
319
|
+
})
|
|
320
|
+
const errorData = computed(() => {
|
|
321
|
+
throw new Error('Failed to load')
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
let pendingCount = 0
|
|
325
|
+
let errorCount = 0
|
|
326
|
+
let successCount = 0
|
|
327
|
+
|
|
328
|
+
// Should be pending initially
|
|
329
|
+
let result = resolve({ syncData, asyncData })
|
|
330
|
+
match(result, {
|
|
331
|
+
ok: () => successCount++,
|
|
332
|
+
err: () => errorCount++,
|
|
333
|
+
nil: () => pendingCount++,
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
expect(pendingCount).toBe(1)
|
|
337
|
+
|
|
338
|
+
// Should have errors when including error signal
|
|
339
|
+
result = resolve({ syncData, asyncData, errorData })
|
|
340
|
+
match(result, {
|
|
341
|
+
ok: () => successCount++,
|
|
342
|
+
err: () => errorCount++,
|
|
343
|
+
nil: () => pendingCount++,
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
expect(pendingCount).toBe(2) // Still pending due to async
|
|
347
|
+
|
|
348
|
+
// Wait for async to resolve
|
|
349
|
+
await wait(20)
|
|
350
|
+
|
|
351
|
+
// Should succeed with just sync and async
|
|
352
|
+
result = resolve({ syncData, asyncData })
|
|
353
|
+
match(result, {
|
|
354
|
+
ok: values => {
|
|
355
|
+
successCount++
|
|
356
|
+
expect(values.syncData).toBe('available')
|
|
357
|
+
expect(values.asyncData).toBe('loaded')
|
|
358
|
+
},
|
|
359
|
+
err: () => errorCount++,
|
|
360
|
+
nil: () => pendingCount++,
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
// Should error when including error signal
|
|
364
|
+
result = resolve({ syncData, asyncData, errorData })
|
|
365
|
+
match(result, {
|
|
366
|
+
ok: () => successCount++,
|
|
367
|
+
err: errors => {
|
|
368
|
+
errorCount++
|
|
369
|
+
expect(errors[0].message).toBe('Failed to load')
|
|
370
|
+
},
|
|
371
|
+
nil: () => pendingCount++,
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
expect(successCount).toBe(1)
|
|
375
|
+
expect(errorCount).toBe(1)
|
|
376
|
+
expect(pendingCount).toBe(2)
|
|
377
|
+
})
|
|
378
|
+
})
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { computed, resolve, state, UNSET } from '../'
|
|
3
|
+
|
|
4
|
+
/* === Tests === */
|
|
5
|
+
|
|
6
|
+
describe('Resolve Function', () => {
|
|
7
|
+
test('should return discriminated union for successful resolution', () => {
|
|
8
|
+
const a = state(10)
|
|
9
|
+
const b = state('hello')
|
|
10
|
+
|
|
11
|
+
const result = resolve({ a, b })
|
|
12
|
+
|
|
13
|
+
expect(result.ok).toBe(true)
|
|
14
|
+
if (result.ok) {
|
|
15
|
+
expect(result.values.a).toBe(10)
|
|
16
|
+
expect(result.values.b).toBe('hello')
|
|
17
|
+
expect(result.errors).toBeUndefined()
|
|
18
|
+
expect(result.pending).toBeUndefined()
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('should return discriminated union for pending signals', () => {
|
|
23
|
+
const a = state(10)
|
|
24
|
+
const b = state(UNSET)
|
|
25
|
+
|
|
26
|
+
const result = resolve({ a, b })
|
|
27
|
+
|
|
28
|
+
expect(result.ok).toBe(false)
|
|
29
|
+
expect(result.pending).toBe(true)
|
|
30
|
+
expect(result.values).toBeUndefined()
|
|
31
|
+
expect(result.errors).toBeUndefined()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('should return discriminated union for error signals', () => {
|
|
35
|
+
const a = state(10)
|
|
36
|
+
const b = computed(() => {
|
|
37
|
+
throw new Error('Test error')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const result = resolve({ a, b })
|
|
41
|
+
|
|
42
|
+
expect(result.ok).toBe(false)
|
|
43
|
+
expect(result.pending).toBeUndefined()
|
|
44
|
+
expect(result.values).toBeUndefined()
|
|
45
|
+
expect(result.errors).toBeDefined()
|
|
46
|
+
if (result.errors) {
|
|
47
|
+
expect(result.errors[0].message).toBe('Test error')
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('should handle mixed error and valid signals', () => {
|
|
52
|
+
const valid = state('valid')
|
|
53
|
+
const error1 = computed(() => {
|
|
54
|
+
throw new Error('Error 1')
|
|
55
|
+
})
|
|
56
|
+
const error2 = computed(() => {
|
|
57
|
+
throw new Error('Error 2')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const result = resolve({ valid, error1, error2 })
|
|
61
|
+
|
|
62
|
+
expect(result.ok).toBe(false)
|
|
63
|
+
expect(result.errors).toBeDefined()
|
|
64
|
+
if (result.errors) {
|
|
65
|
+
expect(result.errors).toHaveLength(2)
|
|
66
|
+
expect(result.errors[0].message).toBe('Error 1')
|
|
67
|
+
expect(result.errors[1].message).toBe('Error 2')
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('should prioritize pending over errors', () => {
|
|
72
|
+
const pending = state(UNSET)
|
|
73
|
+
const error = computed(() => {
|
|
74
|
+
throw new Error('Test error')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const result = resolve({ pending, error })
|
|
78
|
+
|
|
79
|
+
expect(result.ok).toBe(false)
|
|
80
|
+
expect(result.pending).toBe(true)
|
|
81
|
+
expect(result.errors).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('should handle empty signals object', () => {
|
|
85
|
+
const result = resolve({})
|
|
86
|
+
|
|
87
|
+
expect(result.ok).toBe(true)
|
|
88
|
+
if (result.ok) {
|
|
89
|
+
expect(result.values).toEqual({})
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('should handle complex nested object signals', () => {
|
|
94
|
+
const user = state({ name: 'Alice', age: 25 })
|
|
95
|
+
const settings = state({ theme: 'dark', lang: 'en' })
|
|
96
|
+
|
|
97
|
+
const result = resolve({ user, settings })
|
|
98
|
+
|
|
99
|
+
expect(result.ok).toBe(true)
|
|
100
|
+
if (result.ok) {
|
|
101
|
+
expect(result.values.user.name).toBe('Alice')
|
|
102
|
+
expect(result.values.user.age).toBe(25)
|
|
103
|
+
expect(result.values.settings.theme).toBe('dark')
|
|
104
|
+
expect(result.values.settings.lang).toBe('en')
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('should handle async computed signals that resolve', async () => {
|
|
109
|
+
const wait = (ms: number) =>
|
|
110
|
+
new Promise(resolve => setTimeout(resolve, ms))
|
|
111
|
+
|
|
112
|
+
const asyncSignal = computed(async () => {
|
|
113
|
+
await wait(10)
|
|
114
|
+
return 'async result'
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// Initially should be pending
|
|
118
|
+
let result = resolve({ asyncSignal })
|
|
119
|
+
expect(result.ok).toBe(false)
|
|
120
|
+
expect(result.pending).toBe(true)
|
|
121
|
+
|
|
122
|
+
// Wait for resolution
|
|
123
|
+
await wait(20)
|
|
124
|
+
|
|
125
|
+
result = resolve({ asyncSignal })
|
|
126
|
+
expect(result.ok).toBe(true)
|
|
127
|
+
if (result.ok) {
|
|
128
|
+
expect(result.values.asyncSignal).toBe('async result')
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('should handle async computed signals that error', async () => {
|
|
133
|
+
const wait = (ms: number) =>
|
|
134
|
+
new Promise(resolve => setTimeout(resolve, ms))
|
|
135
|
+
|
|
136
|
+
const asyncError = computed(async () => {
|
|
137
|
+
await wait(10)
|
|
138
|
+
throw new Error('Async error')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Initially should be pending
|
|
142
|
+
let result = resolve({ asyncError })
|
|
143
|
+
expect(result.ok).toBe(false)
|
|
144
|
+
expect(result.pending).toBe(true)
|
|
145
|
+
|
|
146
|
+
// Wait for error
|
|
147
|
+
await wait(20)
|
|
148
|
+
|
|
149
|
+
result = resolve({ asyncError })
|
|
150
|
+
expect(result.ok).toBe(false)
|
|
151
|
+
expect(result.errors).toBeDefined()
|
|
152
|
+
if (result.errors) {
|
|
153
|
+
expect(result.errors[0].message).toBe('Async error')
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
})
|