Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 7.5 kB view raw
1// @vitest-environment jsdom 2 3import { FunctionalComponent as FC, h } from 'preact'; 4import { render, cleanup, act } from '@testing-library/preact'; 5import { OperationContext } from '@urql/core'; 6import { map, interval, pipe, never, onStart, onEnd, empty } from 'wonka'; 7 8import { vi, expect, it, beforeEach, describe, afterEach, Mock } from 'vitest'; 9 10import { useQuery, UseQueryArgs, UseQueryState } from './useQuery'; 11import { Provider } from '../context'; 12 13const mock = { 14 executeQuery: vi.fn(() => 15 pipe( 16 interval(400), 17 map((i: number) => ({ data: i, error: i + 1, extensions: { i: 1 } })) 18 ) 19 ), 20}; 21 22const client = mock as { executeQuery: Mock }; 23const props: UseQueryArgs<{ myVar: number }> = { 24 query: '{ example }', 25 variables: { 26 myVar: 1234, 27 }, 28 pause: false, 29}; 30 31let state: UseQueryState<any> | undefined; 32let execute: ((_opts?: Partial<OperationContext>) => void) | undefined; 33 34const QueryUser: FC<UseQueryArgs<{ myVar: number }>> = ({ 35 query, 36 variables, 37 pause, 38}) => { 39 [state, execute] = useQuery({ query, variables, pause }); 40 return h('p', {}, state.data); 41}; 42 43beforeEach(() => { 44 vi.useFakeTimers(); 45 vi.spyOn(globalThis.console, 'error'); 46}); 47 48describe('useQuery', () => { 49 beforeEach(() => { 50 client.executeQuery.mockClear(); 51 state = undefined; 52 execute = undefined; 53 }); 54 55 afterEach(() => cleanup()); 56 57 it('executes subscription', () => { 58 render( 59 h(Provider, { 60 value: client as any, 61 children: [h(QueryUser, { ...props })], 62 }) 63 ); 64 expect(client.executeQuery).toBeCalledTimes(1); 65 }); 66 67 it('passes query and vars to executeQuery', () => { 68 render( 69 h(Provider, { 70 value: client as any, 71 children: [h(QueryUser, { ...props })], 72 }) 73 ); 74 75 expect(client.executeQuery).toBeCalledWith( 76 { 77 key: expect.any(Number), 78 query: expect.any(Object), 79 variables: props.variables, 80 }, 81 expect.objectContaining({ 82 requestPolicy: undefined, 83 }) 84 ); 85 }); 86 87 it('sets fetching to true', () => { 88 const { rerender } = render( 89 h(Provider, { 90 value: client as any, 91 children: [h(QueryUser, { ...props })], 92 }) 93 ); 94 95 rerender( 96 h(Provider, { 97 value: client as any, 98 children: [h(QueryUser, { ...props })], 99 }) 100 ); 101 expect(state).toHaveProperty('fetching', true); 102 }); 103 104 it('forwards data response', () => { 105 const { rerender } = render( 106 h(Provider, { 107 value: client as any, 108 children: [h(QueryUser, { ...props })], 109 }) 110 ); 111 112 rerender( 113 h(Provider, { 114 value: client as any, 115 children: [h(QueryUser, { ...props })], 116 }) 117 ); 118 119 act(() => { 120 vi.advanceTimersByTime(400); 121 rerender( 122 h(Provider, { 123 value: client as any, 124 children: [h(QueryUser, { ...props })], 125 }) 126 ); 127 }); 128 129 expect(state).toHaveProperty('data', 0); 130 }); 131 132 it('forwards error response', () => { 133 const { rerender } = render( 134 h(Provider, { 135 value: client as any, 136 children: [h(QueryUser, { ...props })], 137 }) 138 ); 139 140 rerender( 141 h(Provider, { 142 value: client as any, 143 children: [h(QueryUser, { ...props })], 144 }) 145 ); 146 147 act(() => { 148 vi.advanceTimersByTime(400); 149 rerender( 150 h(Provider, { 151 value: client as any, 152 children: [h(QueryUser, { ...props })], 153 }) 154 ); 155 }); 156 157 expect(state).toHaveProperty('error', 1); 158 }); 159 160 it('forwards extensions response', () => { 161 const { rerender } = render( 162 h(Provider, { 163 value: client as any, 164 children: [h(QueryUser, { ...props })], 165 }) 166 ); 167 168 rerender( 169 h(Provider, { 170 value: client as any, 171 children: [h(QueryUser, { ...props })], 172 }) 173 ); 174 175 act(() => { 176 vi.advanceTimersByTime(400); 177 rerender( 178 h(Provider, { 179 value: client as any, 180 children: [h(QueryUser, { ...props })], 181 }) 182 ); 183 }); 184 185 expect(state).toHaveProperty('extensions', { i: 1 }); 186 }); 187 188 it('sets fetching to false', () => { 189 const { rerender } = render( 190 h(Provider, { 191 value: client as any, 192 children: [h(QueryUser, { ...props })], 193 }) 194 ); 195 196 rerender( 197 h(Provider, { 198 value: client as any, 199 children: [h(QueryUser, { ...props })], 200 }) 201 ); 202 203 act(() => { 204 vi.advanceTimersByTime(400); 205 rerender( 206 h(Provider, { 207 value: client as any, 208 children: [h(QueryUser, { ...props })], 209 }) 210 ); 211 }); 212 213 expect(state).toHaveProperty('fetching', false); 214 }); 215 216 describe('on change', () => { 217 const q = 'query NewQuery { example }'; 218 219 it('new query executes subscription', () => { 220 const { rerender } = render( 221 h(Provider, { 222 value: client as any, 223 children: [h(QueryUser, { ...props })], 224 }) 225 ); 226 227 rerender( 228 h(Provider, { 229 value: client as any, 230 children: [h(QueryUser, { ...props, query: q })], 231 }) 232 ); 233 234 act(() => { 235 rerender( 236 h(Provider, { 237 value: client as any, 238 children: [h(QueryUser, { ...props, query: q })], 239 }) 240 ); 241 }); 242 243 expect(client.executeQuery).toBeCalledTimes(2); 244 }); 245 }); 246 247 describe('on unmount', () => { 248 const start = vi.fn(); 249 const unsubscribe = vi.fn(); 250 251 beforeEach(() => { 252 client.executeQuery.mockReturnValue( 253 pipe(never, onStart(start), onEnd(unsubscribe)) 254 ); 255 }); 256 257 it('unsubscribe is called', () => { 258 const { unmount } = render( 259 h(Provider, { 260 value: client as any, 261 children: [h(QueryUser, { ...props })], 262 }) 263 ); 264 265 act(() => { 266 unmount(); 267 }); 268 269 expect(start).toBeCalledTimes(2); 270 expect(unsubscribe).toBeCalledTimes(2); 271 }); 272 }); 273 274 describe('active teardown', () => { 275 it('sets fetching to false when the source ends', () => { 276 client.executeQuery.mockReturnValueOnce(empty); 277 act(() => { 278 render( 279 h(Provider, { 280 value: client as any, 281 children: [h(QueryUser, { ...props })], 282 }) 283 ); 284 }); 285 expect(client.executeQuery).toHaveBeenCalled(); 286 expect(state).toMatchObject({ fetching: false }); 287 }); 288 }); 289 290 describe('execute query', () => { 291 it('triggers query execution', () => { 292 render( 293 h(Provider, { 294 value: client as any, 295 children: [h(QueryUser, { ...props })], 296 }) 297 ); 298 act(() => execute && execute()); 299 expect(client.executeQuery).toBeCalledTimes(2); 300 }); 301 }); 302 303 describe('pause', () => { 304 it('skips executing the query if pause is true', () => { 305 render( 306 h(Provider, { 307 value: client as any, 308 children: [h(QueryUser, { ...props, pause: true })], 309 }) 310 ); 311 expect(client.executeQuery).not.toBeCalled(); 312 }); 313 314 it('skips executing queries if pause updates to true', () => { 315 const { rerender } = render( 316 h(Provider, { 317 value: client as any, 318 children: [h(QueryUser, { ...props })], 319 }) 320 ); 321 322 rerender( 323 h(Provider, { 324 value: client as any, 325 children: [h(QueryUser, { ...props, pause: true })], 326 }) 327 ); 328 329 expect(client.executeQuery).toBeCalledTimes(1); 330 }); 331 }); 332});