diff --git a/packages/apollo-client/src/__tests__/local-state/export.ts b/packages/apollo-client/src/__tests__/local-state/export.ts index a3f5568b724..713955940f1 100644 --- a/packages/apollo-client/src/__tests__/local-state/export.ts +++ b/packages/apollo-client/src/__tests__/local-state/export.ts @@ -3,6 +3,7 @@ import gql from 'graphql-tag'; import ApolloClient from '../..'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { ApolloLink, Observable } from 'apollo-link'; +import { print } from 'graphql/language/printer'; describe('@client @export tests', () => { it( @@ -156,7 +157,7 @@ describe('@client @export tests', () => { cache.writeData({ data: { currentAuthor: testAuthor, - } + }, }); return client.query({ query }).then(({ data }: any) => { @@ -572,6 +573,79 @@ describe('@client @export tests', () => { }, ); + it('should not add __typename to @export-ed objects (#4691)', () => { + const query = gql` + query GetListItems($where: LessonFilter) { + currentFilter @client @export(as: "where") { + title_contains + enabled + } + lessonCollection(where: $where) { + items { + title + slug + } + } + } + `; + + const expectedServerQuery = gql` + query GetListItems($where: LessonFilter) { + lessonCollection(where: $where) { + items { + title + slug + __typename + } + __typename + } + } + `; + + const currentFilter = { + title_contains: 'full', + enabled: true, + }; + + const data = { + lessonCollection: { + __typename: 'LessonCollection', + items: [ + { + __typename: 'ListItem', + title: 'full title', + slug: 'slug-title', + }, + ], + }, + }; + + const client = new ApolloClient({ + link: new ApolloLink(request => { + expect(request.variables.where).toEqual(currentFilter); + expect(print(request.query)).toBe(print(expectedServerQuery)); + return Observable.of({ data }); + }), + cache: new InMemoryCache({ + addTypename: true, + }), + resolvers: { + Query: { + currentFilter() { + return currentFilter; + }, + }, + }, + }); + + return client.query({ query }).then(result => { + expect(result.data).toEqual({ + currentFilter, + ...data, + }); + }); + }); + it( 'should use the value of the last @export variable defined, if multiple ' + 'variables are defined with the same name', diff --git a/packages/apollo-utilities/src/transform.ts b/packages/apollo-utilities/src/transform.ts index 58e9402a209..d77b8e54e3b 100644 --- a/packages/apollo-utilities/src/transform.ts +++ b/packages/apollo-utilities/src/transform.ts @@ -24,6 +24,7 @@ import { } from './getFromAST'; import { filterInPlace } from './util/filterInPlace'; import { invariant } from 'ts-invariant'; +import { isField, isInlineFragment } from './storeUtils'; export type RemoveNodeConfig = { name?: string; @@ -228,15 +229,26 @@ export function addTypenameToDocument(doc: DocumentNode): DocumentNode { // introspection query, do nothing. const skip = selections.some(selection => { return ( - selection.kind === 'Field' && - ((selection as FieldNode).name.value === '__typename' || - (selection as FieldNode).name.value.lastIndexOf('__', 0) === 0) + isField(selection) && + (selection.name.value === '__typename' || + selection.name.value.lastIndexOf('__', 0) === 0) ); }); if (skip) { return; } + // If this SelectionSet is @export-ed as an input variable, it should + // not have a __typename field (see issue #4691). + const field = parent as FieldNode; + if ( + isField(field) && + field.directives && + field.directives.some(d => d.name.value === 'export') + ) { + return; + } + // Create and return a new SelectionSet with a __typename Field. return { ...node, @@ -292,7 +304,7 @@ function hasDirectivesInSelection( selection: SelectionNode, nestedCheck = true, ): boolean { - if (selection.kind !== 'Field' || !(selection as FieldNode)) { + if (!isField(selection)) { return true; } @@ -446,7 +458,7 @@ function getAllFragmentSpreadsFromSelectionSet( selectionSet.selections.forEach(selection => { if ( - (selection.kind === 'Field' || selection.kind === 'InlineFragment') && + (isField(selection) || isInlineFragment(selection)) && selection.selectionSet ) { getAllFragmentSpreadsFromSelectionSet(selection.selectionSet).forEach( @@ -514,12 +526,8 @@ export function removeClientSetsFromDocument( enter(node) { if (node.selectionSet) { const isTypenameOnly = node.selectionSet.selections.every( - selection => { - return ( - selection.kind === 'Field' && - (selection as FieldNode).name.value === '__typename' - ); - }, + selection => + isField(selection) && selection.name.value === '__typename', ); if (isTypenameOnly) { return null;