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.each([
9 [true],
10 [false],
11])("useMutate - isOptimisticData stays true while refetching (refetchOnSuccess = %s)", async (refetchOnSuccess) => {
12 vi.useFakeTimers({ shouldAdvanceTime: true });
13 const user = userEvent.setup({ delay: null });
14
15 const { client, successMessages, errorMessages } = createTestMutationClient();
16
17 const streamResults = new IterableStream<string>();
18 const streamRefreshes = new IterableStream<string>();
19
20 const mutTest = client.define({
21 id: "test-1",
22 mutate: async () => {
23 return (await streamResults.next()).value;
24 },
25 describe: "Test the action",
26 describeResult: "Tested the action",
27 optimistic: ({ onRefetch }) => {
28 onRefetch(async () => {
29 await streamRefreshes.next();
30 });
31 },
32 refetchOnSuccess,
33 });
34
35 let renders: Array<{ isMutating: boolean; isPending: boolean; isOptimisticData: boolean }> = [];
36 function TestComponent() {
37 const { run, isMutating, isPending, isOptimisticData } = useMutate(mutTest);
38 renders.push({ isMutating, isPending, isOptimisticData });
39
40 return (
41 <>
42 <button
43 data-testid="a"
44 onClick={() => {
45 run();
46 }}
47 >
48 button
49 </button>
50 </>
51 );
52 }
53
54 render(<TestComponent />);
55 // initial state
56 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: false }]);
57 assertEquals(successMessages, []);
58 assertEquals(errorMessages, []);
59 renders = [];
60 vi.runAllTimers();
61
62 // mutation 1
63 await act(() => user.click(screen.getByTestId("a")));
64 assertEquals(renders, [{ isMutating: true, isPending: false, isOptimisticData: true }]);
65 renders = [];
66 await act(() => vi.advanceTimersByTime(150));
67 assertEquals(renders, []);
68 await act(() => vi.advanceTimersByTime(50));
69 assertEquals(renders, [{ isMutating: true, isPending: true, isOptimisticData: true }]);
70 renders = [];
71 assertEquals(successMessages, []);
72 assertEquals(errorMessages, []);
73 await act(async () => {
74 streamResults.push("ok");
75 vi.advanceTimersByTime(150);
76 });
77 assertEquals(successMessages, ["Tested the action"]);
78 assertEquals(errorMessages, []);
79 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: refetchOnSuccess }]);
80 renders = [];
81 if (refetchOnSuccess) {
82 await act(async () => {
83 streamRefreshes.push("refetch");
84 vi.advanceTimersByTime(100);
85 });
86 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: false }]);
87 renders = [];
88 }
89 successMessages.splice(0, successMessages.length);
90
91 // mutation 2 - error
92 await act(() => user.click(screen.getByTestId("a")));
93 assertEquals(renders, [{ isMutating: true, isPending: false, isOptimisticData: true }]);
94 renders = [];
95 await act(() => vi.advanceTimersByTime(150));
96 assertEquals(renders, []);
97 await act(() => vi.advanceTimersByTime(50));
98 assertEquals(renders, [{ isMutating: true, isPending: true, isOptimisticData: true }]);
99 renders = [];
100 assertEquals(successMessages, []);
101 assertEquals(errorMessages, []);
102 const error1 = new Error("damn!");
103 await act(async () => {
104 streamResults.throw(error1);
105 vi.advanceTimersByTime(100);
106 });
107 assertEquals(successMessages, []);
108 assertEquals(errorMessages, [
109 { error: error1, message: "Could not test the action: damn!" },
110 ]);
111 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: true }]);
112 renders = [];
113 await act(async () => {
114 streamRefreshes.push("refetch");
115 vi.advanceTimersByTime(100);
116 });
117 assertEquals(renders, [{ isMutating: false, isPending: false, isOptimisticData: false }]);
118});