From 2cc1f23cbe2e226413b517851975fea4d86b1c23 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Wed, 19 Jul 2023 22:22:37 +0200 Subject: [PATCH] adust tests --- docs/graphcache/local-directives.md | 8 ++- .../src/extras/relayPagination.test.ts | 2 +- .../src/extras/simplePagination.test.ts | 72 ++++++++++++++++++- exchanges/graphcache/src/operations/query.ts | 37 +++++++--- exchanges/graphcache/src/store/store.ts | 4 +- exchanges/graphcache/src/types.ts | 27 ++----- 6 files changed, 110 insertions(+), 40 deletions(-) diff --git a/docs/graphcache/local-directives.md b/docs/graphcache/local-directives.md index a92dacc5b6..923153b8e9 100644 --- a/docs/graphcache/local-directives.md +++ b/docs/graphcache/local-directives.md @@ -20,13 +20,15 @@ If you want to add directives yourself you can do so by performing cacheExchange({ directives: { // If you now add `@_pagination` to your document we will execute this - pagination: () => {}, + pagination: directiveArguments => () => { + /* Resolver */ + }, }, }); ``` -The function signature of a directive is the same as the one of a [Resolver](./local-directives.md). In -case you need to access the arguments you have passed to a directive you can do so by checking `info.directiveArguments`. +The function signature of a directive is a function which receives the arguments the directive is called with in the document. +That function should returns a [Resolver](./local-directives.md). ### Reading on diff --git a/exchanges/graphcache/src/extras/relayPagination.test.ts b/exchanges/graphcache/src/extras/relayPagination.test.ts index 8f45be42b6..8b724499fa 100644 --- a/exchanges/graphcache/src/extras/relayPagination.test.ts +++ b/exchanges/graphcache/src/extras/relayPagination.test.ts @@ -1566,7 +1566,7 @@ describe('as directive', () => { const store = new Store({ directives: { - relayPagination: relayPagination(), + relayPagination: relayPagination, }, }); diff --git a/exchanges/graphcache/src/extras/simplePagination.test.ts b/exchanges/graphcache/src/extras/simplePagination.test.ts index a675f1115f..013bf3db26 100644 --- a/exchanges/graphcache/src/extras/simplePagination.test.ts +++ b/exchanges/graphcache/src/extras/simplePagination.test.ts @@ -453,7 +453,7 @@ describe('as directive', () => { const store = new Store({ directives: { - simplePagination: simplePagination(), + simplePagination: simplePagination, }, }); @@ -508,4 +508,74 @@ describe('as directive', () => { }); expect(pageThreeResult.data).toEqual(null); }); + + it('works with backwards pagination', () => { + const Pagination = gql` + query ($skip: Number, $limit: Number) { + __typename + persons(skip: $skip, limit: $limit) + @_simplePagination(mergeMode: "before") { + __typename + id + name + } + } + `; + + const store = new Store({ + directives: { + simplePagination: simplePagination, + }, + }); + + const pageOne = { + __typename: 'Query', + persons: [ + { id: 7, name: 'Jovi', __typename: 'Person' }, + { id: 8, name: 'Phil', __typename: 'Person' }, + { id: 9, name: 'Andy', __typename: 'Person' }, + ], + }; + + const pageTwo = { + __typename: 'Query', + persons: [ + { id: 4, name: 'Kadi', __typename: 'Person' }, + { id: 5, name: 'Dom', __typename: 'Person' }, + { id: 6, name: 'Sofia', __typename: 'Person' }, + ], + }; + + write( + store, + { query: Pagination, variables: { skip: 0, limit: 3 } }, + pageOne + ); + const pageOneResult = query(store, { + query: Pagination, + variables: { skip: 0, limit: 3 }, + }); + expect(pageOneResult.data).toEqual(pageOne); + + write( + store, + { query: Pagination, variables: { skip: 3, limit: 3 } }, + pageTwo + ); + + const pageTwoResult = query(store, { + query: Pagination, + variables: { skip: 3, limit: 3 }, + }); + expect((pageTwoResult.data as any).persons).toEqual([ + ...pageTwo.persons, + ...pageOne.persons, + ]); + + const pageThreeResult = query(store, { + query: Pagination, + variables: { skip: 6, limit: 3 }, + }); + expect(pageThreeResult.data).toEqual(null); + }); }); diff --git a/exchanges/graphcache/src/operations/query.ts b/exchanges/graphcache/src/operations/query.ts index 4eb97ca97e..77ae94b08a 100644 --- a/exchanges/graphcache/src/operations/query.ts +++ b/exchanges/graphcache/src/operations/query.ts @@ -4,6 +4,7 @@ import { FieldNode, DocumentNode, FragmentDefinitionNode, + DirectiveNode, } from '@0no-co/graphql.web'; import { @@ -16,6 +17,7 @@ import { getMainOperation, normalizeVariables, getFieldArguments, + getDirectives, } from '../ast'; import { @@ -25,6 +27,7 @@ import { Link, OperationRequest, Dependencies, + Directive, } from '../types'; import { joinKeys, keyOfField } from '../store/keys'; @@ -295,6 +298,20 @@ export const _queryFragment = ( return result; }; +function getFieldDirective( + node: FormattedNode, + store: Store +): { storeDirective: Directive; fieldDirective: DirectiveNode } | undefined { + const directives = getDirectives(node); + for (const name in directives) { + if (name !== 'include' && name !== 'skip' && store.directives[name]) + return { + storeDirective: store.directives[name], + fieldDirective: directives[name]!, + }; + } +} + const readSelection = ( ctx: Context, key: string, @@ -356,8 +373,7 @@ const readSelection = ( let node: FormattedNode | void; const output = InMemoryData.makeData(input); while ((node = iterate()) !== undefined) { - const fieldDirectives = Object.keys(node._directives || {}).map(x => x); - const storeDirective = fieldDirectives.find(x => store.directives[x]); + const foundDirective = getFieldDirective(node, store); // Derive the needed data from our node. const fieldName = getName(node); @@ -376,7 +392,7 @@ const readSelection = ( ctx.__internal.path.push(fieldAlias); // We temporarily store the data field in here, but undefined // means that the value is missing from the cache - let dataFieldValue: void | DataField; + let dataFieldValue: void | DataField = undefined; if (fieldName === '__typename') { // We directly assign the typename as it's already available @@ -386,7 +402,7 @@ const readSelection = ( dataFieldValue = resultValue; } else if ( InMemoryData.currentOperation === 'read' && - ((resolvers && resolvers[fieldName]) || storeDirective) + ((resolvers && resolvers[fieldName]) || foundDirective) ) { // We have to update the information in context to reflect the info // that the resolver will receive @@ -398,7 +414,7 @@ const readSelection = ( output[fieldAlias] = fieldValue; } - if (resolvers && resolvers[fieldName] && storeDirective) { + if (resolvers && resolvers[fieldName] && foundDirective) { warn( `A resolver and directive is being used at "${typename}.${fieldName}", only the directive will apply.`, 28 @@ -412,11 +428,12 @@ const readSelection = ( store, ctx ); - } else if (storeDirective) { - const fieldDirective = node._directives![storeDirective]; - const directiveArguments = - getFieldArguments(fieldDirective, ctx.variables) || {}; - dataFieldValue = store.directives[storeDirective]!(directiveArguments)( + } else { + const directiveArguments = getFieldArguments( + foundDirective!.fieldDirective, + ctx.variables + ); + dataFieldValue = foundDirective!.storeDirective(directiveArguments)( output, fieldArgs || ({} as Variables), store, diff --git a/exchanges/graphcache/src/store/store.ts b/exchanges/graphcache/src/store/store.ts index db27dfe774..9c6fbfb00a 100644 --- a/exchanges/graphcache/src/store/store.ts +++ b/exchanges/graphcache/src/store/store.ts @@ -39,11 +39,11 @@ type DocumentNode = TypedDocumentNode; type RootField = 'query' | 'mutation' | 'subscription'; const defaultDirectives: DirectivesConfig = { - optional: (_parent, args, cache, info) => { + optional: () => (_parent, args, cache, info) => { const result = cache.resolve(info.parentFieldKey, info.fieldName, args); return result === undefined ? null : result; }, - required: (_parent, args, cache, info) => { + required: () => (_parent, args, cache, info) => { const result = cache.resolve(info.parentFieldKey, info.fieldName, args); return result === null ? undefined : result; }, diff --git a/exchanges/graphcache/src/types.ts b/exchanges/graphcache/src/types.ts index fcb8818d90..dfeb6aab85 100644 --- a/exchanges/graphcache/src/types.ts +++ b/exchanges/graphcache/src/types.ts @@ -684,29 +684,6 @@ export type Resolver< ): Result; }['bivarianceHack']; -/** Cache Directive, which may resolve or replace data during cache reads. - * - * @param parent - The GraphQL object that is currently being constructed from cache data. - * @param args - This field’s arguments. - * @param cache - {@link Cache} interface. - * @param info - {@link ResolveInfo} interface. - * @returns a {@link ResolverResult}, which is an updated value, partial entity, or entity key - * - * @see {@link https://urql.dev/goto/docs/graphcache/local-directives} for the full directives docs. - */ -export type Directive< - ParentData = DataFields, - Args = Variables, - Result = ResolverResult -> = { - bivarianceHack( - parent: ParentData, - args: Args, - cache: Cache, - info: ResolveInfo & { directiveArguments: Record } - ): Result; -}['bivarianceHack']; - /** Configures resolvers which replace cached reuslts with custom values. * * @remarks @@ -724,6 +701,10 @@ export type ResolverConfig = { } | void; }; +export type Directive = ( + directiveArguments: Record | null +) => Resolver; + export type DirectivesConfig = { [directiveName: string]: Directive; };