Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 8.8 kB view raw
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});