@meistrari/tela-build 1.0.2 → 1.1.0

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.
@@ -224,7 +224,7 @@ const variantConfig: Record<TelaStatusVariant, StatusConfig> = {
224
224
  },
225
225
  'failed': {
226
226
  label: 'Failed',
227
- backgroundColor: 'bg-red-50',
227
+ backgroundColor: 'bg-red-100',
228
228
  textColor: 'text-red-700',
229
229
  iconName: 'close',
230
230
  iconColor: 'red-500',
@@ -0,0 +1,606 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createApp, nextTick, watch, watchEffect } from 'vue'
3
+ import type { App } from 'vue'
4
+ import { useOptimisticAsyncData } from '../optimistic-async-data'
5
+
6
+ /**
7
+ * Helper function to test composables that use provide/inject
8
+ * Creates a Vue app context for the composable
9
+ */
10
+ function withSetup<T>(composable: () => T): [T, App] {
11
+ let result!: T
12
+ const app = createApp({
13
+ setup() {
14
+ result = composable()
15
+ // Return a render function to suppress warnings
16
+ return () => {}
17
+ },
18
+ })
19
+ app.mount(document.createElement('div'))
20
+ return [result, app]
21
+ }
22
+
23
+ describe('useOptimisticAsyncData', () => {
24
+ describe('basic functionality', () => {
25
+ it('should initialize with idle state', () => {
26
+ const [asyncData] = withSetup(() => useOptimisticAsyncData({ key: 'test-1' }))
27
+
28
+ expect(asyncData.state.value).toBe('idle')
29
+ expect(asyncData.data.value).toBe(null)
30
+ expect(asyncData.error.value).toBe(null)
31
+ })
32
+
33
+ it('should generate unique key when not provided', () => {
34
+ const [asyncData1] = withSetup(() => useOptimisticAsyncData())
35
+ const [asyncData2] = withSetup(() => useOptimisticAsyncData())
36
+
37
+ expect(asyncData1.key).toBeDefined()
38
+ expect(asyncData2.key).toBeDefined()
39
+ expect(asyncData1.key).not.toBe(asyncData2.key)
40
+ expect(asyncData1.key).toMatch(/^async-data-/)
41
+ })
42
+
43
+ it('should use provided custom key', () => {
44
+ const customKey = 'my-custom-key'
45
+ const [asyncData] = withSetup(() => useOptimisticAsyncData({ key: customKey }))
46
+
47
+ expect(asyncData.key).toBe(customKey)
48
+ })
49
+ })
50
+
51
+ describe('state transitions - success flow', () => {
52
+ it('should transition from idle → pending → success', async () => {
53
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-2' }))
54
+
55
+ const states: string[] = []
56
+ watchEffect(() => {
57
+ states.push(asyncData.state.value)
58
+ })
59
+
60
+ const promise = asyncData.promise(async () => {
61
+ await new Promise(resolve => setTimeout(resolve, 10))
62
+ return 'test-data'
63
+ })()
64
+
65
+ await nextTick()
66
+ await promise
67
+
68
+ expect(states).toEqual(['idle', 'pending', 'success'])
69
+ expect(asyncData.state.value).toBe('success')
70
+ expect(asyncData.data.value).toBe('test-data')
71
+ expect(asyncData.error.value).toBe(null)
72
+ })
73
+
74
+ it('should return data on successful promise', async () => {
75
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<{ id: number }>({ key: 'test-3' }))
76
+
77
+ const result = await asyncData.promise(async () => ({ id: 123 }))()
78
+
79
+ expect(result).toEqual({ id: 123 })
80
+ expect(asyncData.data.value).toEqual({ id: 123 })
81
+ })
82
+
83
+ it('should update data on subsequent successful operations', async () => {
84
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<number>({ key: 'test-4' }))
85
+
86
+ await asyncData.promise(async () => 1)()
87
+ expect(asyncData.data.value).toBe(1)
88
+
89
+ await asyncData.promise(async () => 2)()
90
+ expect(asyncData.data.value).toBe(2)
91
+
92
+ await asyncData.promise(async () => 3)()
93
+ expect(asyncData.data.value).toBe(3)
94
+ })
95
+
96
+ it('should pass arguments to the async function correctly', async () => {
97
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-args' }))
98
+
99
+ const result = await asyncData.promise(async (a: number, b: string, c: boolean) => {
100
+ return `${a}-${b}-${c}`
101
+ })(42, 'hello', true)
102
+
103
+ expect(result).toBe('42-hello-true')
104
+ expect(asyncData.data.value).toBe('42-hello-true')
105
+ expect(asyncData.state.value).toBe('success')
106
+ })
107
+
108
+ it('should pass object arguments correctly', async () => {
109
+ interface Input { id: number, name: string }
110
+ interface Output { processed: boolean, input: Input }
111
+
112
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<Output>({ key: 'test-obj-args' }))
113
+
114
+ const input: Input = { id: 1, name: 'test' }
115
+ const result = await asyncData.promise(async (data: Input) => {
116
+ return { processed: true, input: data }
117
+ })(input)
118
+
119
+ expect(result).toEqual({ processed: true, input: { id: 1, name: 'test' } })
120
+ expect(asyncData.data.value).toEqual({ processed: true, input: { id: 1, name: 'test' } })
121
+ })
122
+ })
123
+
124
+ describe('state transitions - error flow', () => {
125
+ it('should transition from idle → pending → error', async () => {
126
+ const [asyncData] = withSetup(() => useOptimisticAsyncData({ key: 'test-5' }))
127
+
128
+ const states: string[] = []
129
+ watchEffect(() => {
130
+ states.push(asyncData.state.value)
131
+ })
132
+
133
+ const promise = asyncData.promise(async () => {
134
+ await new Promise(resolve => setTimeout(resolve, 10))
135
+ throw new Error('Test error')
136
+ })()
137
+
138
+ await nextTick()
139
+ await expect(promise).rejects.toThrow('Test error')
140
+
141
+ expect(states).toEqual(['idle', 'pending', 'error'])
142
+ expect(asyncData.state.value).toBe('error')
143
+ expect(asyncData.error.value).toBeInstanceOf(Error)
144
+ expect(asyncData.error.value?.message).toBe('Test error')
145
+ })
146
+
147
+ it('should throw error on failed promise', async () => {
148
+ const [asyncData] = withSetup(() => useOptimisticAsyncData({ key: 'test-6' }))
149
+
150
+ await expect(asyncData.promise(async () => {
151
+ throw new Error('Test error')
152
+ })()).rejects.toThrow('Test error')
153
+
154
+ expect(asyncData.state.value).toBe('error')
155
+ expect(asyncData.error.value).toBeInstanceOf(Error)
156
+ expect(asyncData.error.value?.message).toBe('Test error')
157
+ })
158
+ })
159
+
160
+ describe('optimistic updates and rollback', () => {
161
+ it('should preserve previous data during pending state', async () => {
162
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-9' }))
163
+
164
+ // Set initial data
165
+ await asyncData.promise(async () => 'initial-data')()
166
+ expect(asyncData.data.value).toBe('initial-data')
167
+
168
+ // Start new operation
169
+ const promise = asyncData.promise(async () => {
170
+ await new Promise(resolve => setTimeout(resolve, 10))
171
+ return 'new-data'
172
+ })()
173
+
174
+ // During pending, data should still be available
175
+ await nextTick()
176
+ expect(asyncData.state.value).toBe('pending')
177
+ expect(asyncData.data.value).toBe('initial-data')
178
+
179
+ await promise
180
+ expect(asyncData.data.value).toBe('new-data')
181
+ })
182
+
183
+ it('should rollback to previous data on error', async () => {
184
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-10' }))
185
+
186
+ // Set initial data
187
+ await asyncData.promise(async () => 'initial-data')()
188
+ expect(asyncData.data.value).toBe('initial-data')
189
+
190
+ // Fail operation
191
+ await expect(asyncData.promise(async () => {
192
+ throw new Error('Operation failed')
193
+ })()).rejects.toThrow('Operation failed')
194
+
195
+ // Data should be rolled back
196
+ expect(asyncData.state.value).toBe('error')
197
+ expect(asyncData.data.value).toBe('initial-data')
198
+ expect(asyncData.error.value?.message).toBe('Operation failed')
199
+ })
200
+
201
+ it('should rollback to null when no previous data exists', async () => {
202
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-11' }))
203
+
204
+ await expect(asyncData.promise(async () => {
205
+ throw new Error('First operation failed')
206
+ })()).rejects.toThrow('First operation failed')
207
+
208
+ expect(asyncData.state.value).toBe('error')
209
+ expect(asyncData.data.value).toBe(null)
210
+ expect(asyncData.error.value).toBeInstanceOf(Error)
211
+ expect(asyncData.error.value?.message).toBe('First operation failed')
212
+ })
213
+
214
+ it('should handle multiple consecutive errors', async () => {
215
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<number>({ key: 'test-12' }))
216
+
217
+ // Success
218
+ await asyncData.promise(async () => 100)()
219
+ expect(asyncData.data.value).toBe(100)
220
+
221
+ // First error - should rollback to 100
222
+ await expect(asyncData.promise(async () => {
223
+ throw new Error('Error 1')
224
+ })()).rejects.toThrow('Error 1')
225
+
226
+ expect(asyncData.state.value).toBe('error')
227
+ expect(asyncData.data.value).toBe(100)
228
+ expect(asyncData.error.value).toBeInstanceOf(Error)
229
+ expect(asyncData.error.value?.message).toBe('Error 1')
230
+
231
+ // Second error - should still have 100
232
+ await expect(asyncData.promise(async () => {
233
+ throw new Error('Error 2')
234
+ })()).rejects.toThrow('Error 2')
235
+
236
+ expect(asyncData.state.value).toBe('error')
237
+ expect(asyncData.data.value).toBe(100)
238
+ expect(asyncData.error.value).toBeInstanceOf(Error)
239
+ expect(asyncData.error.value?.message).toBe('Error 2')
240
+ })
241
+ })
242
+
243
+ describe('reset', () => {
244
+ it('should reset the async data state to idle', async () => {
245
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-13' }))
246
+
247
+ await asyncData.promise(async () => 'initial-data')()
248
+ expect(asyncData.data.value).toBe('initial-data')
249
+ expect(asyncData.state.value).toBe('success')
250
+ asyncData.reset()
251
+ expect(asyncData.state.value).toBe('idle')
252
+ expect(asyncData.data.value).toBe(null)
253
+ expect(asyncData.error.value).toBe(null)
254
+ })
255
+ })
256
+
257
+ describe('provide/inject pattern', () => {
258
+ it('should create independent instances when same key used in same component', async () => {
259
+ const key = 'shared-key'
260
+
261
+ // When multiple instances with same key are created in the same component,
262
+ // only the first one provides - subsequent ones get their own independent state
263
+ // This is expected Vue provide/inject behavior within the same component
264
+ const [result] = withSetup(() => {
265
+ const instance1 = useOptimisticAsyncData<string>({ key })
266
+ const instance2 = useOptimisticAsyncData<string>({ key })
267
+ return { instance1, instance2 }
268
+ })
269
+
270
+ // Update through first instance
271
+ await result.instance1.promise(async () => 'shared-data')()
272
+
273
+ // First instance has data
274
+ expect(result.instance1.data.value).toBe('shared-data')
275
+ expect(result.instance1.state.value).toBe('success')
276
+
277
+ // Second instance remains independent (expected behavior in same component)
278
+ expect(result.instance2.state.value).toBe('idle')
279
+ })
280
+
281
+ it('should allow each instance to have independent state', async () => {
282
+ const key = 'independent-key'
283
+
284
+ // Create two separate app instances with the same key
285
+ const [instance1] = withSetup(() => useOptimisticAsyncData<number>({ key }))
286
+ const [instance2] = withSetup(() => useOptimisticAsyncData<number>({ key }))
287
+
288
+ // Update first instance
289
+ await instance1.promise(async () => 100)()
290
+
291
+ // Update second instance
292
+ await instance2.promise(async () => 200)()
293
+
294
+ // Each instance maintains its own state
295
+ expect(instance1.data.value).toBe(100)
296
+ expect(instance2.data.value).toBe(200)
297
+ })
298
+
299
+ it('should maintain separate state for different keys', async () => {
300
+ // Create instances with different keys in the same app context
301
+ const [result] = withSetup(() => {
302
+ const instance1 = useOptimisticAsyncData<string>({ key: 'key-1' })
303
+ const instance2 = useOptimisticAsyncData<string>({ key: 'key-2' })
304
+ return { instance1, instance2 }
305
+ })
306
+
307
+ await result.instance1.promise(async () => 'data-1')()
308
+ await result.instance2.promise(async () => 'data-2')()
309
+
310
+ expect(result.instance1.data.value).toBe('data-1')
311
+ expect(result.instance2.data.value).toBe('data-2')
312
+ })
313
+
314
+ it('should handle errors independently across different keys', async () => {
315
+ const [result] = withSetup(() => {
316
+ const instance1 = useOptimisticAsyncData<string>({ key: 'key-error-1' })
317
+ const instance2 = useOptimisticAsyncData<string>({ key: 'key-error-2' })
318
+ return { instance1, instance2 }
319
+ })
320
+
321
+ await expect(result.instance1.promise(async () => {
322
+ throw new Error('Instance1 error')
323
+ })()).rejects.toThrow('Instance1 error')
324
+
325
+ expect(result.instance1.state.value).toBe('error')
326
+ expect(result.instance1.error.value).toBeInstanceOf(Error)
327
+ expect(result.instance1.error.value?.message).toBe('Instance1 error')
328
+ expect(result.instance1.data.value).toBe(null)
329
+
330
+ expect(result.instance2.state.value).toBe('idle')
331
+ expect(result.instance2.error.value).toBe(null)
332
+ expect(result.instance2.data.value).toBe(null)
333
+
334
+ await expect(result.instance2.promise(async () => {
335
+ throw new Error('Instance2 error')
336
+ })()).rejects.toThrow('Instance2 error')
337
+
338
+ expect(result.instance2.state.value).toBe('error')
339
+ expect(result.instance2.error.value).toBeInstanceOf(Error)
340
+ expect(result.instance2.error.value?.message).toBe('Instance2 error')
341
+ expect(result.instance2.data.value).toBe(null)
342
+
343
+ expect(result.instance1.state.value).toBe('error')
344
+ expect(result.instance1.error.value).toBeInstanceOf(Error)
345
+ expect(result.instance1.error.value?.message).toBe('Instance1 error')
346
+ expect(result.instance1.data.value).toBe(null)
347
+
348
+ expect(result.instance2.state.value).toBe('error')
349
+ expect(result.instance2.error.value).toBeInstanceOf(Error)
350
+ expect(result.instance2.error.value?.message).toBe('Instance2 error')
351
+ expect(result.instance2.data.value).toBe(null)
352
+ })
353
+
354
+ it('should handle errors independently when created in same component', async () => {
355
+ const key = 'error-key'
356
+
357
+ const [result] = withSetup(() => {
358
+ const instance1 = useOptimisticAsyncData<string>({ key })
359
+ const instance2 = useOptimisticAsyncData<string>({ key })
360
+ return { instance1, instance2 }
361
+ })
362
+
363
+ await expect(result.instance1.promise(async () => {
364
+ throw new Error('Instance1 error')
365
+ })()).rejects.toThrow('Instance1 error')
366
+
367
+ // First instance has error
368
+ expect(result.instance1.state.value).toBe('error')
369
+ expect(result.instance1.error.value?.message).toBe('Instance1 error')
370
+ expect(result.instance1.data.value).toBe(null)
371
+
372
+ // Second instance remains independent
373
+ expect(result.instance2.state.value).toBe('idle')
374
+ expect(result.instance2.error.value).toBe(null)
375
+ expect(result.instance2.data.value).toBe(null)
376
+ })
377
+ })
378
+
379
+ describe('concurrent operations', () => {
380
+ it('should handle multiple concurrent promises', async () => {
381
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<number>({ key: 'test-concurrent-1' }))
382
+
383
+ // Start multiple operations
384
+ const promise1 = asyncData.promise(async () => {
385
+ await new Promise(resolve => setTimeout(resolve, 20))
386
+ return 1
387
+ })()
388
+
389
+ const promise2 = asyncData.promise(async () => {
390
+ await new Promise(resolve => setTimeout(resolve, 10))
391
+ return 2
392
+ })()
393
+
394
+ const promise3 = asyncData.promise(async () => {
395
+ await new Promise(resolve => setTimeout(resolve, 5))
396
+ return 3
397
+ })()
398
+
399
+ await Promise.all([promise1, promise2, promise3])
400
+
401
+ // Last operation wins
402
+ expect(asyncData.data.value).toBeDefined()
403
+ expect(asyncData.state.value).toBe('success')
404
+ })
405
+
406
+ it('should maintain state consistency with rapid successive calls', async () => {
407
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<number>({ key: 'test-concurrent-2' }))
408
+
409
+ // Rapid successive operations
410
+ await asyncData.promise(async () => 1)()
411
+ await asyncData.promise(async () => 2)()
412
+ await asyncData.promise(async () => 3)()
413
+ await asyncData.promise(async () => 4)()
414
+ await asyncData.promise(async () => 5)()
415
+
416
+ expect(asyncData.data.value).toBe(5)
417
+ expect(asyncData.state.value).toBe('success')
418
+ })
419
+ })
420
+
421
+ describe('computed properties reactivity', () => {
422
+ it('should trigger watchers on state changes', async () => {
423
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-watch-1' }))
424
+
425
+ const stateChanges: string[] = []
426
+ watch(() => asyncData.state.value, (newState) => {
427
+ stateChanges.push(newState)
428
+ })
429
+
430
+ await asyncData.promise(async () => 'test-data')()
431
+
432
+ expect(stateChanges).toContain('pending')
433
+ expect(stateChanges).toContain('success')
434
+ })
435
+
436
+ it('should trigger watchers on data changes', async () => {
437
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<number>({ key: 'test-watch-2' }))
438
+
439
+ const dataChanges: (number | null)[] = []
440
+ watch(() => asyncData.data.value, (newData) => {
441
+ dataChanges.push(newData)
442
+ })
443
+
444
+ await asyncData.promise(async () => 42)()
445
+
446
+ expect(dataChanges).toContain(42)
447
+ })
448
+
449
+ it('should trigger watchers on error changes', async () => {
450
+ const [asyncData] = withSetup(() => useOptimisticAsyncData({ key: 'test-watch-3' }))
451
+
452
+ const errorChanges: (Error | null)[] = []
453
+ watch(() => asyncData.error.value, (newError) => {
454
+ errorChanges.push(newError)
455
+ })
456
+
457
+ await expect(asyncData.promise(async () => {
458
+ throw new Error('Watch error')
459
+ })()).rejects.toThrow('Watch error')
460
+
461
+ expect(errorChanges.some(err => err?.message === 'Watch error')).toBe(true)
462
+ expect(asyncData.error.value).toBeInstanceOf(Error)
463
+ expect(asyncData.error.value?.message).toBe('Watch error')
464
+ })
465
+ })
466
+
467
+ describe('edge cases', () => {
468
+ it('should handle async functions that return undefined', async () => {
469
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<undefined>({ key: 'test-edge-1' }))
470
+
471
+ const result = await asyncData.promise(async () => undefined)()
472
+
473
+ expect(result).toBe(undefined)
474
+ expect(asyncData.state.value).toBe('success')
475
+ expect(asyncData.data.value).toBe(undefined)
476
+ })
477
+
478
+ it('should handle async functions that return null', async () => {
479
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<null>({ key: 'test-edge-2' }))
480
+
481
+ const result = await asyncData.promise(async () => null)()
482
+
483
+ expect(result).toBe(null)
484
+ expect(asyncData.state.value).toBe('success')
485
+ })
486
+
487
+ it('should handle complex nested objects', async () => {
488
+ interface ComplexData {
489
+ user: { id: number, name: string }
490
+ items: Array<{ id: string, value: number }>
491
+ }
492
+
493
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<ComplexData>({ key: 'test-edge-3' }))
494
+
495
+ const complexData: ComplexData = {
496
+ user: { id: 1, name: 'Test User' },
497
+ items: [
498
+ { id: 'a', value: 10 },
499
+ { id: 'b', value: 20 },
500
+ ],
501
+ }
502
+
503
+ await asyncData.promise(async () => complexData)()
504
+
505
+ expect(asyncData.data.value).toEqual(complexData)
506
+ expect(asyncData.data.value?.user.name).toBe('Test User')
507
+ expect(asyncData.data.value?.items).toHaveLength(2)
508
+ })
509
+
510
+ it('should handle very fast operations', async () => {
511
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-edge-4' }))
512
+
513
+ // Operation that completes immediately
514
+ await asyncData.promise(async () => 'instant')()
515
+
516
+ expect(asyncData.state.value).toBe('success')
517
+ expect(asyncData.data.value).toBe('instant')
518
+ })
519
+
520
+ it('should allow recovery from error state', async () => {
521
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<string>({ key: 'test-edge-5' }))
522
+
523
+ // Initial success
524
+ await asyncData.promise(async () => 'initial')()
525
+ expect(asyncData.state.value).toBe('success')
526
+
527
+ // Error
528
+ await expect(asyncData.promise(async () => {
529
+ throw new Error('Temporary error')
530
+ })()).rejects.toThrow('Temporary error')
531
+ expect(asyncData.state.value).toBe('error')
532
+ expect(asyncData.data.value).toBe('initial')
533
+ expect(asyncData.error.value).toBeInstanceOf(Error)
534
+ expect(asyncData.error.value?.message).toBe('Temporary error')
535
+
536
+ // Recovery
537
+ await asyncData.promise(async () => 'recovered')()
538
+ expect(asyncData.state.value).toBe('success')
539
+ expect(asyncData.data.value).toBe('recovered')
540
+ expect(asyncData.error.value).toBe(null)
541
+ })
542
+ })
543
+
544
+ describe('type safety', () => {
545
+ it('should maintain type safety for generic data types', async () => {
546
+ interface User {
547
+ id: number
548
+ email: string
549
+ }
550
+
551
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<User>({ key: 'test-types-1' }))
552
+
553
+ await asyncData.promise(async () => ({
554
+ id: 1,
555
+ email: 'test@example.com',
556
+ }))()
557
+
558
+ // TypeScript should infer the correct type
559
+ const user = asyncData.data.value
560
+ if (user) {
561
+ expect(user.id).toBe(1)
562
+ expect(user.email).toBe('test@example.com')
563
+ }
564
+ })
565
+
566
+ it('should handle union types', async () => {
567
+ type Result = { success: true, data: string } | { success: false, error: string }
568
+
569
+ const [asyncData] = withSetup(() => useOptimisticAsyncData<Result>({ key: 'test-types-2' }))
570
+
571
+ await asyncData.promise(async () => ({
572
+ success: true,
573
+ data: 'test',
574
+ }))()
575
+
576
+ const result = asyncData.data.value
577
+ expect(result).toBeDefined()
578
+ })
579
+ })
580
+
581
+ describe('injection key caching', () => {
582
+ it('should cache injection keys for reuse', async () => {
583
+ const key = 'cached-key-test'
584
+
585
+ // Create multiple instances with same key
586
+ const [instance1] = withSetup(() => useOptimisticAsyncData({ key }))
587
+ const [instance2] = withSetup(() => useOptimisticAsyncData({ key }))
588
+ const [instance3] = withSetup(() => useOptimisticAsyncData({ key }))
589
+
590
+ // Each instance can work independently
591
+ await instance1.promise(async () => 'value-1')()
592
+ await instance2.promise(async () => 'value-2')()
593
+ await instance3.promise(async () => 'value-3')()
594
+
595
+ // All instances have their own state
596
+ expect(instance1.data.value).toBe('value-1')
597
+ expect(instance2.data.value).toBe('value-2')
598
+ expect(instance3.data.value).toBe('value-3')
599
+
600
+ // But they all use the same key
601
+ expect(instance1.key).toBe(key)
602
+ expect(instance2.key).toBe(key)
603
+ expect(instance3.key).toBe(key)
604
+ })
605
+ })
606
+ })