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});