Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 8.7 kB view raw
1// @vitest-environment jsdom 2 3import { renderHook, testEffect } from '@solidjs/testing-library'; 4import { 5 OperationResult, 6 OperationResultSource, 7 createClient, 8 createRequest, 9 gql, 10} from '@urql/core'; 11import { expect, it, describe, vi } from 'vitest'; 12import { makeSubject } from 'wonka'; 13import { 14 CreateSubscriptionState, 15 createSubscription, 16} from './createSubscription'; 17import { createEffect, createSignal } from 'solid-js'; 18 19const QUERY = gql` 20 subscription { 21 value 22 } 23`; 24 25const client = createClient({ 26 url: '/graphql', 27 exchanges: [], 28 suspense: false, 29}); 30 31vi.mock('./context', () => { 32 const useClient = () => { 33 return client!; 34 }; 35 36 return { useClient }; 37}); 38 39// Given that it is not possible to directly listen to all store changes it is necessary 40// to access all relevant parts on which `createEffect` should listen on 41const markStateDependencies = (state: CreateSubscriptionState<any, any>) => { 42 state.data; 43 state.error; 44 state.extensions; 45 state.fetching; 46 state.operation; 47 state.stale; 48}; 49 50describe('createSubscription', () => { 51 it('should receive data', () => { 52 return testEffect(done => { 53 const subject = 54 makeSubject<Pick<OperationResult<{ value: number }, any>, 'data'>>(); 55 const executeQuery = vi 56 .spyOn(client, 'executeSubscription') 57 .mockImplementation( 58 () => subject.source as OperationResultSource<OperationResult> 59 ); 60 61 const request = createRequest(QUERY, undefined); 62 const operation = client.createRequestOperation('subscription', request); 63 64 const [state] = createSubscription< 65 { value: number }, 66 { value: number }, 67 { variable: number } 68 >({ 69 query: QUERY, 70 }); 71 72 createEffect((run: number = 0) => { 73 markStateDependencies(state); 74 75 switch (run) { 76 case 0: { 77 expect(state).toMatchObject({ 78 data: undefined, 79 stale: false, 80 operation: operation, 81 error: undefined, 82 extensions: undefined, 83 fetching: true, 84 }); 85 expect(executeQuery).toEqual(expect.any(Function)); 86 expect(executeQuery).toHaveBeenCalledOnce(); 87 expect(client.executeSubscription).toBeCalledWith( 88 { 89 key: expect.any(Number), 90 query: expect.any(Object), 91 variables: {}, 92 }, 93 undefined 94 ); 95 subject.next({ data: { value: 0 } }); 96 break; 97 } 98 case 1: { 99 expect(state.data).toEqual({ value: 0 }); 100 subject.next({ data: { value: 1 } }); 101 break; 102 } 103 case 2: { 104 expect(state.data).toEqual({ value: 1 }); 105 // expect(state.fetching).toEqual(true); 106 subject.complete(); 107 break; 108 } 109 case 3: { 110 expect(state.fetching).toEqual(false); 111 expect(state.data).toEqual({ value: 1 }); 112 done(); 113 } 114 } 115 116 return run + 1; 117 }); 118 }); 119 }); 120 121 it('should call handler', () => { 122 const handler = vi.fn(); 123 const subject = 124 makeSubject<Pick<OperationResult<{ value: number }, any>, 'data'>>(); 125 vi.spyOn(client, 'executeSubscription').mockImplementation( 126 () => subject.source as OperationResultSource<OperationResult> 127 ); 128 129 return testEffect(done => { 130 const [state] = createSubscription< 131 { value: number }, 132 { value: number }, 133 { variable: number } 134 >( 135 { 136 query: QUERY, 137 }, 138 handler 139 ); 140 141 createEffect((run: number = 0) => { 142 markStateDependencies(state); 143 switch (run) { 144 case 0: { 145 expect(state.fetching).toEqual(true); 146 subject.next({ data: { value: 0 } }); 147 148 break; 149 } 150 case 1: { 151 expect(handler).toHaveBeenCalledOnce(); 152 expect(handler).toBeCalledWith(undefined, { value: 0 }); 153 done(); 154 break; 155 } 156 } 157 158 return run + 1; 159 }); 160 }); 161 }); 162 163 it('should unsubscribe on teardown', () => { 164 const subject = 165 makeSubject<Pick<OperationResult<{ value: number }, any>, 'data'>>(); 166 vi.spyOn(client, 'executeSubscription').mockImplementation( 167 () => subject.source as OperationResultSource<OperationResult> 168 ); 169 170 const { 171 result: [state], 172 cleanup, 173 } = renderHook(() => 174 createSubscription<{ value: number }, { variable: number }>({ 175 query: QUERY, 176 }) 177 ); 178 179 return testEffect(done => 180 createEffect((run: number = 0) => { 181 if (run === 0) { 182 expect(state.fetching).toEqual(true); 183 cleanup(); 184 } 185 186 if (run === 1) { 187 expect(state.fetching).toEqual(false); 188 done(); 189 } 190 191 return run + 1; 192 }) 193 ); 194 }); 195 196 it('should skip executing query when paused', () => { 197 const subject = 198 makeSubject<Pick<OperationResult<{ value: number }, any>, 'data'>>(); 199 vi.spyOn(client, 'executeSubscription').mockImplementation( 200 () => subject.source as OperationResultSource<OperationResult> 201 ); 202 203 return testEffect(done => { 204 const [pause, setPause] = createSignal<boolean>(true); 205 206 const [state] = createSubscription< 207 { value: number }, 208 { value: number }, 209 { variable: number } 210 >({ query: QUERY, pause: pause }); 211 212 createEffect((run: number = 0) => { 213 switch (run) { 214 case 0: { 215 expect(state.fetching).toBe(false); 216 setPause(false); 217 break; 218 } 219 case 1: { 220 expect(state.fetching).toBe(true); 221 expect(state.data).toBeUndefined(); 222 subject.next({ data: { value: 1 } }); 223 224 break; 225 } 226 case 2: { 227 expect(state.data).toStrictEqual({ value: 1 }); 228 done(); 229 break; 230 } 231 } 232 233 return run + 1; 234 }); 235 }); 236 }); 237 238 it('should override pause when execute executeSubscription', () => { 239 const subject = 240 makeSubject<Pick<OperationResult<{ value: number }, any>, 'data'>>(); 241 const executeQuery = vi 242 .spyOn(client, 'executeSubscription') 243 .mockImplementation( 244 () => subject.source as OperationResultSource<OperationResult> 245 ); 246 247 return testEffect(done => { 248 const [state, executeSubscription] = createSubscription< 249 { value: number }, 250 { value: number }, 251 { variable: number } 252 >({ 253 query: QUERY, 254 pause: true, 255 }); 256 257 createEffect((run: number = 0) => { 258 markStateDependencies(state); 259 260 switch (run) { 261 case 0: { 262 expect(state.fetching).toEqual(false); 263 expect(executeQuery).not.toBeCalled(); 264 265 executeSubscription(); 266 267 break; 268 } 269 case 1: { 270 expect(state.fetching).toEqual(true); 271 expect(executeQuery).toHaveBeenCalledOnce(); 272 subject.next({ data: { value: 1 } }); 273 break; 274 } 275 case 2: { 276 expect(state.data).toStrictEqual({ value: 1 }); 277 done(); 278 break; 279 } 280 } 281 282 return run + 1; 283 }); 284 }); 285 }); 286 287 it('should aggregate results', () => { 288 const subject = 289 makeSubject<Pick<OperationResult<{ value: number }, any>, 'data'>>(); 290 vi.spyOn(client, 'executeSubscription').mockImplementation( 291 () => subject.source as OperationResultSource<OperationResult> 292 ); 293 294 return testEffect(done => { 295 const [state] = createSubscription< 296 { value: number }, 297 { merged: number }, 298 { variable: number } 299 >( 300 { 301 query: QUERY, 302 }, 303 (prev, next) => { 304 if (prev === undefined) { 305 return { 306 merged: 0 + next.value, 307 }; 308 } 309 310 return { merged: prev.merged + next.value }; 311 } 312 ); 313 314 createEffect((run: number = 0) => { 315 markStateDependencies(state); 316 switch (run) { 317 case 0: { 318 expect(state.fetching).toEqual(true); 319 subject.next({ data: { value: 1 } }); 320 321 break; 322 } 323 case 1: { 324 expect(state.data).toEqual({ merged: 1 }); 325 subject.next({ data: { value: 2 } }); 326 327 break; 328 } 329 case 2: { 330 expect(state.data).toEqual({ merged: 3 }); 331 332 done(); 333 break; 334 } 335 } 336 337 return run + 1; 338 }); 339 }); 340 }); 341});