Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

feat(graphcache): local directives (#3306)

Co-authored-by: Phil Pluckthun <phil@kitten.sh>

authored by

Jovi De Croock
Phil Pluckthun
and committed by
GitHub
7c509f48 c37a0fc3

+2057 -1520
+1 -1
docs/graphcache/cache-updates.md
··· 1 1 --- 2 2 title: Cache Updates 3 - order: 3 3 + order: 4 4 4 --- 5 5 6 6 # Cache Updates
+6 -1
docs/graphcache/errors.md
··· 1 1 --- 2 2 title: Errors 3 - order: 7 3 + order: 8 4 4 --- 5 5 6 6 # Help! ··· 411 411 412 412 Please make sure that you're not calling `cache.updateQuery`, 413 413 `cache.writeFragment`, or `cache.link` inside `resolvers`. 414 + 415 + ## (28) Resolver and directive match the same field 416 + 417 + When you have a resolver defined on a field you shouln't be combining it with a directive as the directive 418 + will apply and the resolver will be void.
+35
docs/graphcache/local-directives.md
··· 1 + --- 2 + title: Local Directives 3 + order: 3 4 + --- 5 + 6 + # Local Directives 7 + 8 + Graphcache supports adding directives to GraphQL Documents, when we prefix a 9 + directive with an underscore (`_`) it will be stripped from the document and stored 10 + on the `_directives` property on the AST-node. 11 + 12 + > Ensure you prefix directives with `_` if you only want to alter local behavior. 13 + 14 + By default graphcache will add two directives `@_optional` and `@_required` which 15 + allow you to mark fields as being optional or mandatory. 16 + 17 + If you want to add directives yourself you can do so by performing 18 + 19 + ```js 20 + cacheExchange({ 21 + directives: { 22 + // If you now add `@_pagination` to your document we will execute this 23 + pagination: directiveArguments => () => { 24 + /* Resolver */ 25 + }, 26 + }, 27 + }); 28 + ``` 29 + 30 + The function signature of a directive is a function which receives the arguments the directive is called with in the document. 31 + That function should returns a [Resolver](./local-directives.md). 32 + 33 + ### Reading on 34 + 35 + [On the next page we'll learn about "Cache Updates".](./cache-updates.md)
+1 -1
docs/graphcache/local-resolvers.md
··· 565 565 566 566 ### Reading on 567 567 568 - [On the next page we'll learn about "Cache Updates".](./cache-updates.md) 568 + [On the next page we'll learn about "Cache Directives".](./local-directives.md)
+1 -1
docs/graphcache/offline.md
··· 1 1 --- 2 2 title: Offline Support 3 - order: 6 3 + order: 7 4 4 --- 5 5 6 6 # Offline Support
+4 -4
docs/graphcache/schema-awareness.md
··· 1 1 --- 2 2 title: Schema Awareness 3 - order: 4 3 + order: 5 4 4 --- 5 5 6 6 # Schema Awareness ··· 19 19 ```js 20 20 const introspectedSchema = { 21 21 __schema: { 22 - queryType: { name: 'Query', }, 23 - mutationType: { name: 'Mutation', }, 24 - subscriptionType: { name: 'Subscription', }, 22 + queryType: { name: 'Query' }, 23 + mutationType: { name: 'Mutation' }, 24 + subscriptionType: { name: 'Subscription' }, 25 25 }, 26 26 }; 27 27
+2 -1
exchanges/graphcache/src/ast/variables.ts
··· 1 1 import { 2 2 FieldNode, 3 + DirectiveNode, 3 4 OperationDefinitionNode, 4 5 valueFromASTUntyped, 5 6 } from '@0no-co/graphql.web'; ··· 10 11 11 12 /** Evaluates a fields arguments taking vars into account */ 12 13 export const getFieldArguments = ( 13 - node: FieldNode, 14 + node: FieldNode | DirectiveNode, 14 15 vars: Variables 15 16 ): null | Variables => { 16 17 let args: null | Variables = null;
+142
exchanges/graphcache/src/cacheExchange.test.ts
··· 6 6 OperationResult, 7 7 CombinedError, 8 8 } from '@urql/core'; 9 + import { print, stripIgnoredCharacters } from 'graphql'; 9 10 10 11 import { vi, expect, it, describe } from 'vitest'; 11 12 ··· 674 675 expect(result).toHaveBeenCalledTimes(1); 675 676 expect(reexecuteOperation).toHaveBeenCalledTimes(0); 676 677 expect(result.mock.calls[0][0]).toHaveProperty('data.author', null); 678 + }); 679 + }); 680 + 681 + describe('directives', () => { 682 + it('returns optional fields as partial', () => { 683 + const client = createClient({ 684 + url: 'http://0.0.0.0', 685 + exchanges: [], 686 + }); 687 + const { source: ops$, next } = makeSubject<Operation>(); 688 + 689 + const query = gql` 690 + { 691 + todos { 692 + id 693 + text 694 + completed @_optional 695 + } 696 + } 697 + `; 698 + 699 + const operation = client.createRequestOperation('query', { 700 + key: 1, 701 + query, 702 + variables: undefined, 703 + }); 704 + 705 + const queryResult: OperationResult = { 706 + ...queryResponse, 707 + operation, 708 + data: { 709 + __typename: 'Query', 710 + todos: [ 711 + { 712 + id: '1', 713 + text: 'learn urql', 714 + __typename: 'Todo', 715 + }, 716 + ], 717 + }, 718 + }; 719 + 720 + const reexecuteOperation = vi 721 + .spyOn(client, 'reexecuteOperation') 722 + .mockImplementation(next); 723 + 724 + const response = vi.fn((forwardOp: Operation): OperationResult => { 725 + if (forwardOp.key === 1) return queryResult; 726 + return undefined as any; 727 + }); 728 + 729 + const result = vi.fn(); 730 + const forward: ExchangeIO = ops$ => pipe(ops$, map(response), share); 731 + 732 + pipe( 733 + cacheExchange({})({ forward, client, dispatchDebug })(ops$), 734 + tap(result), 735 + publish 736 + ); 737 + 738 + next(operation); 739 + 740 + expect(response).toHaveBeenCalledTimes(1); 741 + expect(result).toHaveBeenCalledTimes(1); 742 + expect(reexecuteOperation).toHaveBeenCalledTimes(0); 743 + expect(result.mock.calls[0][0].data).toEqual({ 744 + todos: [ 745 + { 746 + completed: null, 747 + id: '1', 748 + text: 'learn urql', 749 + }, 750 + ], 751 + }); 752 + }); 753 + 754 + it('does not return missing required fields', () => { 755 + const client = createClient({ 756 + url: 'http://0.0.0.0', 757 + exchanges: [], 758 + }); 759 + const { source: ops$, next } = makeSubject<Operation>(); 760 + 761 + const query = gql` 762 + { 763 + todos { 764 + id 765 + text 766 + completed @_required 767 + } 768 + } 769 + `; 770 + 771 + const operation = client.createRequestOperation('query', { 772 + key: 1, 773 + query, 774 + variables: undefined, 775 + }); 776 + 777 + const queryResult: OperationResult = { 778 + ...queryResponse, 779 + operation, 780 + data: { 781 + __typename: 'Query', 782 + todos: [ 783 + { 784 + id: '1', 785 + text: 'learn urql', 786 + __typename: 'Todo', 787 + }, 788 + ], 789 + }, 790 + }; 791 + 792 + const reexecuteOperation = vi 793 + .spyOn(client, 'reexecuteOperation') 794 + .mockImplementation(next); 795 + 796 + const response = vi.fn((forwardOp: Operation): OperationResult => { 797 + if (forwardOp.key === 1) return queryResult; 798 + return undefined as any; 799 + }); 800 + 801 + const result = vi.fn(); 802 + const forward: ExchangeIO = ops$ => pipe(ops$, map(response), share); 803 + 804 + pipe( 805 + cacheExchange({})({ forward, client, dispatchDebug })(ops$), 806 + tap(result), 807 + publish 808 + ); 809 + 810 + next(operation); 811 + 812 + expect(response).toHaveBeenCalledTimes(1); 813 + expect(result).toHaveBeenCalledTimes(1); 814 + expect( 815 + stripIgnoredCharacters(print(response.mock.calls[0][0].query)) 816 + ).toEqual('{todos{id text completed __typename}}'); 817 + expect(reexecuteOperation).toHaveBeenCalledTimes(0); 818 + expect(result.mock.calls[0][0].data).toEqual(null); 677 819 }); 678 820 }); 679 821
+1290 -1150
exchanges/graphcache/src/extras/relayPagination.test.ts
··· 1 1 import { gql } from '@urql/core'; 2 - import { it, expect } from 'vitest'; 2 + import { it, expect, describe } from 'vitest'; 3 3 import { __initAnd_query as query } from '../operations/query'; 4 4 import { __initAnd_write as write } from '../operations/write'; 5 5 import { Store } from '../store/store'; ··· 19 19 }; 20 20 } 21 21 22 - it('works with forward pagination', () => { 23 - const Pagination = gql` 24 - query ($cursor: String) { 25 - __typename 26 - items(first: 1, after: $cursor) { 22 + describe('as resolver', () => { 23 + it('works with forward pagination', () => { 24 + const Pagination = gql` 25 + query ($cursor: String) { 27 26 __typename 28 - edges { 27 + items(first: 1, after: $cursor) { 29 28 __typename 30 - node { 29 + edges { 30 + __typename 31 + node { 32 + __typename 33 + id 34 + } 35 + } 36 + nodes { 31 37 __typename 32 38 id 33 39 } 34 - } 35 - nodes { 36 - __typename 37 - id 38 - } 39 - pageInfo { 40 - __typename 41 - hasNextPage 42 - endCursor 40 + pageInfo { 41 + __typename 42 + hasNextPage 43 + endCursor 44 + } 43 45 } 44 46 } 45 - } 46 - `; 47 + `; 47 48 48 - const store = new Store({ 49 - resolvers: { 50 - Query: { 51 - items: relayPagination(), 49 + const store = new Store({ 50 + resolvers: { 51 + Query: { 52 + items: relayPagination(), 53 + }, 52 54 }, 53 - }, 54 - }); 55 + }); 55 56 56 - const pageOne = { 57 - __typename: 'Query', 58 - items: { 59 - __typename: 'ItemsConnection', 60 - edges: [itemEdge(1)], 61 - nodes: [itemNode(1)], 62 - pageInfo: { 63 - __typename: 'PageInfo', 64 - hasNextPage: true, 65 - endCursor: '1', 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 + }, 66 68 }, 67 - }, 68 - }; 69 + }; 69 70 70 - const pageTwo = { 71 - __typename: 'Query', 72 - items: { 73 - __typename: 'ItemsConnection', 74 - edges: [itemEdge(2)], 75 - nodes: [itemNode(2)], 76 - pageInfo: { 77 - __typename: 'PageInfo', 78 - hasNextPage: false, 79 - endCursor: null, 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 + }, 80 82 }, 81 - }, 82 - }; 83 + }; 83 84 84 - write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 85 - write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo); 85 + write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 86 + write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo); 86 87 87 - const res = query(store, { query: Pagination }); 88 + const res = query(store, { query: Pagination }); 88 89 89 - expect(res.partial).toBe(false); 90 - expect(res.data).toEqual({ 91 - ...pageTwo, 92 - items: { 93 - ...pageTwo.items, 94 - edges: [pageOne.items.edges[0], pageTwo.items.edges[0]], 95 - nodes: [pageOne.items.nodes[0], pageTwo.items.nodes[0]], 96 - }, 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 + }); 97 99 }); 98 - }); 99 100 100 - it('works with backwards pagination', () => { 101 - const Pagination = gql` 102 - query ($cursor: String) { 103 - __typename 104 - items(last: 1, before: $cursor) { 101 + it('works with backwards pagination', () => { 102 + const Pagination = gql` 103 + query ($cursor: String) { 105 104 __typename 106 - edges { 105 + items(last: 1, before: $cursor) { 107 106 __typename 108 - node { 107 + edges { 108 + __typename 109 + node { 110 + __typename 111 + id 112 + } 113 + } 114 + nodes { 109 115 __typename 110 116 id 111 117 } 112 - } 113 - nodes { 114 - __typename 115 - id 116 - } 117 - pageInfo { 118 - __typename 119 - hasPreviousPage 120 - startCursor 118 + pageInfo { 119 + __typename 120 + hasPreviousPage 121 + startCursor 122 + } 121 123 } 122 124 } 123 - } 124 - `; 125 + `; 125 126 126 - const store = new Store({ 127 - resolvers: { 128 - Query: { 129 - items: relayPagination(), 127 + const store = new Store({ 128 + resolvers: { 129 + Query: { 130 + items: relayPagination(), 131 + }, 130 132 }, 131 - }, 132 - }); 133 + }); 133 134 134 - const pageOne = { 135 - __typename: 'Query', 136 - items: { 137 - __typename: 'ItemsConnection', 138 - edges: [itemEdge(2)], 139 - nodes: [itemNode(2)], 140 - pageInfo: { 141 - __typename: 'PageInfo', 142 - hasPreviousPage: true, 143 - startCursor: '2', 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 + }, 144 146 }, 145 - }, 146 - }; 147 + }; 147 148 148 - const pageTwo = { 149 - __typename: 'Query', 150 - items: { 151 - __typename: 'ItemsConnection', 152 - edges: [itemEdge(1)], 153 - nodes: [itemNode(1)], 154 - pageInfo: { 155 - __typename: 'PageInfo', 156 - hasPreviousPage: false, 157 - startCursor: null, 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 + }, 158 160 }, 159 - }, 160 - }; 161 + }; 161 162 162 - write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 163 - write(store, { query: Pagination, variables: { cursor: '2' } }, pageTwo); 163 + write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 164 + write(store, { query: Pagination, variables: { cursor: '2' } }, pageTwo); 164 165 165 - const res = query(store, { query: Pagination }); 166 + const res = query(store, { query: Pagination }); 166 167 167 - expect(res.partial).toBe(false); 168 - expect(res.data).toEqual({ 169 - ...pageTwo, 170 - items: { 171 - ...pageTwo.items, 172 - edges: [pageTwo.items.edges[0], pageOne.items.edges[0]], 173 - nodes: [pageTwo.items.nodes[0], pageOne.items.nodes[0]], 174 - }, 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 + }); 175 177 }); 176 - }); 177 178 178 - it('handles duplicate edges', () => { 179 - const Pagination = gql` 180 - query ($cursor: String) { 181 - __typename 182 - items(first: 2, after: $cursor) { 179 + it('handles duplicate edges', () => { 180 + const Pagination = gql` 181 + query ($cursor: String) { 183 182 __typename 184 - edges { 183 + items(first: 2, after: $cursor) { 185 184 __typename 186 - node { 185 + edges { 186 + __typename 187 + node { 188 + __typename 189 + id 190 + } 191 + } 192 + nodes { 187 193 __typename 188 194 id 189 195 } 190 - } 191 - nodes { 192 - __typename 193 - id 194 - } 195 - pageInfo { 196 - __typename 197 - hasNextPage 198 - endCursor 196 + pageInfo { 197 + __typename 198 + hasNextPage 199 + endCursor 200 + } 199 201 } 200 202 } 201 - } 202 - `; 203 + `; 203 204 204 - const store = new Store({ 205 - resolvers: { 206 - Query: { 207 - items: relayPagination(), 205 + const store = new Store({ 206 + resolvers: { 207 + Query: { 208 + items: relayPagination(), 209 + }, 208 210 }, 209 - }, 210 - }); 211 + }); 211 212 212 - const pageOne = { 213 - __typename: 'Query', 214 - items: { 215 - __typename: 'ItemsConnection', 216 - edges: [itemEdge(1), itemEdge(2)], 217 - nodes: [itemNode(1), itemNode(2)], 218 - pageInfo: { 219 - __typename: 'PageInfo', 220 - hasNextPage: true, 221 - endCursor: '2', 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 + }, 222 224 }, 223 - }, 224 - }; 225 + }; 225 226 226 - const pageTwo = { 227 - __typename: 'Query', 228 - items: { 229 - __typename: 'ItemsConnection', 230 - edges: [itemEdge(2), itemEdge(3)], 231 - nodes: [itemNode(2), itemNode(3)], 232 - pageInfo: { 233 - __typename: 'PageInfo', 234 - hasNextPage: false, 235 - endCursor: null, 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 + }, 236 238 }, 237 - }, 238 - }; 239 + }; 239 240 240 - write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 241 - write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo); 241 + write(store, { query: Pagination, variables: { cursor: null } }, pageOne); 242 + write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo); 242 243 243 - const res = query(store, { query: Pagination }); 244 + const res = query(store, { query: Pagination }); 244 245 245 - expect(res.partial).toBe(false); 246 - expect(res.data).toEqual({ 247 - ...pageTwo, 248 - items: { 249 - ...pageTwo.items, 250 - edges: [ 251 - pageOne.items.edges[0], 252 - pageTwo.items.edges[0], 253 - pageTwo.items.edges[1], 254 - ], 255 - nodes: [ 256 - pageOne.items.nodes[0], 257 - pageTwo.items.nodes[0], 258 - pageTwo.items.nodes[1], 259 - ], 260 - }, 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 + }); 261 263 }); 262 - }); 263 264 264 - it('works with simultaneous forward and backward pagination (outwards merging)', () => { 265 - const Pagination = gql` 266 - query ($first: Int, $last: Int, $before: String, $after: String) { 267 - __typename 268 - items(first: $first, last: $last, before: $before, after: $after) { 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) { 269 268 __typename 270 - edges { 269 + items(first: $first, last: $last, before: $before, after: $after) { 271 270 __typename 272 - node { 271 + edges { 272 + __typename 273 + node { 274 + __typename 275 + id 276 + } 277 + } 278 + nodes { 273 279 __typename 274 280 id 275 281 } 276 - } 277 - nodes { 278 - __typename 279 - id 280 - } 281 - pageInfo { 282 - __typename 283 - hasPreviousPage 284 - hasNextPage 285 - startCursor 286 - endCursor 282 + pageInfo { 283 + __typename 284 + hasPreviousPage 285 + hasNextPage 286 + startCursor 287 + endCursor 288 + } 287 289 } 288 290 } 289 - } 290 - `; 291 + `; 291 292 292 - const store = new Store({ 293 - resolvers: { 294 - Query: { 295 - items: relayPagination({ mergeMode: 'outwards' }), 293 + const store = new Store({ 294 + resolvers: { 295 + Query: { 296 + items: relayPagination({ mergeMode: 'outwards' }), 297 + }, 296 298 }, 297 - }, 298 - }); 299 + }); 299 300 300 - const pageOne = { 301 - __typename: 'Query', 302 - items: { 303 - __typename: 'ItemsConnection', 304 - edges: [itemEdge(1)], 305 - nodes: [itemNode(1)], 306 - pageInfo: { 307 - __typename: 'PageInfo', 308 - hasNextPage: true, 309 - hasPreviousPage: false, 310 - startCursor: null, 311 - endCursor: '1', 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 + }, 312 314 }, 313 - }, 314 - }; 315 + }; 315 316 316 - const pageTwo = { 317 - __typename: 'Query', 318 - items: { 319 - __typename: 'ItemsConnection', 320 - edges: [itemEdge(2)], 321 - nodes: [itemNode(2)], 322 - pageInfo: { 323 - __typename: 'PageInfo', 324 - hasNextPage: true, 325 - hasPreviousPage: true, 326 - startCursor: '2', 327 - endCursor: '2', 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 + }, 328 330 }, 329 - }, 330 - }; 331 + }; 331 332 332 - const pageThree = { 333 - __typename: 'Query', 334 - items: { 335 - __typename: 'ItemsConnection', 336 - edges: [itemEdge(-1)], 337 - nodes: [itemNode(-1)], 338 - pageInfo: { 339 - __typename: 'PageInfo', 340 - hasNextPage: false, 341 - hasPreviousPage: true, 342 - startCursor: '-1', 343 - endCursor: null, 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 + }, 344 346 }, 345 - }, 346 - }; 347 + }; 347 348 348 - write( 349 - store, 350 - { query: Pagination, variables: { after: '1', first: 1 } }, 351 - pageOne 352 - ); 353 - write( 354 - store, 355 - { query: Pagination, variables: { after: '2', first: 1 } }, 356 - pageTwo 357 - ); 358 - write( 359 - store, 360 - { query: Pagination, variables: { before: '1', last: 1 } }, 361 - pageThree 362 - ); 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 + ); 363 364 364 - const res = query(store, { 365 - query: Pagination, 366 - variables: { before: '1', last: 1 }, 367 - }); 365 + const res = query(store, { 366 + query: Pagination, 367 + variables: { before: '1', last: 1 }, 368 + }); 368 369 369 - expect(res.partial).toBe(false); 370 - expect(res.data).toEqual({ 371 - ...pageThree, 372 - items: { 373 - ...pageThree.items, 374 - edges: [ 375 - pageThree.items.edges[0], 376 - pageOne.items.edges[0], 377 - pageTwo.items.edges[0], 378 - ], 379 - nodes: [ 380 - pageThree.items.nodes[0], 381 - pageOne.items.nodes[0], 382 - pageTwo.items.nodes[0], 383 - ], 384 - pageInfo: { 385 - ...pageThree.items.pageInfo, 386 - hasPreviousPage: true, 387 - hasNextPage: true, 388 - startCursor: '-1', 389 - endCursor: '2', 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 + }, 390 392 }, 391 - }, 393 + }); 392 394 }); 393 - }); 394 395 395 - it('works with simultaneous forward and backward pagination (inwards merging)', () => { 396 - const Pagination = gql` 397 - query ($first: Int, $last: Int, $before: String, $after: String) { 398 - __typename 399 - items(first: $first, last: $last, before: $before, after: $after) { 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) { 400 399 __typename 401 - edges { 400 + items(first: $first, last: $last, before: $before, after: $after) { 402 401 __typename 403 - node { 402 + edges { 403 + __typename 404 + node { 405 + __typename 406 + id 407 + } 408 + } 409 + nodes { 404 410 __typename 405 411 id 406 412 } 407 - } 408 - nodes { 409 - __typename 410 - id 411 - } 412 - pageInfo { 413 - __typename 414 - hasPreviousPage 415 - hasNextPage 416 - startCursor 417 - endCursor 413 + pageInfo { 414 + __typename 415 + hasPreviousPage 416 + hasNextPage 417 + startCursor 418 + endCursor 419 + } 418 420 } 419 421 } 420 - } 421 - `; 422 + `; 422 423 423 - const store = new Store({ 424 - resolvers: { 425 - Query: { 426 - items: relayPagination({ mergeMode: 'inwards' }), 424 + const store = new Store({ 425 + resolvers: { 426 + Query: { 427 + items: relayPagination({ mergeMode: 'inwards' }), 428 + }, 427 429 }, 428 - }, 429 - }); 430 + }); 430 431 431 - const pageOne = { 432 - __typename: 'Query', 433 - items: { 434 - __typename: 'ItemsConnection', 435 - edges: [itemEdge(1)], 436 - nodes: [itemNode(1)], 437 - pageInfo: { 438 - __typename: 'PageInfo', 439 - hasNextPage: true, 440 - hasPreviousPage: false, 441 - startCursor: null, 442 - endCursor: '1', 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 + }, 443 445 }, 444 - }, 445 - }; 446 + }; 446 447 447 - const pageTwo = { 448 - __typename: 'Query', 449 - items: { 450 - __typename: 'ItemsConnection', 451 - edges: [itemEdge(2)], 452 - nodes: [itemNode(2)], 453 - pageInfo: { 454 - __typename: 'PageInfo', 455 - hasNextPage: true, 456 - hasPreviousPage: true, 457 - startCursor: '2', 458 - endCursor: '2', 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 + }, 459 461 }, 460 - }, 461 - }; 462 + }; 462 463 463 - const pageThree = { 464 - __typename: 'Query', 465 - items: { 466 - __typename: 'ItemsConnection', 467 - edges: [itemEdge(-1)], 468 - nodes: [itemNode(-1)], 469 - pageInfo: { 470 - __typename: 'PageInfo', 471 - hasNextPage: false, 472 - hasPreviousPage: true, 473 - startCursor: '-1', 474 - endCursor: null, 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 + }, 475 477 }, 476 - }, 477 - }; 478 + }; 478 479 479 - write( 480 - store, 481 - { query: Pagination, variables: { after: '1', first: 1 } }, 482 - pageOne 483 - ); 484 - write( 485 - store, 486 - { query: Pagination, variables: { after: '2', first: 1 } }, 487 - pageTwo 488 - ); 489 - write( 490 - store, 491 - { query: Pagination, variables: { before: '1', last: 1 } }, 492 - pageThree 493 - ); 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 + ); 494 495 495 - const res = query(store, { 496 - query: Pagination, 497 - variables: { before: '1', last: 1 }, 498 - }); 496 + const res = query(store, { 497 + query: Pagination, 498 + variables: { before: '1', last: 1 }, 499 + }); 499 500 500 - expect(res.partial).toBe(false); 501 - expect(res.data).toEqual({ 502 - ...pageThree, 503 - items: { 504 - ...pageThree.items, 505 - edges: [ 506 - pageOne.items.edges[0], 507 - pageTwo.items.edges[0], 508 - pageThree.items.edges[0], 509 - ], 510 - nodes: [ 511 - pageOne.items.nodes[0], 512 - pageTwo.items.nodes[0], 513 - pageThree.items.nodes[0], 514 - ], 515 - pageInfo: { 516 - ...pageThree.items.pageInfo, 517 - hasPreviousPage: true, 518 - hasNextPage: true, 519 - startCursor: '-1', 520 - endCursor: '2', 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 + }, 521 523 }, 522 - }, 524 + }); 523 525 }); 524 - }); 525 526 526 - it('prevents overlapping of pagination on different arguments', () => { 527 - const Pagination = gql` 528 - query ($filter: String) { 529 - items(first: 1, filter: $filter) { 530 - __typename 531 - edges { 527 + it('prevents overlapping of pagination on different arguments', () => { 528 + const Pagination = gql` 529 + query ($filter: String) { 530 + items(first: 1, filter: $filter) { 532 531 __typename 533 - node { 532 + edges { 533 + __typename 534 + node { 535 + __typename 536 + id 537 + } 538 + } 539 + nodes { 534 540 __typename 535 541 id 536 542 } 537 - } 538 - nodes { 539 - __typename 540 - id 541 - } 542 - pageInfo { 543 - __typename 544 - hasNextPage 545 - endCursor 543 + pageInfo { 544 + __typename 545 + hasNextPage 546 + endCursor 547 + } 546 548 } 547 549 } 548 - } 549 - `; 550 + `; 550 551 551 - const store = new Store({ 552 - resolvers: { 553 - Query: { 554 - items: relayPagination(), 552 + const store = new Store({ 553 + resolvers: { 554 + Query: { 555 + items: relayPagination(), 556 + }, 555 557 }, 556 - }, 557 - }); 558 + }); 558 559 559 - const page = withId => ({ 560 - __typename: 'Query', 561 - items: { 562 - __typename: 'ItemsConnection', 563 - edges: [itemEdge(withId)], 564 - nodes: [itemNode(withId)], 565 - pageInfo: { 566 - __typename: 'PageInfo', 567 - hasNextPage: false, 568 - endCursor: null, 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 + }, 569 571 }, 570 - }, 571 - }); 572 + }); 572 573 573 - write( 574 - store, 575 - { query: Pagination, variables: { filter: 'one' } }, 576 - page('one') 577 - ); 578 - write( 579 - store, 580 - { query: Pagination, variables: { filter: 'two' } }, 581 - page('two') 582 - ); 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 + ); 583 584 584 - const resOne = query(store, { 585 - query: Pagination, 586 - variables: { filter: 'one' }, 587 - }); 588 - const resTwo = query(store, { 589 - query: Pagination, 590 - variables: { filter: 'two' }, 591 - }); 592 - const resThree = query(store, { 593 - query: Pagination, 594 - variables: { filter: 'three' }, 595 - }); 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 + }); 596 597 597 - expect(resOne.data).toHaveProperty('items.edges[0].node.id', 'one'); 598 - expect(resOne.data).toHaveProperty('items.edges.length', 1); 598 + expect(resOne.data).toHaveProperty('items.edges[0].node.id', 'one'); 599 + expect(resOne.data).toHaveProperty('items.edges.length', 1); 599 600 600 - expect(resTwo.data).toHaveProperty('items.edges[0].node.id', 'two'); 601 - expect(resTwo.data).toHaveProperty('items.edges.length', 1); 601 + expect(resTwo.data).toHaveProperty('items.edges[0].node.id', 'two'); 602 + expect(resTwo.data).toHaveProperty('items.edges.length', 1); 602 603 603 - expect(resThree.data).toEqual(null); 604 - }); 604 + expect(resThree.data).toEqual(null); 605 + }); 605 606 606 - it('returns an empty array of edges when the cache has zero edges stored', () => { 607 - const Pagination = gql` 608 - query { 609 - items(first: 1) { 610 - __typename 611 - edges { 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) { 612 611 __typename 613 - } 614 - nodes { 615 - __typename 612 + edges { 613 + __typename 614 + } 615 + nodes { 616 + __typename 617 + } 616 618 } 617 619 } 618 - } 619 - `; 620 + `; 620 621 621 - const store = new Store({ 622 - resolvers: { 623 - Query: { 624 - items: relayPagination(), 622 + const store = new Store({ 623 + resolvers: { 624 + Query: { 625 + items: relayPagination(), 626 + }, 625 627 }, 626 - }, 627 - }); 628 + }); 628 629 629 - write( 630 - store, 631 - { query: Pagination }, 632 - { 633 - __typename: 'Query', 634 - items: { 635 - __typename: 'ItemsConnection', 636 - edges: [], 637 - nodes: [], 638 - }, 639 - } 640 - ); 630 + write( 631 + store, 632 + { query: Pagination }, 633 + { 634 + __typename: 'Query', 635 + items: { 636 + __typename: 'ItemsConnection', 637 + edges: [], 638 + nodes: [], 639 + }, 640 + } 641 + ); 641 642 642 - const res = query(store, { 643 - query: Pagination, 644 - }); 643 + const res = query(store, { 644 + query: Pagination, 645 + }); 645 646 646 - expect(res.data).toHaveProperty('items', { 647 - __typename: 'ItemsConnection', 648 - edges: [], 649 - nodes: [], 647 + expect(res.data).toHaveProperty('items', { 648 + __typename: 'ItemsConnection', 649 + edges: [], 650 + nodes: [], 651 + }); 650 652 }); 651 - }); 652 653 653 - it('returns other fields on the same level as the edges', () => { 654 - const Pagination = gql` 655 - query { 656 - __typename 657 - items(first: 1) { 654 + it('returns other fields on the same level as the edges', () => { 655 + const Pagination = gql` 656 + query { 658 657 __typename 659 - totalCount 658 + items(first: 1) { 659 + __typename 660 + totalCount 661 + } 660 662 } 661 - } 662 - `; 663 + `; 663 664 664 - const store = new Store({ 665 - resolvers: { 666 - Query: { 667 - items: relayPagination(), 665 + const store = new Store({ 666 + resolvers: { 667 + Query: { 668 + items: relayPagination(), 669 + }, 668 670 }, 669 - }, 670 - }); 671 + }); 671 672 672 - write( 673 - store, 674 - { query: Pagination }, 675 - { 676 - __typename: 'Query', 677 - items: { 678 - __typename: 'ItemsConnection', 679 - totalCount: 2, 680 - }, 681 - } 682 - ); 673 + write( 674 + store, 675 + { query: Pagination }, 676 + { 677 + __typename: 'Query', 678 + items: { 679 + __typename: 'ItemsConnection', 680 + totalCount: 2, 681 + }, 682 + } 683 + ); 683 684 684 - const resOne = query(store, { 685 - query: Pagination, 686 - }); 685 + const resOne = query(store, { 686 + query: Pagination, 687 + }); 687 688 688 - expect(resOne.data).toHaveProperty('items', { 689 - __typename: 'ItemsConnection', 690 - totalCount: 2, 689 + expect(resOne.data).toHaveProperty('items', { 690 + __typename: 'ItemsConnection', 691 + totalCount: 2, 692 + }); 691 693 }); 692 - }); 693 694 694 - it('returns a subset of the cached items if the query requests less items than the cached ones', () => { 695 - const Pagination = gql` 696 - query ($first: Int, $last: Int, $before: String, $after: String) { 697 - __typename 698 - items(first: $first, last: $last, before: $before, after: $after) { 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) { 699 698 __typename 700 - edges { 699 + items(first: $first, last: $last, before: $before, after: $after) { 701 700 __typename 702 - node { 701 + edges { 702 + __typename 703 + node { 704 + __typename 705 + id 706 + } 707 + } 708 + nodes { 703 709 __typename 704 710 id 705 711 } 706 - } 707 - nodes { 708 - __typename 709 - id 710 - } 711 - pageInfo { 712 - __typename 713 - hasPreviousPage 714 - hasNextPage 715 - startCursor 716 - endCursor 712 + pageInfo { 713 + __typename 714 + hasPreviousPage 715 + hasNextPage 716 + startCursor 717 + endCursor 718 + } 717 719 } 718 720 } 719 - } 720 - `; 721 + `; 721 722 722 - const store = new Store({ 723 - schema: require('../test-utils/relayPagination_schema.json'), 724 - resolvers: { 725 - Query: { 726 - items: relayPagination({ mergeMode: 'outwards' }), 723 + const store = new Store({ 724 + schema: require('../test-utils/relayPagination_schema.json'), 725 + resolvers: { 726 + Query: { 727 + items: relayPagination({ mergeMode: 'outwards' }), 728 + }, 727 729 }, 728 - }, 729 - }); 730 + }); 730 731 731 - const results = { 732 - __typename: 'Query', 733 - items: { 734 - __typename: 'ItemsConnection', 735 - edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)], 736 - nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)], 737 - pageInfo: { 738 - __typename: 'PageInfo', 739 - hasNextPage: true, 740 - hasPreviousPage: false, 741 - startCursor: '1', 742 - endCursor: '5', 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 + }, 743 757 }, 744 - }, 745 - }; 758 + }; 746 759 747 - write(store, { query: Pagination, variables: { first: 2 } }, results); 760 + write(store, { query: Pagination, variables: { first: 2 } }, results); 748 761 749 - const res = query(store, { 750 - query: Pagination, 751 - variables: { first: 2 }, 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); 752 769 }); 753 770 754 - expect(res.partial).toBe(false); 755 - expect(res.data).toEqual(results); 756 - }); 757 - 758 - it("returns the cached items even if they don't fullfil the query", () => { 759 - const Pagination = gql` 760 - query ($first: Int, $last: Int, $before: String, $after: String) { 761 - __typename 762 - items(first: $first, last: $last, before: $before, after: $after) { 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) { 763 774 __typename 764 - edges { 775 + items(first: $first, last: $last, before: $before, after: $after) { 765 776 __typename 766 - node { 777 + edges { 778 + __typename 779 + node { 780 + __typename 781 + id 782 + } 783 + } 784 + nodes { 767 785 __typename 768 786 id 769 787 } 770 - } 771 - nodes { 772 - __typename 773 - id 774 - } 775 - pageInfo { 776 - __typename 777 - hasPreviousPage 778 - hasNextPage 779 - startCursor 780 - endCursor 788 + pageInfo { 789 + __typename 790 + hasPreviousPage 791 + hasNextPage 792 + startCursor 793 + endCursor 794 + } 781 795 } 782 796 } 783 - } 784 - `; 797 + `; 785 798 786 - const store = new Store({ 787 - schema: require('../test-utils/relayPagination_schema.json'), 788 - resolvers: { 789 - Query: { 790 - items: relayPagination(), 799 + const store = new Store({ 800 + schema: require('../test-utils/relayPagination_schema.json'), 801 + resolvers: { 802 + Query: { 803 + items: relayPagination(), 804 + }, 791 805 }, 792 - }, 793 - }); 806 + }); 794 807 795 - const results = { 796 - __typename: 'Query', 797 - items: { 798 - __typename: 'ItemsConnection', 799 - edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)], 800 - nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)], 801 - pageInfo: { 802 - __typename: 'PageInfo', 803 - hasNextPage: true, 804 - hasPreviousPage: false, 805 - startCursor: '1', 806 - endCursor: '5', 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 + }, 807 833 }, 808 - }, 809 - }; 834 + }; 835 + 836 + write( 837 + store, 838 + { query: Pagination, variables: { after: '3', first: 3, last: 3 } }, 839 + results 840 + ); 810 841 811 - write( 812 - store, 813 - { query: Pagination, variables: { after: '3', first: 3, last: 3 } }, 814 - results 815 - ); 842 + const res = query(store, { 843 + query: Pagination, 844 + variables: { after: '3', first: 3, last: 3 }, 845 + }); 816 846 817 - const res = query(store, { 818 - query: Pagination, 819 - variables: { after: '3', first: 3, last: 3 }, 847 + expect(res.partial).toBe(false); 848 + expect(res.data).toEqual(results); 820 849 }); 821 850 822 - expect(res.partial).toBe(false); 823 - expect(res.data).toEqual(results); 824 - }); 825 - 826 - it('returns the cached items even when they come from a different query', () => { 827 - const Pagination = gql` 828 - query ($first: Int, $last: Int, $before: String, $after: String) { 829 - __typename 830 - items(first: $first, last: $last, before: $before, after: $after) { 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) { 831 854 __typename 832 - edges { 855 + items(first: $first, last: $last, before: $before, after: $after) { 833 856 __typename 834 - node { 857 + edges { 858 + __typename 859 + node { 860 + __typename 861 + id 862 + } 863 + } 864 + nodes { 835 865 __typename 836 866 id 837 867 } 838 - } 839 - nodes { 840 - __typename 841 - id 842 - } 843 - pageInfo { 844 - __typename 845 - hasPreviousPage 846 - hasNextPage 847 - startCursor 848 - endCursor 868 + pageInfo { 869 + __typename 870 + hasPreviousPage 871 + hasNextPage 872 + startCursor 873 + endCursor 874 + } 849 875 } 850 876 } 851 - } 852 - `; 877 + `; 853 878 854 - const store = new Store({ 855 - schema: require('../test-utils/relayPagination_schema.json'), 856 - resolvers: { 857 - Query: { 858 - items: relayPagination(), 879 + const store = new Store({ 880 + schema: require('../test-utils/relayPagination_schema.json'), 881 + resolvers: { 882 + Query: { 883 + items: relayPagination(), 884 + }, 859 885 }, 860 - }, 861 - }); 886 + }); 862 887 863 - const results = { 864 - __typename: 'Query', 865 - items: { 866 - __typename: 'ItemsConnection', 867 - edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)], 868 - nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)], 869 - pageInfo: { 870 - __typename: 'PageInfo', 871 - hasNextPage: true, 872 - hasPreviousPage: false, 873 - startCursor: '1', 874 - endCursor: '5', 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 + }, 875 913 }, 876 - }, 877 - }; 914 + }; 878 915 879 - write(store, { query: Pagination, variables: { first: 5 } }, results); 916 + write(store, { query: Pagination, variables: { first: 5 } }, results); 880 917 881 - const res = query(store, { 882 - query: Pagination, 883 - variables: { after: '3', first: 2, last: 2 }, 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); 884 925 }); 885 926 886 - expect(res.partial).toBe(true); 887 - expect(res.data).toEqual(results); 888 - }); 889 - 890 - it('caches and retrieves correctly queries with inwards pagination', () => { 891 - const Pagination = gql` 892 - query ($first: Int, $last: Int, $before: String, $after: String) { 893 - __typename 894 - items(first: $first, last: $last, before: $before, after: $after) { 927 + it('caches and retrieves correctly queries with inwards pagination', () => { 928 + const Pagination = gql` 929 + query ($first: Int, $last: Int, $before: String, $after: String) { 895 930 __typename 896 - edges { 931 + items(first: $first, last: $last, before: $before, after: $after) { 897 932 __typename 898 - node { 933 + edges { 934 + __typename 935 + node { 936 + __typename 937 + id 938 + } 939 + } 940 + nodes { 899 941 __typename 900 942 id 901 943 } 902 - } 903 - nodes { 904 - __typename 905 - id 906 - } 907 - pageInfo { 908 - __typename 909 - hasPreviousPage 910 - hasNextPage 911 - startCursor 912 - endCursor 944 + pageInfo { 945 + __typename 946 + hasPreviousPage 947 + hasNextPage 948 + startCursor 949 + endCursor 950 + } 913 951 } 914 952 } 915 - } 916 - `; 953 + `; 917 954 918 - const store = new Store({ 919 - schema: require('../test-utils/relayPagination_schema.json'), 920 - resolvers: { 921 - Query: { 922 - items: relayPagination(), 955 + const store = new Store({ 956 + schema: require('../test-utils/relayPagination_schema.json'), 957 + resolvers: { 958 + Query: { 959 + items: relayPagination(), 960 + }, 923 961 }, 924 - }, 925 - }); 962 + }); 926 963 927 - const results = { 928 - __typename: 'Query', 929 - items: { 930 - __typename: 'ItemsConnection', 931 - edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)], 932 - nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)], 933 - pageInfo: { 934 - __typename: 'PageInfo', 935 - hasNextPage: true, 936 - hasPreviousPage: false, 937 - startCursor: '1', 938 - endCursor: '5', 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 + }, 939 989 }, 940 - }, 941 - }; 990 + }; 942 991 943 - write( 944 - store, 945 - { query: Pagination, variables: { after: '2', first: 2, last: 2 } }, 946 - results 947 - ); 992 + write( 993 + store, 994 + { query: Pagination, variables: { after: '2', first: 2, last: 2 } }, 995 + results 996 + ); 948 997 949 - const res = query(store, { 950 - query: Pagination, 951 - variables: { after: '2', first: 2, last: 2 }, 952 - }); 998 + const res = query(store, { 999 + query: Pagination, 1000 + variables: { after: '2', first: 2, last: 2 }, 1001 + }); 953 1002 954 - expect(res.partial).toBe(false); 955 - expect(res.data).toEqual(results); 956 - }); 1003 + expect(res.partial).toBe(false); 1004 + expect(res.data).toEqual(results); 1005 + }); 957 1006 958 - it('does not include a previous result when adding parameters', () => { 959 - const Pagination = gql` 960 - query ($first: Int, $filter: String) { 961 - __typename 962 - items(first: $first, filter: $filter) { 1007 + it('does not include a previous result when adding parameters', () => { 1008 + const Pagination = gql` 1009 + query ($first: Int, $filter: String) { 963 1010 __typename 964 - edges { 1011 + items(first: $first, filter: $filter) { 965 1012 __typename 966 - node { 1013 + edges { 1014 + __typename 1015 + node { 1016 + __typename 1017 + id 1018 + } 1019 + } 1020 + nodes { 967 1021 __typename 968 1022 id 969 1023 } 970 - } 971 - nodes { 972 - __typename 973 - id 974 - } 975 - pageInfo { 976 - __typename 977 - hasPreviousPage 978 - hasNextPage 979 - startCursor 980 - endCursor 1024 + pageInfo { 1025 + __typename 1026 + hasPreviousPage 1027 + hasNextPage 1028 + startCursor 1029 + endCursor 1030 + } 981 1031 } 982 1032 } 983 - } 984 - `; 1033 + `; 985 1034 986 - const store = new Store({ 987 - resolvers: { 988 - Query: { 989 - items: relayPagination(), 1035 + const store = new Store({ 1036 + resolvers: { 1037 + Query: { 1038 + items: relayPagination(), 1039 + }, 990 1040 }, 991 - }, 992 - }); 1041 + }); 993 1042 994 - const results = { 995 - __typename: 'Query', 996 - items: { 997 - __typename: 'ItemsConnection', 998 - edges: [itemEdge(1), itemEdge(2)], 999 - nodes: [itemNode(1), itemNode(2)], 1000 - pageInfo: { 1001 - __typename: 'PageInfo', 1002 - hasNextPage: true, 1003 - hasPreviousPage: false, 1004 - startCursor: '1', 1005 - endCursor: '2', 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 + }, 1006 1056 }, 1007 - }, 1008 - }; 1057 + }; 1009 1058 1010 - const results2 = { 1011 - __typename: 'Query', 1012 - items: { 1013 - __typename: 'ItemsConnection', 1014 - edges: [], 1015 - nodes: [], 1016 - pageInfo: { 1017 - __typename: 'PageInfo', 1018 - hasNextPage: false, 1019 - hasPreviousPage: false, 1020 - startCursor: '1', 1021 - endCursor: '2', 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 + }, 1022 1072 }, 1023 - }, 1024 - }; 1073 + }; 1025 1074 1026 - write(store, { query: Pagination, variables: { first: 2 } }, results); 1075 + write(store, { query: Pagination, variables: { first: 2 } }, results); 1027 1076 1028 - write( 1029 - store, 1030 - { query: Pagination, variables: { first: 2, filter: 'b' } }, 1031 - results2 1032 - ); 1077 + write( 1078 + store, 1079 + { query: Pagination, variables: { first: 2, filter: 'b' } }, 1080 + results2 1081 + ); 1033 1082 1034 - const res = query(store, { 1035 - query: Pagination, 1036 - variables: { first: 2, filter: 'b' }, 1083 + const res = query(store, { 1084 + query: Pagination, 1085 + variables: { first: 2, filter: 'b' }, 1086 + }); 1087 + expect(res.data).toEqual(results2); 1037 1088 }); 1038 - expect(res.data).toEqual(results2); 1039 - }); 1040 1089 1041 - it('Works with edges absent from query', () => { 1042 - const Pagination = gql` 1043 - query ($first: Int, $last: Int, $before: String, $after: String) { 1044 - __typename 1045 - items(first: $first, last: $last, before: $before, after: $after) { 1090 + it('Works with edges absent from query', () => { 1091 + const Pagination = gql` 1092 + query ($first: Int, $last: Int, $before: String, $after: String) { 1046 1093 __typename 1047 - nodes { 1094 + items(first: $first, last: $last, before: $before, after: $after) { 1048 1095 __typename 1049 - id 1050 - } 1051 - pageInfo { 1052 - __typename 1053 - hasPreviousPage 1054 - hasNextPage 1055 - startCursor 1056 - endCursor 1096 + nodes { 1097 + __typename 1098 + id 1099 + } 1100 + pageInfo { 1101 + __typename 1102 + hasPreviousPage 1103 + hasNextPage 1104 + startCursor 1105 + endCursor 1106 + } 1057 1107 } 1058 1108 } 1059 - } 1060 - `; 1109 + `; 1061 1110 1062 - const store = new Store({ 1063 - schema: require('../test-utils/relayPagination_schema.json'), 1064 - resolvers: { 1065 - Query: { 1066 - items: relayPagination({ mergeMode: 'outwards' }), 1111 + const store = new Store({ 1112 + schema: require('../test-utils/relayPagination_schema.json'), 1113 + resolvers: { 1114 + Query: { 1115 + items: relayPagination({ mergeMode: 'outwards' }), 1116 + }, 1067 1117 }, 1068 - }, 1069 - }); 1118 + }); 1070 1119 1071 - const results = { 1072 - __typename: 'Query', 1073 - items: { 1074 - __typename: 'ItemsConnection', 1075 - nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)], 1076 - pageInfo: { 1077 - __typename: 'PageInfo', 1078 - hasNextPage: true, 1079 - hasPreviousPage: false, 1080 - startCursor: '1', 1081 - endCursor: '5', 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 + }, 1082 1138 }, 1083 - }, 1084 - }; 1139 + }; 1085 1140 1086 - write(store, { query: Pagination, variables: { first: 2 } }, results); 1141 + write(store, { query: Pagination, variables: { first: 2 } }, results); 1087 1142 1088 - const res = query(store, { 1089 - query: Pagination, 1090 - variables: { first: 2 }, 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); 1091 1150 }); 1092 1151 1093 - expect(res.partial).toBe(false); 1094 - expect(res.data).toEqual(results); 1095 - }); 1096 - 1097 - it('Works with nodes absent from query', () => { 1098 - const Pagination = gql` 1099 - query ($first: Int, $last: Int, $before: String, $after: String) { 1100 - __typename 1101 - items(first: $first, last: $last, before: $before, after: $after) { 1152 + it('Works with nodes absent from query', () => { 1153 + const Pagination = gql` 1154 + query ($first: Int, $last: Int, $before: String, $after: String) { 1102 1155 __typename 1103 - edges { 1156 + items(first: $first, last: $last, before: $before, after: $after) { 1104 1157 __typename 1105 - node { 1158 + edges { 1106 1159 __typename 1107 - id 1160 + node { 1161 + __typename 1162 + id 1163 + } 1108 1164 } 1109 - } 1110 - pageInfo { 1111 - __typename 1112 - hasPreviousPage 1113 - hasNextPage 1114 - startCursor 1115 - endCursor 1165 + pageInfo { 1166 + __typename 1167 + hasPreviousPage 1168 + hasNextPage 1169 + startCursor 1170 + endCursor 1171 + } 1116 1172 } 1117 1173 } 1118 - } 1119 - `; 1174 + `; 1120 1175 1121 - const store = new Store({ 1122 - schema: require('../test-utils/relayPagination_schema.json'), 1123 - resolvers: { 1124 - Query: { 1125 - items: relayPagination({ mergeMode: 'outwards' }), 1176 + const store = new Store({ 1177 + schema: require('../test-utils/relayPagination_schema.json'), 1178 + resolvers: { 1179 + Query: { 1180 + items: relayPagination({ mergeMode: 'outwards' }), 1181 + }, 1126 1182 }, 1127 - }, 1128 - }); 1183 + }); 1129 1184 1130 - const results = { 1131 - __typename: 'Query', 1132 - items: { 1133 - __typename: 'ItemsConnection', 1134 - edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)], 1135 - pageInfo: { 1136 - __typename: 'PageInfo', 1137 - hasNextPage: true, 1138 - hasPreviousPage: false, 1139 - startCursor: '1', 1140 - endCursor: '5', 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 + }, 1141 1203 }, 1142 - }, 1143 - }; 1204 + }; 1144 1205 1145 - write(store, { query: Pagination, variables: { first: 2 } }, results); 1206 + write(store, { query: Pagination, variables: { first: 2 } }, results); 1207 + 1208 + const res = query(store, { 1209 + query: Pagination, 1210 + variables: { first: 2 }, 1211 + }); 1146 1212 1147 - const res = query(store, { 1148 - query: Pagination, 1149 - variables: { first: 2 }, 1213 + expect(res.partial).toBe(false); 1214 + expect(res.data).toEqual(results); 1150 1215 }); 1151 1216 1152 - expect(res.partial).toBe(false); 1153 - expect(res.data).toEqual(results); 1154 - }); 1155 - 1156 - it('handles subsequent queries with larger last values', () => { 1157 - const Pagination = gql` 1158 - query ($last: Int!) { 1159 - __typename 1160 - items(last: $last) { 1217 + it('handles subsequent queries with larger last values', () => { 1218 + const Pagination = gql` 1219 + query ($last: Int!) { 1161 1220 __typename 1162 - edges { 1221 + items(last: $last) { 1163 1222 __typename 1164 - node { 1223 + edges { 1224 + __typename 1225 + node { 1226 + __typename 1227 + id 1228 + } 1229 + } 1230 + nodes { 1165 1231 __typename 1166 1232 id 1167 1233 } 1168 - } 1169 - nodes { 1170 - __typename 1171 - id 1172 - } 1173 - pageInfo { 1174 - __typename 1175 - hasPreviousPage 1176 - startCursor 1234 + pageInfo { 1235 + __typename 1236 + hasPreviousPage 1237 + startCursor 1238 + } 1177 1239 } 1178 1240 } 1179 - } 1180 - `; 1241 + `; 1181 1242 1182 - const store = new Store({ 1183 - resolvers: { 1184 - Query: { 1185 - items: relayPagination(), 1243 + const store = new Store({ 1244 + resolvers: { 1245 + Query: { 1246 + items: relayPagination(), 1247 + }, 1186 1248 }, 1187 - }, 1188 - }); 1249 + }); 1189 1250 1190 - const pageOne = { 1191 - __typename: 'Query', 1192 - items: { 1193 - __typename: 'ItemsConnection', 1194 - edges: [itemEdge(2)], 1195 - nodes: [itemNode(2)], 1196 - pageInfo: { 1197 - __typename: 'PageInfo', 1198 - hasPreviousPage: true, 1199 - startCursor: '2', 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 + }, 1200 1262 }, 1201 - }, 1202 - }; 1263 + }; 1203 1264 1204 - const pageTwo = { 1205 - __typename: 'Query', 1206 - items: { 1207 - __typename: 'ItemsConnection', 1208 - edges: [itemEdge(1), itemEdge(2)], 1209 - nodes: [itemNode(1), itemNode(2)], 1210 - pageInfo: { 1211 - __typename: 'PageInfo', 1212 - hasPreviousPage: false, 1213 - startCursor: '1', 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 + }, 1214 1276 }, 1215 - }, 1216 - }; 1277 + }; 1217 1278 1218 - const pageThree = { 1219 - __typename: 'Query', 1220 - items: { 1221 - __typename: 'ItemsConnection', 1222 - edges: [itemEdge(0), itemEdge(1), itemEdge(2)], 1223 - nodes: [itemNode(0), itemNode(1), itemNode(2)], 1224 - pageInfo: { 1225 - __typename: 'PageInfo', 1226 - hasPreviousPage: false, 1227 - startCursor: '0', 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 + }, 1228 1290 }, 1229 - }, 1230 - }; 1291 + }; 1231 1292 1232 - write(store, { query: Pagination, variables: { last: 1 } }, pageOne); 1233 - write(store, { query: Pagination, variables: { last: 2 } }, pageTwo); 1293 + write(store, { query: Pagination, variables: { last: 1 } }, pageOne); 1294 + write(store, { query: Pagination, variables: { last: 2 } }, pageTwo); 1234 1295 1235 - let res = query(store, { query: Pagination, variables: { last: 2 } }); 1296 + let res = query(store, { query: Pagination, variables: { last: 2 } }); 1236 1297 1237 - expect(res.partial).toBe(false); 1238 - expect(res.data).toEqual(pageTwo); 1298 + expect(res.partial).toBe(false); 1299 + expect(res.data).toEqual(pageTwo); 1239 1300 1240 - write(store, { query: Pagination, variables: { last: 3 } }, pageThree); 1301 + write(store, { query: Pagination, variables: { last: 3 } }, pageThree); 1241 1302 1242 - res = query(store, { query: Pagination, variables: { last: 3 } }); 1303 + res = query(store, { query: Pagination, variables: { last: 3 } }); 1243 1304 1244 - expect(res.partial).toBe(false); 1245 - expect(res.data).toEqual(pageThree); 1246 - }); 1305 + expect(res.partial).toBe(false); 1306 + expect(res.data).toEqual(pageThree); 1307 + }); 1247 1308 1248 - it('handles subsequent queries with larger first values', () => { 1249 - const Pagination = gql` 1250 - query ($first: Int!) { 1251 - __typename 1252 - items(first: $first) { 1309 + it('handles subsequent queries with larger first values', () => { 1310 + const Pagination = gql` 1311 + query ($first: Int!) { 1253 1312 __typename 1254 - edges { 1313 + items(first: $first) { 1255 1314 __typename 1256 - node { 1315 + edges { 1316 + __typename 1317 + node { 1318 + __typename 1319 + id 1320 + } 1321 + } 1322 + nodes { 1257 1323 __typename 1258 1324 id 1259 1325 } 1260 - } 1261 - nodes { 1262 - __typename 1263 - id 1264 - } 1265 - pageInfo { 1266 - __typename 1267 - hasNextPage 1268 - endCursor 1326 + pageInfo { 1327 + __typename 1328 + hasNextPage 1329 + endCursor 1330 + } 1269 1331 } 1270 1332 } 1271 - } 1272 - `; 1333 + `; 1273 1334 1274 - const store = new Store({ 1275 - resolvers: { 1276 - Query: { 1277 - items: relayPagination(), 1335 + const store = new Store({ 1336 + resolvers: { 1337 + Query: { 1338 + items: relayPagination(), 1339 + }, 1278 1340 }, 1279 - }, 1280 - }); 1341 + }); 1281 1342 1282 - const pageOne = { 1283 - __typename: 'Query', 1284 - items: { 1285 - __typename: 'ItemsConnection', 1286 - edges: [itemEdge(1)], 1287 - nodes: [itemNode(1)], 1288 - pageInfo: { 1289 - __typename: 'PageInfo', 1290 - hasNextPage: true, 1291 - endCursor: '1', 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 + }, 1292 1354 }, 1293 - }, 1294 - }; 1355 + }; 1295 1356 1296 - const pageTwo = { 1297 - __typename: 'Query', 1298 - items: { 1299 - __typename: 'ItemsConnection', 1300 - edges: [itemEdge(1), itemEdge(2)], 1301 - nodes: [itemNode(1), itemNode(2)], 1302 - pageInfo: { 1303 - __typename: 'PageInfo', 1304 - hasNextPage: false, 1305 - endCursor: '2', 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 + }, 1306 1368 }, 1307 - }, 1308 - }; 1369 + }; 1309 1370 1310 - write(store, { query: Pagination, variables: { first: 1 } }, pageOne); 1311 - write(store, { query: Pagination, variables: { first: 2 } }, pageTwo); 1371 + write(store, { query: Pagination, variables: { first: 1 } }, pageOne); 1372 + write(store, { query: Pagination, variables: { first: 2 } }, pageTwo); 1312 1373 1313 - const res = query(store, { query: Pagination, variables: { first: 2 } }); 1374 + const res = query(store, { query: Pagination, variables: { first: 2 } }); 1314 1375 1315 - expect(res.partial).toBe(false); 1316 - expect(res.data).toEqual(pageTwo); 1317 - }); 1376 + expect(res.partial).toBe(false); 1377 + expect(res.data).toEqual(pageTwo); 1378 + }); 1318 1379 1319 - it('ignores empty pages when paginating', () => { 1320 - const PaginationForward = gql` 1321 - query ($first: Int!, $after: String) { 1322 - __typename 1323 - items(first: $first, after: $after) { 1380 + it('ignores empty pages when paginating', () => { 1381 + const PaginationForward = gql` 1382 + query ($first: Int!, $after: String) { 1324 1383 __typename 1325 - nodes { 1384 + items(first: $first, after: $after) { 1326 1385 __typename 1327 - id 1328 - } 1329 - pageInfo { 1330 - __typename 1331 - startCursor 1332 - endCursor 1386 + nodes { 1387 + __typename 1388 + id 1389 + } 1390 + pageInfo { 1391 + __typename 1392 + startCursor 1393 + endCursor 1394 + } 1333 1395 } 1334 1396 } 1335 - } 1336 - `; 1337 - const PaginationBackward = gql` 1338 - query ($last: Int!, $before: String) { 1339 - __typename 1340 - items(last: $last, before: $before) { 1397 + `; 1398 + const PaginationBackward = gql` 1399 + query ($last: Int!, $before: String) { 1341 1400 __typename 1342 - nodes { 1401 + items(last: $last, before: $before) { 1343 1402 __typename 1344 - id 1345 - } 1346 - pageInfo { 1347 - __typename 1348 - startCursor 1349 - endCursor 1403 + nodes { 1404 + __typename 1405 + id 1406 + } 1407 + pageInfo { 1408 + __typename 1409 + startCursor 1410 + endCursor 1411 + } 1350 1412 } 1351 1413 } 1352 - } 1353 - `; 1414 + `; 1354 1415 1355 - const store = new Store({ 1356 - resolvers: { 1357 - Query: { 1358 - items: relayPagination(), 1416 + const store = new Store({ 1417 + resolvers: { 1418 + Query: { 1419 + items: relayPagination(), 1420 + }, 1359 1421 }, 1360 - }, 1361 - }); 1422 + }); 1362 1423 1363 - const forwardOne = { 1364 - __typename: 'Query', 1365 - items: { 1366 - __typename: 'ItemsConnection', 1367 - nodes: [itemNode(1), itemNode(2)], 1368 - pageInfo: { 1369 - __typename: 'PageInfo', 1370 - startCursor: '1', 1371 - endCursor: '2', 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 + }, 1372 1434 }, 1373 - }, 1374 - }; 1375 - const forwardAfter = { 1376 - __typename: 'Query', 1377 - items: { 1378 - __typename: 'ItemsConnection', 1379 - nodes: [], 1380 - pageInfo: { 1381 - __typename: 'PageInfo', 1382 - startCursor: null, 1383 - endCursor: null, 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 + }, 1384 1446 }, 1385 - }, 1386 - }; 1387 - const backwardBefore = { 1388 - __typename: 'Query', 1389 - items: { 1390 - __typename: 'ItemsConnection', 1391 - nodes: [], 1392 - pageInfo: { 1393 - __typename: 'PageInfo', 1394 - startCursor: null, 1395 - endCursor: null, 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 + }, 1396 1458 }, 1397 - }, 1398 - }; 1459 + }; 1399 1460 1400 - write( 1401 - store, 1402 - { query: PaginationForward, variables: { first: 2 } }, 1403 - forwardOne 1404 - ); 1405 - write( 1406 - store, 1407 - { query: PaginationBackward, variables: { last: 1, before: '1' } }, 1408 - backwardBefore 1409 - ); 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 + }); 1410 1476 1411 - const res = query(store, { 1412 - query: PaginationForward, 1413 - variables: { first: 2 }, 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); 1414 1487 }); 1415 1488 1416 - expect(res.partial).toBe(false); 1417 - expect(res.data).toEqual(forwardOne); 1418 - write( 1419 - store, 1420 - { query: PaginationForward, variables: { first: 1, after: '2' } }, 1421 - forwardAfter 1422 - ); 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 + `; 1423 1507 1424 - expect(res.partial).toBe(false); 1425 - expect(res.data).toEqual(forwardOne); 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 + }); 1426 1538 }); 1427 1539 1428 - it('allows for an empty page when this is the only result', () => { 1429 - const Pagination = gql` 1430 - query ($first: Int!, $after: String) { 1431 - __typename 1432 - items(first: $first, after: $after) { 1540 + describe('as directive', () => { 1541 + it('works with forward pagination', () => { 1542 + const Pagination = gql` 1543 + query ($cursor: String) { 1433 1544 __typename 1434 - nodes { 1545 + items(first: 1, after: $cursor) @_relayPagination { 1435 1546 __typename 1436 - id 1437 - } 1438 - pageInfo { 1439 - __typename 1440 - startCursor 1441 - endCursor 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 + } 1442 1563 } 1443 1564 } 1444 - } 1445 - `; 1565 + `; 1446 1566 1447 - const store = new Store({ 1448 - resolvers: { 1449 - Query: { 1450 - items: relayPagination(), 1567 + const store = new Store({ 1568 + directives: { 1569 + relayPagination: () => relayPagination(), 1451 1570 }, 1452 - }, 1453 - }); 1571 + }); 1454 1572 1455 - const pageOne = { 1456 - __typename: 'Query', 1457 - items: { 1458 - __typename: 'ItemsConnection', 1459 - nodes: [], 1460 - pageInfo: { 1461 - __typename: 'PageInfo', 1462 - startCursor: null, 1463 - endCursor: null, 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 + }, 1464 1584 }, 1465 - }, 1466 - }; 1585 + }; 1467 1586 1468 - write(store, { query: Pagination, variables: { first: 2 } }, pageOne); 1469 - const res = query(store, { 1470 - query: Pagination, 1471 - variables: { first: 2 }, 1472 - }); 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 + }; 1473 1600 1474 - expect(res.partial).toBe(false); 1475 - expect(res.data).toEqual(pageOne); 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 + }); 1476 1616 });
+499 -352
exchanges/graphcache/src/extras/simplePagination.test.ts
··· 1 1 import { gql } from '@urql/core'; 2 - import { it, expect } from 'vitest'; 2 + import { it, expect, describe } from 'vitest'; 3 3 import { __initAnd_query as query } from '../operations/query'; 4 4 import { __initAnd_write as write } from '../operations/write'; 5 5 import { Store } from '../store/store'; 6 - import { simplePagination } from './simplePagination'; 6 + import { MergeMode, simplePagination } from './simplePagination'; 7 7 8 - it('works with forward pagination', () => { 9 - const Pagination = gql` 10 - query ($skip: Number, $limit: Number) { 11 - __typename 12 - persons(skip: $skip, limit: $limit) { 8 + describe('as resolver', () => { 9 + it('works with forward pagination', () => { 10 + const Pagination = gql` 11 + query ($skip: Number, $limit: Number) { 13 12 __typename 14 - id 15 - name 13 + persons(skip: $skip, limit: $limit) { 14 + __typename 15 + id 16 + name 17 + } 16 18 } 17 - } 18 - `; 19 + `; 19 20 20 - const store = new Store({ 21 - resolvers: { 22 - Query: { 23 - persons: simplePagination(), 21 + const store = new Store({ 22 + resolvers: { 23 + Query: { 24 + persons: simplePagination(), 25 + }, 24 26 }, 25 - }, 26 - }); 27 + }); 27 28 28 - const pageOne = { 29 - __typename: 'Query', 30 - persons: [ 31 - { id: 1, name: 'Jovi', __typename: 'Person' }, 32 - { id: 2, name: 'Phil', __typename: 'Person' }, 33 - { id: 3, name: 'Andy', __typename: 'Person' }, 34 - ], 35 - }; 29 + const pageOne = { 30 + __typename: 'Query', 31 + persons: [ 32 + { id: 1, name: 'Jovi', __typename: 'Person' }, 33 + { id: 2, name: 'Phil', __typename: 'Person' }, 34 + { id: 3, name: 'Andy', __typename: 'Person' }, 35 + ], 36 + }; 36 37 37 - const pageTwo = { 38 - __typename: 'Query', 39 - persons: [ 40 - { id: 4, name: 'Kadi', __typename: 'Person' }, 41 - { id: 5, name: 'Dom', __typename: 'Person' }, 42 - { id: 6, name: 'Sofia', __typename: 'Person' }, 43 - ], 44 - }; 38 + const pageTwo = { 39 + __typename: 'Query', 40 + persons: [ 41 + { id: 4, name: 'Kadi', __typename: 'Person' }, 42 + { id: 5, name: 'Dom', __typename: 'Person' }, 43 + { id: 6, name: 'Sofia', __typename: 'Person' }, 44 + ], 45 + }; 45 46 46 - write( 47 - store, 48 - { query: Pagination, variables: { skip: 0, limit: 3 } }, 49 - pageOne 50 - ); 51 - const pageOneResult = query(store, { 52 - query: Pagination, 53 - variables: { skip: 0, limit: 3 }, 54 - }); 55 - expect(pageOneResult.data).toEqual(pageOne); 47 + write( 48 + store, 49 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 50 + pageOne 51 + ); 52 + const pageOneResult = query(store, { 53 + query: Pagination, 54 + variables: { skip: 0, limit: 3 }, 55 + }); 56 + expect(pageOneResult.data).toEqual(pageOne); 56 57 57 - write( 58 - store, 59 - { query: Pagination, variables: { skip: 3, limit: 3 } }, 60 - pageTwo 61 - ); 58 + write( 59 + store, 60 + { query: Pagination, variables: { skip: 3, limit: 3 } }, 61 + pageTwo 62 + ); 62 63 63 - const pageTwoResult = query(store, { 64 - query: Pagination, 65 - variables: { skip: 3, limit: 3 }, 66 - }); 67 - expect((pageTwoResult.data as any).persons).toEqual([ 68 - ...pageOne.persons, 69 - ...pageTwo.persons, 70 - ]); 64 + const pageTwoResult = query(store, { 65 + query: Pagination, 66 + variables: { skip: 3, limit: 3 }, 67 + }); 68 + expect((pageTwoResult.data as any).persons).toEqual([ 69 + ...pageOne.persons, 70 + ...pageTwo.persons, 71 + ]); 71 72 72 - const pageThreeResult = query(store, { 73 - query: Pagination, 74 - variables: { skip: 6, limit: 3 }, 73 + const pageThreeResult = query(store, { 74 + query: Pagination, 75 + variables: { skip: 6, limit: 3 }, 76 + }); 77 + expect(pageThreeResult.data).toEqual(null); 75 78 }); 76 - expect(pageThreeResult.data).toEqual(null); 77 - }); 78 79 79 - it('works with backwards pagination', () => { 80 - const Pagination = gql` 81 - query ($skip: Number, $limit: Number) { 82 - __typename 83 - persons(skip: $skip, limit: $limit) { 80 + it('works with backwards pagination', () => { 81 + const Pagination = gql` 82 + query ($skip: Number, $limit: Number) { 84 83 __typename 85 - id 86 - name 84 + persons(skip: $skip, limit: $limit) { 85 + __typename 86 + id 87 + name 88 + } 87 89 } 88 - } 89 - `; 90 + `; 90 91 91 - const store = new Store({ 92 - resolvers: { 93 - Query: { 94 - persons: simplePagination({ mergeMode: 'before' }), 92 + const store = new Store({ 93 + resolvers: { 94 + Query: { 95 + persons: simplePagination({ mergeMode: 'before' }), 96 + }, 95 97 }, 96 - }, 97 - }); 98 + }); 98 99 99 - const pageOne = { 100 - __typename: 'Query', 101 - persons: [ 102 - { id: 7, name: 'Jovi', __typename: 'Person' }, 103 - { id: 8, name: 'Phil', __typename: 'Person' }, 104 - { id: 9, name: 'Andy', __typename: 'Person' }, 105 - ], 106 - }; 100 + const pageOne = { 101 + __typename: 'Query', 102 + persons: [ 103 + { id: 7, name: 'Jovi', __typename: 'Person' }, 104 + { id: 8, name: 'Phil', __typename: 'Person' }, 105 + { id: 9, name: 'Andy', __typename: 'Person' }, 106 + ], 107 + }; 107 108 108 - const pageTwo = { 109 - __typename: 'Query', 110 - persons: [ 111 - { id: 4, name: 'Kadi', __typename: 'Person' }, 112 - { id: 5, name: 'Dom', __typename: 'Person' }, 113 - { id: 6, name: 'Sofia', __typename: 'Person' }, 114 - ], 115 - }; 109 + const pageTwo = { 110 + __typename: 'Query', 111 + persons: [ 112 + { id: 4, name: 'Kadi', __typename: 'Person' }, 113 + { id: 5, name: 'Dom', __typename: 'Person' }, 114 + { id: 6, name: 'Sofia', __typename: 'Person' }, 115 + ], 116 + }; 116 117 117 - write( 118 - store, 119 - { query: Pagination, variables: { skip: 0, limit: 3 } }, 120 - pageOne 121 - ); 122 - const pageOneResult = query(store, { 123 - query: Pagination, 124 - variables: { skip: 0, limit: 3 }, 125 - }); 126 - expect(pageOneResult.data).toEqual(pageOne); 118 + write( 119 + store, 120 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 121 + pageOne 122 + ); 123 + const pageOneResult = query(store, { 124 + query: Pagination, 125 + variables: { skip: 0, limit: 3 }, 126 + }); 127 + expect(pageOneResult.data).toEqual(pageOne); 127 128 128 - write( 129 - store, 130 - { query: Pagination, variables: { skip: 3, limit: 3 } }, 131 - pageTwo 132 - ); 129 + write( 130 + store, 131 + { query: Pagination, variables: { skip: 3, limit: 3 } }, 132 + pageTwo 133 + ); 133 134 134 - const pageTwoResult = query(store, { 135 - query: Pagination, 136 - variables: { skip: 3, limit: 3 }, 135 + const pageTwoResult = query(store, { 136 + query: Pagination, 137 + variables: { skip: 3, limit: 3 }, 138 + }); 139 + expect((pageTwoResult.data as any).persons).toEqual([ 140 + ...pageTwo.persons, 141 + ...pageOne.persons, 142 + ]); 143 + 144 + const pageThreeResult = query(store, { 145 + query: Pagination, 146 + variables: { skip: 6, limit: 3 }, 147 + }); 148 + expect(pageThreeResult.data).toEqual(null); 137 149 }); 138 - expect((pageTwoResult.data as any).persons).toEqual([ 139 - ...pageTwo.persons, 140 - ...pageOne.persons, 141 - ]); 150 + 151 + it('handles duplicates', () => { 152 + const Pagination = gql` 153 + query ($skip: Number, $limit: Number) { 154 + __typename 155 + persons(skip: $skip, limit: $limit) { 156 + __typename 157 + id 158 + name 159 + } 160 + } 161 + `; 162 + 163 + const store = new Store({ 164 + resolvers: { 165 + Query: { 166 + persons: simplePagination(), 167 + }, 168 + }, 169 + }); 170 + 171 + const pageOne = { 172 + __typename: 'Query', 173 + persons: [ 174 + { id: 1, name: 'Jovi', __typename: 'Person' }, 175 + { id: 2, name: 'Phil', __typename: 'Person' }, 176 + { id: 3, name: 'Andy', __typename: 'Person' }, 177 + ], 178 + }; 179 + 180 + const pageTwo = { 181 + __typename: 'Query', 182 + persons: [ 183 + { id: 3, name: 'Andy', __typename: 'Person' }, 184 + { id: 4, name: 'Kadi', __typename: 'Person' }, 185 + { id: 5, name: 'Dom', __typename: 'Person' }, 186 + ], 187 + }; 188 + 189 + write( 190 + store, 191 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 192 + pageOne 193 + ); 194 + write( 195 + store, 196 + { query: Pagination, variables: { skip: 2, limit: 3 } }, 197 + pageTwo 198 + ); 142 199 143 - const pageThreeResult = query(store, { 144 - query: Pagination, 145 - variables: { skip: 6, limit: 3 }, 200 + const result = query(store, { 201 + query: Pagination, 202 + variables: { skip: 2, limit: 3 }, 203 + }); 204 + expect(result.data).toEqual({ 205 + __typename: 'Query', 206 + persons: [...pageOne.persons, pageTwo.persons[1], pageTwo.persons[2]], 207 + }); 146 208 }); 147 - expect(pageThreeResult.data).toEqual(null); 148 - }); 149 209 150 - it('handles duplicates', () => { 151 - const Pagination = gql` 152 - query ($skip: Number, $limit: Number) { 153 - __typename 154 - persons(skip: $skip, limit: $limit) { 210 + it('should not return previous result when adding a parameter', () => { 211 + const Pagination = gql` 212 + query ($skip: Number, $limit: Number, $filter: String) { 155 213 __typename 156 - id 157 - name 214 + persons(skip: $skip, limit: $limit, filter: $filter) { 215 + __typename 216 + id 217 + name 218 + } 158 219 } 159 - } 160 - `; 220 + `; 161 221 162 - const store = new Store({ 163 - resolvers: { 164 - Query: { 165 - persons: simplePagination(), 222 + const store = new Store({ 223 + resolvers: { 224 + Query: { 225 + persons: simplePagination(), 226 + }, 166 227 }, 167 - }, 168 - }); 228 + }); 169 229 170 - const pageOne = { 171 - __typename: 'Query', 172 - persons: [ 173 - { id: 1, name: 'Jovi', __typename: 'Person' }, 174 - { id: 2, name: 'Phil', __typename: 'Person' }, 175 - { id: 3, name: 'Andy', __typename: 'Person' }, 176 - ], 177 - }; 230 + const pageOne = { 231 + __typename: 'Query', 232 + persons: [ 233 + { id: 1, name: 'Jovi', __typename: 'Person' }, 234 + { id: 2, name: 'Phil', __typename: 'Person' }, 235 + { id: 3, name: 'Andy', __typename: 'Person' }, 236 + ], 237 + }; 178 238 179 - const pageTwo = { 180 - __typename: 'Query', 181 - persons: [ 182 - { id: 3, name: 'Andy', __typename: 'Person' }, 183 - { id: 4, name: 'Kadi', __typename: 'Person' }, 184 - { id: 5, name: 'Dom', __typename: 'Person' }, 185 - ], 186 - }; 239 + const emptyPage = { 240 + __typename: 'Query', 241 + persons: [], 242 + }; 187 243 188 - write( 189 - store, 190 - { query: Pagination, variables: { skip: 0, limit: 3 } }, 191 - pageOne 192 - ); 193 - write( 194 - store, 195 - { query: Pagination, variables: { skip: 2, limit: 3 } }, 196 - pageTwo 197 - ); 244 + write( 245 + store, 246 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 247 + pageOne 248 + ); 249 + write( 250 + store, 251 + { query: Pagination, variables: { skip: 0, limit: 3, filter: 'b' } }, 252 + emptyPage 253 + ); 198 254 199 - const result = query(store, { 200 - query: Pagination, 201 - variables: { skip: 2, limit: 3 }, 255 + const res = query(store, { 256 + query: Pagination, 257 + variables: { skip: 0, limit: 3, filter: 'b' }, 258 + }); 259 + expect(res.data).toEqual({ __typename: 'Query', persons: [] }); 202 260 }); 203 - expect(result.data).toEqual({ 204 - __typename: 'Query', 205 - persons: [...pageOne.persons, pageTwo.persons[1], pageTwo.persons[2]], 206 - }); 207 - }); 208 261 209 - it('should not return previous result when adding a parameter', () => { 210 - const Pagination = gql` 211 - query ($skip: Number, $limit: Number, $filter: String) { 212 - __typename 213 - persons(skip: $skip, limit: $limit, filter: $filter) { 262 + it('should preserve the correct order in forward pagination', () => { 263 + const Pagination = gql` 264 + query ($skip: Number, $limit: Number) { 214 265 __typename 215 - id 216 - name 266 + persons(skip: $skip, limit: $limit) { 267 + __typename 268 + id 269 + name 270 + } 217 271 } 218 - } 219 - `; 272 + `; 220 273 221 - const store = new Store({ 222 - resolvers: { 223 - Query: { 224 - persons: simplePagination(), 274 + const store = new Store({ 275 + resolvers: { 276 + Query: { 277 + persons: simplePagination({ mergeMode: 'after' }), 278 + }, 225 279 }, 226 - }, 227 - }); 280 + }); 228 281 229 - const pageOne = { 230 - __typename: 'Query', 231 - persons: [ 232 - { id: 1, name: 'Jovi', __typename: 'Person' }, 233 - { id: 2, name: 'Phil', __typename: 'Person' }, 234 - { id: 3, name: 'Andy', __typename: 'Person' }, 235 - ], 236 - }; 282 + const pageOne = { 283 + __typename: 'Query', 284 + persons: [ 285 + { id: 1, name: 'Jovi', __typename: 'Person' }, 286 + { id: 2, name: 'Phil', __typename: 'Person' }, 287 + { id: 3, name: 'Andy', __typename: 'Person' }, 288 + ], 289 + }; 237 290 238 - const emptyPage = { 239 - __typename: 'Query', 240 - persons: [], 241 - }; 291 + const pageTwo = { 292 + __typename: 'Query', 293 + persons: [ 294 + { id: 4, name: 'Kadi', __typename: 'Person' }, 295 + { id: 5, name: 'Dom', __typename: 'Person' }, 296 + { id: 6, name: 'Sofia', __typename: 'Person' }, 297 + ], 298 + }; 242 299 243 - write( 244 - store, 245 - { query: Pagination, variables: { skip: 0, limit: 3 } }, 246 - pageOne 247 - ); 248 - write( 249 - store, 250 - { query: Pagination, variables: { skip: 0, limit: 3, filter: 'b' } }, 251 - emptyPage 252 - ); 300 + write( 301 + store, 302 + { query: Pagination, variables: { skip: 3, limit: 3 } }, 303 + pageTwo 304 + ); 305 + write( 306 + store, 307 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 308 + pageOne 309 + ); 253 310 254 - const res = query(store, { 255 - query: Pagination, 256 - variables: { skip: 0, limit: 3, filter: 'b' }, 311 + const result = query(store, { 312 + query: Pagination, 313 + variables: { skip: 3, limit: 3 }, 314 + }); 315 + expect(result.data).toEqual({ 316 + __typename: 'Query', 317 + persons: [...pageOne.persons, ...pageTwo.persons], 318 + }); 257 319 }); 258 - expect(res.data).toEqual({ __typename: 'Query', persons: [] }); 259 - }); 260 320 261 - it('should preserve the correct order in forward pagination', () => { 262 - const Pagination = gql` 263 - query ($skip: Number, $limit: Number) { 264 - __typename 265 - persons(skip: $skip, limit: $limit) { 321 + it('should preserve the correct order in backward pagination', () => { 322 + const Pagination = gql` 323 + query ($skip: Number, $limit: Number) { 266 324 __typename 267 - id 268 - name 325 + persons(skip: $skip, limit: $limit) { 326 + __typename 327 + id 328 + name 329 + } 269 330 } 270 - } 271 - `; 331 + `; 272 332 273 - const store = new Store({ 274 - resolvers: { 275 - Query: { 276 - persons: simplePagination({ mergeMode: 'after' }), 333 + const store = new Store({ 334 + resolvers: { 335 + Query: { 336 + persons: simplePagination({ mergeMode: 'before' }), 337 + }, 277 338 }, 278 - }, 279 - }); 339 + }); 280 340 281 - const pageOne = { 282 - __typename: 'Query', 283 - persons: [ 284 - { id: 1, name: 'Jovi', __typename: 'Person' }, 285 - { id: 2, name: 'Phil', __typename: 'Person' }, 286 - { id: 3, name: 'Andy', __typename: 'Person' }, 287 - ], 288 - }; 341 + const pageOne = { 342 + __typename: 'Query', 343 + persons: [ 344 + { id: 7, name: 'Jovi', __typename: 'Person' }, 345 + { id: 8, name: 'Phil', __typename: 'Person' }, 346 + { id: 9, name: 'Andy', __typename: 'Person' }, 347 + ], 348 + }; 349 + 350 + const pageTwo = { 351 + __typename: 'Query', 352 + persons: [ 353 + { id: 4, name: 'Kadi', __typename: 'Person' }, 354 + { id: 5, name: 'Dom', __typename: 'Person' }, 355 + { id: 6, name: 'Sofia', __typename: 'Person' }, 356 + ], 357 + }; 289 358 290 - const pageTwo = { 291 - __typename: 'Query', 292 - persons: [ 293 - { id: 4, name: 'Kadi', __typename: 'Person' }, 294 - { id: 5, name: 'Dom', __typename: 'Person' }, 295 - { id: 6, name: 'Sofia', __typename: 'Person' }, 296 - ], 297 - }; 359 + write( 360 + store, 361 + { query: Pagination, variables: { skip: 3, limit: 3 } }, 362 + pageTwo 363 + ); 364 + write( 365 + store, 366 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 367 + pageOne 368 + ); 298 369 299 - write( 300 - store, 301 - { query: Pagination, variables: { skip: 3, limit: 3 } }, 302 - pageTwo 303 - ); 304 - write( 305 - store, 306 - { query: Pagination, variables: { skip: 0, limit: 3 } }, 307 - pageOne 308 - ); 370 + const result = query(store, { 371 + query: Pagination, 372 + variables: { skip: 3, limit: 3 }, 373 + }); 309 374 310 - const result = query(store, { 311 - query: Pagination, 312 - variables: { skip: 3, limit: 3 }, 313 - }); 314 - expect(result.data).toEqual({ 315 - __typename: 'Query', 316 - persons: [...pageOne.persons, ...pageTwo.persons], 375 + expect(result.data).toEqual({ 376 + __typename: 'Query', 377 + persons: [...pageTwo.persons, ...pageOne.persons], 378 + }); 317 379 }); 318 - }); 319 380 320 - it('should preserve the correct order in backward pagination', () => { 321 - const Pagination = gql` 322 - query ($skip: Number, $limit: Number) { 323 - __typename 324 - persons(skip: $skip, limit: $limit) { 381 + it('prevents overlapping of pagination on different arguments', () => { 382 + const Pagination = gql` 383 + query ($skip: Number, $limit: Number, $filter: string) { 325 384 __typename 326 - id 327 - name 385 + persons(skip: $skip, limit: $limit, filter: $filter) { 386 + __typename 387 + id 388 + name 389 + } 328 390 } 329 - } 330 - `; 391 + `; 331 392 332 - const store = new Store({ 333 - resolvers: { 334 - Query: { 335 - persons: simplePagination({ mergeMode: 'before' }), 393 + const store = new Store({ 394 + resolvers: { 395 + Query: { 396 + persons: simplePagination(), 397 + }, 336 398 }, 337 - }, 338 - }); 399 + }); 400 + 401 + const page = withId => ({ 402 + __typename: 'Query', 403 + persons: [{ id: withId, name: withId, __typename: 'Person' }], 404 + }); 405 + 406 + write( 407 + store, 408 + { query: Pagination, variables: { filter: 'one', skip: 0, limit: 1 } }, 409 + page('one') 410 + ); 339 411 340 - const pageOne = { 341 - __typename: 'Query', 342 - persons: [ 343 - { id: 7, name: 'Jovi', __typename: 'Person' }, 344 - { id: 8, name: 'Phil', __typename: 'Person' }, 345 - { id: 9, name: 'Andy', __typename: 'Person' }, 346 - ], 347 - }; 412 + write( 413 + store, 414 + { query: Pagination, variables: { filter: 'two', skip: 1, limit: 1 } }, 415 + page('two') 416 + ); 348 417 349 - const pageTwo = { 350 - __typename: 'Query', 351 - persons: [ 352 - { id: 4, name: 'Kadi', __typename: 'Person' }, 353 - { id: 5, name: 'Dom', __typename: 'Person' }, 354 - { id: 6, name: 'Sofia', __typename: 'Person' }, 355 - ], 356 - }; 418 + const resOne = query(store, { 419 + query: Pagination, 420 + variables: { filter: 'one', skip: 0, limit: 1 }, 421 + }); 422 + const resTwo = query(store, { 423 + query: Pagination, 424 + variables: { filter: 'two', skip: 1, limit: 1 }, 425 + }); 426 + const resThree = query(store, { 427 + query: Pagination, 428 + variables: { filter: 'three', skip: 2, limit: 1 }, 429 + }); 357 430 358 - write( 359 - store, 360 - { query: Pagination, variables: { skip: 3, limit: 3 } }, 361 - pageTwo 362 - ); 363 - write( 364 - store, 365 - { query: Pagination, variables: { skip: 0, limit: 3 } }, 366 - pageOne 367 - ); 431 + expect(resOne.data).toHaveProperty('persons[0].id', 'one'); 432 + expect(resOne.data).toHaveProperty('persons.length', 1); 368 433 369 - const result = query(store, { 370 - query: Pagination, 371 - variables: { skip: 3, limit: 3 }, 372 - }); 434 + expect(resTwo.data).toHaveProperty('persons[0].id', 'two'); 435 + expect(resTwo.data).toHaveProperty('persons.length', 1); 373 436 374 - expect(result.data).toEqual({ 375 - __typename: 'Query', 376 - persons: [...pageTwo.persons, ...pageOne.persons], 437 + expect(resThree.data).toEqual(null); 377 438 }); 378 439 }); 379 440 380 - it('prevents overlapping of pagination on different arguments', () => { 381 - const Pagination = gql` 382 - query ($skip: Number, $limit: Number, $filter: string) { 383 - __typename 384 - persons(skip: $skip, limit: $limit, filter: $filter) { 441 + describe('as directive', () => { 442 + it('works with forward pagination', () => { 443 + const Pagination = gql` 444 + query ($skip: Number, $limit: Number) { 385 445 __typename 386 - id 387 - name 446 + persons(skip: $skip, limit: $limit) @_simplePagination { 447 + __typename 448 + id 449 + name 450 + } 388 451 } 389 - } 390 - `; 452 + `; 391 453 392 - const store = new Store({ 393 - resolvers: { 394 - Query: { 395 - persons: simplePagination(), 454 + const store = new Store({ 455 + directives: { 456 + simplePagination: () => simplePagination(), 396 457 }, 397 - }, 398 - }); 458 + }); 399 459 400 - const page = withId => ({ 401 - __typename: 'Query', 402 - persons: [{ id: withId, name: withId, __typename: 'Person' }], 403 - }); 460 + const pageOne = { 461 + __typename: 'Query', 462 + persons: [ 463 + { id: 1, name: 'Jovi', __typename: 'Person' }, 464 + { id: 2, name: 'Phil', __typename: 'Person' }, 465 + { id: 3, name: 'Andy', __typename: 'Person' }, 466 + ], 467 + }; 404 468 405 - write( 406 - store, 407 - { query: Pagination, variables: { filter: 'one', skip: 0, limit: 1 } }, 408 - page('one') 409 - ); 469 + const pageTwo = { 470 + __typename: 'Query', 471 + persons: [ 472 + { id: 4, name: 'Kadi', __typename: 'Person' }, 473 + { id: 5, name: 'Dom', __typename: 'Person' }, 474 + { id: 6, name: 'Sofia', __typename: 'Person' }, 475 + ], 476 + }; 410 477 411 - write( 412 - store, 413 - { query: Pagination, variables: { filter: 'two', skip: 1, limit: 1 } }, 414 - page('two') 415 - ); 478 + write( 479 + store, 480 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 481 + pageOne 482 + ); 483 + const pageOneResult = query(store, { 484 + query: Pagination, 485 + variables: { skip: 0, limit: 3 }, 486 + }); 487 + expect(pageOneResult.data).toEqual(pageOne); 416 488 417 - const resOne = query(store, { 418 - query: Pagination, 419 - variables: { filter: 'one', skip: 0, limit: 1 }, 420 - }); 421 - const resTwo = query(store, { 422 - query: Pagination, 423 - variables: { filter: 'two', skip: 1, limit: 1 }, 424 - }); 425 - const resThree = query(store, { 426 - query: Pagination, 427 - variables: { filter: 'three', skip: 2, limit: 1 }, 489 + write( 490 + store, 491 + { query: Pagination, variables: { skip: 3, limit: 3 } }, 492 + pageTwo 493 + ); 494 + 495 + const pageTwoResult = query(store, { 496 + query: Pagination, 497 + variables: { skip: 3, limit: 3 }, 498 + }); 499 + 500 + expect((pageTwoResult.data as any).persons).toEqual([ 501 + ...pageOne.persons, 502 + ...pageTwo.persons, 503 + ]); 504 + 505 + const pageThreeResult = query(store, { 506 + query: Pagination, 507 + variables: { skip: 6, limit: 3 }, 508 + }); 509 + expect(pageThreeResult.data).toEqual(null); 428 510 }); 429 511 430 - expect(resOne.data).toHaveProperty('persons[0].id', 'one'); 431 - expect(resOne.data).toHaveProperty('persons.length', 1); 512 + it('works with backwards pagination', () => { 513 + const Pagination = gql` 514 + query ($skip: Number, $limit: Number) { 515 + __typename 516 + persons(skip: $skip, limit: $limit) 517 + @_simplePagination(mergeMode: "before") { 518 + __typename 519 + id 520 + name 521 + } 522 + } 523 + `; 432 524 433 - expect(resTwo.data).toHaveProperty('persons[0].id', 'two'); 434 - expect(resTwo.data).toHaveProperty('persons.length', 1); 525 + const store = new Store({ 526 + directives: { 527 + simplePagination: directiveArguments => 528 + simplePagination({ 529 + mergeMode: directiveArguments!.mergeMode as MergeMode, 530 + }), 531 + }, 532 + }); 435 533 436 - expect(resThree.data).toEqual(null); 534 + const pageOne = { 535 + __typename: 'Query', 536 + persons: [ 537 + { id: 7, name: 'Jovi', __typename: 'Person' }, 538 + { id: 8, name: 'Phil', __typename: 'Person' }, 539 + { id: 9, name: 'Andy', __typename: 'Person' }, 540 + ], 541 + }; 542 + 543 + const pageTwo = { 544 + __typename: 'Query', 545 + persons: [ 546 + { id: 4, name: 'Kadi', __typename: 'Person' }, 547 + { id: 5, name: 'Dom', __typename: 'Person' }, 548 + { id: 6, name: 'Sofia', __typename: 'Person' }, 549 + ], 550 + }; 551 + 552 + write( 553 + store, 554 + { query: Pagination, variables: { skip: 0, limit: 3 } }, 555 + pageOne 556 + ); 557 + const pageOneResult = query(store, { 558 + query: Pagination, 559 + variables: { skip: 0, limit: 3 }, 560 + }); 561 + expect(pageOneResult.data).toEqual(pageOne); 562 + 563 + write( 564 + store, 565 + { query: Pagination, variables: { skip: 3, limit: 3 } }, 566 + pageTwo 567 + ); 568 + 569 + const pageTwoResult = query(store, { 570 + query: Pagination, 571 + variables: { skip: 3, limit: 3 }, 572 + }); 573 + expect((pageTwoResult.data as any).persons).toEqual([ 574 + ...pageTwo.persons, 575 + ...pageOne.persons, 576 + ]); 577 + 578 + const pageThreeResult = query(store, { 579 + query: Pagination, 580 + variables: { skip: 6, limit: 3 }, 581 + }); 582 + expect(pageThreeResult.data).toEqual(null); 583 + }); 437 584 });
+2 -1
exchanges/graphcache/src/helpers/help.ts
··· 36 36 | 24 37 37 | 25 38 38 | 26 39 - | 27; 39 + | 27 40 + | 28; 40 41 41 42 type DebugNode = ExecutableDefinitionNode | InlineFragmentNode; 42 43
+43 -8
exchanges/graphcache/src/operations/query.ts
··· 16 16 getMainOperation, 17 17 normalizeVariables, 18 18 getFieldArguments, 19 + getDirectives, 19 20 } from '../ast'; 20 21 21 22 import { ··· 25 26 Link, 26 27 OperationRequest, 27 28 Dependencies, 29 + Resolver, 28 30 } from '../types'; 29 31 30 32 import { joinKeys, keyOfField } from '../store/keys'; ··· 295 297 return result; 296 298 }; 297 299 300 + function getFieldResolver( 301 + ctx: Context, 302 + node: FormattedNode<FieldNode>, 303 + typename: string, 304 + fieldName: string 305 + ): Resolver | void { 306 + const resolvers = ctx.store.resolvers[typename]; 307 + const fieldResolver = resolvers && resolvers[fieldName]; 308 + const directives = getDirectives(node); 309 + 310 + let directiveResolver: Resolver | undefined; 311 + for (const name in directives) { 312 + const directiveNode = directives[name]; 313 + if ( 314 + directiveNode && 315 + name !== 'include' && 316 + name !== 'skip' && 317 + ctx.store.directives[name] 318 + ) { 319 + directiveResolver = ctx.store.directives[name]( 320 + getFieldArguments(directiveNode, ctx.variables) 321 + ); 322 + if (process.env.NODE_ENV === 'production') return directiveResolver; 323 + break; 324 + } 325 + } 326 + 327 + if (fieldResolver && directiveResolver) { 328 + warn( 329 + `A resolver and directive is being used at "${typename}.${fieldName}" simultaneously. Only the directive will apply.`, 330 + 28 331 + ); 332 + } 333 + 334 + return directiveResolver || fieldResolver; 335 + } 336 + 298 337 const readSelection = ( 299 338 ctx: Context, 300 339 key: string, ··· 340 379 return; 341 380 } 342 381 343 - const resolvers = store.resolvers[typename]; 344 382 const iterate = makeSelectionIterator( 345 383 typename, 346 384 entityKey, ··· 361 399 const fieldArgs = getFieldArguments(node, ctx.variables); 362 400 const fieldAlias = getFieldAlias(node); 363 401 const fieldKey = keyOfField(fieldName, fieldArgs); 402 + const resolver = getFieldResolver(ctx, node, typename, fieldName); 364 403 const key = joinKeys(entityKey, fieldKey); 365 404 const fieldValue = InMemoryData.readRecord(entityKey, fieldKey); 366 405 const resultValue = result ? result[fieldName] : undefined; ··· 373 412 ctx.__internal.path.push(fieldAlias); 374 413 // We temporarily store the data field in here, but undefined 375 414 // means that the value is missing from the cache 376 - let dataFieldValue: void | DataField; 415 + let dataFieldValue: void | DataField = undefined; 377 416 378 417 if (fieldName === '__typename') { 379 418 // We directly assign the typename as it's already available ··· 381 420 } else if (resultValue !== undefined && node.selectionSet === undefined) { 382 421 // The field is a scalar and can be retrieved directly from the result 383 422 dataFieldValue = resultValue; 384 - } else if ( 385 - InMemoryData.currentOperation === 'read' && 386 - resolvers && 387 - resolvers[fieldName] 388 - ) { 423 + } else if (InMemoryData.currentOperation === 'read' && resolver) { 389 424 // We have to update the information in context to reflect the info 390 425 // that the resolver will receive 391 426 updateContext(ctx, output, typename, entityKey, key, fieldName); ··· 396 431 output[fieldAlias] = fieldValue; 397 432 } 398 433 399 - dataFieldValue = resolvers[fieldName]!( 434 + dataFieldValue = resolver( 400 435 output, 401 436 fieldArgs || ({} as Variables), 402 437 store,
+15
exchanges/graphcache/src/store/store.ts
··· 15 15 KeyingConfig, 16 16 Entity, 17 17 CacheExchangeOpts, 18 + DirectivesConfig, 18 19 } from '../types'; 19 20 20 21 import { invariant } from '../helpers/help'; ··· 37 38 type DocumentNode = TypedDocumentNode<any, any>; 38 39 type RootField = 'query' | 'mutation' | 'subscription'; 39 40 41 + const defaultDirectives: DirectivesConfig = { 42 + optional: () => (_parent, args, cache, info) => { 43 + const result = cache.resolve(info.parentFieldKey, info.fieldName, args); 44 + return result === undefined ? null : result; 45 + }, 46 + required: () => (_parent, args, cache, info) => { 47 + const result = cache.resolve(info.parentFieldKey, info.fieldName, args); 48 + return result === null ? undefined : result; 49 + }, 50 + }; 51 + 40 52 /** Implementation of the {@link Cache} interface as created internally by the {@link cacheExchange}. 41 53 * @internal 42 54 */ ··· 46 58 { 47 59 data: InMemoryData.InMemoryData; 48 60 61 + directives: DirectivesConfig; 49 62 resolvers: ResolverConfig; 50 63 updates: UpdatesConfig; 51 64 optimisticMutations: OptimisticMutationConfig; ··· 60 73 if (!opts) opts = {} as C; 61 74 62 75 this.resolvers = opts.resolvers || {}; 76 + this.directives = 77 + { ...defaultDirectives, ...opts.directives } || defaultDirectives; 63 78 this.optimisticMutations = opts.optimistic || {}; 64 79 this.keys = opts.keys || {}; 65 80
+16
exchanges/graphcache/src/types.ts
··· 555 555 * @see {@link https://urql.dev/goto/docs/graphcache/local-resolvers} for the full resolvers docs. 556 556 */ 557 557 resolvers?: ResolverConfig; 558 + /** Configures directives which can perform custom logic on fields. 559 + * 560 + * @remarks 561 + * A {@link DirectivesConfig} may be passed to allow local directives to be used. For example, when `@_custom` is placed on a field and the configuration contains `custom` then this directive is executed by Graphcache. 562 + * 563 + * @see {@link https://urql.dev/goto/docs/graphcache/local-directives} for the full directives docs. 564 + */ 565 + directives?: DirectivesConfig; 558 566 /** Configures optimistic updates to react to mutations instantly before an API response. 559 567 * 560 568 * @remarks ··· 691 699 [typeName: string]: { 692 700 [fieldName: string]: Resolver | void; 693 701 } | void; 702 + }; 703 + 704 + export type Directive = ( 705 + directiveArguments: Record<string, unknown> | null 706 + ) => Resolver; 707 + 708 + export type DirectivesConfig = { 709 + [directiveName: string]: Directive; 694 710 }; 695 711 696 712 /** Cache Updater, which defines additional cache updates after cache writes.