1// @vitest-environment jsdom
2
3import { expect, it, describe, vi } from 'vitest';
4import { CreateQueryState, createQuery } from './createQuery';
5import { renderHook, testEffect } from '@solidjs/testing-library';
6import { createClient } from '@urql/core';
7import { createEffect, createSignal } from 'solid-js';
8import { makeSubject } from 'wonka';
9import { OperationResult, OperationResultSource } from '@urql/core';
10
11const client = createClient({
12 url: '/graphql',
13 exchanges: [],
14 suspense: false,
15});
16
17vi.mock('./context', () => {
18 const useClient = () => {
19 return client!;
20 };
21
22 return { useClient };
23});
24
25// Given that it is not possible to directly listen to all store changes it is necessary
26// to access all relevant parts on which `createEffect` should listen on
27const markStateDependencies = (state: CreateQueryState<any, any>) => {
28 state.data;
29 state.error;
30 state.extensions;
31 state.fetching;
32 state.operation;
33 state.stale;
34};
35
36describe('createQuery', () => {
37 it('should fetch when query is resumed', () => {
38 const subject =
39 makeSubject<Pick<OperationResult<{ test: boolean }, any>, 'data'>>();
40 const executeQuery = vi
41 .spyOn(client, 'executeQuery')
42 .mockImplementation(
43 () => subject.source as OperationResultSource<OperationResult>
44 );
45
46 return testEffect(done => {
47 const [pause, setPause] = createSignal<boolean>(true);
48 const [state] = createQuery<{ test: boolean }, { variable: number }>({
49 query: '{ test }',
50 pause: pause,
51 });
52
53 createEffect((run: number = 0) => {
54 markStateDependencies(state);
55
56 switch (run) {
57 case 0: {
58 expect(state.fetching).toEqual(false);
59 expect(executeQuery).not.toHaveBeenCalled();
60 setPause(false);
61 break;
62 }
63 case 1: {
64 expect(state.fetching).toEqual(true);
65 expect(executeQuery).toHaveBeenCalledOnce();
66 subject.next({ data: { test: true } });
67 break;
68 }
69 case 2: {
70 expect(state.fetching).toEqual(false);
71 expect(state.data).toStrictEqual({ test: true });
72 done();
73 break;
74 }
75 }
76
77 return run + 1;
78 });
79 });
80 });
81
82 it('should override pause when execute via refetch', () => {
83 const subject =
84 makeSubject<Pick<OperationResult<{ test: boolean }, any>, 'data'>>();
85 const executeQuery = vi
86 .spyOn(client, 'executeQuery')
87 .mockImplementation(
88 () => subject.source as OperationResultSource<OperationResult>
89 );
90
91 return testEffect(done => {
92 const [state, refetch] = createQuery<
93 { test: boolean },
94 { variable: number }
95 >({
96 query: '{ test }',
97 pause: true,
98 });
99
100 createEffect((run: number = 0) => {
101 markStateDependencies(state);
102
103 switch (run) {
104 case 0: {
105 expect(state.fetching).toEqual(false);
106 expect(executeQuery).not.toBeCalled();
107 refetch();
108 break;
109 }
110 case 1: {
111 expect(state.fetching).toEqual(true);
112 expect(executeQuery).toHaveBeenCalledOnce();
113 subject.next({ data: { test: true } });
114 break;
115 }
116 case 2: {
117 expect(state.fetching).toEqual(false);
118 expect(state.data).toStrictEqual({ test: true });
119 done();
120 break;
121 }
122 }
123
124 return run + 1;
125 });
126 });
127 });
128
129 it('should trigger refetch on variables change', () => {
130 const subject =
131 makeSubject<Pick<OperationResult<{ test: boolean }, any>, 'data'>>();
132 const executeQuery = vi
133 .spyOn(client, 'executeQuery')
134 .mockImplementation(
135 () => subject.source as OperationResultSource<OperationResult>
136 );
137
138 return testEffect(done => {
139 const [variables, setVariables] = createSignal<{ variable: number }>({
140 variable: 1,
141 });
142
143 const [state] = createQuery<{ test: boolean }, { variable: number }>({
144 query: '{ test }',
145 variables: variables,
146 });
147
148 createEffect((run: number = 0) => {
149 markStateDependencies(state);
150
151 switch (run) {
152 case 0: {
153 expect(state.fetching).toEqual(true);
154
155 subject.next({ data: { test: true } });
156
157 break;
158 }
159 case 1: {
160 expect(state.fetching).toEqual(false);
161 expect(state.data).toEqual({ test: true });
162 setVariables({ variable: 2 });
163 break;
164 }
165 case 2: {
166 expect(state.fetching).toEqual(true);
167 expect(executeQuery).toHaveBeenCalledTimes(2);
168
169 subject.next({ data: { test: false } });
170 break;
171 }
172 case 3: {
173 expect(state.fetching).toEqual(false);
174 expect(state.data).toEqual({ test: false });
175 done();
176 break;
177 }
178 }
179
180 return run + 1;
181 });
182 });
183 });
184
185 it('should receive data', () => {
186 const subject =
187 makeSubject<Pick<OperationResult<{ test: boolean }, any>, 'data'>>();
188 const executeQuery = vi
189 .spyOn(client, 'executeQuery')
190 .mockImplementation(
191 () => subject.source as OperationResultSource<OperationResult>
192 );
193
194 return testEffect(done => {
195 const [state] = createQuery<{ test: boolean }, { variable: number }>({
196 query: '{ test }',
197 });
198
199 createEffect((run: number = 0) => {
200 markStateDependencies(state);
201
202 switch (run) {
203 case 0: {
204 expect(state.fetching).toEqual(true);
205 expect(state.data).toBeUndefined();
206
207 subject.next({ data: { test: true } });
208 break;
209 }
210
211 case 1: {
212 expect(state.fetching).toEqual(false);
213 expect(state.data).toStrictEqual({ test: true });
214 expect(executeQuery).toHaveBeenCalledTimes(1);
215 done();
216 break;
217 }
218 }
219
220 return run + 1;
221 });
222 });
223 });
224
225 it('should unsubscribe on teardown', () => {
226 const subject =
227 makeSubject<Pick<OperationResult<{ value: number }, any>, 'data'>>();
228 vi.spyOn(client, 'executeQuery').mockImplementation(
229 () => subject.source as OperationResultSource<OperationResult>
230 );
231
232 const {
233 result: [state],
234 cleanup,
235 } = renderHook(() =>
236 createQuery<{ test: number }, { variable: number }>({
237 query: '{ test }',
238 })
239 );
240
241 return testEffect(done => {
242 markStateDependencies(state);
243
244 createEffect((run: number = 0) => {
245 switch (run) {
246 case 0: {
247 expect(state.fetching).toEqual(true);
248 cleanup();
249 break;
250 }
251 case 1: {
252 expect(state.fetching).toEqual(false);
253 done();
254 break;
255 }
256 }
257
258 return run + 1;
259 });
260 });
261 });
262});