1import { vi, expect, it, beforeEach, afterEach, describe, Mock } from 'vitest';
2
3vi.mock('graphql', async () => {
4 const graphql = await vi.importActual<typeof import('graphql')>('graphql');
5
6 return {
7 __esModule: true,
8 ...(graphql as object),
9 print: vi.fn(() => '{ placeholder }'),
10 execute: vi.fn(() => ({ key: 'value' })),
11 subscribe: vi.fn(),
12 };
13});
14
15import { fetchExchange } from '@urql/core';
16import { executeExchange } from './execute';
17import { execute, print, subscribe } from 'graphql';
18import {
19 pipe,
20 fromValue,
21 toPromise,
22 take,
23 makeSubject,
24 empty,
25 Source,
26} from 'wonka';
27import {
28 context,
29 queryOperation,
30 subscriptionOperation,
31} from '../../../packages/core/src/test-utils';
32import {
33 makeErrorResult,
34 makeOperation,
35 Client,
36 OperationResult,
37} from '@urql/core';
38
39const mocked = (x: any): any => x;
40
41const schema = 'STUB_SCHEMA' as any;
42const exchangeArgs = {
43 forward: a => a,
44 client: {},
45} as any;
46
47const expectedQueryOperationName = 'getUser';
48const expectedSubscribeOperationName = 'subscribeToUser';
49
50const fetchMock = (globalThis as any).fetch as Mock;
51const mockHttpResponseData = { key: 'value' };
52
53beforeEach(() => {
54 vi.clearAllMocks();
55 mocked(print).mockImplementation(a => a as any);
56 mocked(execute).mockResolvedValue({ data: mockHttpResponseData });
57 mocked(subscribe).mockImplementation(async function* x(this: any) {
58 yield { data: { key: 'value1' } };
59 yield { data: { key: 'value2' } };
60 yield { data: { key: 'value3' } };
61 });
62});
63
64afterEach(() => {
65 fetchMock.mockClear();
66});
67
68describe('on operation', () => {
69 it('calls execute with args', async () => {
70 const context = 'USER_ID=123';
71
72 await pipe(
73 fromValue(queryOperation),
74 executeExchange({ schema, context })(exchangeArgs),
75 take(1),
76 toPromise
77 );
78
79 expect(mocked(execute)).toBeCalledTimes(1);
80 expect(mocked(execute)).toBeCalledWith({
81 schema,
82 document: queryOperation.query,
83 rootValue: undefined,
84 contextValue: context,
85 variableValues: queryOperation.variables,
86 operationName: expectedQueryOperationName,
87 fieldResolver: undefined,
88 typeResolver: undefined,
89 subscribeFieldResolver: undefined,
90 });
91 });
92
93 it('calls subscribe with args', async () => {
94 const context = 'USER_ID=123';
95
96 await pipe(
97 fromValue(subscriptionOperation),
98 executeExchange({ schema, context })(exchangeArgs),
99 take(3),
100 toPromise
101 );
102
103 expect(mocked(subscribe)).toBeCalledTimes(1);
104 expect(mocked(subscribe)).toBeCalledWith({
105 schema,
106 document: subscriptionOperation.query,
107 rootValue: undefined,
108 contextValue: context,
109 variableValues: subscriptionOperation.variables,
110 operationName: expectedSubscribeOperationName,
111 fieldResolver: undefined,
112 typeResolver: undefined,
113 subscribeFieldResolver: undefined,
114 });
115 });
116
117 it('calls execute after executing context as a function', async () => {
118 const context = operation => {
119 expect(operation).toBe(queryOperation);
120 return 'CALCULATED_USER_ID=' + 8 * 10;
121 };
122
123 await pipe(
124 fromValue(queryOperation),
125 executeExchange({ schema, context })(exchangeArgs),
126 take(1),
127 toPromise
128 );
129
130 expect(mocked(execute)).toBeCalledTimes(1);
131 expect(mocked(execute)).toBeCalledWith({
132 schema,
133 document: queryOperation.query,
134 rootValue: undefined,
135 contextValue: 'CALCULATED_USER_ID=80',
136 variableValues: queryOperation.variables,
137 operationName: expectedQueryOperationName,
138 fieldResolver: undefined,
139 typeResolver: undefined,
140 subscribeFieldResolver: undefined,
141 });
142 });
143
144 it('calls execute after executing context as a function returning a Promise', async () => {
145 const context = async operation => {
146 expect(operation).toBe(queryOperation);
147 return 'CALCULATED_USER_ID=' + 8 * 10;
148 };
149
150 await pipe(
151 fromValue(queryOperation),
152 executeExchange({ schema, context })(exchangeArgs),
153 take(1),
154 toPromise
155 );
156
157 expect(mocked(execute)).toBeCalledTimes(1);
158 expect(mocked(execute)).toBeCalledWith({
159 schema,
160 document: queryOperation.query,
161 rootValue: undefined,
162 contextValue: 'CALCULATED_USER_ID=80',
163 variableValues: queryOperation.variables,
164 operationName: expectedQueryOperationName,
165 fieldResolver: undefined,
166 typeResolver: undefined,
167 subscribeFieldResolver: undefined,
168 });
169 });
170
171 it('should return data from subscribe', async () => {
172 const context = 'USER_ID=123';
173
174 const responseFromExecuteExchange = await pipe(
175 fromValue(subscriptionOperation),
176 executeExchange({ schema, context })(exchangeArgs),
177 take(3),
178 toPromise
179 );
180
181 expect(responseFromExecuteExchange.data).toEqual({ key: 'value3' });
182 });
183
184 it('should return the same data as the fetch exchange', async () => {
185 const context = 'USER_ID=123';
186
187 const responseFromExecuteExchange = await pipe(
188 fromValue(queryOperation),
189 executeExchange({ schema, context })(exchangeArgs),
190 take(1),
191 toPromise
192 );
193
194 fetchMock.mockResolvedValue({
195 status: 200,
196 headers: { get: () => 'application/json' },
197 text: vi
198 .fn()
199 .mockResolvedValue(JSON.stringify({ data: mockHttpResponseData })),
200 });
201
202 const responseFromFetchExchange = await pipe(
203 fromValue(queryOperation),
204 fetchExchange({
205 dispatchDebug: vi.fn(),
206 forward: () => empty as Source<OperationResult>,
207 client: {} as Client,
208 }),
209 toPromise
210 );
211
212 expect(responseFromExecuteExchange.data).toEqual(
213 responseFromFetchExchange.data
214 );
215 expect(mocked(execute)).toBeCalledTimes(1);
216 expect(fetchMock).toBeCalledTimes(1);
217 });
218
219 it('should trim undefined values before calling execute()', async () => {
220 const contextValue = 'USER_ID=123';
221
222 const operation = makeOperation(
223 'query',
224 {
225 ...queryOperation,
226 variables: { ...queryOperation.variables, withLastName: undefined },
227 },
228 context
229 );
230
231 await pipe(
232 fromValue(operation),
233 executeExchange({ schema, context: contextValue })(exchangeArgs),
234 take(1),
235 toPromise
236 );
237
238 expect(mocked(execute)).toBeCalledTimes(1);
239 expect(mocked(execute)).toBeCalledWith({
240 schema,
241 document: queryOperation.query,
242 rootValue: undefined,
243 contextValue: contextValue,
244 variableValues: queryOperation.variables,
245 operationName: expectedQueryOperationName,
246 fieldResolver: undefined,
247 typeResolver: undefined,
248 subscribeFieldResolver: undefined,
249 });
250
251 const variables = mocked(execute).mock.calls[0][0].variableValues;
252
253 for (const key in variables) {
254 expect(variables[key]).not.toBeUndefined();
255 }
256 });
257});
258
259describe('on success response', () => {
260 it('returns operation result', async () => {
261 const response = await pipe(
262 fromValue(queryOperation),
263 executeExchange({ schema })(exchangeArgs),
264 take(1),
265 toPromise
266 );
267
268 expect(response).toEqual({
269 operation: queryOperation,
270 data: mockHttpResponseData,
271 hasNext: false,
272 stale: false,
273 });
274 });
275});
276
277describe('on error response', () => {
278 const errors = ['error'] as any;
279
280 beforeEach(() => {
281 mocked(execute).mockResolvedValue({ errors });
282 });
283
284 it('returns operation result', async () => {
285 const response = await pipe(
286 fromValue(queryOperation),
287 executeExchange({ schema })(exchangeArgs),
288 take(1),
289 toPromise
290 );
291
292 expect(response).toHaveProperty('operation', queryOperation);
293 expect(response).toHaveProperty('error');
294 });
295});
296
297describe('on thrown error', () => {
298 const errors = ['error'] as any;
299
300 beforeEach(() => {
301 mocked(execute).mockRejectedValue({ errors });
302 });
303
304 it('returns operation result', async () => {
305 const response = await pipe(
306 fromValue(queryOperation),
307 executeExchange({ schema })(exchangeArgs),
308 take(1),
309 toPromise
310 );
311
312 const expected = makeErrorResult(queryOperation, errors);
313
314 expect(response.operation).toBe(expected.operation);
315 expect(response.data).toEqual(expected.data);
316 expect(response.error).toEqual(expected.error);
317 });
318});
319
320describe('on unsupported operation', () => {
321 const operation = makeOperation(
322 'teardown',
323 queryOperation,
324 queryOperation.context
325 );
326
327 it('returns operation result', async () => {
328 const { source, next } = makeSubject<any>();
329
330 const response = pipe(
331 source,
332 executeExchange({ schema })(exchangeArgs),
333 take(1),
334 toPromise
335 );
336
337 next(operation);
338 expect(await response).toEqual(operation);
339 });
340});