@signalium/query 0.0.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.
@@ -0,0 +1,820 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { SyncQueryStore, MemoryPersistentStore } from '../QueryStore.js';
3
+ import { QueryClient } from '../QueryClient.js';
4
+ import { t } from '../typeDefs.js';
5
+ import { query } from '../query.js';
6
+ import { parseValue } from '../proxy.js';
7
+ import { watcher } from 'signalium';
8
+ import { createMockFetch, testWithClient } from './utils.js';
9
+
10
+ /**
11
+ * Type Validation and Edge Case Tests
12
+ *
13
+ * Tests type system validation, error handling, boundary conditions,
14
+ * and edge cases.
15
+ */
16
+
17
+ describe('Type Validation and Edge Cases', () => {
18
+ let client: QueryClient;
19
+ let mockFetch: ReturnType<typeof createMockFetch>;
20
+
21
+ beforeEach(() => {
22
+ const store = new SyncQueryStore(new MemoryPersistentStore());
23
+ mockFetch = createMockFetch();
24
+ client = new QueryClient(store, { fetch: mockFetch as any });
25
+ });
26
+
27
+ describe('Primitive Type Validation', () => {
28
+ it('should validate string types', async () => {
29
+ mockFetch.get('/item', { name: 'Test String' });
30
+
31
+ await testWithClient(client, async () => {
32
+ const getItem = query(t => ({
33
+ path: '/item',
34
+ response: { name: t.string },
35
+ }));
36
+
37
+ const relay = getItem();
38
+ const result = await relay;
39
+
40
+ expect(result.name).toBe('Test String');
41
+ expect(typeof result.name).toBe('string');
42
+ });
43
+ });
44
+
45
+ it('should validate number types', async () => {
46
+ mockFetch.get('/item', { count: 42, price: 19.99 });
47
+
48
+ await testWithClient(client, async () => {
49
+ const getItem = query(t => ({
50
+ path: '/item',
51
+ response: { count: t.number, price: t.number },
52
+ }));
53
+
54
+ const relay = getItem();
55
+ const result = await relay;
56
+
57
+ expect(result.count).toBe(42);
58
+ expect(result.price).toBe(19.99);
59
+ expect(typeof result.count).toBe('number');
60
+ });
61
+ });
62
+
63
+ it('should validate boolean types', async () => {
64
+ mockFetch.get('/item', { active: true, deleted: false });
65
+
66
+ await testWithClient(client, async () => {
67
+ const getItem = query(t => ({
68
+ path: '/item',
69
+ response: { active: t.boolean, deleted: t.boolean },
70
+ }));
71
+
72
+ const relay = getItem();
73
+ const result = await relay;
74
+
75
+ expect(result.active).toBe(true);
76
+ expect(result.deleted).toBe(false);
77
+ });
78
+ });
79
+
80
+ it('should handle null values', async () => {
81
+ mockFetch.get('/item', { value: null });
82
+
83
+ await testWithClient(client, async () => {
84
+ const getItem = query(t => ({
85
+ path: '/item',
86
+ response: { value: t.union(t.string, t.null) },
87
+ }));
88
+
89
+ const relay = getItem();
90
+ const result = await relay;
91
+
92
+ expect(result.value).toBeNull();
93
+ });
94
+ });
95
+
96
+ it('should handle undefined values', async () => {
97
+ mockFetch.get('/item', { optional: undefined });
98
+
99
+ await testWithClient(client, async () => {
100
+ const getItem = query(t => ({
101
+ path: '/item',
102
+ response: { optional: t.union(t.string, t.undefined) },
103
+ }));
104
+
105
+ const relay = getItem();
106
+ const result = await relay;
107
+
108
+ expect(result.optional).toBeUndefined();
109
+ });
110
+ });
111
+ });
112
+
113
+ describe('Complex Type Validation', () => {
114
+ it('should validate objects with mixed types', async () => {
115
+ mockFetch.get('/item', {
116
+ data: {
117
+ id: 1,
118
+ name: 'Test',
119
+ active: true,
120
+ tags: ['tag1', 'tag2'],
121
+ },
122
+ });
123
+
124
+ await testWithClient(client, async () => {
125
+ const getItem = query(t => ({
126
+ path: '/item',
127
+ response: {
128
+ data: t.object({
129
+ id: t.number,
130
+ name: t.string,
131
+ active: t.boolean,
132
+ tags: t.array(t.string),
133
+ }),
134
+ },
135
+ }));
136
+
137
+ const relay = getItem();
138
+ const result = await relay;
139
+
140
+ expect(result.data.id).toBe(1);
141
+ expect(result.data.name).toBe('Test');
142
+ expect(result.data.active).toBe(true);
143
+ expect(result.data.tags).toEqual(['tag1', 'tag2']);
144
+ });
145
+ });
146
+
147
+ it('should validate arrays of primitives', async () => {
148
+ mockFetch.get('/arrays', {
149
+ numbers: [1, 2, 3, 4, 5],
150
+ strings: ['a', 'b', 'c'],
151
+ booleans: [true, false, true],
152
+ });
153
+
154
+ await testWithClient(client, async () => {
155
+ const getArrays = query(t => ({
156
+ path: '/arrays',
157
+ response: {
158
+ numbers: t.array(t.number),
159
+ strings: t.array(t.string),
160
+ booleans: t.array(t.boolean),
161
+ },
162
+ }));
163
+
164
+ const relay = getArrays();
165
+ const result = await relay;
166
+
167
+ expect(result.numbers).toEqual([1, 2, 3, 4, 5]);
168
+ expect(result.strings).toEqual(['a', 'b', 'c']);
169
+ expect(result.booleans).toEqual([true, false, true]);
170
+ });
171
+ });
172
+
173
+ it('should validate arrays of objects', async () => {
174
+ mockFetch.get('/items', {
175
+ items: [
176
+ { id: 1, name: 'Item 1' },
177
+ { id: 2, name: 'Item 2' },
178
+ ],
179
+ });
180
+
181
+ await testWithClient(client, async () => {
182
+ const getItems = query(t => ({
183
+ path: '/items',
184
+ response: {
185
+ items: t.array(t.object({ id: t.number, name: t.string })),
186
+ },
187
+ }));
188
+
189
+ const relay = getItems();
190
+ const result = await relay;
191
+
192
+ expect(result.items).toHaveLength(2);
193
+ expect(result.items[0].name).toBe('Item 1');
194
+ expect(result.items[1].name).toBe('Item 2');
195
+ });
196
+ });
197
+
198
+ it('should validate record types', async () => {
199
+ mockFetch.get('/metadata', {
200
+ metadata: {
201
+ key1: 'value1',
202
+ key2: 'value2',
203
+ key3: 'value3',
204
+ },
205
+ });
206
+
207
+ await testWithClient(client, async () => {
208
+ const getMetadata = query(t => ({
209
+ path: '/metadata',
210
+ response: {
211
+ metadata: t.record(t.string),
212
+ },
213
+ }));
214
+
215
+ const relay = getMetadata();
216
+ const result = await relay;
217
+
218
+ expect(result.metadata.key1).toBe('value1');
219
+ expect(result.metadata.key2).toBe('value2');
220
+ expect(result.metadata.key3).toBe('value3');
221
+ });
222
+ });
223
+
224
+ it('should validate union types with primitives', async () => {
225
+ mockFetch.get('/values', {
226
+ value1: 'string',
227
+ value2: 42,
228
+ value3: true,
229
+ });
230
+
231
+ await testWithClient(client, async () => {
232
+ const UnionType = t.union(t.string, t.number, t.boolean);
233
+
234
+ const getValues = query(t => ({
235
+ path: '/values',
236
+ response: {
237
+ value1: UnionType,
238
+ value2: UnionType,
239
+ value3: UnionType,
240
+ },
241
+ }));
242
+
243
+ const relay = getValues();
244
+ const result = await relay;
245
+
246
+ expect(result.value1).toBe('string');
247
+ expect(result.value2).toBe(42);
248
+ expect(result.value3).toBe(true);
249
+ });
250
+ });
251
+ });
252
+
253
+ describe('Const Value Validation', () => {
254
+ it('should validate string constants', async () => {
255
+ mockFetch.get('/item', { type: 'user', status: 'active' });
256
+
257
+ await testWithClient(client, async () => {
258
+ const getItem = query(t => ({
259
+ path: '/item',
260
+ response: {
261
+ type: t.const('user'),
262
+ status: t.const('active'),
263
+ },
264
+ }));
265
+
266
+ const relay = getItem();
267
+ const result = await relay;
268
+
269
+ expect(result.type).toBe('user');
270
+ expect(result.status).toBe('active');
271
+ });
272
+ });
273
+
274
+ it('should validate boolean constants', async () => {
275
+ mockFetch.get('/config', { isEnabled: true });
276
+
277
+ await testWithClient(client, async () => {
278
+ const getConfig = query(t => ({
279
+ path: '/config',
280
+ response: {
281
+ isEnabled: t.const(true),
282
+ },
283
+ }));
284
+
285
+ const relay = getConfig();
286
+ const result = await relay;
287
+
288
+ expect(result.isEnabled).toBe(true);
289
+ });
290
+ });
291
+
292
+ it('should validate number constants', async () => {
293
+ mockFetch.get('/version', { version: 1 });
294
+
295
+ await testWithClient(client, async () => {
296
+ const getVersion = query(t => ({
297
+ path: '/version',
298
+ response: {
299
+ version: t.const(1),
300
+ },
301
+ }));
302
+
303
+ const relay = getVersion();
304
+ const result = await relay;
305
+
306
+ expect(result.version).toBe(1);
307
+ });
308
+ });
309
+ });
310
+
311
+ describe('Boundary Conditions', () => {
312
+ it('should handle empty arrays', async () => {
313
+ mockFetch.get('/items', { items: [] });
314
+
315
+ await testWithClient(client, async () => {
316
+ const getItems = query(t => ({
317
+ path: '/items',
318
+ response: { items: t.array(t.number) },
319
+ }));
320
+
321
+ const relay = getItems();
322
+ const result = await relay;
323
+
324
+ expect(result.items).toEqual([]);
325
+ expect(result.items).toHaveLength(0);
326
+ });
327
+ });
328
+
329
+ it('should handle empty objects', async () => {
330
+ mockFetch.get('/metadata', { metadata: {} });
331
+
332
+ await testWithClient(client, async () => {
333
+ const getMetadata = query(t => ({
334
+ path: '/metadata',
335
+ response: { metadata: t.record(t.string) },
336
+ }));
337
+
338
+ const relay = getMetadata();
339
+ const result = await relay;
340
+
341
+ expect(result.metadata).toEqual({});
342
+ expect(Object.keys(result.metadata)).toHaveLength(0);
343
+ });
344
+ });
345
+
346
+ it('should handle empty strings', async () => {
347
+ mockFetch.get('/item', { value: '' });
348
+
349
+ await testWithClient(client, async () => {
350
+ const getItem = query(t => ({
351
+ path: '/item',
352
+ response: { value: t.string },
353
+ }));
354
+
355
+ const relay = getItem();
356
+ const result = await relay;
357
+
358
+ expect(result.value).toBe('');
359
+ });
360
+ });
361
+
362
+ it('should handle zero and negative numbers', async () => {
363
+ mockFetch.get('/numbers', { zero: 0, negative: -42, float: -3.14 });
364
+
365
+ await testWithClient(client, async () => {
366
+ const getNumbers = query(t => ({
367
+ path: '/numbers',
368
+ response: { zero: t.number, negative: t.number, float: t.number },
369
+ }));
370
+
371
+ const relay = getNumbers();
372
+ const result = await relay;
373
+
374
+ expect(result.zero).toBe(0);
375
+ expect(result.negative).toBe(-42);
376
+ expect(result.float).toBe(-3.14);
377
+ });
378
+ });
379
+
380
+ it('should handle large arrays', async () => {
381
+ const largeArray = Array.from({ length: 1000 }, (_, i) => i);
382
+ mockFetch.get('/numbers', { numbers: largeArray });
383
+
384
+ await testWithClient(client, async () => {
385
+ const getNumbers = query(t => ({
386
+ path: '/numbers',
387
+ response: { numbers: t.array(t.number) },
388
+ }));
389
+
390
+ const relay = getNumbers();
391
+ const result = await relay;
392
+
393
+ expect(result.numbers).toHaveLength(1000);
394
+ expect(result.numbers[0]).toBe(0);
395
+ expect(result.numbers[999]).toBe(999);
396
+ });
397
+ });
398
+
399
+ it('should handle deeply nested objects', async () => {
400
+ mockFetch.get('/deep', {
401
+ level1: {
402
+ level2: {
403
+ level3: {
404
+ level4: {
405
+ level5: {
406
+ value: 'deep',
407
+ },
408
+ },
409
+ },
410
+ },
411
+ },
412
+ });
413
+
414
+ await testWithClient(client, async () => {
415
+ const getDeep = query(t => ({
416
+ path: '/deep',
417
+ response: {
418
+ level1: t.object({
419
+ level2: t.object({
420
+ level3: t.object({
421
+ level4: t.object({
422
+ level5: t.object({
423
+ value: t.string,
424
+ }),
425
+ }),
426
+ }),
427
+ }),
428
+ }),
429
+ },
430
+ }));
431
+
432
+ const relay = getDeep();
433
+ const result = await relay;
434
+
435
+ expect(result.level1.level2.level3.level4.level5.value).toBe('deep');
436
+ });
437
+ });
438
+ });
439
+
440
+ describe('Optional and Nullable Types', () => {
441
+ it('should accept undefined for optional types', async () => {
442
+ mockFetch.get('/item', { required: 'test', optional: undefined });
443
+
444
+ await testWithClient(client, async () => {
445
+ const getItem = query(t => ({
446
+ path: '/item',
447
+ response: {
448
+ required: t.string,
449
+ optional: t.union(t.string, t.undefined),
450
+ },
451
+ }));
452
+
453
+ const relay = getItem();
454
+ const result = await relay;
455
+
456
+ expect(result.required).toBe('test');
457
+ expect(result.optional).toBeUndefined();
458
+ });
459
+ });
460
+
461
+ it('should accept null for nullable types', async () => {
462
+ mockFetch.get('/item', { required: 'test', nullable: null });
463
+
464
+ await testWithClient(client, async () => {
465
+ const getItem = query(t => ({
466
+ path: '/item',
467
+ response: {
468
+ required: t.string,
469
+ nullable: t.union(t.string, t.null),
470
+ },
471
+ }));
472
+
473
+ const relay = getItem();
474
+ const result = await relay;
475
+
476
+ expect(result.required).toBe('test');
477
+ expect(result.nullable).toBeNull();
478
+ });
479
+ });
480
+
481
+ it('should handle nullish types (null or undefined)', async () => {
482
+ mockFetch.get('/item', {
483
+ nullValue: null,
484
+ undefinedValue: undefined,
485
+ stringValue: 'present',
486
+ });
487
+
488
+ await testWithClient(client, async () => {
489
+ const getItem = query(t => ({
490
+ path: '/item',
491
+ response: {
492
+ nullValue: t.union(t.string, t.null, t.undefined),
493
+ undefinedValue: t.union(t.string, t.null, t.undefined),
494
+ stringValue: t.union(t.string, t.null, t.undefined),
495
+ },
496
+ }));
497
+
498
+ const relay = getItem();
499
+ const result = await relay;
500
+
501
+ expect(result.nullValue).toBeNull();
502
+ expect(result.undefinedValue).toBeUndefined();
503
+ expect(result.stringValue).toBe('present');
504
+ });
505
+ });
506
+ });
507
+
508
+ describe('Validation Error Messages', () => {
509
+ it('should show descriptive error messages with typeToString formatting', () => {
510
+ // Test nested object path
511
+ expect(() => {
512
+ parseValue('not a number', t.number, 'GET:/user.profile.age');
513
+ }).toThrow('Validation error at GET:/user.profile.age: expected number, got string');
514
+
515
+ // Test array index path
516
+ expect(() => {
517
+ parseValue('wrong', t.number, 'GET:/items[2].id');
518
+ }).toThrow('Validation error at GET:/items[2].id: expected number, got string');
519
+
520
+ // Test union types
521
+ expect(() => {
522
+ parseValue({}, t.union(t.string, t.number), 'GET:/status.value');
523
+ }).toThrow(/Validation error at GET:\/status\.value: expected .*(string.*number|number.*string).*, got object/);
524
+
525
+ // Test constant values
526
+ expect(() => {
527
+ parseValue('admin', t.const('user'), 'GET:/config.type');
528
+ }).toThrow('Validation error at GET:/config.type: expected "user", got string');
529
+
530
+ // Test null values
531
+ expect(() => {
532
+ parseValue(null, t.string, 'GET:/data.required');
533
+ }).toThrow('Validation error at GET:/data.required: expected string, got null');
534
+
535
+ // Test record key path
536
+ expect(() => {
537
+ parseValue(123, t.string, 'GET:/metadata["key2"]');
538
+ }).toThrow('Validation error at GET:/metadata["key2"]: expected string, got number');
539
+ });
540
+ });
541
+
542
+ describe('Error Handling', () => {
543
+ it('should handle fetch errors gracefully', async () => {
544
+ mockFetch.get('/item', null, {
545
+ error: new Error('Network error'),
546
+ });
547
+
548
+ await testWithClient(client, async () => {
549
+ const getItem = query(t => ({
550
+ path: '/item',
551
+ response: { data: t.string },
552
+ }));
553
+
554
+ const relay = getItem();
555
+
556
+ await expect(relay).rejects.toThrow('Network error');
557
+ expect(relay.isRejected).toBe(true);
558
+ });
559
+ });
560
+
561
+ it('should handle JSON parsing errors', async () => {
562
+ mockFetch.get('/item', null, {
563
+ jsonError: new Error('Invalid JSON'),
564
+ });
565
+
566
+ await testWithClient(client, async () => {
567
+ const getItem = query(t => ({
568
+ path: '/item',
569
+ response: { data: t.string },
570
+ }));
571
+
572
+ const relay = getItem();
573
+
574
+ await expect(relay).rejects.toThrow('Invalid JSON');
575
+ });
576
+ });
577
+
578
+ it('should require QueryClient context', () => {
579
+ const getItem = query(t => ({
580
+ path: '/item',
581
+ response: { data: t.string },
582
+ }));
583
+
584
+ // Call without context should throw
585
+ expect(() => getItem()).toThrow('QueryClient not found');
586
+ });
587
+ });
588
+
589
+ describe('Path Interpolation Edge Cases', () => {
590
+ it('should handle paths with no parameters', async () => {
591
+ mockFetch.get('/static/path', { data: 'test' });
592
+
593
+ await testWithClient(client, async () => {
594
+ const getItem = query(t => ({
595
+ path: '/static/path',
596
+ response: { data: t.string },
597
+ }));
598
+
599
+ const relay = getItem();
600
+ await relay;
601
+
602
+ expect(mockFetch.calls[0].url).toBe('/static/path');
603
+ });
604
+ });
605
+
606
+ it('should handle multiple path parameters', async () => {
607
+ mockFetch.get('/org/[orgId]/team/[teamId]/user/[userId]', { data: 'test' });
608
+
609
+ await testWithClient(client, async () => {
610
+ const getItem = query(t => ({
611
+ path: '/org/[orgId]/team/[teamId]/user/[userId]',
612
+ response: { data: t.string },
613
+ }));
614
+
615
+ const relay = getItem({ orgId: '1', teamId: '2', userId: '3' });
616
+ await relay;
617
+
618
+ expect(mockFetch.calls[0].url).toContain('/org/1/team/2/user/3');
619
+ });
620
+ });
621
+ });
622
+
623
+ describe('Memory and Performance', () => {
624
+ it('should handle many concurrent queries', async () => {
625
+ // Set up 50 individual mock responses
626
+ for (let i = 0; i < 50; i++) {
627
+ mockFetch.get('/items/[id]', { url: `/items/${i}` });
628
+ }
629
+
630
+ await testWithClient(client, async () => {
631
+ const getItem = query(t => ({
632
+ path: '/items/[id]',
633
+ response: { url: t.string },
634
+ }));
635
+
636
+ const relays = [];
637
+
638
+ // Create 50 concurrent queries
639
+ for (let i = 0; i < 50; i++) {
640
+ const relay = getItem({ id: String(i) });
641
+ relays.push(relay);
642
+ }
643
+
644
+ // Wait for all to complete
645
+ await Promise.all(relays);
646
+
647
+ // Should have handled all queries
648
+ expect(mockFetch.calls).toHaveLength(50);
649
+ });
650
+ });
651
+
652
+ it('should cleanup watchers properly', async () => {
653
+ mockFetch.get('/counter', { count: 1 });
654
+
655
+ await testWithClient(client, async () => {
656
+ const getCounter = query(t => ({
657
+ path: '/counter',
658
+ response: { count: t.number },
659
+ }));
660
+
661
+ const relay = getCounter();
662
+
663
+ // Create and immediately cleanup multiple watchers
664
+ for (let i = 0; i < 10; i++) {
665
+ const w = watcher(() => relay.value);
666
+ const unsub = w.addListener(() => {});
667
+ unsub();
668
+ }
669
+
670
+ // Relay should still work
671
+ await relay;
672
+
673
+ expect(relay.isReady).toBe(true);
674
+ });
675
+ });
676
+ });
677
+
678
+ describe('Special Number Values', () => {
679
+ it('should handle special number values if API returns them', async () => {
680
+ mockFetch.get('/numbers', {
681
+ zero: 0,
682
+ maxSafe: Number.MAX_SAFE_INTEGER,
683
+ minSafe: Number.MIN_SAFE_INTEGER,
684
+ });
685
+
686
+ await testWithClient(client, async () => {
687
+ const getNumbers = query(t => ({
688
+ path: '/numbers',
689
+ response: {
690
+ zero: t.number,
691
+ maxSafe: t.number,
692
+ minSafe: t.number,
693
+ },
694
+ }));
695
+
696
+ const relay = getNumbers();
697
+ const result = await relay;
698
+
699
+ expect(result.zero).toBe(0);
700
+ expect(result.maxSafe).toBe(Number.MAX_SAFE_INTEGER);
701
+ expect(result.minSafe).toBe(Number.MIN_SAFE_INTEGER);
702
+ });
703
+ });
704
+ });
705
+
706
+ describe('Query Definition Caching', () => {
707
+ it('should cache query definition across calls', async () => {
708
+ mockFetch.get('/counter', { count: 1 });
709
+
710
+ await testWithClient(client, async () => {
711
+ const getCounter = query(t => ({
712
+ path: '/counter',
713
+ response: { count: t.number },
714
+ }));
715
+
716
+ // Call multiple times
717
+ const relay1 = getCounter();
718
+ const relay2 = getCounter();
719
+ const relay3 = getCounter();
720
+
721
+ // Should all return the same relay (deduplication)
722
+ expect(relay1).toBe(relay2);
723
+ expect(relay2).toBe(relay3);
724
+
725
+ await relay1;
726
+
727
+ // Should only fetch once
728
+ expect(mockFetch.calls).toHaveLength(1);
729
+ });
730
+ });
731
+
732
+ it('should create definition only once per query function', async () => {
733
+ let definitionBuildCount = 0;
734
+
735
+ mockFetch.get('/item', { data: 'test' });
736
+
737
+ await testWithClient(client, async () => {
738
+ const getItem = query(t => {
739
+ definitionBuildCount++;
740
+ return {
741
+ path: '/item',
742
+ response: { data: t.string },
743
+ };
744
+ });
745
+
746
+ // Call multiple times
747
+ const relay1 = getItem();
748
+ const relay2 = getItem();
749
+ const relay3 = getItem();
750
+
751
+ await Promise.all([relay1, relay2, relay3]);
752
+
753
+ // Definition should only be built once
754
+ expect(definitionBuildCount).toBe(1);
755
+ });
756
+ });
757
+ });
758
+
759
+ describe('Method Support', () => {
760
+ it('should include method in query ID', async () => {
761
+ mockFetch.get('/items', { success: true });
762
+ mockFetch.post('/items', { success: true });
763
+
764
+ await testWithClient(client, async () => {
765
+ const getItem = query(t => ({
766
+ path: '/items',
767
+ method: 'GET',
768
+ response: { success: t.boolean },
769
+ }));
770
+
771
+ const postItem = query(t => ({
772
+ path: '/items',
773
+ method: 'POST',
774
+ response: { success: t.boolean },
775
+ }));
776
+
777
+ const relay1 = getItem();
778
+ const relay2 = postItem();
779
+
780
+ // Different methods should create different relays
781
+ expect(relay1).not.toBe(relay2);
782
+
783
+ await Promise.all([relay1, relay2]);
784
+ });
785
+ });
786
+ });
787
+
788
+ describe('Concurrent Operations', () => {
789
+ it('should handle race conditions safely', async () => {
790
+ // Set up single mock response with delay - testing query deduplication
791
+ mockFetch.get('/counter', { count: 1 }, { delay: 25 });
792
+
793
+ await testWithClient(client, async () => {
794
+ const getCounter = query(t => ({
795
+ path: '/counter',
796
+ response: { count: t.number },
797
+ }));
798
+
799
+ // Start many concurrent requests with random delays
800
+ const promises = [];
801
+
802
+ for (let i = 0; i < 20; i++) {
803
+ const relay = getCounter();
804
+ promises.push(relay);
805
+ }
806
+
807
+ // All should resolve to the same value (deduplication)
808
+ const results = await Promise.all(promises);
809
+
810
+ // Should only fetch once despite concurrent requests
811
+ expect(mockFetch.calls).toHaveLength(1);
812
+
813
+ // All results should be identical
814
+ results.forEach(result => {
815
+ expect(result.count).toBe(1);
816
+ });
817
+ });
818
+ });
819
+ });
820
+ });