@tanstack/solid-query 5.59.17 → 5.60.4

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.
@@ -1,732 +0,0 @@
1
- import { describe, expect, expectTypeOf, it, vi } from 'vitest'
2
- import { fireEvent, render, waitFor } from '@solidjs/testing-library'
3
- import * as QueryCore from '@tanstack/query-core'
4
- import { createRenderEffect, createSignal } from 'solid-js'
5
- import {
6
- QueriesObserver,
7
- QueryCache,
8
- QueryClientProvider,
9
- createQueries,
10
- } from '..'
11
- import { createQueryClient, queryKey, sleep } from './utils'
12
- import type { QueryFunctionContext, QueryKey } from '@tanstack/query-core'
13
- import type { CreateQueryResult, QueryFunction, SolidQueryOptions } from '..'
14
-
15
- describe('useQueries', () => {
16
- const queryCache = new QueryCache()
17
- const queryClient = createQueryClient({ queryCache })
18
-
19
- it('should return the correct states', async () => {
20
- const key1 = queryKey()
21
- const key2 = queryKey()
22
- const results: Array<Array<CreateQueryResult>> = []
23
-
24
- function Page() {
25
- const result = createQueries(() => ({
26
- queries: [
27
- {
28
- queryKey: key1,
29
- queryFn: async () => {
30
- await sleep(10)
31
- return 1
32
- },
33
- },
34
- {
35
- queryKey: key2,
36
- queryFn: async () => {
37
- await sleep(100)
38
- return 2
39
- },
40
- },
41
- ],
42
- }))
43
-
44
- createRenderEffect(() => {
45
- results.push([{ ...result[0] }, { ...result[1] }])
46
- })
47
-
48
- return (
49
- <div>
50
- <div>
51
- data1: {String(result[0].data ?? 'null')}, data2:{' '}
52
- {String(result[1].data ?? 'null')}
53
- </div>
54
- </div>
55
- )
56
- }
57
-
58
- const rendered = render(() => (
59
- <QueryClientProvider client={queryClient}>
60
- <Page />
61
- </QueryClientProvider>
62
- ))
63
-
64
- await waitFor(() => rendered.getByText('data1: 1, data2: 2'))
65
-
66
- expect(results.length).toBe(3)
67
- expect(results[0]).toMatchObject([{ data: undefined }, { data: undefined }])
68
- expect(results[1]).toMatchObject([{ data: 1 }, { data: undefined }])
69
- expect(results[2]).toMatchObject([{ data: 1 }, { data: 2 }])
70
- })
71
-
72
- it('handles type parameter - tuple of tuples', async () => {
73
- const key1 = queryKey()
74
- const key2 = queryKey()
75
- const key3 = queryKey()
76
-
77
- // @ts-expect-error (Page component is not rendered)
78
- function Page() {
79
- const result1 = createQueries<
80
- [[number], [string], [Array<string>, boolean]]
81
- >(() => ({
82
- queries: [
83
- {
84
- queryKey: key1,
85
- queryFn: () => 1,
86
- },
87
- {
88
- queryKey: key2,
89
- queryFn: () => 'string',
90
- },
91
- {
92
- queryKey: key3,
93
- queryFn: () => ['string[]'],
94
- },
95
- ],
96
- }))
97
- expectTypeOf(result1[0]).toEqualTypeOf<CreateQueryResult<number>>()
98
- expectTypeOf(result1[1]).toEqualTypeOf<CreateQueryResult<string>>()
99
- expectTypeOf(result1[2]).toEqualTypeOf<
100
- CreateQueryResult<Array<string>, boolean>
101
- >()
102
- expectTypeOf(result1[0].data).toEqualTypeOf<number | undefined>()
103
- expectTypeOf(result1[1].data).toEqualTypeOf<string | undefined>()
104
- expectTypeOf(result1[2].data).toEqualTypeOf<Array<string> | undefined>()
105
- expectTypeOf(result1[2].error).toEqualTypeOf<boolean | null>()
106
-
107
- // TData (3rd element) takes precedence over TQueryFnData (1st element)
108
- const result2 = createQueries<
109
- [[string, unknown, string], [string, unknown, number]]
110
- >(() => ({
111
- queries: [
112
- {
113
- queryKey: key1,
114
- queryFn: () => 'string',
115
- select: (a) => {
116
- expectTypeOf(a).toEqualTypeOf<string>()
117
- return a.toLowerCase()
118
- },
119
- },
120
- {
121
- queryKey: key2,
122
- queryFn: () => 'string',
123
- select: (a) => {
124
- expectTypeOf(a).toEqualTypeOf<string>()
125
- return parseInt(a)
126
- },
127
- },
128
- ],
129
- }))
130
- expectTypeOf(result2[0]).toEqualTypeOf<
131
- CreateQueryResult<string, unknown>
132
- >()
133
- expectTypeOf(result2[1]).toEqualTypeOf<
134
- CreateQueryResult<number, unknown>
135
- >()
136
- expectTypeOf(result2[0].data).toEqualTypeOf<string | undefined>()
137
- expectTypeOf(result2[1].data).toEqualTypeOf<number | undefined>()
138
-
139
- // types should be enforced
140
- createQueries<[[string, unknown, string], [string, boolean, number]]>(
141
- () => ({
142
- queries: [
143
- {
144
- queryKey: key1,
145
- queryFn: () => 'string',
146
- select: (a) => {
147
- expectTypeOf(a).toEqualTypeOf<string>()
148
- return a.toLowerCase()
149
- },
150
- placeholderData: 'string',
151
- // @ts-expect-error (initialData: string)
152
- initialData: 123,
153
- },
154
- {
155
- queryKey: key2,
156
- queryFn: () => 'string',
157
- select: (a) => {
158
- expectTypeOf(a).toEqualTypeOf<string>()
159
- return parseInt(a)
160
- },
161
- placeholderData: 'string',
162
- // @ts-expect-error (initialData: string)
163
- initialData: 123,
164
- },
165
- ],
166
- }),
167
- )
168
-
169
- // field names should be enforced
170
- createQueries<[[string]]>(() => ({
171
- queries: [
172
- {
173
- queryKey: key1,
174
- queryFn: () => 'string',
175
- },
176
- ],
177
- }))
178
- }
179
- })
180
-
181
- it('handles type parameter - tuple of objects', async () => {
182
- const key1 = queryKey()
183
- const key2 = queryKey()
184
- const key3 = queryKey()
185
-
186
- // @ts-expect-error (Page component is not rendered)
187
- function Page() {
188
- const result1 = createQueries<
189
- [
190
- { queryFnData: number },
191
- { queryFnData: string },
192
- { queryFnData: Array<string>; error: boolean },
193
- ]
194
- >(() => ({
195
- queries: [
196
- {
197
- queryKey: key1,
198
- queryFn: () => 1,
199
- },
200
- {
201
- queryKey: key2,
202
- queryFn: () => 'string',
203
- },
204
- {
205
- queryKey: key3,
206
- queryFn: () => ['string[]'],
207
- },
208
- ],
209
- }))
210
- expectTypeOf(result1[0]).toEqualTypeOf<
211
- CreateQueryResult<number, unknown>
212
- >()
213
- expectTypeOf(result1[1]).toEqualTypeOf<
214
- CreateQueryResult<string, unknown>
215
- >()
216
- expectTypeOf(result1[2]).toEqualTypeOf<
217
- CreateQueryResult<Array<string>, boolean>
218
- >()
219
- expectTypeOf(result1[0].data).toEqualTypeOf<number | undefined>()
220
- expectTypeOf(result1[1].data).toEqualTypeOf<string | undefined>()
221
- expectTypeOf(result1[2].data).toEqualTypeOf<Array<string> | undefined>()
222
- expectTypeOf(result1[2].error).toEqualTypeOf<boolean | null>()
223
-
224
- // TData (data prop) takes precedence over TQueryFnData (queryFnData prop)
225
- const result2 = createQueries<
226
- [
227
- { queryFnData: string; data: string },
228
- { queryFnData: string; data: number },
229
- ]
230
- >(() => ({
231
- queries: [
232
- {
233
- queryKey: key1,
234
- queryFn: () => 'string',
235
- select: (a) => {
236
- expectTypeOf(a).toEqualTypeOf<string>()
237
- return a.toLowerCase()
238
- },
239
- },
240
- {
241
- queryKey: key2,
242
- queryFn: () => 'string',
243
- select: (a) => {
244
- expectTypeOf(a).toEqualTypeOf<string>()
245
- return parseInt(a)
246
- },
247
- },
248
- ],
249
- }))
250
- expectTypeOf(result2[0]).toEqualTypeOf<
251
- CreateQueryResult<string, unknown>
252
- >()
253
- expectTypeOf(result2[1]).toEqualTypeOf<
254
- CreateQueryResult<number, unknown>
255
- >()
256
- expectTypeOf(result2[0].data).toEqualTypeOf<string | undefined>()
257
- expectTypeOf(result2[1].data).toEqualTypeOf<number | undefined>()
258
-
259
- // can pass only TData (data prop) although TQueryFnData will be left unknown
260
- const result3 = createQueries<[{ data: string }, { data: number }]>(
261
- () => ({
262
- queries: [
263
- {
264
- queryKey: key1,
265
- queryFn: () => 'string',
266
- select: (a) => {
267
- expectTypeOf(a).toEqualTypeOf<unknown>()
268
- return a as string
269
- },
270
- },
271
- {
272
- queryKey: key2,
273
- queryFn: () => 'string',
274
- select: (a) => {
275
- expectTypeOf(a).toEqualTypeOf<unknown>()
276
- return a as number
277
- },
278
- },
279
- ],
280
- }),
281
- )
282
- expectTypeOf(result3[0]).toEqualTypeOf<
283
- CreateQueryResult<string, unknown>
284
- >()
285
- expectTypeOf(result3[1]).toEqualTypeOf<
286
- CreateQueryResult<number, unknown>
287
- >()
288
- expectTypeOf(result3[0].data).toEqualTypeOf<string | undefined>()
289
- expectTypeOf(result3[1].data).toEqualTypeOf<number | undefined>()
290
-
291
- // types should be enforced
292
- createQueries<
293
- [
294
- { queryFnData: string; data: string },
295
- { queryFnData: string; data: number; error: boolean },
296
- ]
297
- >(() => ({
298
- queries: [
299
- {
300
- queryKey: key1,
301
- queryFn: () => 'string',
302
- select: (a) => {
303
- expectTypeOf(a).toEqualTypeOf<string>()
304
- return a.toLowerCase()
305
- },
306
- placeholderData: 'string',
307
- // @ts-expect-error (initialData: string)
308
- initialData: 123,
309
- },
310
- {
311
- queryKey: key2,
312
- queryFn: () => 'string',
313
- select: (a) => {
314
- expectTypeOf(a).toEqualTypeOf<string>()
315
- return parseInt(a)
316
- },
317
- placeholderData: 'string',
318
- // @ts-expect-error (initialData: string)
319
- initialData: 123,
320
- },
321
- ],
322
- }))
323
-
324
- // field names should be enforced
325
- createQueries<[{ queryFnData: string }]>(() => ({
326
- queries: [
327
- {
328
- queryKey: key1,
329
- queryFn: () => 'string',
330
- },
331
- ],
332
- }))
333
- }
334
- })
335
-
336
- it('handles array literal without type parameter to infer result type', async () => {
337
- const key1 = queryKey()
338
- const key2 = queryKey()
339
- const key3 = queryKey()
340
- const key4 = queryKey()
341
-
342
- // @ts-expect-error (Page component is not rendered)
343
- function Page() {
344
- // Array.map preserves TQueryFnData
345
- const result1 = createQueries(() => ({
346
- queries: Array(50).map((_, i) => ({
347
- queryKey: ['key', i] as const,
348
- queryFn: () => i + 10,
349
- })),
350
- }))
351
- expectTypeOf(result1).toEqualTypeOf<
352
- Array<CreateQueryResult<number, Error>>
353
- >()
354
- if (result1[0]) {
355
- expectTypeOf(result1[0].data).toEqualTypeOf<number | undefined>()
356
- }
357
-
358
- // Array.map preserves TData
359
- const result2 = createQueries(() => ({
360
- queries: Array(50).map((_, i) => ({
361
- queryKey: ['key', i] as const,
362
- queryFn: () => i + 10,
363
- select: (data: number) => data.toString(),
364
- })),
365
- }))
366
- expectTypeOf(result2).toEqualTypeOf<
367
- Array<CreateQueryResult<string, Error>>
368
- >()
369
-
370
- const result3 = createQueries(() => ({
371
- queries: [
372
- {
373
- queryKey: key1,
374
- queryFn: () => 1,
375
- },
376
- {
377
- queryKey: key2,
378
- queryFn: () => 'string',
379
- },
380
- {
381
- queryKey: key3,
382
- queryFn: () => ['string[]'],
383
- select: () => 123,
384
- },
385
- ],
386
- }))
387
- expectTypeOf(result3[0]).toEqualTypeOf<CreateQueryResult<number, Error>>()
388
- expectTypeOf(result3[1]).toEqualTypeOf<CreateQueryResult<string, Error>>()
389
- expectTypeOf(result3[2]).toEqualTypeOf<CreateQueryResult<number, Error>>()
390
- expectTypeOf(result3[0].data).toEqualTypeOf<number | undefined>()
391
- expectTypeOf(result3[1].data).toEqualTypeOf<string | undefined>()
392
- // select takes precedence over queryFn
393
- expectTypeOf(result3[2].data).toEqualTypeOf<number | undefined>()
394
-
395
- // initialData/placeholderData are enforced
396
- createQueries(() => ({
397
- queries: [
398
- {
399
- queryKey: key1,
400
- queryFn: () => 'string',
401
- placeholderData: 'string',
402
- // @ts-expect-error (initialData: string)
403
- initialData: 123,
404
- },
405
- {
406
- queryKey: key2,
407
- queryFn: () => 123,
408
- // @ts-expect-error (placeholderData: number)
409
- placeholderData: 'string',
410
- initialData: 123,
411
- },
412
- ],
413
- }))
414
-
415
- // select params are "indirectly" enforced
416
- createQueries(() => ({
417
- queries: [
418
- // unfortunately TS will not suggest the type for you
419
- {
420
- queryKey: key1,
421
- queryFn: () => 'string',
422
- },
423
- // however you can add a type to the callback
424
- {
425
- queryKey: key2,
426
- queryFn: () => 'string',
427
- },
428
- // the type you do pass is enforced
429
- {
430
- queryKey: key3,
431
- queryFn: () => 'string',
432
- },
433
- {
434
- queryKey: key4,
435
- queryFn: () => 'string',
436
- select: (a: string) => parseInt(a),
437
- },
438
- ],
439
- }))
440
-
441
- // callbacks are also indirectly enforced with Array.map
442
- createQueries(() => ({
443
- queries: Array(50).map((_, i) => ({
444
- queryKey: ['key', i] as const,
445
- queryFn: () => i + 10,
446
- select: (data: number) => data.toString(),
447
- })),
448
- }))
449
-
450
- createQueries(() => ({
451
- queries: Array(50).map((_, i) => ({
452
- queryKey: ['key', i] as const,
453
- queryFn: () => i + 10,
454
- select: (data: number) => data.toString(),
455
- })),
456
- }))
457
-
458
- // results inference works when all the handlers are defined
459
- const result4 = createQueries(() => ({
460
- queries: [
461
- {
462
- queryKey: key1,
463
- queryFn: () => 'string',
464
- },
465
- {
466
- queryKey: key2,
467
- queryFn: () => 'string',
468
- },
469
- {
470
- queryKey: key4,
471
- queryFn: () => 'string',
472
- select: (a: string) => parseInt(a),
473
- },
474
- ],
475
- }))
476
- expectTypeOf(result4[0]).toEqualTypeOf<CreateQueryResult<string, Error>>()
477
- expectTypeOf(result4[1]).toEqualTypeOf<CreateQueryResult<string, Error>>()
478
- expectTypeOf(result4[2]).toEqualTypeOf<CreateQueryResult<number, Error>>()
479
-
480
- // handles when queryFn returns a Promise
481
- const result5 = createQueries(() => ({
482
- queries: [
483
- {
484
- queryKey: key1,
485
- queryFn: () => Promise.resolve('string'),
486
- },
487
- ],
488
- }))
489
- expectTypeOf(result5[0]).toEqualTypeOf<CreateQueryResult<string, Error>>()
490
-
491
- // Array as const does not throw error
492
- const result6 = createQueries(
493
- () =>
494
- ({
495
- queries: [
496
- {
497
- queryKey: ['key1'],
498
- queryFn: () => 'string',
499
- },
500
- {
501
- queryKey: ['key1'],
502
- queryFn: () => 123,
503
- },
504
- ],
505
- }) as const,
506
- )
507
- expectTypeOf(result6[0]).toEqualTypeOf<CreateQueryResult<string, Error>>()
508
- expectTypeOf(result6[1]).toEqualTypeOf<CreateQueryResult<number, Error>>()
509
-
510
- // field names should be enforced - array literal
511
- createQueries(() => ({
512
- queries: [
513
- {
514
- queryKey: key1,
515
- queryFn: () => 'string',
516
- },
517
- ],
518
- }))
519
-
520
- // field names should be enforced - Array.map() result
521
- createQueries(() => ({
522
- // @ts-expect-error (invalidField)
523
- queries: Array(10).map(() => ({
524
- someInvalidField: '',
525
- })),
526
- }))
527
-
528
- // field names should be enforced - array literal
529
- createQueries(() => ({
530
- queries: [
531
- {
532
- queryKey: key1,
533
- queryFn: () => 'string',
534
- },
535
- ],
536
- }))
537
-
538
- // supports queryFn using fetch() to return Promise<any> - Array.map() result
539
- createQueries(() => ({
540
- queries: Array(50).map((_, i) => ({
541
- queryKey: ['key', i] as const,
542
- queryFn: () =>
543
- fetch('return Promise<any>').then((resp) => resp.json()),
544
- })),
545
- }))
546
-
547
- // supports queryFn using fetch() to return Promise<any> - array literal
548
- createQueries(() => ({
549
- queries: [
550
- {
551
- queryKey: key1,
552
- queryFn: () =>
553
- fetch('return Promise<any>').then((resp) => resp.json()),
554
- },
555
- ],
556
- }))
557
- }
558
- })
559
-
560
- it('handles strongly typed queryFn factories and useQueries wrappers', () => {
561
- // QueryKey + queryFn factory
562
- type QueryKeyA = ['queryA']
563
- const getQueryKeyA = (): QueryKeyA => ['queryA']
564
- type GetQueryFunctionA = () => QueryFunction<number, QueryKeyA>
565
- const getQueryFunctionA: GetQueryFunctionA = () => async () => {
566
- return 1
567
- }
568
- type SelectorA = (data: number) => [number, string]
569
- const getSelectorA = (): SelectorA => (data) => [data, data.toString()]
570
-
571
- type QueryKeyB = ['queryB', string]
572
- const getQueryKeyB = (id: string): QueryKeyB => ['queryB', id]
573
- type GetQueryFunctionB = () => QueryFunction<string, QueryKeyB>
574
- const getQueryFunctionB: GetQueryFunctionB = () => async () => {
575
- return '1'
576
- }
577
- type SelectorB = (data: string) => [string, number]
578
- const getSelectorB = (): SelectorB => (data) => [data, +data]
579
-
580
- // Wrapper with strongly typed array-parameter
581
- function useWrappedQueries<
582
- TQueryFnData,
583
- TError,
584
- TData,
585
- TQueryKey extends QueryKey,
586
- >(
587
- queries: Array<SolidQueryOptions<TQueryFnData, TError, TData, TQueryKey>>,
588
- ) {
589
- return createQueries(() => ({
590
- queries: queries.map(
591
- // no need to type the mapped query
592
- (query) => {
593
- const { queryFn: fn, queryKey: key } = query
594
- expectTypeOf(fn).toEqualTypeOf<
595
- | typeof QueryCore.skipToken
596
- | QueryCore.QueryFunction<TQueryFnData, TQueryKey, never>
597
- | undefined
598
- >()
599
- return {
600
- queryKey: key,
601
- queryFn: fn
602
- ? (ctx: QueryFunctionContext<TQueryKey>) => {
603
- // eslint-disable-next-line vitest/valid-expect
604
- expectTypeOf<TQueryKey>(ctx.queryKey)
605
- return (fn as QueryFunction<TQueryFnData, TQueryKey>).call(
606
- {},
607
- ctx,
608
- )
609
- }
610
- : undefined,
611
- }
612
- },
613
- ),
614
- }))
615
- }
616
-
617
- // @ts-expect-error (Page component is not rendered)
618
- function Page() {
619
- const result = createQueries(() => ({
620
- queries: [
621
- {
622
- queryKey: getQueryKeyA(),
623
- queryFn: getQueryFunctionA(),
624
- },
625
- {
626
- queryKey: getQueryKeyB('id'),
627
- queryFn: getQueryFunctionB(),
628
- },
629
- ],
630
- }))
631
- expectTypeOf(result[0]).toEqualTypeOf<CreateQueryResult<number, Error>>()
632
- expectTypeOf(result[1]).toEqualTypeOf<CreateQueryResult<string, Error>>()
633
-
634
- const withSelector = createQueries(() => ({
635
- queries: [
636
- {
637
- queryKey: getQueryKeyA(),
638
- queryFn: getQueryFunctionA(),
639
- select: getSelectorA(),
640
- },
641
- {
642
- queryKey: getQueryKeyB('id'),
643
- queryFn: getQueryFunctionB(),
644
- select: getSelectorB(),
645
- },
646
- ],
647
- }))
648
- expectTypeOf(withSelector[0]).toEqualTypeOf<
649
- CreateQueryResult<[number, string], Error>
650
- >()
651
- expectTypeOf(withSelector[1]).toEqualTypeOf<
652
- CreateQueryResult<[string, number], Error>
653
- >()
654
-
655
- const withWrappedQueries = useWrappedQueries(
656
- Array(10).map(() => ({
657
- queryKey: getQueryKeyA(),
658
- queryFn: getQueryFunctionA(),
659
- select: getSelectorA(),
660
- })),
661
- )
662
-
663
- expectTypeOf(withWrappedQueries).toEqualTypeOf<
664
- Array<CreateQueryResult<number, Error>>
665
- >()
666
- }
667
- })
668
-
669
- it('should not change state if unmounted', async () => {
670
- const key1 = queryKey()
671
-
672
- // We have to mock the QueriesObserver to not unsubscribe
673
- // the listener when the component is unmounted
674
- class QueriesObserverMock extends QueriesObserver {
675
- subscribe(listener: any) {
676
- super.subscribe(listener)
677
- return () => void 0
678
- }
679
- }
680
-
681
- const QueriesObserverSpy = vi
682
- .spyOn(QueryCore, 'QueriesObserver')
683
- .mockImplementation((fn) => {
684
- return new QueriesObserverMock(fn, [])
685
- })
686
-
687
- function Queries() {
688
- createQueries(() => ({
689
- queries: [
690
- {
691
- queryKey: key1,
692
- queryFn: async () => {
693
- await sleep(10)
694
- return 1
695
- },
696
- },
697
- ],
698
- }))
699
-
700
- return (
701
- <div>
702
- <span>queries</span>
703
- </div>
704
- )
705
- }
706
-
707
- function Page() {
708
- const [mounted, setMounted] = createSignal(true)
709
-
710
- return (
711
- <div>
712
- <button onClick={() => setMounted(false)}>unmount</button>
713
- {mounted() && <Queries />}
714
- </div>
715
- )
716
- }
717
-
718
- const rendered = render(() => (
719
- <QueryClientProvider client={queryClient}>
720
- <Page />
721
- </QueryClientProvider>
722
- ))
723
-
724
- fireEvent.click(rendered.getByText('unmount'))
725
-
726
- // Should not display the console error
727
- // "Warning: Can't perform a React state update on an unmounted component"
728
-
729
- await sleep(20)
730
- QueriesObserverSpy.mockRestore()
731
- })
732
- })