Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 36 kB view raw
1import { gql } from '@urql/core'; 2import { it, expect, describe } from 'vitest'; 3import { __initAnd_query as query } from '../operations/query'; 4import { __initAnd_write as write } from '../operations/write'; 5import { Store } from '../store/store'; 6import { relayPagination } from './relayPagination'; 7 8function itemNode(numItem: number) { 9 return { 10 __typename: 'Item', 11 id: numItem + '', 12 }; 13} 14 15function itemEdge(numItem: number) { 16 return { 17 __typename: 'ItemEdge', 18 node: itemNode(numItem), 19 }; 20} 21 22describe('as resolver', () => { 23 it('works with forward pagination', () => { 24 const Pagination = gql` 25 query ($cursor: String) { 26 __typename 27 items(first: 1, after: $cursor) { 28 __typename 29 edges { 30 __typename 31 node { 32 __typename 33 id 34 } 35 } 36 nodes { 37 __typename 38 id 39 } 40 pageInfo { 41 __typename 42 hasNextPage 43 endCursor 44 } 45 } 46 } 47 `; 48 49 const store = new Store({ 50 resolvers: { 51 Query: { 52 items: relayPagination(), 53 }, 54 }, 55 }); 56 57 const pageOne = { 58 __typename: 'Query', 59 items: { 60 __typename: 'ItemsConnection', 61 edges: [itemEdge(1)], 62 nodes: [itemNode(1)], 63 pageInfo: { 64 __typename: 'PageInfo', 65 hasNextPage: true, 66 endCursor: '1', 67 }, 68 }, 69 }; 70 71 const pageTwo = { 72 __typename: 'Query', 73 items: { 74 __typename: 'ItemsConnection', 75 edges: [itemEdge(2)], 76 nodes: [itemNode(2)], 77 pageInfo: { 78 __typename: 'PageInfo', 79 hasNextPage: false, 80 endCursor: null, 81 }, 82 }, 83 }; 84 85 write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 86 write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo); 87 88 const res = query(store, { query: Pagination }); 89 90 expect(res.partial).toBe(false); 91 expect(res.data).toEqual({ 92 ...pageTwo, 93 items: { 94 ...pageTwo.items, 95 edges: [pageOne.items.edges[0], pageTwo.items.edges[0]], 96 nodes: [pageOne.items.nodes[0], pageTwo.items.nodes[0]], 97 }, 98 }); 99 }); 100 101 it('works with backwards pagination', () => { 102 const Pagination = gql` 103 query ($cursor: String) { 104 __typename 105 items(last: 1, before: $cursor) { 106 __typename 107 edges { 108 __typename 109 node { 110 __typename 111 id 112 } 113 } 114 nodes { 115 __typename 116 id 117 } 118 pageInfo { 119 __typename 120 hasPreviousPage 121 startCursor 122 } 123 } 124 } 125 `; 126 127 const store = new Store({ 128 resolvers: { 129 Query: { 130 items: relayPagination(), 131 }, 132 }, 133 }); 134 135 const pageOne = { 136 __typename: 'Query', 137 items: { 138 __typename: 'ItemsConnection', 139 edges: [itemEdge(2)], 140 nodes: [itemNode(2)], 141 pageInfo: { 142 __typename: 'PageInfo', 143 hasPreviousPage: true, 144 startCursor: '2', 145 }, 146 }, 147 }; 148 149 const pageTwo = { 150 __typename: 'Query', 151 items: { 152 __typename: 'ItemsConnection', 153 edges: [itemEdge(1)], 154 nodes: [itemNode(1)], 155 pageInfo: { 156 __typename: 'PageInfo', 157 hasPreviousPage: false, 158 startCursor: null, 159 }, 160 }, 161 }; 162 163 write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 164 write(store, { query: Pagination, variables: { cursor: '2' } }, pageTwo); 165 166 const res = query(store, { query: Pagination }); 167 168 expect(res.partial).toBe(false); 169 expect(res.data).toEqual({ 170 ...pageTwo, 171 items: { 172 ...pageTwo.items, 173 edges: [pageTwo.items.edges[0], pageOne.items.edges[0]], 174 nodes: [pageTwo.items.nodes[0], pageOne.items.nodes[0]], 175 }, 176 }); 177 }); 178 179 it('handles duplicate edges', () => { 180 const Pagination = gql` 181 query ($cursor: String) { 182 __typename 183 items(first: 2, after: $cursor) { 184 __typename 185 edges { 186 __typename 187 node { 188 __typename 189 id 190 } 191 } 192 nodes { 193 __typename 194 id 195 } 196 pageInfo { 197 __typename 198 hasNextPage 199 endCursor 200 } 201 } 202 } 203 `; 204 205 const store = new Store({ 206 resolvers: { 207 Query: { 208 items: relayPagination(), 209 }, 210 }, 211 }); 212 213 const pageOne = { 214 __typename: 'Query', 215 items: { 216 __typename: 'ItemsConnection', 217 edges: [itemEdge(1), itemEdge(2)], 218 nodes: [itemNode(1), itemNode(2)], 219 pageInfo: { 220 __typename: 'PageInfo', 221 hasNextPage: true, 222 endCursor: '2', 223 }, 224 }, 225 }; 226 227 const pageTwo = { 228 __typename: 'Query', 229 items: { 230 __typename: 'ItemsConnection', 231 edges: [itemEdge(2), itemEdge(3)], 232 nodes: [itemNode(2), itemNode(3)], 233 pageInfo: { 234 __typename: 'PageInfo', 235 hasNextPage: false, 236 endCursor: null, 237 }, 238 }, 239 }; 240 241 write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 242 write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo); 243 244 const res = query(store, { query: Pagination }); 245 246 expect(res.partial).toBe(false); 247 expect(res.data).toEqual({ 248 ...pageTwo, 249 items: { 250 ...pageTwo.items, 251 edges: [ 252 pageOne.items.edges[0], 253 pageTwo.items.edges[0], 254 pageTwo.items.edges[1], 255 ], 256 nodes: [ 257 pageOne.items.nodes[0], 258 pageTwo.items.nodes[0], 259 pageTwo.items.nodes[1], 260 ], 261 }, 262 }); 263 }); 264 265 it('works with simultaneous forward and backward pagination (outwards merging)', () => { 266 const Pagination = gql` 267 query ($first: Int, $last: Int, $before: String, $after: String) { 268 __typename 269 items(first: $first, last: $last, before: $before, after: $after) { 270 __typename 271 edges { 272 __typename 273 node { 274 __typename 275 id 276 } 277 } 278 nodes { 279 __typename 280 id 281 } 282 pageInfo { 283 __typename 284 hasPreviousPage 285 hasNextPage 286 startCursor 287 endCursor 288 } 289 } 290 } 291 `; 292 293 const store = new Store({ 294 resolvers: { 295 Query: { 296 items: relayPagination({ mergeMode: 'outwards' }), 297 }, 298 }, 299 }); 300 301 const pageOne = { 302 __typename: 'Query', 303 items: { 304 __typename: 'ItemsConnection', 305 edges: [itemEdge(1)], 306 nodes: [itemNode(1)], 307 pageInfo: { 308 __typename: 'PageInfo', 309 hasNextPage: true, 310 hasPreviousPage: false, 311 startCursor: null, 312 endCursor: '1', 313 }, 314 }, 315 }; 316 317 const pageTwo = { 318 __typename: 'Query', 319 items: { 320 __typename: 'ItemsConnection', 321 edges: [itemEdge(2)], 322 nodes: [itemNode(2)], 323 pageInfo: { 324 __typename: 'PageInfo', 325 hasNextPage: true, 326 hasPreviousPage: true, 327 startCursor: '2', 328 endCursor: '2', 329 }, 330 }, 331 }; 332 333 const pageThree = { 334 __typename: 'Query', 335 items: { 336 __typename: 'ItemsConnection', 337 edges: [itemEdge(-1)], 338 nodes: [itemNode(-1)], 339 pageInfo: { 340 __typename: 'PageInfo', 341 hasNextPage: false, 342 hasPreviousPage: true, 343 startCursor: '-1', 344 endCursor: null, 345 }, 346 }, 347 }; 348 349 write( 350 store, 351 { query: Pagination, variables: { after: '1', first: 1 } }, 352 pageOne 353 ); 354 write( 355 store, 356 { query: Pagination, variables: { after: '2', first: 1 } }, 357 pageTwo 358 ); 359 write( 360 store, 361 { query: Pagination, variables: { before: '1', last: 1 } }, 362 pageThree 363 ); 364 365 const res = query(store, { 366 query: Pagination, 367 variables: { before: '1', last: 1 }, 368 }); 369 370 expect(res.partial).toBe(false); 371 expect(res.data).toEqual({ 372 ...pageThree, 373 items: { 374 ...pageThree.items, 375 edges: [ 376 pageThree.items.edges[0], 377 pageOne.items.edges[0], 378 pageTwo.items.edges[0], 379 ], 380 nodes: [ 381 pageThree.items.nodes[0], 382 pageOne.items.nodes[0], 383 pageTwo.items.nodes[0], 384 ], 385 pageInfo: { 386 ...pageThree.items.pageInfo, 387 hasPreviousPage: true, 388 hasNextPage: true, 389 startCursor: '-1', 390 endCursor: '2', 391 }, 392 }, 393 }); 394 }); 395 396 it('works with simultaneous forward and backward pagination (inwards merging)', () => { 397 const Pagination = gql` 398 query ($first: Int, $last: Int, $before: String, $after: String) { 399 __typename 400 items(first: $first, last: $last, before: $before, after: $after) { 401 __typename 402 edges { 403 __typename 404 node { 405 __typename 406 id 407 } 408 } 409 nodes { 410 __typename 411 id 412 } 413 pageInfo { 414 __typename 415 hasPreviousPage 416 hasNextPage 417 startCursor 418 endCursor 419 } 420 } 421 } 422 `; 423 424 const store = new Store({ 425 resolvers: { 426 Query: { 427 items: relayPagination({ mergeMode: 'inwards' }), 428 }, 429 }, 430 }); 431 432 const pageOne = { 433 __typename: 'Query', 434 items: { 435 __typename: 'ItemsConnection', 436 edges: [itemEdge(1)], 437 nodes: [itemNode(1)], 438 pageInfo: { 439 __typename: 'PageInfo', 440 hasNextPage: true, 441 hasPreviousPage: false, 442 startCursor: null, 443 endCursor: '1', 444 }, 445 }, 446 }; 447 448 const pageTwo = { 449 __typename: 'Query', 450 items: { 451 __typename: 'ItemsConnection', 452 edges: [itemEdge(2)], 453 nodes: [itemNode(2)], 454 pageInfo: { 455 __typename: 'PageInfo', 456 hasNextPage: true, 457 hasPreviousPage: true, 458 startCursor: '2', 459 endCursor: '2', 460 }, 461 }, 462 }; 463 464 const pageThree = { 465 __typename: 'Query', 466 items: { 467 __typename: 'ItemsConnection', 468 edges: [itemEdge(-1)], 469 nodes: [itemNode(-1)], 470 pageInfo: { 471 __typename: 'PageInfo', 472 hasNextPage: false, 473 hasPreviousPage: true, 474 startCursor: '-1', 475 endCursor: null, 476 }, 477 }, 478 }; 479 480 write( 481 store, 482 { query: Pagination, variables: { after: '1', first: 1 } }, 483 pageOne 484 ); 485 write( 486 store, 487 { query: Pagination, variables: { after: '2', first: 1 } }, 488 pageTwo 489 ); 490 write( 491 store, 492 { query: Pagination, variables: { before: '1', last: 1 } }, 493 pageThree 494 ); 495 496 const res = query(store, { 497 query: Pagination, 498 variables: { before: '1', last: 1 }, 499 }); 500 501 expect(res.partial).toBe(false); 502 expect(res.data).toEqual({ 503 ...pageThree, 504 items: { 505 ...pageThree.items, 506 edges: [ 507 pageOne.items.edges[0], 508 pageTwo.items.edges[0], 509 pageThree.items.edges[0], 510 ], 511 nodes: [ 512 pageOne.items.nodes[0], 513 pageTwo.items.nodes[0], 514 pageThree.items.nodes[0], 515 ], 516 pageInfo: { 517 ...pageThree.items.pageInfo, 518 hasPreviousPage: true, 519 hasNextPage: true, 520 startCursor: '-1', 521 endCursor: '2', 522 }, 523 }, 524 }); 525 }); 526 527 it('prevents overlapping of pagination on different arguments', () => { 528 const Pagination = gql` 529 query ($filter: String) { 530 items(first: 1, filter: $filter) { 531 __typename 532 edges { 533 __typename 534 node { 535 __typename 536 id 537 } 538 } 539 nodes { 540 __typename 541 id 542 } 543 pageInfo { 544 __typename 545 hasNextPage 546 endCursor 547 } 548 } 549 } 550 `; 551 552 const store = new Store({ 553 resolvers: { 554 Query: { 555 items: relayPagination(), 556 }, 557 }, 558 }); 559 560 const page = withId => ({ 561 __typename: 'Query', 562 items: { 563 __typename: 'ItemsConnection', 564 edges: [itemEdge(withId)], 565 nodes: [itemNode(withId)], 566 pageInfo: { 567 __typename: 'PageInfo', 568 hasNextPage: false, 569 endCursor: null, 570 }, 571 }, 572 }); 573 574 write( 575 store, 576 { query: Pagination, variables: { filter: 'one' } }, 577 page('one') 578 ); 579 write( 580 store, 581 { query: Pagination, variables: { filter: 'two' } }, 582 page('two') 583 ); 584 585 const resOne = query(store, { 586 query: Pagination, 587 variables: { filter: 'one' }, 588 }); 589 const resTwo = query(store, { 590 query: Pagination, 591 variables: { filter: 'two' }, 592 }); 593 const resThree = query(store, { 594 query: Pagination, 595 variables: { filter: 'three' }, 596 }); 597 598 expect(resOne.data).toHaveProperty('items.edges[0].node.id', 'one'); 599 expect(resOne.data).toHaveProperty('items.edges.length', 1); 600 601 expect(resTwo.data).toHaveProperty('items.edges[0].node.id', 'two'); 602 expect(resTwo.data).toHaveProperty('items.edges.length', 1); 603 604 expect(resThree.data).toEqual(null); 605 }); 606 607 it('returns an empty array of edges when the cache has zero edges stored', () => { 608 const Pagination = gql` 609 query { 610 items(first: 1) { 611 __typename 612 edges { 613 __typename 614 } 615 nodes { 616 __typename 617 } 618 } 619 } 620 `; 621 622 const store = new Store({ 623 resolvers: { 624 Query: { 625 items: relayPagination(), 626 }, 627 }, 628 }); 629 630 write( 631 store, 632 { query: Pagination }, 633 { 634 __typename: 'Query', 635 items: { 636 __typename: 'ItemsConnection', 637 edges: [], 638 nodes: [], 639 }, 640 } 641 ); 642 643 const res = query(store, { 644 query: Pagination, 645 }); 646 647 expect(res.data).toHaveProperty('items', { 648 __typename: 'ItemsConnection', 649 edges: [], 650 nodes: [], 651 }); 652 }); 653 654 it('returns other fields on the same level as the edges', () => { 655 const Pagination = gql` 656 query { 657 __typename 658 items(first: 1) { 659 __typename 660 totalCount 661 } 662 } 663 `; 664 665 const store = new Store({ 666 resolvers: { 667 Query: { 668 items: relayPagination(), 669 }, 670 }, 671 }); 672 673 write( 674 store, 675 { query: Pagination }, 676 { 677 __typename: 'Query', 678 items: { 679 __typename: 'ItemsConnection', 680 totalCount: 2, 681 }, 682 } 683 ); 684 685 const resOne = query(store, { 686 query: Pagination, 687 }); 688 689 expect(resOne.data).toHaveProperty('items', { 690 __typename: 'ItemsConnection', 691 totalCount: 2, 692 }); 693 }); 694 695 it('returns a subset of the cached items if the query requests less items than the cached ones', () => { 696 const Pagination = gql` 697 query ($first: Int, $last: Int, $before: String, $after: String) { 698 __typename 699 items(first: $first, last: $last, before: $before, after: $after) { 700 __typename 701 edges { 702 __typename 703 node { 704 __typename 705 id 706 } 707 } 708 nodes { 709 __typename 710 id 711 } 712 pageInfo { 713 __typename 714 hasPreviousPage 715 hasNextPage 716 startCursor 717 endCursor 718 } 719 } 720 } 721 `; 722 723 const store = new Store({ 724 schema: require('../test-utils/relayPagination_schema.json'), 725 resolvers: { 726 Query: { 727 items: relayPagination({ mergeMode: 'outwards' }), 728 }, 729 }, 730 }); 731 732 const results = { 733 __typename: 'Query', 734 items: { 735 __typename: 'ItemsConnection', 736 edges: [ 737 itemEdge(1), 738 itemEdge(2), 739 itemEdge(3), 740 itemEdge(4), 741 itemEdge(5), 742 ], 743 nodes: [ 744 itemNode(1), 745 itemNode(2), 746 itemNode(3), 747 itemNode(4), 748 itemNode(5), 749 ], 750 pageInfo: { 751 __typename: 'PageInfo', 752 hasNextPage: true, 753 hasPreviousPage: false, 754 startCursor: '1', 755 endCursor: '5', 756 }, 757 }, 758 }; 759 760 write(store, { query: Pagination, variables: { first: 2 } }, results); 761 762 const res = query(store, { 763 query: Pagination, 764 variables: { first: 2 }, 765 }); 766 767 expect(res.partial).toBe(false); 768 expect(res.data).toEqual(results); 769 }); 770 771 it("returns the cached items even if they don't fullfil the query", () => { 772 const Pagination = gql` 773 query ($first: Int, $last: Int, $before: String, $after: String) { 774 __typename 775 items(first: $first, last: $last, before: $before, after: $after) { 776 __typename 777 edges { 778 __typename 779 node { 780 __typename 781 id 782 } 783 } 784 nodes { 785 __typename 786 id 787 } 788 pageInfo { 789 __typename 790 hasPreviousPage 791 hasNextPage 792 startCursor 793 endCursor 794 } 795 } 796 } 797 `; 798 799 const store = new Store({ 800 schema: require('../test-utils/relayPagination_schema.json'), 801 resolvers: { 802 Query: { 803 items: relayPagination(), 804 }, 805 }, 806 }); 807 808 const results = { 809 __typename: 'Query', 810 items: { 811 __typename: 'ItemsConnection', 812 edges: [ 813 itemEdge(1), 814 itemEdge(2), 815 itemEdge(3), 816 itemEdge(4), 817 itemEdge(5), 818 ], 819 nodes: [ 820 itemNode(1), 821 itemNode(2), 822 itemNode(3), 823 itemNode(4), 824 itemNode(5), 825 ], 826 pageInfo: { 827 __typename: 'PageInfo', 828 hasNextPage: true, 829 hasPreviousPage: false, 830 startCursor: '1', 831 endCursor: '5', 832 }, 833 }, 834 }; 835 836 write( 837 store, 838 { query: Pagination, variables: { after: '3', first: 3, last: 3 } }, 839 results 840 ); 841 842 const res = query(store, { 843 query: Pagination, 844 variables: { after: '3', first: 3, last: 3 }, 845 }); 846 847 expect(res.partial).toBe(false); 848 expect(res.data).toEqual(results); 849 }); 850 851 it('returns the cached items even when they come from a different query', () => { 852 const Pagination = gql` 853 query ($first: Int, $last: Int, $before: String, $after: String) { 854 __typename 855 items(first: $first, last: $last, before: $before, after: $after) { 856 __typename 857 edges { 858 __typename 859 node { 860 __typename 861 id 862 } 863 } 864 nodes { 865 __typename 866 id 867 } 868 pageInfo { 869 __typename 870 hasPreviousPage 871 hasNextPage 872 startCursor 873 endCursor 874 } 875 } 876 } 877 `; 878 879 const store = new Store({ 880 schema: require('../test-utils/relayPagination_schema.json'), 881 resolvers: { 882 Query: { 883 items: relayPagination(), 884 }, 885 }, 886 }); 887 888 const results = { 889 __typename: 'Query', 890 items: { 891 __typename: 'ItemsConnection', 892 edges: [ 893 itemEdge(1), 894 itemEdge(2), 895 itemEdge(3), 896 itemEdge(4), 897 itemEdge(5), 898 ], 899 nodes: [ 900 itemNode(1), 901 itemNode(2), 902 itemNode(3), 903 itemNode(4), 904 itemNode(5), 905 ], 906 pageInfo: { 907 __typename: 'PageInfo', 908 hasNextPage: true, 909 hasPreviousPage: false, 910 startCursor: '1', 911 endCursor: '5', 912 }, 913 }, 914 }; 915 916 write(store, { query: Pagination, variables: { first: 5 } }, results); 917 918 const res = query(store, { 919 query: Pagination, 920 variables: { after: '3', first: 2, last: 2 }, 921 }); 922 923 expect(res.partial).toBe(true); 924 expect(res.data).toEqual(results); 925 }); 926 927 it('caches and retrieves correctly queries with inwards pagination', () => { 928 const Pagination = gql` 929 query ($first: Int, $last: Int, $before: String, $after: String) { 930 __typename 931 items(first: $first, last: $last, before: $before, after: $after) { 932 __typename 933 edges { 934 __typename 935 node { 936 __typename 937 id 938 } 939 } 940 nodes { 941 __typename 942 id 943 } 944 pageInfo { 945 __typename 946 hasPreviousPage 947 hasNextPage 948 startCursor 949 endCursor 950 } 951 } 952 } 953 `; 954 955 const store = new Store({ 956 schema: require('../test-utils/relayPagination_schema.json'), 957 resolvers: { 958 Query: { 959 items: relayPagination(), 960 }, 961 }, 962 }); 963 964 const results = { 965 __typename: 'Query', 966 items: { 967 __typename: 'ItemsConnection', 968 edges: [ 969 itemEdge(1), 970 itemEdge(2), 971 itemEdge(3), 972 itemEdge(4), 973 itemEdge(5), 974 ], 975 nodes: [ 976 itemNode(1), 977 itemNode(2), 978 itemNode(3), 979 itemNode(4), 980 itemNode(5), 981 ], 982 pageInfo: { 983 __typename: 'PageInfo', 984 hasNextPage: true, 985 hasPreviousPage: false, 986 startCursor: '1', 987 endCursor: '5', 988 }, 989 }, 990 }; 991 992 write( 993 store, 994 { query: Pagination, variables: { after: '2', first: 2, last: 2 } }, 995 results 996 ); 997 998 const res = query(store, { 999 query: Pagination, 1000 variables: { after: '2', first: 2, last: 2 }, 1001 }); 1002 1003 expect(res.partial).toBe(false); 1004 expect(res.data).toEqual(results); 1005 }); 1006 1007 it('does not include a previous result when adding parameters', () => { 1008 const Pagination = gql` 1009 query ($first: Int, $filter: String) { 1010 __typename 1011 items(first: $first, filter: $filter) { 1012 __typename 1013 edges { 1014 __typename 1015 node { 1016 __typename 1017 id 1018 } 1019 } 1020 nodes { 1021 __typename 1022 id 1023 } 1024 pageInfo { 1025 __typename 1026 hasPreviousPage 1027 hasNextPage 1028 startCursor 1029 endCursor 1030 } 1031 } 1032 } 1033 `; 1034 1035 const store = new Store({ 1036 resolvers: { 1037 Query: { 1038 items: relayPagination(), 1039 }, 1040 }, 1041 }); 1042 1043 const results = { 1044 __typename: 'Query', 1045 items: { 1046 __typename: 'ItemsConnection', 1047 edges: [itemEdge(1), itemEdge(2)], 1048 nodes: [itemNode(1), itemNode(2)], 1049 pageInfo: { 1050 __typename: 'PageInfo', 1051 hasNextPage: true, 1052 hasPreviousPage: false, 1053 startCursor: '1', 1054 endCursor: '2', 1055 }, 1056 }, 1057 }; 1058 1059 const results2 = { 1060 __typename: 'Query', 1061 items: { 1062 __typename: 'ItemsConnection', 1063 edges: [], 1064 nodes: [], 1065 pageInfo: { 1066 __typename: 'PageInfo', 1067 hasNextPage: false, 1068 hasPreviousPage: false, 1069 startCursor: '1', 1070 endCursor: '2', 1071 }, 1072 }, 1073 }; 1074 1075 write(store, { query: Pagination, variables: { first: 2 } }, results); 1076 1077 write( 1078 store, 1079 { query: Pagination, variables: { first: 2, filter: 'b' } }, 1080 results2 1081 ); 1082 1083 const res = query(store, { 1084 query: Pagination, 1085 variables: { first: 2, filter: 'b' }, 1086 }); 1087 expect(res.data).toEqual(results2); 1088 }); 1089 1090 it('Works with edges absent from query', () => { 1091 const Pagination = gql` 1092 query ($first: Int, $last: Int, $before: String, $after: String) { 1093 __typename 1094 items(first: $first, last: $last, before: $before, after: $after) { 1095 __typename 1096 nodes { 1097 __typename 1098 id 1099 } 1100 pageInfo { 1101 __typename 1102 hasPreviousPage 1103 hasNextPage 1104 startCursor 1105 endCursor 1106 } 1107 } 1108 } 1109 `; 1110 1111 const store = new Store({ 1112 schema: require('../test-utils/relayPagination_schema.json'), 1113 resolvers: { 1114 Query: { 1115 items: relayPagination({ mergeMode: 'outwards' }), 1116 }, 1117 }, 1118 }); 1119 1120 const results = { 1121 __typename: 'Query', 1122 items: { 1123 __typename: 'ItemsConnection', 1124 nodes: [ 1125 itemNode(1), 1126 itemNode(2), 1127 itemNode(3), 1128 itemNode(4), 1129 itemNode(5), 1130 ], 1131 pageInfo: { 1132 __typename: 'PageInfo', 1133 hasNextPage: true, 1134 hasPreviousPage: false, 1135 startCursor: '1', 1136 endCursor: '5', 1137 }, 1138 }, 1139 }; 1140 1141 write(store, { query: Pagination, variables: { first: 2 } }, results); 1142 1143 const res = query(store, { 1144 query: Pagination, 1145 variables: { first: 2 }, 1146 }); 1147 1148 expect(res.partial).toBe(false); 1149 expect(res.data).toEqual(results); 1150 }); 1151 1152 it('Works with nodes absent from query', () => { 1153 const Pagination = gql` 1154 query ($first: Int, $last: Int, $before: String, $after: String) { 1155 __typename 1156 items(first: $first, last: $last, before: $before, after: $after) { 1157 __typename 1158 edges { 1159 __typename 1160 node { 1161 __typename 1162 id 1163 } 1164 } 1165 pageInfo { 1166 __typename 1167 hasPreviousPage 1168 hasNextPage 1169 startCursor 1170 endCursor 1171 } 1172 } 1173 } 1174 `; 1175 1176 const store = new Store({ 1177 schema: require('../test-utils/relayPagination_schema.json'), 1178 resolvers: { 1179 Query: { 1180 items: relayPagination({ mergeMode: 'outwards' }), 1181 }, 1182 }, 1183 }); 1184 1185 const results = { 1186 __typename: 'Query', 1187 items: { 1188 __typename: 'ItemsConnection', 1189 edges: [ 1190 itemEdge(1), 1191 itemEdge(2), 1192 itemEdge(3), 1193 itemEdge(4), 1194 itemEdge(5), 1195 ], 1196 pageInfo: { 1197 __typename: 'PageInfo', 1198 hasNextPage: true, 1199 hasPreviousPage: false, 1200 startCursor: '1', 1201 endCursor: '5', 1202 }, 1203 }, 1204 }; 1205 1206 write(store, { query: Pagination, variables: { first: 2 } }, results); 1207 1208 const res = query(store, { 1209 query: Pagination, 1210 variables: { first: 2 }, 1211 }); 1212 1213 expect(res.partial).toBe(false); 1214 expect(res.data).toEqual(results); 1215 }); 1216 1217 it('handles subsequent queries with larger last values', () => { 1218 const Pagination = gql` 1219 query ($last: Int!) { 1220 __typename 1221 items(last: $last) { 1222 __typename 1223 edges { 1224 __typename 1225 node { 1226 __typename 1227 id 1228 } 1229 } 1230 nodes { 1231 __typename 1232 id 1233 } 1234 pageInfo { 1235 __typename 1236 hasPreviousPage 1237 startCursor 1238 } 1239 } 1240 } 1241 `; 1242 1243 const store = new Store({ 1244 resolvers: { 1245 Query: { 1246 items: relayPagination(), 1247 }, 1248 }, 1249 }); 1250 1251 const pageOne = { 1252 __typename: 'Query', 1253 items: { 1254 __typename: 'ItemsConnection', 1255 edges: [itemEdge(2)], 1256 nodes: [itemNode(2)], 1257 pageInfo: { 1258 __typename: 'PageInfo', 1259 hasPreviousPage: true, 1260 startCursor: '2', 1261 }, 1262 }, 1263 }; 1264 1265 const pageTwo = { 1266 __typename: 'Query', 1267 items: { 1268 __typename: 'ItemsConnection', 1269 edges: [itemEdge(1), itemEdge(2)], 1270 nodes: [itemNode(1), itemNode(2)], 1271 pageInfo: { 1272 __typename: 'PageInfo', 1273 hasPreviousPage: false, 1274 startCursor: '1', 1275 }, 1276 }, 1277 }; 1278 1279 const pageThree = { 1280 __typename: 'Query', 1281 items: { 1282 __typename: 'ItemsConnection', 1283 edges: [itemEdge(0), itemEdge(1), itemEdge(2)], 1284 nodes: [itemNode(0), itemNode(1), itemNode(2)], 1285 pageInfo: { 1286 __typename: 'PageInfo', 1287 hasPreviousPage: false, 1288 startCursor: '0', 1289 }, 1290 }, 1291 }; 1292 1293 write(store, { query: Pagination, variables: { last: 1 } }, pageOne); 1294 write(store, { query: Pagination, variables: { last: 2 } }, pageTwo); 1295 1296 let res = query(store, { query: Pagination, variables: { last: 2 } }); 1297 1298 expect(res.partial).toBe(false); 1299 expect(res.data).toEqual(pageTwo); 1300 1301 write(store, { query: Pagination, variables: { last: 3 } }, pageThree); 1302 1303 res = query(store, { query: Pagination, variables: { last: 3 } }); 1304 1305 expect(res.partial).toBe(false); 1306 expect(res.data).toEqual(pageThree); 1307 }); 1308 1309 it('handles subsequent queries with larger first values', () => { 1310 const Pagination = gql` 1311 query ($first: Int!) { 1312 __typename 1313 items(first: $first) { 1314 __typename 1315 edges { 1316 __typename 1317 node { 1318 __typename 1319 id 1320 } 1321 } 1322 nodes { 1323 __typename 1324 id 1325 } 1326 pageInfo { 1327 __typename 1328 hasNextPage 1329 endCursor 1330 } 1331 } 1332 } 1333 `; 1334 1335 const store = new Store({ 1336 resolvers: { 1337 Query: { 1338 items: relayPagination(), 1339 }, 1340 }, 1341 }); 1342 1343 const pageOne = { 1344 __typename: 'Query', 1345 items: { 1346 __typename: 'ItemsConnection', 1347 edges: [itemEdge(1)], 1348 nodes: [itemNode(1)], 1349 pageInfo: { 1350 __typename: 'PageInfo', 1351 hasNextPage: true, 1352 endCursor: '1', 1353 }, 1354 }, 1355 }; 1356 1357 const pageTwo = { 1358 __typename: 'Query', 1359 items: { 1360 __typename: 'ItemsConnection', 1361 edges: [itemEdge(1), itemEdge(2)], 1362 nodes: [itemNode(1), itemNode(2)], 1363 pageInfo: { 1364 __typename: 'PageInfo', 1365 hasNextPage: false, 1366 endCursor: '2', 1367 }, 1368 }, 1369 }; 1370 1371 write(store, { query: Pagination, variables: { first: 1 } }, pageOne); 1372 write(store, { query: Pagination, variables: { first: 2 } }, pageTwo); 1373 1374 const res = query(store, { query: Pagination, variables: { first: 2 } }); 1375 1376 expect(res.partial).toBe(false); 1377 expect(res.data).toEqual(pageTwo); 1378 }); 1379 1380 it('ignores empty pages when paginating', () => { 1381 const PaginationForward = gql` 1382 query ($first: Int!, $after: String) { 1383 __typename 1384 items(first: $first, after: $after) { 1385 __typename 1386 nodes { 1387 __typename 1388 id 1389 } 1390 pageInfo { 1391 __typename 1392 startCursor 1393 endCursor 1394 } 1395 } 1396 } 1397 `; 1398 const PaginationBackward = gql` 1399 query ($last: Int!, $before: String) { 1400 __typename 1401 items(last: $last, before: $before) { 1402 __typename 1403 nodes { 1404 __typename 1405 id 1406 } 1407 pageInfo { 1408 __typename 1409 startCursor 1410 endCursor 1411 } 1412 } 1413 } 1414 `; 1415 1416 const store = new Store({ 1417 resolvers: { 1418 Query: { 1419 items: relayPagination(), 1420 }, 1421 }, 1422 }); 1423 1424 const forwardOne = { 1425 __typename: 'Query', 1426 items: { 1427 __typename: 'ItemsConnection', 1428 nodes: [itemNode(1), itemNode(2)], 1429 pageInfo: { 1430 __typename: 'PageInfo', 1431 startCursor: '1', 1432 endCursor: '2', 1433 }, 1434 }, 1435 }; 1436 const forwardAfter = { 1437 __typename: 'Query', 1438 items: { 1439 __typename: 'ItemsConnection', 1440 nodes: [], 1441 pageInfo: { 1442 __typename: 'PageInfo', 1443 startCursor: null, 1444 endCursor: null, 1445 }, 1446 }, 1447 }; 1448 const backwardBefore = { 1449 __typename: 'Query', 1450 items: { 1451 __typename: 'ItemsConnection', 1452 nodes: [], 1453 pageInfo: { 1454 __typename: 'PageInfo', 1455 startCursor: null, 1456 endCursor: null, 1457 }, 1458 }, 1459 }; 1460 1461 write( 1462 store, 1463 { query: PaginationForward, variables: { first: 2 } }, 1464 forwardOne 1465 ); 1466 write( 1467 store, 1468 { query: PaginationBackward, variables: { last: 1, before: '1' } }, 1469 backwardBefore 1470 ); 1471 1472 const res = query(store, { 1473 query: PaginationForward, 1474 variables: { first: 2 }, 1475 }); 1476 1477 expect(res.partial).toBe(false); 1478 expect(res.data).toEqual(forwardOne); 1479 write( 1480 store, 1481 { query: PaginationForward, variables: { first: 1, after: '2' } }, 1482 forwardAfter 1483 ); 1484 1485 expect(res.partial).toBe(false); 1486 expect(res.data).toEqual(forwardOne); 1487 }); 1488 1489 it('allows for an empty page when this is the only result', () => { 1490 const Pagination = gql` 1491 query ($first: Int!, $after: String) { 1492 __typename 1493 items(first: $first, after: $after) { 1494 __typename 1495 nodes { 1496 __typename 1497 id 1498 } 1499 pageInfo { 1500 __typename 1501 startCursor 1502 endCursor 1503 } 1504 } 1505 } 1506 `; 1507 1508 const store = new Store({ 1509 resolvers: { 1510 Query: { 1511 items: relayPagination(), 1512 }, 1513 }, 1514 }); 1515 1516 const pageOne = { 1517 __typename: 'Query', 1518 items: { 1519 __typename: 'ItemsConnection', 1520 nodes: [], 1521 pageInfo: { 1522 __typename: 'PageInfo', 1523 startCursor: null, 1524 endCursor: null, 1525 }, 1526 }, 1527 }; 1528 1529 write(store, { query: Pagination, variables: { first: 2 } }, pageOne); 1530 const res = query(store, { 1531 query: Pagination, 1532 variables: { first: 2 }, 1533 }); 1534 1535 expect(res.partial).toBe(false); 1536 expect(res.data).toEqual(pageOne); 1537 }); 1538}); 1539 1540describe('as directive', () => { 1541 it('works with forward pagination', () => { 1542 const Pagination = gql` 1543 query ($cursor: String) { 1544 __typename 1545 items(first: 1, after: $cursor) @_relayPagination { 1546 __typename 1547 edges { 1548 __typename 1549 node { 1550 __typename 1551 id 1552 } 1553 } 1554 nodes { 1555 __typename 1556 id 1557 } 1558 pageInfo { 1559 __typename 1560 hasNextPage 1561 endCursor 1562 } 1563 } 1564 } 1565 `; 1566 1567 const store = new Store({ 1568 directives: { 1569 relayPagination: () => relayPagination(), 1570 }, 1571 }); 1572 1573 const pageOne = { 1574 __typename: 'Query', 1575 items: { 1576 __typename: 'ItemsConnection', 1577 edges: [itemEdge(1)], 1578 nodes: [itemNode(1)], 1579 pageInfo: { 1580 __typename: 'PageInfo', 1581 hasNextPage: true, 1582 endCursor: '1', 1583 }, 1584 }, 1585 }; 1586 1587 const pageTwo = { 1588 __typename: 'Query', 1589 items: { 1590 __typename: 'ItemsConnection', 1591 edges: [itemEdge(2)], 1592 nodes: [itemNode(2)], 1593 pageInfo: { 1594 __typename: 'PageInfo', 1595 hasNextPage: false, 1596 endCursor: null, 1597 }, 1598 }, 1599 }; 1600 1601 write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 1602 write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo); 1603 1604 const res = query(store, { query: Pagination }); 1605 1606 expect(res.partial).toBe(false); 1607 expect(res.data).toEqual({ 1608 ...pageTwo, 1609 items: { 1610 ...pageTwo.items, 1611 edges: [pageOne.items.edges[0], pageTwo.items.edges[0]], 1612 nodes: [pageOne.items.nodes[0], pageTwo.items.nodes[0]], 1613 }, 1614 }); 1615 }); 1616});