@tanstack/angular-query-experimental 5.103.2 → 5.104.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.
- package/dist/create-base-query.d.mts +168 -0
- package/dist/create-base-query.d.mts.map +1 -0
- package/dist/create-base-query.mjs +91 -0
- package/dist/create-base-query.mjs.map +1 -0
- package/dist/devtools/index.d.mts +3 -0
- package/dist/devtools/index.mjs +2 -0
- package/dist/devtools/production/index.d.mts +4 -0
- package/dist/devtools/production/index.mjs +2 -0
- package/dist/devtools/stub.d.mts +5 -0
- package/dist/devtools/stub.d.mts.map +1 -0
- package/dist/devtools/stub.mjs +9 -0
- package/dist/devtools/stub.mjs.map +1 -0
- package/dist/devtools/types.d.mts +106 -0
- package/dist/devtools/types.d.mts.map +1 -0
- package/dist/devtools/types.mjs +1 -0
- package/{devtools/with-devtools.d.ts → dist/devtools/with-devtools.d.mts} +4 -1
- package/dist/devtools/with-devtools.d.mts.map +1 -0
- package/dist/devtools/with-devtools.mjs +110 -0
- package/dist/devtools/with-devtools.mjs.map +1 -0
- package/dist/devtools-panel/index.d.mts +3 -0
- package/dist/devtools-panel/index.mjs +2 -0
- package/{devtools-panel/inject-devtools-panel.d.ts → dist/devtools-panel/inject-devtools-panel.d.mts} +4 -1
- package/dist/devtools-panel/inject-devtools-panel.d.mts.map +1 -0
- package/dist/devtools-panel/inject-devtools-panel.mjs +67 -0
- package/dist/devtools-panel/inject-devtools-panel.mjs.map +1 -0
- package/dist/devtools-panel/production/index.d.mts +4 -0
- package/dist/devtools-panel/production/index.mjs +2 -0
- package/dist/devtools-panel/stub.d.mts +5 -0
- package/dist/devtools-panel/stub.d.mts.map +1 -0
- package/dist/devtools-panel/stub.mjs +7 -0
- package/dist/devtools-panel/stub.mjs.map +1 -0
- package/dist/devtools-panel/types.d.mts +51 -0
- package/dist/devtools-panel/types.d.mts.map +1 -0
- package/dist/devtools-panel/types.mjs +1 -0
- package/dist/index.d.mts +16 -0
- package/{index.mjs → dist/index.mjs} +8 -21
- package/dist/index.mjs.map +1 -0
- package/{infinite-query-options.d.ts → dist/infinite-query-options.d.mts} +28 -25
- package/dist/infinite-query-options.d.mts.map +1 -0
- package/dist/infinite-query-options.mjs +8 -0
- package/dist/infinite-query-options.mjs.map +1 -0
- package/{inject-infinite-query.d.ts → dist/inject-infinite-query.d.mts} +13 -10
- package/dist/inject-infinite-query.d.mts.map +1 -0
- package/dist/inject-infinite-query.mjs +13 -0
- package/dist/inject-infinite-query.mjs.map +1 -0
- package/{inject-is-fetching.d.ts → dist/inject-is-fetching.d.mts} +11 -8
- package/dist/inject-is-fetching.d.mts.map +1 -0
- package/dist/inject-is-fetching.mjs +69 -0
- package/dist/inject-is-fetching.mjs.map +1 -0
- package/{inject-is-mutating.d.ts → dist/inject-is-mutating.d.mts} +11 -8
- package/dist/inject-is-mutating.d.mts.map +1 -0
- package/dist/inject-is-mutating.mjs +52 -0
- package/dist/inject-is-mutating.mjs.map +1 -0
- package/{inject-is-restoring.d.ts → dist/inject-is-restoring.d.mts} +10 -8
- package/dist/inject-is-restoring.d.mts.map +1 -0
- package/dist/inject-is-restoring.mjs +35 -0
- package/dist/inject-is-restoring.mjs.map +1 -0
- package/{inject-mutation-state.d.ts → dist/inject-mutation-state.d.mts} +13 -11
- package/dist/inject-mutation-state.d.mts.map +1 -0
- package/dist/inject-mutation-state.mjs +103 -0
- package/dist/inject-mutation-state.mjs.map +1 -0
- package/{inject-mutation.d.ts → dist/inject-mutation.d.mts} +12 -9
- package/dist/inject-mutation.d.mts.map +1 -0
- package/dist/inject-mutation.mjs +226 -0
- package/dist/inject-mutation.mjs.map +1 -0
- package/dist/inject-queries-experimental/index.d.mts +2 -0
- package/dist/inject-queries-experimental/index.mjs +2 -0
- package/{inject-queries.d.ts → dist/inject-queries.d.mts} +33 -53
- package/dist/inject-queries.d.mts.map +1 -0
- package/dist/inject-queries.mjs +203 -0
- package/dist/inject-queries.mjs.map +1 -0
- package/{inject-query-client.d.ts → dist/inject-query-client.d.mts} +6 -3
- package/dist/inject-query-client.d.mts.map +1 -0
- package/dist/inject-query-client.mjs +23 -0
- package/dist/inject-query-client.mjs.map +1 -0
- package/{inject-query.d.ts → dist/inject-query.d.mts} +13 -10
- package/dist/inject-query.d.mts.map +1 -0
- package/dist/inject-query.mjs +12 -0
- package/dist/inject-query.mjs.map +1 -0
- package/{mutation-options.d.ts → dist/mutation-options.d.mts} +5 -2
- package/dist/mutation-options.d.mts.map +1 -0
- package/dist/mutation-options.mjs +8 -0
- package/dist/mutation-options.mjs.map +1 -0
- package/dist/pending-tasks-compat.d.mts +9 -0
- package/dist/pending-tasks-compat.d.mts.map +1 -0
- package/dist/pending-tasks-compat.mjs +13 -0
- package/dist/pending-tasks-compat.mjs.map +1 -0
- package/{providers.d.ts → dist/providers.d.mts} +7 -5
- package/dist/providers.d.mts.map +1 -0
- package/dist/providers.mjs +124 -0
- package/dist/providers.mjs.map +1 -0
- package/{query-options.d.ts → dist/query-options.d.mts} +34 -31
- package/dist/query-options.d.mts.map +1 -0
- package/dist/query-options.mjs +8 -0
- package/dist/query-options.mjs.map +1 -0
- package/{signal-proxy.d.ts → dist/signal-proxy.d.mts} +5 -4
- package/dist/signal-proxy.d.mts.map +1 -0
- package/dist/signal-proxy.mjs +35 -0
- package/dist/signal-proxy.mjs.map +1 -0
- package/{types.d.ts → dist/types.d.mts} +24 -26
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +6 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +50 -20
- package/src/__tests__/infinite-query-options.test-d.ts +314 -0
- package/src/__tests__/infinite-query-options.test.ts +19 -0
- package/src/__tests__/inject-devtools-panel.test.ts +196 -0
- package/src/__tests__/inject-infinite-query.test-d.ts +66 -0
- package/src/__tests__/inject-infinite-query.test.ts +227 -0
- package/src/__tests__/inject-is-fetching.test.ts +108 -0
- package/src/__tests__/inject-is-mutating.test.ts +113 -0
- package/src/__tests__/inject-is-restoring.test.ts +93 -0
- package/src/__tests__/inject-mutation-state.test-d.ts +22 -0
- package/src/__tests__/inject-mutation-state.test.ts +198 -0
- package/src/__tests__/inject-mutation.test-d.ts +158 -0
- package/src/__tests__/inject-mutation.test.ts +1161 -0
- package/src/__tests__/inject-queries.test-d.ts +196 -0
- package/src/__tests__/inject-queries.test.ts +240 -0
- package/src/__tests__/inject-query.test-d.ts +357 -0
- package/src/__tests__/inject-query.test.ts +700 -0
- package/src/__tests__/mutation-options.test-d.ts +234 -0
- package/src/__tests__/mutation-options.test.ts +344 -0
- package/src/__tests__/pending-tasks.test.ts +644 -0
- package/src/__tests__/provide-query-client.test.ts +38 -0
- package/src/__tests__/provide-tanstack-query.test.ts +38 -0
- package/src/__tests__/query-options.test-d.ts +262 -0
- package/src/__tests__/query-options.test.ts +14 -0
- package/src/__tests__/signal-proxy.test.ts +27 -0
- package/src/__tests__/test-utils.ts +77 -0
- package/src/__tests__/with-devtools.test.ts +674 -0
- package/src/create-base-query.ts +176 -0
- package/src/devtools/index.ts +8 -0
- package/src/devtools/production/index.ts +1 -0
- package/src/devtools/stub.ts +7 -0
- package/src/devtools/types.ts +116 -0
- package/src/devtools/with-devtools.ts +182 -0
- package/src/devtools-panel/index.ts +8 -0
- package/src/devtools-panel/inject-devtools-panel.ts +110 -0
- package/src/devtools-panel/production/index.ts +1 -0
- package/src/devtools-panel/stub.ts +7 -0
- package/src/devtools-panel/types.ts +57 -0
- package/src/index.ts +60 -0
- package/src/infinite-query-options.ts +354 -0
- package/src/inject-infinite-query.ts +296 -0
- package/src/inject-is-fetching.ts +97 -0
- package/src/inject-is-mutating.ts +80 -0
- package/src/inject-is-restoring.ts +53 -0
- package/src/inject-mutation-state.ts +167 -0
- package/src/inject-mutation.ts +328 -0
- package/src/inject-queries-experimental/index.ts +1 -0
- package/src/inject-queries.ts +480 -0
- package/src/inject-query-client.ts +22 -0
- package/src/inject-query.ts +208 -0
- package/src/mutation-options.ts +121 -0
- package/src/pending-tasks-compat.ts +27 -0
- package/src/providers.ts +170 -0
- package/src/query-options.ts +292 -0
- package/src/signal-proxy.ts +46 -0
- package/src/types.ts +429 -0
- package/create-base-query.d.ts +0 -164
- package/create-base-query.mjs +0 -99
- package/create-base-query.mjs.map +0 -1
- package/devtools/index.d.ts +0 -2
- package/devtools/index.mjs +0 -5
- package/devtools/index.mjs.map +0 -1
- package/devtools/production/index.d.ts +0 -1
- package/devtools/stub.d.ts +0 -2
- package/devtools/stub.mjs +0 -8
- package/devtools/stub.mjs.map +0 -1
- package/devtools/types.d.ts +0 -103
- package/devtools/with-devtools.mjs +0 -106
- package/devtools/with-devtools.mjs.map +0 -1
- package/devtools-panel/index.d.ts +0 -2
- package/devtools-panel/index.mjs +0 -5
- package/devtools-panel/index.mjs.map +0 -1
- package/devtools-panel/inject-devtools-panel.mjs +0 -72
- package/devtools-panel/inject-devtools-panel.mjs.map +0 -1
- package/devtools-panel/production/index.d.ts +0 -1
- package/devtools-panel/stub.d.ts +0 -2
- package/devtools-panel/stub.mjs +0 -8
- package/devtools-panel/stub.mjs.map +0 -1
- package/devtools-panel/types.d.ts +0 -48
- package/index.d.ts +0 -25
- package/index.mjs.map +0 -1
- package/infinite-query-options.mjs +0 -7
- package/infinite-query-options.mjs.map +0 -1
- package/inject-infinite-query.mjs +0 -18
- package/inject-infinite-query.mjs.map +0 -1
- package/inject-is-fetching.mjs +0 -31
- package/inject-is-fetching.mjs.map +0 -1
- package/inject-is-mutating.mjs +0 -31
- package/inject-is-mutating.mjs.map +0 -1
- package/inject-is-restoring.mjs +0 -21
- package/inject-is-restoring.mjs.map +0 -1
- package/inject-mutation-state.mjs +0 -51
- package/inject-mutation-state.mjs.map +0 -1
- package/inject-mutation.mjs +0 -94
- package/inject-mutation.mjs.map +0 -1
- package/inject-queries-experimental/index.d.ts +0 -1
- package/inject-queries-experimental/index.mjs +0 -5
- package/inject-queries-experimental/index.mjs.map +0 -1
- package/inject-queries.mjs +0 -82
- package/inject-queries.mjs.map +0 -1
- package/inject-query-client.mjs +0 -9
- package/inject-query-client.mjs.map +0 -1
- package/inject-query.mjs +0 -14
- package/inject-query.mjs.map +0 -1
- package/mutation-options.mjs +0 -7
- package/mutation-options.mjs.map +0 -1
- package/pending-tasks-compat.d.ts +0 -7
- package/pending-tasks-compat.mjs +0 -19
- package/pending-tasks-compat.mjs.map +0 -1
- package/providers.mjs +0 -32
- package/providers.mjs.map +0 -1
- package/query-options.mjs +0 -7
- package/query-options.mjs.map +0 -1
- package/signal-proxy.mjs +0 -29
- package/signal-proxy.mjs.map +0 -1
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApplicationRef,
|
|
3
|
+
Component,
|
|
4
|
+
Injector,
|
|
5
|
+
computed,
|
|
6
|
+
input,
|
|
7
|
+
provideZonelessChangeDetection,
|
|
8
|
+
signal,
|
|
9
|
+
} from '@angular/core'
|
|
10
|
+
import { TestBed } from '@angular/core/testing'
|
|
11
|
+
import { HttpClient, provideHttpClient } from '@angular/common/http'
|
|
12
|
+
import {
|
|
13
|
+
HttpTestingController,
|
|
14
|
+
provideHttpClientTesting,
|
|
15
|
+
} from '@angular/common/http/testing'
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
17
|
+
import { render } from '@testing-library/angular'
|
|
18
|
+
import { queryKey, sleep } from '@tanstack/query-test-utils'
|
|
19
|
+
import { lastValueFrom } from 'rxjs'
|
|
20
|
+
import {
|
|
21
|
+
QueryCache,
|
|
22
|
+
QueryClient,
|
|
23
|
+
injectQuery,
|
|
24
|
+
provideIsRestoring,
|
|
25
|
+
provideTanStackQuery,
|
|
26
|
+
} from '..'
|
|
27
|
+
import { setSignalInputs } from './test-utils'
|
|
28
|
+
|
|
29
|
+
describe('injectQuery', () => {
|
|
30
|
+
let queryCache: QueryCache
|
|
31
|
+
let queryClient: QueryClient
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.useFakeTimers()
|
|
35
|
+
queryCache = new QueryCache()
|
|
36
|
+
queryClient = new QueryClient({ queryCache })
|
|
37
|
+
TestBed.configureTestingModule({
|
|
38
|
+
providers: [
|
|
39
|
+
provideZonelessChangeDetection(),
|
|
40
|
+
provideTanStackQuery(queryClient),
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.useRealTimers()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should return pending status initially', () => {
|
|
50
|
+
const key = queryKey()
|
|
51
|
+
const query = TestBed.runInInjectionContext(() => {
|
|
52
|
+
return injectQuery(() => ({
|
|
53
|
+
queryKey: key,
|
|
54
|
+
queryFn: () => sleep(10).then(() => 'Some data'),
|
|
55
|
+
}))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
expect(query.status()).toBe('pending')
|
|
59
|
+
expect(query.isPending()).toBe(true)
|
|
60
|
+
expect(query.isFetching()).toBe(true)
|
|
61
|
+
expect(query.isStale()).toBe(true)
|
|
62
|
+
expect(query.isFetched()).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should resolve to success and update signal: injectQuery()', async () => {
|
|
66
|
+
const key = queryKey()
|
|
67
|
+
|
|
68
|
+
@Component({
|
|
69
|
+
template: `
|
|
70
|
+
<div>status: {{ query.status() }}</div>
|
|
71
|
+
<div>data: {{ query.data() ?? 'none' }}</div>
|
|
72
|
+
<div>isPending: {{ query.isPending() }}</div>
|
|
73
|
+
<div>isFetching: {{ query.isFetching() }}</div>
|
|
74
|
+
<div>isFetched: {{ query.isFetched() }}</div>
|
|
75
|
+
<div>isSuccess: {{ query.isSuccess() }}</div>
|
|
76
|
+
`,
|
|
77
|
+
})
|
|
78
|
+
class Page {
|
|
79
|
+
readonly query = injectQuery(() => ({
|
|
80
|
+
queryKey: key,
|
|
81
|
+
queryFn: () => sleep(10).then(() => 'result2'),
|
|
82
|
+
}))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const rendered = await render(Page)
|
|
86
|
+
|
|
87
|
+
await vi.advanceTimersByTimeAsync(11)
|
|
88
|
+
rendered.fixture.detectChanges()
|
|
89
|
+
|
|
90
|
+
expect(rendered.getByText('status: success')).toBeInTheDocument()
|
|
91
|
+
expect(rendered.getByText('data: result2')).toBeInTheDocument()
|
|
92
|
+
expect(rendered.getByText('isPending: false')).toBeInTheDocument()
|
|
93
|
+
expect(rendered.getByText('isFetching: false')).toBeInTheDocument()
|
|
94
|
+
expect(rendered.getByText('isFetched: true')).toBeInTheDocument()
|
|
95
|
+
expect(rendered.getByText('isSuccess: true')).toBeInTheDocument()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should reject and update signal', async () => {
|
|
99
|
+
const key = queryKey()
|
|
100
|
+
|
|
101
|
+
@Component({
|
|
102
|
+
template: `
|
|
103
|
+
<div>status: {{ query.status() }}</div>
|
|
104
|
+
<div>data: {{ query.data() ?? 'none' }}</div>
|
|
105
|
+
<div>error: {{ query.error()?.message ?? 'none' }}</div>
|
|
106
|
+
<div>isPending: {{ query.isPending() }}</div>
|
|
107
|
+
<div>isFetching: {{ query.isFetching() }}</div>
|
|
108
|
+
<div>isError: {{ query.isError() }}</div>
|
|
109
|
+
<div>failureCount: {{ query.failureCount() }}</div>
|
|
110
|
+
<div>failureReason: {{ query.failureReason()?.message ?? 'none' }}</div>
|
|
111
|
+
`,
|
|
112
|
+
})
|
|
113
|
+
class Page {
|
|
114
|
+
readonly query = injectQuery(() => ({
|
|
115
|
+
retry: false,
|
|
116
|
+
queryKey: key,
|
|
117
|
+
queryFn: () =>
|
|
118
|
+
sleep(10).then(() => Promise.reject(new Error('Some error'))),
|
|
119
|
+
}))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const rendered = await render(Page)
|
|
123
|
+
|
|
124
|
+
await vi.advanceTimersByTimeAsync(11)
|
|
125
|
+
rendered.fixture.detectChanges()
|
|
126
|
+
|
|
127
|
+
expect(rendered.getByText('status: error')).toBeInTheDocument()
|
|
128
|
+
expect(rendered.getByText('data: none')).toBeInTheDocument()
|
|
129
|
+
expect(rendered.getByText('error: Some error')).toBeInTheDocument()
|
|
130
|
+
expect(rendered.getByText('isPending: false')).toBeInTheDocument()
|
|
131
|
+
expect(rendered.getByText('isFetching: false')).toBeInTheDocument()
|
|
132
|
+
expect(rendered.getByText('isError: true')).toBeInTheDocument()
|
|
133
|
+
expect(rendered.getByText('failureCount: 1')).toBeInTheDocument()
|
|
134
|
+
expect(rendered.getByText('failureReason: Some error')).toBeInTheDocument()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should be able to select a part of the data with select', async () => {
|
|
138
|
+
const key = queryKey()
|
|
139
|
+
|
|
140
|
+
@Component({
|
|
141
|
+
template: `<div>data: {{ query.data() ?? 'none' }}</div>`,
|
|
142
|
+
})
|
|
143
|
+
class Page {
|
|
144
|
+
readonly query = injectQuery<{ name: string }, Error, string>(() => ({
|
|
145
|
+
queryKey: key,
|
|
146
|
+
queryFn: () => sleep(10).then(() => ({ name: 'test' })),
|
|
147
|
+
select: (data) => data.name,
|
|
148
|
+
}))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const rendered = await render(Page)
|
|
152
|
+
|
|
153
|
+
expect(rendered.getByText('data: none')).toBeInTheDocument()
|
|
154
|
+
|
|
155
|
+
await vi.advanceTimersByTimeAsync(11)
|
|
156
|
+
rendered.fixture.detectChanges()
|
|
157
|
+
|
|
158
|
+
expect(rendered.getByText('data: test')).toBeInTheDocument()
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should show placeholderData until queryFn resolves and then expose real data', async () => {
|
|
162
|
+
const key = queryKey()
|
|
163
|
+
|
|
164
|
+
@Component({
|
|
165
|
+
template: `
|
|
166
|
+
<div>data: {{ query.data() }}</div>
|
|
167
|
+
<div>isPlaceholderData: {{ query.isPlaceholderData() }}</div>
|
|
168
|
+
<div>isSuccess: {{ query.isSuccess() }}</div>
|
|
169
|
+
`,
|
|
170
|
+
})
|
|
171
|
+
class Page {
|
|
172
|
+
readonly query = injectQuery(() => ({
|
|
173
|
+
queryKey: key,
|
|
174
|
+
queryFn: () => sleep(10).then(() => 'real-data'),
|
|
175
|
+
placeholderData: 'placeholder',
|
|
176
|
+
}))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const rendered = await render(Page)
|
|
180
|
+
|
|
181
|
+
expect(rendered.getByText('data: placeholder')).toBeInTheDocument()
|
|
182
|
+
expect(rendered.getByText('isPlaceholderData: true')).toBeInTheDocument()
|
|
183
|
+
expect(rendered.getByText('isSuccess: true')).toBeInTheDocument()
|
|
184
|
+
|
|
185
|
+
await vi.advanceTimersByTimeAsync(11)
|
|
186
|
+
rendered.fixture.detectChanges()
|
|
187
|
+
|
|
188
|
+
expect(rendered.getByText('data: real-data')).toBeInTheDocument()
|
|
189
|
+
expect(rendered.getByText('isPlaceholderData: false')).toBeInTheDocument()
|
|
190
|
+
expect(rendered.getByText('isSuccess: true')).toBeInTheDocument()
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should update query on options contained signal change', async () => {
|
|
194
|
+
const key1 = queryKey()
|
|
195
|
+
const key2 = queryKey()
|
|
196
|
+
const key = signal(key1)
|
|
197
|
+
const queryFn = vi.fn(() => sleep(10).then(() => 'Some data'))
|
|
198
|
+
|
|
199
|
+
const query = TestBed.runInInjectionContext(() => {
|
|
200
|
+
return injectQuery(() => ({
|
|
201
|
+
queryKey: key(),
|
|
202
|
+
queryFn,
|
|
203
|
+
}))
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
207
|
+
expect(queryFn).toHaveBeenCalledTimes(1)
|
|
208
|
+
|
|
209
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
210
|
+
expect(query.status()).toBe('success')
|
|
211
|
+
|
|
212
|
+
key.set(key2)
|
|
213
|
+
TestBed.tick()
|
|
214
|
+
|
|
215
|
+
expect(queryFn).toHaveBeenCalledTimes(2)
|
|
216
|
+
// should call queryFn with context containing the new queryKey
|
|
217
|
+
expect(queryFn).toHaveBeenNthCalledWith(2, {
|
|
218
|
+
client: queryClient,
|
|
219
|
+
meta: undefined,
|
|
220
|
+
queryKey: key2,
|
|
221
|
+
signal: expect.anything(),
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should only run query once enabled signal is set to true', async () => {
|
|
226
|
+
const key = queryKey()
|
|
227
|
+
const queryFn = vi.fn(() => sleep(10).then(() => 'Some data'))
|
|
228
|
+
const enabled = signal(false)
|
|
229
|
+
|
|
230
|
+
const query = TestBed.runInInjectionContext(() => {
|
|
231
|
+
return injectQuery(() => ({
|
|
232
|
+
queryKey: key,
|
|
233
|
+
queryFn,
|
|
234
|
+
enabled: enabled(),
|
|
235
|
+
}))
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
expect(queryFn).not.toHaveBeenCalled()
|
|
239
|
+
expect(query.status()).toBe('pending')
|
|
240
|
+
|
|
241
|
+
enabled.set(true)
|
|
242
|
+
|
|
243
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
244
|
+
expect(queryFn).toHaveBeenCalledTimes(1)
|
|
245
|
+
expect(query.status()).toBe('success')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should not fetch while the enabled signal is false, and fetch again once it is truthy', async () => {
|
|
249
|
+
const key = queryKey()
|
|
250
|
+
const queryFn = vi.fn(() => sleep(10).then(() => 'Some data'))
|
|
251
|
+
const filter = signal('a')
|
|
252
|
+
|
|
253
|
+
const query = TestBed.runInInjectionContext(() => {
|
|
254
|
+
return injectQuery(() => ({
|
|
255
|
+
queryKey: [...key, filter()],
|
|
256
|
+
queryFn,
|
|
257
|
+
enabled: !!filter(),
|
|
258
|
+
}))
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
262
|
+
expect(queryFn).toHaveBeenCalledTimes(1)
|
|
263
|
+
expect(query.status()).toBe('success')
|
|
264
|
+
|
|
265
|
+
filter.set('')
|
|
266
|
+
|
|
267
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
268
|
+
expect(queryFn).toHaveBeenCalledTimes(1)
|
|
269
|
+
expect(query.isFetching()).toBe(false)
|
|
270
|
+
|
|
271
|
+
filter.set('b')
|
|
272
|
+
|
|
273
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
274
|
+
expect(queryFn).toHaveBeenCalledTimes(2)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('should properly execute dependent queries', async () => {
|
|
278
|
+
const key1 = queryKey()
|
|
279
|
+
const key2 = queryKey()
|
|
280
|
+
|
|
281
|
+
const query1 = TestBed.runInInjectionContext(() => {
|
|
282
|
+
return injectQuery(() => ({
|
|
283
|
+
queryKey: key1,
|
|
284
|
+
queryFn: () => sleep(10).then(() => 'Some data'),
|
|
285
|
+
}))
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const dependentQueryFn = vi
|
|
289
|
+
.fn()
|
|
290
|
+
.mockImplementation(() => sleep(1000).then(() => 'Some data'))
|
|
291
|
+
|
|
292
|
+
const query2 = TestBed.runInInjectionContext(() => {
|
|
293
|
+
return injectQuery(
|
|
294
|
+
computed(() => ({
|
|
295
|
+
queryKey: key2,
|
|
296
|
+
queryFn: dependentQueryFn,
|
|
297
|
+
enabled: !!query1.data(),
|
|
298
|
+
})),
|
|
299
|
+
)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
expect(query1.data()).toStrictEqual(undefined)
|
|
303
|
+
expect(query2.fetchStatus()).toStrictEqual('idle')
|
|
304
|
+
expect(dependentQueryFn).not.toHaveBeenCalled()
|
|
305
|
+
|
|
306
|
+
await vi.advanceTimersByTimeAsync(11)
|
|
307
|
+
|
|
308
|
+
expect(query1.data()).toStrictEqual('Some data')
|
|
309
|
+
expect(query2.fetchStatus()).toStrictEqual('fetching')
|
|
310
|
+
|
|
311
|
+
await vi.advanceTimersByTimeAsync(1002)
|
|
312
|
+
|
|
313
|
+
expect(query2.fetchStatus()).toStrictEqual('idle')
|
|
314
|
+
expect(query2.status()).toStrictEqual('success')
|
|
315
|
+
expect(dependentQueryFn).toHaveBeenCalledTimes(1)
|
|
316
|
+
expect(dependentQueryFn).toHaveBeenCalledWith(
|
|
317
|
+
expect.objectContaining({ queryKey: key2 }),
|
|
318
|
+
)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('should use the current value for the queryKey when refetch is called', async () => {
|
|
322
|
+
const key = queryKey()
|
|
323
|
+
const queryFn = vi.fn(() => sleep(10).then(() => 'Some data'))
|
|
324
|
+
const keySignal = signal('key11')
|
|
325
|
+
|
|
326
|
+
const query = TestBed.runInInjectionContext(() => {
|
|
327
|
+
return injectQuery(() => ({
|
|
328
|
+
queryKey: [...key, keySignal()],
|
|
329
|
+
queryFn,
|
|
330
|
+
enabled: false,
|
|
331
|
+
}))
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
expect(queryFn).not.toHaveBeenCalled()
|
|
335
|
+
|
|
336
|
+
void query.refetch()
|
|
337
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
338
|
+
|
|
339
|
+
expect(queryFn).toHaveBeenCalledTimes(1)
|
|
340
|
+
expect(queryFn).toHaveBeenNthCalledWith(
|
|
341
|
+
1,
|
|
342
|
+
expect.objectContaining({
|
|
343
|
+
queryKey: [...key, 'key11'],
|
|
344
|
+
}),
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
keySignal.set('key12')
|
|
348
|
+
|
|
349
|
+
void query.refetch()
|
|
350
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
351
|
+
|
|
352
|
+
expect(queryFn).toHaveBeenCalledTimes(2)
|
|
353
|
+
expect(queryFn).toHaveBeenNthCalledWith(
|
|
354
|
+
2,
|
|
355
|
+
expect.objectContaining({
|
|
356
|
+
queryKey: [...key, 'key12'],
|
|
357
|
+
}),
|
|
358
|
+
)
|
|
359
|
+
expect(query.data()).toBe('Some data')
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('should keep initialData visible alongside the error when a refetch fails', async () => {
|
|
363
|
+
const key = queryKey()
|
|
364
|
+
|
|
365
|
+
@Component({
|
|
366
|
+
template: `
|
|
367
|
+
<div>data: {{ query.data() }}</div>
|
|
368
|
+
<div>isError: {{ query.isError() }}</div>
|
|
369
|
+
`,
|
|
370
|
+
})
|
|
371
|
+
class Page {
|
|
372
|
+
readonly query = injectQuery(() => ({
|
|
373
|
+
queryKey: key,
|
|
374
|
+
queryFn: () =>
|
|
375
|
+
sleep(10).then(() => Promise.reject(new Error('Some error'))),
|
|
376
|
+
initialData: 'initial',
|
|
377
|
+
retry: false,
|
|
378
|
+
}))
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const rendered = await render(Page)
|
|
382
|
+
|
|
383
|
+
expect(rendered.getByText('data: initial')).toBeInTheDocument()
|
|
384
|
+
expect(rendered.getByText('isError: false')).toBeInTheDocument()
|
|
385
|
+
|
|
386
|
+
await vi.advanceTimersByTimeAsync(11)
|
|
387
|
+
rendered.fixture.detectChanges()
|
|
388
|
+
expect(rendered.getByText('data: initial')).toBeInTheDocument()
|
|
389
|
+
expect(rendered.getByText('isError: true')).toBeInTheDocument()
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
describe('throwOnError', () => {
|
|
393
|
+
it('should evaluate throwOnError when query is expected to throw', async () => {
|
|
394
|
+
const key = queryKey()
|
|
395
|
+
const throwOnError = vi.fn()
|
|
396
|
+
TestBed.runInInjectionContext(() => {
|
|
397
|
+
return injectQuery(() => ({
|
|
398
|
+
queryKey: key,
|
|
399
|
+
queryFn: () =>
|
|
400
|
+
sleep(10).then(() => Promise.reject(new Error('Some error'))),
|
|
401
|
+
retry: false,
|
|
402
|
+
throwOnError,
|
|
403
|
+
}))
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
await vi.advanceTimersByTimeAsync(11)
|
|
407
|
+
expect(throwOnError).toHaveBeenCalledTimes(1)
|
|
408
|
+
expect(throwOnError).toHaveBeenCalledWith(
|
|
409
|
+
Error('Some error'),
|
|
410
|
+
expect.objectContaining({
|
|
411
|
+
state: expect.objectContaining({ status: 'error' }),
|
|
412
|
+
}),
|
|
413
|
+
)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it('should throw when throwOnError is true', async () => {
|
|
417
|
+
const key = queryKey()
|
|
418
|
+
TestBed.runInInjectionContext(() => {
|
|
419
|
+
return injectQuery(() => ({
|
|
420
|
+
queryKey: key,
|
|
421
|
+
queryFn: () =>
|
|
422
|
+
sleep(0).then(() => Promise.reject(new Error('Some error'))),
|
|
423
|
+
throwOnError: true,
|
|
424
|
+
}))
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
await expect(vi.runAllTimersAsync()).rejects.toThrow('Some error')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('should throw when throwOnError function returns true', async () => {
|
|
431
|
+
const key = queryKey()
|
|
432
|
+
TestBed.runInInjectionContext(() => {
|
|
433
|
+
return injectQuery(() => ({
|
|
434
|
+
queryKey: key,
|
|
435
|
+
queryFn: () =>
|
|
436
|
+
sleep(0).then(() => Promise.reject(new Error('Some error'))),
|
|
437
|
+
throwOnError: () => true,
|
|
438
|
+
}))
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
await expect(vi.runAllTimersAsync()).rejects.toThrow('Some error')
|
|
442
|
+
})
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('should render with required signal inputs', async () => {
|
|
446
|
+
@Component({
|
|
447
|
+
selector: 'app-fake',
|
|
448
|
+
template: `{{ query.data() }}`,
|
|
449
|
+
})
|
|
450
|
+
class FakeComponent {
|
|
451
|
+
name = input.required<string>()
|
|
452
|
+
|
|
453
|
+
query = injectQuery(() => ({
|
|
454
|
+
queryKey: ['fake', this.name()],
|
|
455
|
+
queryFn: () => this.name(),
|
|
456
|
+
}))
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const fixture = TestBed.createComponent(FakeComponent)
|
|
460
|
+
setSignalInputs(fixture.componentInstance, {
|
|
461
|
+
name: 'signal-input-required-test',
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
fixture.detectChanges()
|
|
465
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
466
|
+
|
|
467
|
+
expect(fixture.componentInstance.query.data()).toEqual(
|
|
468
|
+
'signal-input-required-test',
|
|
469
|
+
)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
describe('isRestoring', () => {
|
|
473
|
+
it('should not fetch for the duration of the restoring period when isRestoring is true', async () => {
|
|
474
|
+
const key = queryKey()
|
|
475
|
+
const queryFn = vi
|
|
476
|
+
.fn()
|
|
477
|
+
.mockImplementation(() => sleep(10).then(() => 'data'))
|
|
478
|
+
|
|
479
|
+
TestBed.configureTestingModule({
|
|
480
|
+
providers: [provideIsRestoring(signal(true).asReadonly())],
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
const query = TestBed.runInInjectionContext(() =>
|
|
484
|
+
injectQuery(() => ({
|
|
485
|
+
queryKey: key,
|
|
486
|
+
queryFn,
|
|
487
|
+
})),
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
491
|
+
expect(query.status()).toBe('pending')
|
|
492
|
+
expect(query.fetchStatus()).toBe('idle')
|
|
493
|
+
expect(query.data()).toBeUndefined()
|
|
494
|
+
expect(queryFn).toHaveBeenCalledTimes(0)
|
|
495
|
+
|
|
496
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
497
|
+
expect(query.status()).toBe('pending')
|
|
498
|
+
expect(query.fetchStatus()).toBe('idle')
|
|
499
|
+
expect(query.data()).toBeUndefined()
|
|
500
|
+
expect(queryFn).toHaveBeenCalledTimes(0)
|
|
501
|
+
})
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
describe('injection context', () => {
|
|
505
|
+
it('should throw NG0203 with descriptive error outside injection context', () => {
|
|
506
|
+
const key = queryKey()
|
|
507
|
+
expect(() => {
|
|
508
|
+
injectQuery(() => ({
|
|
509
|
+
queryKey: key,
|
|
510
|
+
queryFn: () => sleep(0).then(() => 'Some data'),
|
|
511
|
+
}))
|
|
512
|
+
}).toThrow(/NG0203(.*?)injectQuery/)
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('should be usable outside injection context when passing an injector', () => {
|
|
516
|
+
const key = queryKey()
|
|
517
|
+
const query = injectQuery(
|
|
518
|
+
() => ({
|
|
519
|
+
queryKey: key,
|
|
520
|
+
queryFn: () => sleep(0).then(() => 'Some data'),
|
|
521
|
+
}),
|
|
522
|
+
{
|
|
523
|
+
injector: TestBed.inject(Injector),
|
|
524
|
+
},
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
expect(query.status()).toBe('pending')
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
it('should complete queries before whenStable() resolves', async () => {
|
|
531
|
+
const key = queryKey()
|
|
532
|
+
const app = TestBed.inject(ApplicationRef)
|
|
533
|
+
|
|
534
|
+
const query = TestBed.runInInjectionContext(() =>
|
|
535
|
+
injectQuery(() => ({
|
|
536
|
+
queryKey: key,
|
|
537
|
+
queryFn: () => sleep(50).then(() => 'test data'),
|
|
538
|
+
})),
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
expect(query.status()).toBe('pending')
|
|
542
|
+
expect(query.data()).toBeUndefined()
|
|
543
|
+
|
|
544
|
+
const stablePromise = app.whenStable()
|
|
545
|
+
await vi.advanceTimersByTimeAsync(60)
|
|
546
|
+
await stablePromise
|
|
547
|
+
|
|
548
|
+
expect(query.status()).toBe('success')
|
|
549
|
+
expect(query.data()).toBe('test data')
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
it('should complete HttpClient-based queries before whenStable() resolves', async () => {
|
|
553
|
+
TestBed.configureTestingModule({
|
|
554
|
+
providers: [provideHttpClient(), provideHttpClientTesting()],
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
const app = TestBed.inject(ApplicationRef)
|
|
558
|
+
const httpClient = TestBed.inject(HttpClient)
|
|
559
|
+
const httpTestingController = TestBed.inject(HttpTestingController)
|
|
560
|
+
|
|
561
|
+
// Create a query using HttpClient
|
|
562
|
+
const key = queryKey()
|
|
563
|
+
const query = TestBed.runInInjectionContext(() =>
|
|
564
|
+
injectQuery(() => ({
|
|
565
|
+
queryKey: key,
|
|
566
|
+
queryFn: () =>
|
|
567
|
+
lastValueFrom(httpClient.get<{ message: string }>('/api/test')),
|
|
568
|
+
})),
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
// Schedule the HTTP response
|
|
572
|
+
setTimeout(() => {
|
|
573
|
+
const req = httpTestingController.expectOne('/api/test')
|
|
574
|
+
req.flush({ message: 'http test data' })
|
|
575
|
+
}, 10)
|
|
576
|
+
|
|
577
|
+
// Initial state
|
|
578
|
+
expect(query.status()).toBe('pending')
|
|
579
|
+
|
|
580
|
+
// Advance timers and wait for Angular to be "stable"
|
|
581
|
+
const stablePromise = app.whenStable()
|
|
582
|
+
await vi.advanceTimersByTimeAsync(20)
|
|
583
|
+
await stablePromise
|
|
584
|
+
|
|
585
|
+
// Query should be complete after whenStable() thanks to PendingTasks integration
|
|
586
|
+
expect(query.status()).toBe('success')
|
|
587
|
+
expect(query.data()).toEqual({ message: 'http test data' })
|
|
588
|
+
|
|
589
|
+
httpTestingController.verify()
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
it('should handle synchronous queryFn with staleTime', async () => {
|
|
593
|
+
const app = TestBed.inject(ApplicationRef)
|
|
594
|
+
let callCount = 0
|
|
595
|
+
|
|
596
|
+
const key = queryKey()
|
|
597
|
+
const query = TestBed.runInInjectionContext(() =>
|
|
598
|
+
injectQuery(() => ({
|
|
599
|
+
queryKey: key,
|
|
600
|
+
staleTime: 1000,
|
|
601
|
+
queryFn: () => {
|
|
602
|
+
callCount++
|
|
603
|
+
return `sync-data-${callCount}`
|
|
604
|
+
},
|
|
605
|
+
})),
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
// Synchronize pending effects
|
|
609
|
+
TestBed.tick()
|
|
610
|
+
|
|
611
|
+
const stablePromise = app.whenStable()
|
|
612
|
+
await stablePromise
|
|
613
|
+
|
|
614
|
+
expect(query.status()).toBe('success')
|
|
615
|
+
expect(query.data()).toBe('sync-data-1')
|
|
616
|
+
expect(callCount).toBe(1)
|
|
617
|
+
|
|
618
|
+
await query.refetch()
|
|
619
|
+
await Promise.resolve()
|
|
620
|
+
await vi.runAllTimersAsync()
|
|
621
|
+
await app.whenStable()
|
|
622
|
+
|
|
623
|
+
expect(query.status()).toBe('success')
|
|
624
|
+
expect(query.data()).toBe('sync-data-2')
|
|
625
|
+
expect(callCount).toBe(2)
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
it('should handle enabled/disabled transitions with synchronous queryFn', async () => {
|
|
629
|
+
const app = TestBed.inject(ApplicationRef)
|
|
630
|
+
const enabledSignal = signal(false)
|
|
631
|
+
let callCount = 0
|
|
632
|
+
|
|
633
|
+
const key = queryKey()
|
|
634
|
+
const query = TestBed.runInInjectionContext(() =>
|
|
635
|
+
injectQuery(() => ({
|
|
636
|
+
queryKey: key,
|
|
637
|
+
enabled: enabledSignal(),
|
|
638
|
+
queryFn: () => {
|
|
639
|
+
callCount++
|
|
640
|
+
return `sync-data-${callCount}`
|
|
641
|
+
},
|
|
642
|
+
})),
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
// Initially disabled
|
|
646
|
+
TestBed.tick()
|
|
647
|
+
await app.whenStable()
|
|
648
|
+
expect(query.status()).toBe('pending')
|
|
649
|
+
expect(query.data()).toBeUndefined()
|
|
650
|
+
expect(callCount).toBe(0)
|
|
651
|
+
|
|
652
|
+
// Enable the query
|
|
653
|
+
enabledSignal.set(true)
|
|
654
|
+
TestBed.tick()
|
|
655
|
+
|
|
656
|
+
await app.whenStable()
|
|
657
|
+
expect(query.status()).toBe('success')
|
|
658
|
+
expect(query.data()).toBe('sync-data-1')
|
|
659
|
+
expect(callCount).toBe(1)
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
it('should handle query invalidation with synchronous data', async () => {
|
|
663
|
+
const app = TestBed.inject(ApplicationRef)
|
|
664
|
+
const testKey = queryKey()
|
|
665
|
+
let callCount = 0
|
|
666
|
+
|
|
667
|
+
const query = TestBed.runInInjectionContext(() =>
|
|
668
|
+
injectQuery(() => ({
|
|
669
|
+
queryKey: testKey,
|
|
670
|
+
queryFn: () => {
|
|
671
|
+
callCount++
|
|
672
|
+
return `sync-data-${callCount}`
|
|
673
|
+
},
|
|
674
|
+
})),
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
// Synchronize pending effects
|
|
678
|
+
TestBed.tick()
|
|
679
|
+
|
|
680
|
+
await app.whenStable()
|
|
681
|
+
expect(query.status()).toBe('success')
|
|
682
|
+
expect(query.data()).toBe('sync-data-1')
|
|
683
|
+
expect(callCount).toBe(1)
|
|
684
|
+
|
|
685
|
+
// Invalidate the query
|
|
686
|
+
queryClient.invalidateQueries({ queryKey: testKey })
|
|
687
|
+
TestBed.tick()
|
|
688
|
+
|
|
689
|
+
// Wait for the invalidation to trigger a refetch
|
|
690
|
+
await Promise.resolve()
|
|
691
|
+
await vi.advanceTimersByTimeAsync(10)
|
|
692
|
+
TestBed.tick()
|
|
693
|
+
|
|
694
|
+
await app.whenStable()
|
|
695
|
+
expect(query.status()).toBe('success')
|
|
696
|
+
expect(query.data()).toBe('sync-data-2')
|
|
697
|
+
expect(callCount).toBe(2)
|
|
698
|
+
})
|
|
699
|
+
})
|
|
700
|
+
})
|