1import { useMutate } from "@clo/react-mutation";
2import { assertEquals } from "@std/assert";
3import { act, render, screen } from "@testing-library/react";
4import { userEvent } from "@testing-library/user-event";
5import { test, vi } from "vitest";
6import { createTestMutationClient, IterableStream } from "./share.ts";
7
8test("debounceMs: waits before first call", async () => {
9 vi.useFakeTimers({ shouldAdvanceTime: true });
10 const user = userEvent.setup({ delay: null });
11
12 const { client, successMessages, errorMessages } = createTestMutationClient();
13 const s = new IterableStream<string>();
14
15 const mutTest = client.define({
16 id: "test-1",
17 mutate: async () => {
18 return (await s.next()).value;
19 },
20 describe: "Test the action",
21 describeResult: "Tested the action",
22 optimistic: () => {},
23 debounceMs: 500,
24 });
25
26 let renders: Array<{ isMutating: boolean; isPending: boolean; isOptimisticData: boolean; isDisabled: boolean }> = [];
27 function TestComponent() {
28 const { run, isMutating, isPending, isOptimisticData, isDisabled } = useMutate(mutTest);
29 renders.push({ isMutating, isPending, isOptimisticData, isDisabled });
30
31 return (
32 <>
33 <button
34 data-testid="a"
35 onClick={() => {
36 run();
37 }}
38 >
39 button
40 </button>
41 </>
42 );
43 }
44
45 render(<TestComponent />);
46 // initial state
47 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: false, isDisabled: false }]);
48 assertEquals(successMessages, []);
49 assertEquals(errorMessages, []);
50 renders = [];
51 vi.runAllTimers();
52
53 // mutation 1
54 await act(() => user.click(screen.getByTestId("a")));
55 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: true, isDisabled: false }]);
56 renders = [];
57 await act(() => vi.advanceTimersByTime(200));
58 assertEquals(renders, []);
59 await act(() => vi.advanceTimersByTime(300));
60 assertEquals(renders, [{ isMutating: true, isPending: false, isDisabled: false, isOptimisticData: true }]);
61 renders = [];
62 await act(() => vi.advanceTimersByTime(300));
63 assertEquals(renders, []);
64 assertEquals(successMessages, []);
65 assertEquals(errorMessages, []);
66 await act(async () => {
67 s.push("ok");
68 vi.advanceTimersByTime(100);
69 });
70 assertEquals(successMessages, ["Tested the action"]);
71 assertEquals(errorMessages, []);
72 assertEquals(renders, [{ isMutating: false, isPending: false, isDisabled: false, isOptimisticData: false }]);
73 renders = [];
74 successMessages.splice(0, successMessages.length);
75
76 vi.advanceTimersByTime(1000);
77
78 // mutation 2
79 await act(() => user.click(screen.getByTestId("a")));
80 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: true, isDisabled: false }]);
81 renders = [];
82 await act(() => vi.advanceTimersByTime(200));
83 assertEquals(renders, []);
84 await act(() => vi.advanceTimersByTime(300));
85 assertEquals(renders, [{ isMutating: true, isPending: false, isDisabled: false, isOptimisticData: true }]);
86 renders = [];
87 await act(() => vi.advanceTimersByTime(300));
88 assertEquals(renders, []);
89 assertEquals(successMessages, []);
90 assertEquals(errorMessages, []);
91 const error1 = new Error("damn!");
92 await act(async () => {
93 s.throw(error1);
94 vi.advanceTimersByTime(100);
95 });
96 assertEquals(successMessages, []);
97 assertEquals(errorMessages, [
98 { error: error1, message: "Could not test the action: damn!" },
99 ]);
100});
101
102test("debounceMs: batch multiple calls together", async () => {
103 vi.useFakeTimers({ shouldAdvanceTime: true });
104 const user = userEvent.setup({ delay: null });
105
106 const { client, successMessages, errorMessages } = createTestMutationClient();
107 const s = new IterableStream<string>();
108
109 const mutations: number[] = [];
110 const mutTest = client.define({
111 id: "test-2",
112 mutate: async (k: number) => {
113 mutations.push(k);
114 return (await s.next()).value;
115 },
116 describe: "Test the action",
117 describeResult: "Tested the action",
118 optimistic: () => {},
119 debounceMs: 500,
120 });
121
122 let i = 0;
123
124 let renders: Array<{ isMutating: boolean; isPending: boolean; isOptimisticData: boolean; isDisabled: boolean }> = [];
125 function TestComponent() {
126 const { run, isMutating, isPending, isOptimisticData, isDisabled } = useMutate(mutTest);
127 renders.push({ isMutating, isPending, isOptimisticData, isDisabled });
128
129 return (
130 <>
131 <button
132 data-testid="a"
133 onClick={() => {
134 run(i++);
135 }}
136 >
137 button
138 </button>
139 </>
140 );
141 }
142
143 render(<TestComponent />);
144 // initial state
145 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: false, isDisabled: false }]);
146 assertEquals(successMessages, []);
147 assertEquals(errorMessages, []);
148 renders = [];
149 vi.runAllTimers();
150
151 // mutation 1
152 await act(() => user.click(screen.getByTestId("a")));
153 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: true, isDisabled: false }]);
154 renders = [];
155 await act(() => user.click(screen.getByTestId("a")));
156 await act(() => user.click(screen.getByTestId("a")));
157 await act(() => user.click(screen.getByTestId("a")));
158 await act(() => user.click(screen.getByTestId("a")));
159 await act(() => vi.advanceTimersByTime(200));
160 assertEquals(renders, []);
161 await act(() => vi.advanceTimersByTime(300));
162 assertEquals(renders, [{ isMutating: true, isPending: false, isDisabled: false, isOptimisticData: true }]);
163 renders = [];
164 await act(() => vi.advanceTimersByTime(300));
165 assertEquals(renders, []);
166 assertEquals(successMessages, []);
167 assertEquals(errorMessages, []);
168 await act(async () => {
169 assertEquals(mutations, [4]);
170 s.push("ok");
171 vi.advanceTimersByTime(100);
172 });
173 assertEquals(successMessages, ["Tested the action"]);
174 assertEquals(errorMessages, []);
175 assertEquals(renders, [{ isMutating: false, isPending: false, isDisabled: false, isOptimisticData: false }]);
176 renders = [];
177 successMessages.splice(0, successMessages.length);
178
179 vi.advanceTimersByTime(1000);
180 assertEquals(renders, []);
181 renders = [];
182});
183
184test.todo("debounceImmediate runs the first one right away", async () => {
185 vi.useFakeTimers({ shouldAdvanceTime: true });
186 const user = userEvent.setup({ delay: null });
187
188 const { client, successMessages, errorMessages } = createTestMutationClient();
189 const s = new IterableStream<string>();
190
191 const mutations: number[] = [];
192 const mutTest = client.define({
193 id: "test-3",
194 mutate: async (k: number) => {
195 mutations.push(k);
196 return (await s.next()).value;
197 },
198 describe: "Test the action",
199 describeResult: "Tested the action",
200 optimistic: () => {},
201 debounceMs: 500,
202 debounceImmediate: true,
203 });
204
205 let i = 0;
206
207 let renders: Array<{ isMutating: boolean; isPending: boolean; isOptimisticData: boolean; isDisabled: boolean }> = [];
208 function TestComponent() {
209 const { run, isMutating, isPending, isOptimisticData, isDisabled } = useMutate(mutTest);
210 renders.push({ isMutating, isPending, isOptimisticData, isDisabled });
211
212 return (
213 <>
214 <button
215 data-testid="a"
216 onClick={() => {
217 run(i++);
218 }}
219 >
220 button
221 </button>
222 </>
223 );
224 }
225
226 render(<TestComponent />);
227 // initial state
228 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: false, isDisabled: false }]);
229 assertEquals(successMessages, []);
230 assertEquals(errorMessages, []);
231 renders = [];
232 vi.runAllTimers();
233
234 // mutation 1
235 await act(() => user.click(screen.getByTestId("a")));
236 assertEquals(renders, [{ isMutating: true, isPending: false, isOptimisticData: true, isDisabled: false }]);
237 renders = [];
238 await act(async () => {
239 assertEquals(mutations, [0]);
240 s.push("ok");
241 vi.advanceTimersByTime(100);
242 });
243 assertEquals(renders, [{ isMutating: false, isPending: false, isDisabled: false, isOptimisticData: false }]);
244 renders = [];
245
246 // mutation 2
247 await act(() => user.click(screen.getByTestId("a")));
248 assertEquals(renders, [{ isMutating: true, isPending: false, isDisabled: false, isOptimisticData: true }]);
249 renders = [];
250
251 await act(() => user.click(screen.getByTestId("a")));
252 await act(() => user.click(screen.getByTestId("a")));
253 await act(() => user.click(screen.getByTestId("a")));
254 await act(() => vi.advanceTimersByTime(200));
255 assertEquals(renders, []);
256 await act(() => vi.advanceTimersByTime(300));
257 assertEquals(renders, [{ isMutating: true, isPending: false, isDisabled: false, isOptimisticData: true }]);
258 renders = [];
259 await act(() => vi.advanceTimersByTime(300));
260 assertEquals(renders, []);
261 assertEquals(successMessages, []);
262 assertEquals(errorMessages, []);
263 await act(async () => {
264 assertEquals(mutations, [1]);
265 s.push("ok");
266 vi.advanceTimersByTime(100);
267 });
268 assertEquals(successMessages, ["Tested the action"]);
269 assertEquals(errorMessages, []);
270 assertEquals(renders, [{ isMutating: false, isPending: false, isDisabled: false, isOptimisticData: false }]);
271 renders = [];
272 successMessages.splice(0, successMessages.length);
273
274 vi.advanceTimersByTime(1000);
275 assertEquals(renders, []);
276 renders = [];
277});