Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 12 kB view raw
1/* eslint-disable @typescript-eslint/no-var-requires */ 2 3import { gql } from '@urql/core'; 4import { minifyIntrospectionQuery } from '@urql/introspection'; 5import { describe, it, beforeEach, beforeAll, expect } from 'vitest'; 6 7import { Store } from '../store/store'; 8import { __initAnd_write as write } from './write'; 9import { __initAnd_query as query } from './query'; 10 11const TODO_QUERY = gql` 12 query Todos { 13 todos { 14 id 15 text 16 complete 17 author { 18 id 19 name 20 known 21 __typename 22 } 23 __typename 24 } 25 } 26`; 27 28describe('Query', () => { 29 let schema, store, alteredRoot; 30 31 beforeAll(() => { 32 schema = minifyIntrospectionQuery( 33 require('../test-utils/simple_schema.json') 34 ); 35 alteredRoot = minifyIntrospectionQuery( 36 require('../test-utils/altered_root_schema.json') 37 ); 38 }); 39 40 beforeEach(() => { 41 store = new Store({ schema }); 42 write( 43 store, 44 { query: TODO_QUERY }, 45 { 46 __typename: 'Query', 47 todos: [ 48 { id: '0', text: 'Teach', __typename: 'Todo' }, 49 { id: '1', text: 'Learn', __typename: 'Todo' }, 50 ], 51 } 52 ); 53 }); 54 55 it('test partial results', () => { 56 const result = query(store, { query: TODO_QUERY }); 57 expect(result.partial).toBe(true); 58 expect(result.data).toEqual({ 59 todos: [ 60 { 61 id: '0', 62 text: 'Teach', 63 __typename: 'Todo', 64 author: null, 65 complete: null, 66 }, 67 { 68 id: '1', 69 text: 'Learn', 70 __typename: 'Todo', 71 author: null, 72 complete: null, 73 }, 74 ], 75 }); 76 }); 77 78 it('should warn once for invalid fields on an entity', () => { 79 const INVALID_TODO_QUERY = gql` 80 query InvalidTodo { 81 todos { 82 id 83 text 84 incomplete 85 } 86 } 87 `; 88 89 query(store, { query: INVALID_TODO_QUERY }); 90 expect(console.warn).toHaveBeenCalledTimes(1); 91 expect((console.warn as any).mock.calls[0][0]).toMatch( 92 /Caused At: "InvalidTodo" query/ 93 ); 94 95 query(store, { query: INVALID_TODO_QUERY }); 96 expect(console.warn).toHaveBeenCalledTimes(1); 97 98 expect((console.warn as any).mock.calls[0][0]).toMatch(/incomplete/); 99 }); 100 101 it('should warn once for invalid sub-entities on an entity at the right stack', () => { 102 const INVALID_TODO_QUERY = gql` 103 query InvalidTodo { 104 todos { 105 ...ValidTodo 106 ...InvalidFields 107 } 108 } 109 110 fragment ValidTodo on Todo { 111 id 112 text 113 } 114 115 fragment InvalidFields on Todo { 116 id 117 writer { 118 id 119 } 120 } 121 `; 122 123 query(store, { query: INVALID_TODO_QUERY }); 124 expect(console.warn).toHaveBeenCalledTimes(1); 125 expect((console.warn as any).mock.calls[0][0]).toMatch( 126 /Caused At: "InvalidTodo" query, "InvalidFields" Fragment/ 127 ); 128 129 query(store, { query: INVALID_TODO_QUERY }); 130 expect(console.warn).toHaveBeenCalledTimes(1); 131 expect((console.warn as any).mock.calls[0][0]).toMatch(/writer/); 132 }); 133 134 // Issue#64 135 it('should not crash for valid queries', () => { 136 const VALID_QUERY = gql` 137 query getTodos { 138 __typename 139 todos { 140 __typename 141 id 142 text 143 } 144 } 145 `; 146 // Use new store to ensure bug reproduction 147 const store = new Store({ schema }); 148 149 let { data } = query(store, { query: VALID_QUERY }); 150 expect(data).toEqual(null); 151 152 write( 153 store, 154 { query: VALID_QUERY }, 155 // @ts-ignore 156 { 157 // Removing typename here would formerly crash this. 158 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }], 159 } 160 ); 161 ({ data } = query(store, { query: VALID_QUERY })); 162 expect(data).toEqual({ 163 __typename: 'Query', 164 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }], 165 }); 166 167 expect(console.error).not.toHaveBeenCalled(); 168 }); 169 170 it('should respect altered root types', () => { 171 const QUERY = gql` 172 query getTodos { 173 __typename 174 todos { 175 __typename 176 id 177 text 178 } 179 } 180 `; 181 182 const store = new Store({ schema: alteredRoot }); 183 184 let { data } = query(store, { query: QUERY }); 185 expect(data).toEqual(null); 186 187 write( 188 store, 189 { query: QUERY }, 190 { 191 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }], 192 __typename: 'query_root', 193 } 194 ); 195 196 ({ data } = query(store, { query: QUERY })); 197 expect(data).toEqual({ 198 __typename: 'query_root', 199 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }], 200 }); 201 202 expect(console.warn).not.toHaveBeenCalled(); 203 expect(console.error).not.toHaveBeenCalled(); 204 }); 205 206 it('should not allow subsequent reads when first result was null', () => { 207 const QUERY_WRITE = gql` 208 query writeTodos { 209 __typename 210 todos { 211 __typename 212 ...ValidRead 213 } 214 } 215 216 fragment ValidRead on Todo { 217 id 218 } 219 `; 220 221 const QUERY_READ = gql` 222 query getTodos { 223 __typename 224 todos { 225 __typename 226 ...MissingRead 227 } 228 todos { 229 __typename 230 id 231 } 232 } 233 234 fragment MissingRead on Todo { 235 id 236 text 237 } 238 `; 239 240 const store = new Store({ 241 schema: alteredRoot, 242 }); 243 244 let { data } = query(store, { query: QUERY_READ }); 245 expect(data).toEqual(null); 246 247 write( 248 store, 249 { query: QUERY_WRITE }, 250 { 251 todos: [ 252 { 253 __typename: 'Todo', 254 id: '0', 255 }, 256 ], 257 __typename: 'Query', 258 } 259 ); 260 261 ({ data } = query(store, { query: QUERY_READ })); 262 expect(data).toEqual({ 263 __typename: 'query_root', 264 todos: [null], 265 }); 266 267 expect(console.warn).not.toHaveBeenCalled(); 268 expect(console.error).not.toHaveBeenCalled(); 269 }); 270 271 it('should not allow subsequent reads when first result was null (with resolvers)', () => { 272 const QUERY_WRITE = gql` 273 query writeTodos { 274 __typename 275 todos { 276 __typename 277 ...ValidRead 278 } 279 } 280 281 fragment ValidRead on Todo { 282 id 283 } 284 `; 285 286 const QUERY_READ = gql` 287 query getTodos { 288 __typename 289 todos { 290 __typename 291 ...MissingRead 292 } 293 todos { 294 __typename 295 id 296 } 297 } 298 299 fragment MissingRead on Todo { 300 id 301 text 302 } 303 `; 304 305 const store = new Store({ 306 schema, 307 resolvers: { 308 Query: { 309 todos: (_parent, _args, cache) => cache.resolve('Query', 'todos'), 310 }, 311 }, 312 }); 313 314 let { data } = query(store, { query: QUERY_READ }); 315 expect(data).toEqual(null); 316 317 write( 318 store, 319 { query: QUERY_WRITE }, 320 { 321 todos: [ 322 { 323 __typename: 'Todo', 324 id: '0', 325 }, 326 ], 327 __typename: 'Query', 328 } 329 ); 330 331 ({ data } = query(store, { query: QUERY_READ })); 332 expect(data).toEqual({ 333 __typename: 'Query', 334 todos: [null], 335 }); 336 337 expect(console.warn).not.toHaveBeenCalled(); 338 expect(console.error).not.toHaveBeenCalled(); 339 }); 340 341 it('should not mix references', () => { 342 const QUERY_WRITE = gql` 343 query writeTodos { 344 __typename 345 todos { 346 __typename 347 id 348 textA 349 textB 350 } 351 } 352 `; 353 354 const QUERY_READ = gql` 355 query getTodos { 356 __typename 357 todos { 358 __typename 359 id 360 textA 361 } 362 todos { 363 __typename 364 textB 365 } 366 } 367 `; 368 369 const store = new Store({ 370 schema: alteredRoot, 371 }); 372 373 write( 374 store, 375 { query: QUERY_WRITE }, 376 { 377 todos: [ 378 { 379 __typename: 'Todo', 380 id: '0', 381 textA: 'a', 382 textB: 'b', 383 }, 384 ], 385 __typename: 'Query', 386 } 387 ); 388 389 let data: any; 390 data = query(store, { query: QUERY_READ }).data; 391 392 expect(data).toEqual({ 393 __typename: 'query_root', 394 todos: [ 395 { 396 __typename: 'Todo', 397 id: '0', 398 textA: 'a', 399 textB: 'b', 400 }, 401 ], 402 }); 403 404 const previousData = { 405 __typename: 'query_root', 406 todos: [ 407 { 408 __typename: 'Todo', 409 id: '0', 410 textA: 'a', 411 textB: 'old', 412 }, 413 ], 414 }; 415 416 data = query(store, { query: QUERY_READ }, previousData).data; 417 418 expect(data).toEqual({ 419 __typename: 'query_root', 420 todos: [ 421 { 422 __typename: 'Todo', 423 id: '0', 424 textA: 'a', 425 textB: 'b', 426 }, 427 ], 428 }); 429 430 expect(previousData).toHaveProperty('todos.0.textA', 'a'); 431 expect(previousData).toHaveProperty('todos.0.textB', 'old'); 432 }); 433 434 it('should keep references stable (1)', () => { 435 const QUERY = gql` 436 query todos { 437 __typename 438 todos { 439 __typename 440 test 441 } 442 todos { 443 __typename 444 id 445 } 446 } 447 `; 448 449 const store = new Store({ 450 schema: alteredRoot, 451 }); 452 453 const expected = { 454 todos: [ 455 { 456 __typename: 'Todo', 457 id: '0', 458 test: '0', 459 }, 460 { 461 __typename: 'Todo', 462 id: '1', 463 test: '1', 464 }, 465 { 466 __typename: 'Todo', 467 id: '2', 468 test: '2', 469 }, 470 ], 471 __typename: 'query_root', 472 }; 473 474 write(store, { query: QUERY }, expected); 475 476 const prevData = query( 477 store, 478 { query: QUERY }, 479 { 480 todos: [ 481 { 482 __typename: 'Todo', 483 id: '0', 484 test: 'prev-0', 485 }, 486 { 487 __typename: 'Todo', 488 id: '1', 489 test: '1', 490 }, 491 { 492 __typename: 'Todo', 493 id: '2', 494 test: '2', 495 }, 496 ], 497 __typename: 'query_root', 498 } 499 ).data as any; 500 501 const data = query(store, { query: QUERY }, prevData).data as any; 502 expect(data).toEqual(expected); 503 504 expect(prevData.todos[0]).toBe(data.todos[0]); 505 expect(prevData.todos[1]).toBe(data.todos[1]); 506 expect(prevData.todos[2]).toBe(data.todos[2]); 507 expect(prevData.todos).toBe(data.todos); 508 expect(prevData).toBe(data); 509 }); 510 511 it('should keep references stable (negative test)', () => { 512 const QUERY = gql` 513 query todos { 514 __typename 515 todos { 516 __typename 517 id 518 } 519 todos { 520 __typename 521 test 522 } 523 } 524 `; 525 526 const store = new Store({ 527 schema: alteredRoot, 528 }); 529 530 const expected = { 531 todos: [ 532 { 533 __typename: 'Todo', 534 id: '0', 535 test: '0', 536 }, 537 { 538 __typename: 'Todo', 539 id: '1', 540 test: '1', 541 }, 542 { 543 __typename: 'Todo', 544 id: '2', 545 test: '2', 546 }, 547 ], 548 __typename: 'query_root', 549 }; 550 551 write(store, { query: QUERY }, expected); 552 553 const prevData = query( 554 store, 555 { query: QUERY }, 556 { 557 todos: [ 558 { 559 __typename: 'Todo', 560 id: '0', 561 test: 'prev-0', 562 }, 563 { 564 __typename: 'Todo', 565 id: '1', 566 test: '1', 567 }, 568 { 569 __typename: 'Todo', 570 id: '2', 571 test: '2', 572 }, 573 ], 574 __typename: 'query_root', 575 } 576 ).data as any; 577 578 expected.todos[0].test = 'x'; 579 write(store, { query: QUERY }, expected); 580 581 const data = query(store, { query: QUERY }, prevData).data as any; 582 expect(data).toEqual(expected); 583 584 expect(prevData.todos[1]).toBe(data.todos[1]); 585 expect(prevData.todos[2]).toBe(data.todos[2]); 586 expect(prevData.todos[0]).not.toBe(data.todos[0]); 587 expect(prevData.todos).not.toBe(data.todos); 588 expect(prevData).not.toBe(data); 589 }); 590});