@tanstack/solid-query 4.11.1 → 4.13.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/build/lib/__tests__/QueryClientProvider.test.d.ts +1 -0
- package/build/lib/__tests__/createInfiniteQuery.test.d.ts +1 -0
- package/build/lib/__tests__/createMutation.test.d.ts +1 -0
- package/build/lib/__tests__/createQueries.test.d.ts +1 -0
- package/build/lib/__tests__/createQuery.test.d.ts +1 -0
- package/build/lib/__tests__/createQuery.types.test.d.ts +2 -0
- package/build/lib/__tests__/suspense.test.d.ts +1 -0
- package/build/lib/__tests__/transition.test.d.ts +1 -0
- package/build/lib/__tests__/useIsFetching.test.d.ts +1 -0
- package/build/lib/__tests__/useIsMutating.test.d.ts +1 -0
- package/build/lib/__tests__/utils.d.ts +27 -0
- package/build/solid/__tests__/QueryClientProvider.test.jsx +185 -0
- package/build/solid/__tests__/createInfiniteQuery.test.jsx +1442 -0
- package/build/solid/__tests__/createMutation.test.jsx +857 -0
- package/build/solid/__tests__/createQueries.test.jsx +958 -0
- package/build/solid/__tests__/createQuery.test.jsx +4588 -0
- package/build/solid/__tests__/createQuery.types.test.jsx +124 -0
- package/build/solid/__tests__/suspense.test.jsx +691 -0
- package/build/solid/__tests__/transition.test.jsx +39 -0
- package/build/solid/__tests__/useIsFetching.test.jsx +209 -0
- package/build/solid/__tests__/useIsMutating.test.jsx +216 -0
- package/build/solid/__tests__/utils.jsx +55 -0
- package/build/umd/index.development.js +27 -10
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +5 -6
- package/src/__tests__/createInfiniteQuery.test.tsx +2 -0
- package/src/__tests__/createMutation.test.tsx +65 -1
- package/src/__tests__/createQuery.test.tsx +43 -51
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from 'solid-testing-library';
|
|
2
|
+
import { createSignal, Show, startTransition, Suspense } from 'solid-js';
|
|
3
|
+
import { createQuery, QueryCache, QueryClientProvider } from '..';
|
|
4
|
+
import { createQueryClient, queryKey, sleep } from './utils';
|
|
5
|
+
describe("useQuery's in Suspense mode with transitions", () => {
|
|
6
|
+
const queryCache = new QueryCache();
|
|
7
|
+
const queryClient = createQueryClient({ queryCache });
|
|
8
|
+
it('should render the content when the transition is done', async () => {
|
|
9
|
+
const key = queryKey();
|
|
10
|
+
function Suspended() {
|
|
11
|
+
const state = createQuery(key, async () => {
|
|
12
|
+
await sleep(10);
|
|
13
|
+
return true;
|
|
14
|
+
});
|
|
15
|
+
return <Show when={state.data}>Message</Show>;
|
|
16
|
+
}
|
|
17
|
+
function Page() {
|
|
18
|
+
const [showSignal, setShowSignal] = createSignal(false);
|
|
19
|
+
return (<div>
|
|
20
|
+
<button aria-label="toggle" onClick={() => startTransition(() => setShowSignal((value) => !value))}>
|
|
21
|
+
{showSignal() ? 'Hide' : 'Show'}
|
|
22
|
+
</button>
|
|
23
|
+
<Suspense fallback="Loading">
|
|
24
|
+
<Show when={showSignal()}>
|
|
25
|
+
<Suspended />
|
|
26
|
+
</Show>
|
|
27
|
+
</Suspense>
|
|
28
|
+
</div>);
|
|
29
|
+
}
|
|
30
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
31
|
+
<Page />
|
|
32
|
+
</QueryClientProvider>));
|
|
33
|
+
await waitFor(() => screen.getByText('Show'));
|
|
34
|
+
fireEvent.click(screen.getByLabelText('toggle'));
|
|
35
|
+
await waitFor(() => screen.getByText('Message'));
|
|
36
|
+
// verify that the button also updated. See https://github.com/solidjs/solid/issues/1249
|
|
37
|
+
await waitFor(() => screen.getByText('Hide'));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from 'solid-testing-library';
|
|
2
|
+
import { createContext, createEffect, createRenderEffect, createSignal, ErrorBoundary, Show, } from 'solid-js';
|
|
3
|
+
import { createQuery, QueryCache, QueryClientProvider, useIsFetching } from '..';
|
|
4
|
+
import { createQueryClient, queryKey, setActTimeout, sleep } from './utils';
|
|
5
|
+
describe('useIsFetching', () => {
|
|
6
|
+
// See https://github.com/tannerlinsley/react-query/issues/105
|
|
7
|
+
it('should update as queries start and stop fetching', async () => {
|
|
8
|
+
const queryCache = new QueryCache();
|
|
9
|
+
const queryClient = createQueryClient({ queryCache });
|
|
10
|
+
const key = queryKey();
|
|
11
|
+
function IsFetching() {
|
|
12
|
+
const isFetching = useIsFetching();
|
|
13
|
+
return <div>isFetching: {isFetching()}</div>;
|
|
14
|
+
}
|
|
15
|
+
function Query() {
|
|
16
|
+
const [ready, setReady] = createSignal(false);
|
|
17
|
+
createQuery(key, async () => {
|
|
18
|
+
await sleep(50);
|
|
19
|
+
return 'test';
|
|
20
|
+
}, {
|
|
21
|
+
get enabled() {
|
|
22
|
+
return ready();
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
return <button onClick={() => setReady(true)}>setReady</button>;
|
|
26
|
+
}
|
|
27
|
+
function Page() {
|
|
28
|
+
return (<div>
|
|
29
|
+
<IsFetching />
|
|
30
|
+
<Query />
|
|
31
|
+
</div>);
|
|
32
|
+
}
|
|
33
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
34
|
+
<Page />
|
|
35
|
+
</QueryClientProvider>));
|
|
36
|
+
await screen.findByText('isFetching: 0');
|
|
37
|
+
fireEvent.click(screen.getByRole('button', { name: /setReady/i }));
|
|
38
|
+
await screen.findByText('isFetching: 1');
|
|
39
|
+
await screen.findByText('isFetching: 0');
|
|
40
|
+
});
|
|
41
|
+
it('should not update state while rendering', async () => {
|
|
42
|
+
const queryCache = new QueryCache();
|
|
43
|
+
const queryClient = createQueryClient({ queryCache });
|
|
44
|
+
const key1 = queryKey();
|
|
45
|
+
const key2 = queryKey();
|
|
46
|
+
const isFetchings = [];
|
|
47
|
+
function IsFetching() {
|
|
48
|
+
const isFetching = useIsFetching();
|
|
49
|
+
createRenderEffect(() => {
|
|
50
|
+
isFetchings.push(isFetching());
|
|
51
|
+
});
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function FirstQuery() {
|
|
55
|
+
createQuery(key1, async () => {
|
|
56
|
+
await sleep(100);
|
|
57
|
+
return 'data';
|
|
58
|
+
});
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function SecondQuery() {
|
|
62
|
+
createQuery(key2, async () => {
|
|
63
|
+
await sleep(100);
|
|
64
|
+
return 'data';
|
|
65
|
+
});
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function Page() {
|
|
69
|
+
const [renderSecond, setRenderSecond] = createSignal(false);
|
|
70
|
+
createEffect(() => {
|
|
71
|
+
setActTimeout(() => {
|
|
72
|
+
setRenderSecond(true);
|
|
73
|
+
}, 50);
|
|
74
|
+
});
|
|
75
|
+
return (<>
|
|
76
|
+
<IsFetching />
|
|
77
|
+
<FirstQuery />
|
|
78
|
+
<Show when={renderSecond()}>
|
|
79
|
+
<SecondQuery />
|
|
80
|
+
</Show>
|
|
81
|
+
</>);
|
|
82
|
+
}
|
|
83
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
84
|
+
<Page />
|
|
85
|
+
</QueryClientProvider>));
|
|
86
|
+
// unlike react, Updating renderSecond wont cause a rerender for FirstQuery
|
|
87
|
+
await waitFor(() => expect(isFetchings).toEqual([0, 1, 2, 1, 0]));
|
|
88
|
+
});
|
|
89
|
+
it('should be able to filter', async () => {
|
|
90
|
+
const queryClient = createQueryClient();
|
|
91
|
+
const key1 = queryKey();
|
|
92
|
+
const key2 = queryKey();
|
|
93
|
+
const isFetchings = [];
|
|
94
|
+
function One() {
|
|
95
|
+
createQuery(key1, async () => {
|
|
96
|
+
await sleep(10);
|
|
97
|
+
return 'test';
|
|
98
|
+
});
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function Two() {
|
|
102
|
+
createQuery(key2, async () => {
|
|
103
|
+
await sleep(20);
|
|
104
|
+
return 'test';
|
|
105
|
+
});
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
function Page() {
|
|
109
|
+
const [started, setStarted] = createSignal(false);
|
|
110
|
+
const isFetching = useIsFetching(key1);
|
|
111
|
+
createRenderEffect(() => {
|
|
112
|
+
isFetchings.push(isFetching());
|
|
113
|
+
});
|
|
114
|
+
return (<div>
|
|
115
|
+
<button onClick={() => setStarted(true)}>setStarted</button>
|
|
116
|
+
<div>isFetching: {isFetching()}</div>
|
|
117
|
+
<Show when={started()}>
|
|
118
|
+
<>
|
|
119
|
+
<One />
|
|
120
|
+
<Two />
|
|
121
|
+
</>
|
|
122
|
+
</Show>
|
|
123
|
+
</div>);
|
|
124
|
+
}
|
|
125
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
126
|
+
<Page />
|
|
127
|
+
</QueryClientProvider>));
|
|
128
|
+
await screen.findByText('isFetching: 0');
|
|
129
|
+
fireEvent.click(screen.getByRole('button', { name: /setStarted/i }));
|
|
130
|
+
await screen.findByText('isFetching: 1');
|
|
131
|
+
await screen.findByText('isFetching: 0');
|
|
132
|
+
// at no point should we have isFetching: 2
|
|
133
|
+
expect(isFetchings).toEqual(expect.not.arrayContaining([2]));
|
|
134
|
+
});
|
|
135
|
+
describe('with custom context', () => {
|
|
136
|
+
it('should update as queries start and stop fetching', async () => {
|
|
137
|
+
const context = createContext(undefined);
|
|
138
|
+
const queryCache = new QueryCache();
|
|
139
|
+
const queryClient = createQueryClient({ queryCache });
|
|
140
|
+
const key = queryKey();
|
|
141
|
+
function Page() {
|
|
142
|
+
const [ready, setReady] = createSignal(false);
|
|
143
|
+
const isFetching = useIsFetching(undefined, { context: context });
|
|
144
|
+
createQuery(key, async () => {
|
|
145
|
+
await sleep(50);
|
|
146
|
+
return 'test';
|
|
147
|
+
}, {
|
|
148
|
+
get enabled() {
|
|
149
|
+
return ready();
|
|
150
|
+
},
|
|
151
|
+
context,
|
|
152
|
+
});
|
|
153
|
+
return (<div>
|
|
154
|
+
<div>isFetching: {isFetching}</div>
|
|
155
|
+
<button onClick={() => setReady(true)}>setReady</button>
|
|
156
|
+
</div>);
|
|
157
|
+
}
|
|
158
|
+
render(() => (<QueryClientProvider client={queryClient} context={context}>
|
|
159
|
+
<Page />
|
|
160
|
+
</QueryClientProvider>));
|
|
161
|
+
await screen.findByText('isFetching: 0');
|
|
162
|
+
fireEvent.click(screen.getByRole('button', { name: /setReady/i }));
|
|
163
|
+
await screen.findByText('isFetching: 1');
|
|
164
|
+
await screen.findByText('isFetching: 0');
|
|
165
|
+
});
|
|
166
|
+
it('should throw if the context is not passed to useIsFetching', async () => {
|
|
167
|
+
const context = createContext(undefined);
|
|
168
|
+
const queryCache = new QueryCache();
|
|
169
|
+
const queryClient = createQueryClient({ queryCache });
|
|
170
|
+
const key = queryKey();
|
|
171
|
+
function Page() {
|
|
172
|
+
const isFetching = useIsFetching();
|
|
173
|
+
createQuery(key, async () => 'test', {
|
|
174
|
+
enabled: true,
|
|
175
|
+
context,
|
|
176
|
+
useErrorBoundary: true,
|
|
177
|
+
});
|
|
178
|
+
return (<div>
|
|
179
|
+
<div>isFetching: {isFetching}</div>
|
|
180
|
+
</div>);
|
|
181
|
+
}
|
|
182
|
+
render(() => (<QueryClientProvider client={queryClient} context={context}>
|
|
183
|
+
<ErrorBoundary fallback={() => <div>error boundary</div>}>
|
|
184
|
+
<Page />
|
|
185
|
+
</ErrorBoundary>
|
|
186
|
+
</QueryClientProvider>));
|
|
187
|
+
await waitFor(() => screen.getByText('error boundary'));
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
it('should show the correct fetching state when mounted after a query', async () => {
|
|
191
|
+
const queryClient = createQueryClient();
|
|
192
|
+
const key = queryKey();
|
|
193
|
+
function Page() {
|
|
194
|
+
createQuery(key, async () => {
|
|
195
|
+
await sleep(10);
|
|
196
|
+
return 'test';
|
|
197
|
+
});
|
|
198
|
+
const isFetching = useIsFetching();
|
|
199
|
+
return (<div>
|
|
200
|
+
<div>isFetching: {isFetching()}</div>
|
|
201
|
+
</div>);
|
|
202
|
+
}
|
|
203
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
204
|
+
<Page />
|
|
205
|
+
</QueryClientProvider>));
|
|
206
|
+
await screen.findByText('isFetching: 1');
|
|
207
|
+
await screen.findByText('isFetching: 0');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { fireEvent, screen, waitFor } from 'solid-testing-library';
|
|
2
|
+
import { createMutation, QueryClient, QueryClientProvider, useIsMutating, } from '..';
|
|
3
|
+
import { createQueryClient, sleep } from './utils';
|
|
4
|
+
import { createContext, createEffect, createRenderEffect, createSignal, ErrorBoundary, Show, } from 'solid-js';
|
|
5
|
+
import { render } from 'solid-testing-library';
|
|
6
|
+
import * as MutationCacheModule from '../../../query-core/src/mutationCache';
|
|
7
|
+
import { setActTimeout } from './utils';
|
|
8
|
+
describe('useIsMutating', () => {
|
|
9
|
+
it('should return the number of fetching mutations', async () => {
|
|
10
|
+
const isMutatings = [];
|
|
11
|
+
const queryClient = createQueryClient();
|
|
12
|
+
function IsMutating() {
|
|
13
|
+
const isMutating = useIsMutating();
|
|
14
|
+
createRenderEffect(() => {
|
|
15
|
+
isMutatings.push(isMutating());
|
|
16
|
+
});
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function Mutations() {
|
|
20
|
+
const { mutate: mutate1 } = createMutation(['mutation1'], async () => {
|
|
21
|
+
await sleep(150);
|
|
22
|
+
return 'data';
|
|
23
|
+
});
|
|
24
|
+
const { mutate: mutate2 } = createMutation(['mutation2'], async () => {
|
|
25
|
+
await sleep(50);
|
|
26
|
+
return 'data';
|
|
27
|
+
});
|
|
28
|
+
createEffect(() => {
|
|
29
|
+
mutate1();
|
|
30
|
+
setActTimeout(() => {
|
|
31
|
+
mutate2();
|
|
32
|
+
}, 50);
|
|
33
|
+
});
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function Page() {
|
|
37
|
+
return (<div>
|
|
38
|
+
<IsMutating />
|
|
39
|
+
<Mutations />
|
|
40
|
+
</div>);
|
|
41
|
+
}
|
|
42
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
43
|
+
<Page />
|
|
44
|
+
</QueryClientProvider>));
|
|
45
|
+
await waitFor(() => expect(isMutatings).toEqual([0, 1, 2, 1, 0]));
|
|
46
|
+
});
|
|
47
|
+
it('should filter correctly by mutationKey', async () => {
|
|
48
|
+
const isMutatings = [];
|
|
49
|
+
const queryClient = createQueryClient();
|
|
50
|
+
function IsMutating() {
|
|
51
|
+
const isMutating = useIsMutating(['mutation1']);
|
|
52
|
+
createRenderEffect(() => {
|
|
53
|
+
isMutatings.push(isMutating());
|
|
54
|
+
});
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function Page() {
|
|
58
|
+
const { mutate: mutate1 } = createMutation(['mutation1'], async () => {
|
|
59
|
+
await sleep(100);
|
|
60
|
+
return 'data';
|
|
61
|
+
});
|
|
62
|
+
const { mutate: mutate2 } = createMutation(['mutation2'], async () => {
|
|
63
|
+
await sleep(100);
|
|
64
|
+
return 'data';
|
|
65
|
+
});
|
|
66
|
+
createEffect(() => {
|
|
67
|
+
mutate1();
|
|
68
|
+
mutate2();
|
|
69
|
+
});
|
|
70
|
+
return <IsMutating />;
|
|
71
|
+
}
|
|
72
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
73
|
+
<Page />
|
|
74
|
+
</QueryClientProvider>));
|
|
75
|
+
// Unlike React, IsMutating Wont re-render twice with mutation2
|
|
76
|
+
await waitFor(() => expect(isMutatings).toEqual([0, 1, 0]));
|
|
77
|
+
});
|
|
78
|
+
it('should filter correctly by predicate', async () => {
|
|
79
|
+
const isMutatings = [];
|
|
80
|
+
const queryClient = createQueryClient();
|
|
81
|
+
function IsMutating() {
|
|
82
|
+
const isMutating = useIsMutating({
|
|
83
|
+
predicate: (mutation) => mutation.options.mutationKey?.[0] === 'mutation1',
|
|
84
|
+
});
|
|
85
|
+
createRenderEffect(() => {
|
|
86
|
+
isMutatings.push(isMutating());
|
|
87
|
+
});
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function Page() {
|
|
91
|
+
const { mutate: mutate1 } = createMutation(['mutation1'], async () => {
|
|
92
|
+
await sleep(100);
|
|
93
|
+
return 'data';
|
|
94
|
+
});
|
|
95
|
+
const { mutate: mutate2 } = createMutation(['mutation2'], async () => {
|
|
96
|
+
await sleep(100);
|
|
97
|
+
return 'data';
|
|
98
|
+
});
|
|
99
|
+
createEffect(() => {
|
|
100
|
+
mutate1();
|
|
101
|
+
mutate2();
|
|
102
|
+
});
|
|
103
|
+
return <IsMutating />;
|
|
104
|
+
}
|
|
105
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
106
|
+
<Page />
|
|
107
|
+
</QueryClientProvider>));
|
|
108
|
+
// Again, No unnecessary re-renders like React
|
|
109
|
+
await waitFor(() => expect(isMutatings).toEqual([0, 1, 0]));
|
|
110
|
+
});
|
|
111
|
+
it('should not change state if unmounted', async () => {
|
|
112
|
+
// We have to mock the MutationCache to not unsubscribe
|
|
113
|
+
// the listener when the component is unmounted
|
|
114
|
+
class MutationCacheMock extends MutationCacheModule.MutationCache {
|
|
115
|
+
subscribe(listener) {
|
|
116
|
+
super.subscribe(listener);
|
|
117
|
+
return () => void 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const MutationCacheSpy = jest
|
|
121
|
+
.spyOn(MutationCacheModule, 'MutationCache')
|
|
122
|
+
.mockImplementation((fn) => {
|
|
123
|
+
return new MutationCacheMock(fn);
|
|
124
|
+
});
|
|
125
|
+
const queryClient = createQueryClient();
|
|
126
|
+
function IsMutating() {
|
|
127
|
+
useIsMutating();
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
function Page() {
|
|
131
|
+
const [mounted, setMounted] = createSignal(true);
|
|
132
|
+
const { mutate: mutate1 } = createMutation(['mutation1'], async () => {
|
|
133
|
+
await sleep(10);
|
|
134
|
+
return 'data';
|
|
135
|
+
});
|
|
136
|
+
createEffect(() => {
|
|
137
|
+
mutate1();
|
|
138
|
+
});
|
|
139
|
+
return (<div>
|
|
140
|
+
<button onClick={() => setMounted(false)}>unmount</button>
|
|
141
|
+
<Show when={mounted()}>
|
|
142
|
+
<IsMutating />
|
|
143
|
+
</Show>
|
|
144
|
+
</div>);
|
|
145
|
+
}
|
|
146
|
+
render(() => (<QueryClientProvider client={queryClient}>
|
|
147
|
+
<Page />
|
|
148
|
+
</QueryClientProvider>));
|
|
149
|
+
fireEvent.click(screen.getByText('unmount'));
|
|
150
|
+
// Should not display the console error
|
|
151
|
+
// "Warning: Can't perform a React state update on an unmounted component"
|
|
152
|
+
await sleep(20);
|
|
153
|
+
MutationCacheSpy.mockRestore();
|
|
154
|
+
});
|
|
155
|
+
describe('with custom context', () => {
|
|
156
|
+
it('should return the number of fetching mutations', async () => {
|
|
157
|
+
const context = createContext(undefined);
|
|
158
|
+
const isMutatings = [];
|
|
159
|
+
const queryClient = new QueryClient();
|
|
160
|
+
function IsMutating() {
|
|
161
|
+
const isMutating = useIsMutating(undefined, { context });
|
|
162
|
+
createRenderEffect(() => {
|
|
163
|
+
isMutatings.push(isMutating());
|
|
164
|
+
});
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function Page() {
|
|
168
|
+
const { mutate: mutate1 } = createMutation(['mutation1'], async () => {
|
|
169
|
+
await sleep(150);
|
|
170
|
+
return 'data';
|
|
171
|
+
}, { context });
|
|
172
|
+
const { mutate: mutate2 } = createMutation(['mutation2'], async () => {
|
|
173
|
+
await sleep(50);
|
|
174
|
+
return 'data';
|
|
175
|
+
}, { context });
|
|
176
|
+
createEffect(() => {
|
|
177
|
+
mutate1();
|
|
178
|
+
setActTimeout(() => {
|
|
179
|
+
mutate2();
|
|
180
|
+
}, 50);
|
|
181
|
+
});
|
|
182
|
+
return <IsMutating />;
|
|
183
|
+
}
|
|
184
|
+
render(() => (<QueryClientProvider client={queryClient} context={context}>
|
|
185
|
+
<Page />
|
|
186
|
+
</QueryClientProvider>));
|
|
187
|
+
await waitFor(() => expect(isMutatings).toEqual([0, 1, 2, 1, 0]));
|
|
188
|
+
});
|
|
189
|
+
it('should throw if the context is not passed to useIsMutating', async () => {
|
|
190
|
+
const context = createContext(undefined);
|
|
191
|
+
const isMutatings = [];
|
|
192
|
+
const queryClient = new QueryClient();
|
|
193
|
+
function IsMutating() {
|
|
194
|
+
const isMutating = useIsMutating(undefined);
|
|
195
|
+
isMutatings.push(isMutating());
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
function Page() {
|
|
199
|
+
const { mutate } = createMutation(['mutation'], async () => 'data', {
|
|
200
|
+
useErrorBoundary: true,
|
|
201
|
+
context,
|
|
202
|
+
});
|
|
203
|
+
createEffect(() => {
|
|
204
|
+
mutate();
|
|
205
|
+
});
|
|
206
|
+
return <IsMutating />;
|
|
207
|
+
}
|
|
208
|
+
render(() => (<QueryClientProvider client={queryClient} context={context}>
|
|
209
|
+
<ErrorBoundary fallback={() => <div>error boundary</div>}>
|
|
210
|
+
<Page />
|
|
211
|
+
</ErrorBoundary>
|
|
212
|
+
</QueryClientProvider>));
|
|
213
|
+
await waitFor(() => screen.getByText('error boundary'));
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/query-core';
|
|
2
|
+
import { createEffect, createSignal, onCleanup, Show } from 'solid-js';
|
|
3
|
+
let queryKeyCount = 0;
|
|
4
|
+
export function queryKey() {
|
|
5
|
+
const localQueryKeyCount = queryKeyCount++;
|
|
6
|
+
return () => [`query_${localQueryKeyCount}`];
|
|
7
|
+
}
|
|
8
|
+
export const Blink = (props) => {
|
|
9
|
+
const [shouldShow, setShouldShow] = createSignal(true);
|
|
10
|
+
createEffect(() => {
|
|
11
|
+
setShouldShow(true);
|
|
12
|
+
const timeout = setActTimeout(() => setShouldShow(false), props.duration);
|
|
13
|
+
onCleanup(() => clearTimeout(timeout));
|
|
14
|
+
});
|
|
15
|
+
return (<Show when={shouldShow()} fallback={<>off</>}>
|
|
16
|
+
<>{props.children}</>
|
|
17
|
+
</Show>);
|
|
18
|
+
};
|
|
19
|
+
export function createQueryClient(config) {
|
|
20
|
+
jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
21
|
+
return new QueryClient({ logger: mockLogger, ...config });
|
|
22
|
+
}
|
|
23
|
+
export function mockVisibilityState(value) {
|
|
24
|
+
return jest.spyOn(document, 'visibilityState', 'get').mockReturnValue(value);
|
|
25
|
+
}
|
|
26
|
+
export function mockNavigatorOnLine(value) {
|
|
27
|
+
return jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(value);
|
|
28
|
+
}
|
|
29
|
+
export const mockLogger = {
|
|
30
|
+
log: jest.fn(),
|
|
31
|
+
warn: jest.fn(),
|
|
32
|
+
error: jest.fn(),
|
|
33
|
+
};
|
|
34
|
+
export function sleep(timeout) {
|
|
35
|
+
return new Promise((resolve, _reject) => {
|
|
36
|
+
setTimeout(resolve, timeout);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function setActTimeout(fn, ms) {
|
|
40
|
+
return setTimeout(() => {
|
|
41
|
+
fn();
|
|
42
|
+
}, ms);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Assert the parameter is of a specific type.
|
|
46
|
+
*/
|
|
47
|
+
export function expectType(_) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Assert the parameter is not typed as `any`
|
|
52
|
+
*/
|
|
53
|
+
export function expectTypeNotAny(_) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
@@ -1112,9 +1112,11 @@
|
|
|
1112
1112
|
this.isFetchingOptimistic = false;
|
|
1113
1113
|
},
|
|
1114
1114
|
onError,
|
|
1115
|
-
onFail: () => {
|
|
1115
|
+
onFail: (failureCount, error) => {
|
|
1116
1116
|
this.dispatch({
|
|
1117
|
-
type: 'failed'
|
|
1117
|
+
type: 'failed',
|
|
1118
|
+
failureCount,
|
|
1119
|
+
error
|
|
1118
1120
|
});
|
|
1119
1121
|
},
|
|
1120
1122
|
onPause: () => {
|
|
@@ -1142,7 +1144,8 @@
|
|
|
1142
1144
|
switch (action.type) {
|
|
1143
1145
|
case 'failed':
|
|
1144
1146
|
return { ...state,
|
|
1145
|
-
fetchFailureCount:
|
|
1147
|
+
fetchFailureCount: action.failureCount,
|
|
1148
|
+
fetchFailureReason: action.error
|
|
1146
1149
|
};
|
|
1147
1150
|
|
|
1148
1151
|
case 'pause':
|
|
@@ -1158,6 +1161,7 @@
|
|
|
1158
1161
|
case 'fetch':
|
|
1159
1162
|
return { ...state,
|
|
1160
1163
|
fetchFailureCount: 0,
|
|
1164
|
+
fetchFailureReason: null,
|
|
1161
1165
|
fetchMeta: (_action$meta = action.meta) != null ? _action$meta : null,
|
|
1162
1166
|
fetchStatus: canFetch(this.options.networkMode) ? 'fetching' : 'paused',
|
|
1163
1167
|
...(!state.dataUpdatedAt && {
|
|
@@ -1176,7 +1180,8 @@
|
|
|
1176
1180
|
status: 'success',
|
|
1177
1181
|
...(!action.manual && {
|
|
1178
1182
|
fetchStatus: 'idle',
|
|
1179
|
-
fetchFailureCount: 0
|
|
1183
|
+
fetchFailureCount: 0,
|
|
1184
|
+
fetchFailureReason: null
|
|
1180
1185
|
})
|
|
1181
1186
|
};
|
|
1182
1187
|
|
|
@@ -1193,6 +1198,7 @@
|
|
|
1193
1198
|
errorUpdateCount: state.errorUpdateCount + 1,
|
|
1194
1199
|
errorUpdatedAt: Date.now(),
|
|
1195
1200
|
fetchFailureCount: state.fetchFailureCount + 1,
|
|
1201
|
+
fetchFailureReason: error,
|
|
1196
1202
|
fetchStatus: 'idle',
|
|
1197
1203
|
status: 'error'
|
|
1198
1204
|
};
|
|
@@ -1237,6 +1243,7 @@
|
|
|
1237
1243
|
errorUpdateCount: 0,
|
|
1238
1244
|
errorUpdatedAt: 0,
|
|
1239
1245
|
fetchFailureCount: 0,
|
|
1246
|
+
fetchFailureReason: null,
|
|
1240
1247
|
fetchMeta: null,
|
|
1241
1248
|
isInvalidated: false,
|
|
1242
1249
|
status: hasData ? 'success' : 'loading',
|
|
@@ -1441,9 +1448,11 @@
|
|
|
1441
1448
|
|
|
1442
1449
|
return this.options.mutationFn(this.state.variables);
|
|
1443
1450
|
},
|
|
1444
|
-
onFail: () => {
|
|
1451
|
+
onFail: (failureCount, error) => {
|
|
1445
1452
|
this.dispatch({
|
|
1446
|
-
type: 'failed'
|
|
1453
|
+
type: 'failed',
|
|
1454
|
+
failureCount,
|
|
1455
|
+
error
|
|
1447
1456
|
});
|
|
1448
1457
|
},
|
|
1449
1458
|
onPause: () => {
|
|
@@ -1476,7 +1485,7 @@
|
|
|
1476
1485
|
variables: this.options.variables
|
|
1477
1486
|
}); // Notify cache callback
|
|
1478
1487
|
|
|
1479
|
-
(_this$mutationCache$c = (_this$mutationCache$c2 = this.mutationCache.config).onMutate) == null ? void 0 : _this$mutationCache$c.call(_this$mutationCache$c2, this.state.variables, this);
|
|
1488
|
+
await ((_this$mutationCache$c = (_this$mutationCache$c2 = this.mutationCache.config).onMutate) == null ? void 0 : _this$mutationCache$c.call(_this$mutationCache$c2, this.state.variables, this));
|
|
1480
1489
|
const context = await ((_this$options$onMutat = (_this$options = this.options).onMutate) == null ? void 0 : _this$options$onMutat.call(_this$options, this.state.variables));
|
|
1481
1490
|
|
|
1482
1491
|
if (context !== this.state.context) {
|
|
@@ -1490,7 +1499,7 @@
|
|
|
1490
1499
|
|
|
1491
1500
|
const data = await executeMutation(); // Notify cache callback
|
|
1492
1501
|
|
|
1493
|
-
(_this$mutationCache$c3 = (_this$mutationCache$c4 = this.mutationCache.config).onSuccess) == null ? void 0 : _this$mutationCache$c3.call(_this$mutationCache$c4, data, this.state.variables, this.state.context, this);
|
|
1502
|
+
await ((_this$mutationCache$c3 = (_this$mutationCache$c4 = this.mutationCache.config).onSuccess) == null ? void 0 : _this$mutationCache$c3.call(_this$mutationCache$c4, data, this.state.variables, this.state.context, this));
|
|
1494
1503
|
await ((_this$options$onSucce = (_this$options2 = this.options).onSuccess) == null ? void 0 : _this$options$onSucce.call(_this$options2, data, this.state.variables, this.state.context));
|
|
1495
1504
|
await ((_this$options$onSettl = (_this$options3 = this.options).onSettled) == null ? void 0 : _this$options$onSettl.call(_this$options3, data, null, this.state.variables, this.state.context));
|
|
1496
1505
|
this.dispatch({
|
|
@@ -1503,7 +1512,7 @@
|
|
|
1503
1512
|
var _this$mutationCache$c5, _this$mutationCache$c6, _this$options$onError, _this$options4, _this$options$onSettl2, _this$options5;
|
|
1504
1513
|
|
|
1505
1514
|
// Notify cache callback
|
|
1506
|
-
(_this$mutationCache$c5 = (_this$mutationCache$c6 = this.mutationCache.config).onError) == null ? void 0 : _this$mutationCache$c5.call(_this$mutationCache$c6, error, this.state.variables, this.state.context, this);
|
|
1515
|
+
await ((_this$mutationCache$c5 = (_this$mutationCache$c6 = this.mutationCache.config).onError) == null ? void 0 : _this$mutationCache$c5.call(_this$mutationCache$c6, error, this.state.variables, this.state.context, this));
|
|
1507
1516
|
|
|
1508
1517
|
if ("development" !== 'production') {
|
|
1509
1518
|
this.logger.error(error);
|
|
@@ -1526,7 +1535,8 @@
|
|
|
1526
1535
|
switch (action.type) {
|
|
1527
1536
|
case 'failed':
|
|
1528
1537
|
return { ...state,
|
|
1529
|
-
failureCount:
|
|
1538
|
+
failureCount: action.failureCount,
|
|
1539
|
+
failureReason: action.error
|
|
1530
1540
|
};
|
|
1531
1541
|
|
|
1532
1542
|
case 'pause':
|
|
@@ -1543,6 +1553,8 @@
|
|
|
1543
1553
|
return { ...state,
|
|
1544
1554
|
context: action.context,
|
|
1545
1555
|
data: undefined,
|
|
1556
|
+
failureCount: 0,
|
|
1557
|
+
failureReason: null,
|
|
1546
1558
|
error: null,
|
|
1547
1559
|
isPaused: !canFetch(this.options.networkMode),
|
|
1548
1560
|
status: 'loading',
|
|
@@ -1552,6 +1564,8 @@
|
|
|
1552
1564
|
case 'success':
|
|
1553
1565
|
return { ...state,
|
|
1554
1566
|
data: action.data,
|
|
1567
|
+
failureCount: 0,
|
|
1568
|
+
failureReason: null,
|
|
1555
1569
|
error: null,
|
|
1556
1570
|
status: 'success',
|
|
1557
1571
|
isPaused: false
|
|
@@ -1562,6 +1576,7 @@
|
|
|
1562
1576
|
data: undefined,
|
|
1563
1577
|
error: action.error,
|
|
1564
1578
|
failureCount: state.failureCount + 1,
|
|
1579
|
+
failureReason: action.error,
|
|
1565
1580
|
isPaused: false,
|
|
1566
1581
|
status: 'error'
|
|
1567
1582
|
};
|
|
@@ -1593,6 +1608,7 @@
|
|
|
1593
1608
|
data: undefined,
|
|
1594
1609
|
error: null,
|
|
1595
1610
|
failureCount: 0,
|
|
1611
|
+
failureReason: null,
|
|
1596
1612
|
isPaused: false,
|
|
1597
1613
|
status: 'idle',
|
|
1598
1614
|
variables: undefined
|
|
@@ -2494,6 +2510,7 @@
|
|
|
2494
2510
|
error,
|
|
2495
2511
|
errorUpdatedAt,
|
|
2496
2512
|
failureCount: state.fetchFailureCount,
|
|
2513
|
+
failureReason: state.fetchFailureReason,
|
|
2497
2514
|
errorUpdateCount: state.errorUpdateCount,
|
|
2498
2515
|
isFetched: state.dataUpdateCount > 0 || state.errorUpdateCount > 0,
|
|
2499
2516
|
isFetchedAfterMount: state.dataUpdateCount > queryInitialState.dataUpdateCount || state.errorUpdateCount > queryInitialState.errorUpdateCount,
|