UNPKG

apollo-codegen

Version:

Generate API code or type annotations based on a GraphQL schema and query documents

438 lines (362 loc) 13 kB
import { stripIndent } from 'common-tags'; import { parse, isType, GraphQLID, GraphQLString, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLInputObjectType, GraphQLEnumType } from 'graphql'; import { classDeclarationForOperation, initializerDeclarationForProperties, structDeclarationForFragment, structDeclarationForSelectionSet, typeDeclarationForGraphQLType, } from '../../src/swift/codeGeneration'; import { dictionaryLiteralForFieldArguments, } from '../../src/swift/values'; import { loadSchema } from '../../src/loading'; const schema = loadSchema(require.resolve('../starwars/schema.json')); import CodeGenerator from '../../src/utilities/CodeGenerator'; import { compileToIR } from '../../src/compilation'; describe('Swift code generation', function() { let generator; let compileFromSource; let addFragment; beforeEach(function() { const context = { schema: schema, operations: {}, fragments: {}, typesUsed: {} } generator = new CodeGenerator(context); compileFromSource = (source) => { const document = parse(source); const context = compileToIR(schema, document); generator.context = context; return context; }; addFragment = (fragment) => { generator.context.fragments[fragment.fragmentName] = fragment; }; }); describe('#classDeclarationForOperation()', function() { test(`should generate a class declaration for a query with variables`, function() { const { operations } = compileFromSource(` query HeroName($episode: Episode) { hero(episode: $episode) { name } } `); classDeclarationForOperation(generator, operations['HeroName']); expect(generator.output).toMatchSnapshot(); }); test(`should generate a class declaration for a query with fragment spreads`, function() { const { operations } = compileFromSource(` query Hero { hero { ...HeroDetails } } fragment HeroDetails on Character { name } `); classDeclarationForOperation(generator, operations['Hero']); expect(generator.output).toMatchSnapshot(); }); test(`should generate a class declaration for a query with conditional fragment spreads`, function() { const { operations } = compileFromSource(` query Hero { hero { ...DroidDetails } } fragment DroidDetails on Droid { primaryFunction } `); classDeclarationForOperation(generator, operations['Hero']); expect(generator.output).toMatchSnapshot(); }); test(`should generate a class declaration for a query with a fragment spread nested in an inline fragment`, function() { const { operations } = compileFromSource(` query Hero { hero { ... on Droid { ...HeroDetails } } } fragment HeroDetails on Character { name } `); classDeclarationForOperation(generator, operations['Hero']); expect(generator.output).toMatchSnapshot(); }); test(`should generate a class declaration for a mutation with variables`, function() { const { operations } = compileFromSource(` mutation CreateReview($episode: Episode) { createReview(episode: $episode, review: { stars: 5, commentary: "Wow!" }) { stars commentary } } `); classDeclarationForOperation(generator, operations['CreateReview']); expect(generator.output).toMatchSnapshot(); }); }); describe('#initializerDeclarationForProperties()', function() { test(`should generate initializer for a property`, function() { initializerDeclarationForProperties(generator, [ { propertyName: 'episode', type: new GraphQLNonNull(schema.getType('Episode')), typeName: 'Episode' } ]); expect(generator.output).toMatchSnapshot(); }); test(`should generate initializer for an optional property`, function() { initializerDeclarationForProperties(generator, [ { propertyName: 'episode', type: schema.getType('Episode'), typeName: 'Episode?', isOptional: true } ]); expect(generator.output).toMatchSnapshot(); }); test(`should generate initializer for multiple properties`, function() { initializerDeclarationForProperties(generator, [ { propertyName: 'episode', type: schema.getType('Episode'), typeName: 'Episode?', isOptional: true }, { propertyName: 'scene', type: GraphQLString, typeName: 'String?', isOptional: true } ]); expect(generator.output).toMatchSnapshot(); }); }); describe('#structDeclarationForFragment()', function() { test(`should generate a struct declaration for a fragment with an abstract type condition`, function() { const { fragments } = compileFromSource(` fragment HeroDetails on Character { name appearsIn } `); structDeclarationForFragment(generator, fragments['HeroDetails']); expect(generator.output).toMatchSnapshot(); }); test(`should generate a struct declaration for a fragment with a concrete type condition`, function() { const { fragments } = compileFromSource(` fragment DroidDetails on Droid { name primaryFunction } `); structDeclarationForFragment(generator, fragments['DroidDetails']); expect(generator.output).toMatchSnapshot(); }); test(`should generate a struct declaration for a fragment with a subselection`, function() { const { fragments } = compileFromSource(` fragment HeroDetails on Character { name friends { name } } `); structDeclarationForFragment(generator, fragments['HeroDetails']); expect(generator.output).toMatchSnapshot(); }); test(`should generate a struct declaration for a fragment that includes a fragment spread`, function() { const { fragments } = compileFromSource(` fragment HeroDetails on Character { name ...MoreHeroDetails } fragment MoreHeroDetails on Character { appearsIn } `); structDeclarationForFragment(generator, fragments['HeroDetails']); expect(generator.output).toMatchSnapshot(); }); }); describe('#structDeclarationForSelectionSet()', function() { test(`should generate a struct declaration for a selection set`, function() { structDeclarationForSelectionSet(generator, { structName: 'Hero', parentType: schema.getType('Character'), fields: [ { responseName: 'name', fieldName: 'name', type: GraphQLString } ] }); expect(generator.output).toMatchSnapshot(); }); test(`should escape reserved keywords in a struct declaration for a selection set`, function() { structDeclarationForSelectionSet(generator, { structName: 'Hero', parentType: schema.getType('Character'), fields: [ { responseName: 'private', fieldName: 'name', type: GraphQLString } ] }); expect(generator.output).toMatchSnapshot(); }); test(`should generate a nested struct declaration for a selection set with subselections`, function() { structDeclarationForSelectionSet(generator, { structName: 'Hero', parentType: schema.getType('Character'), fields: [ { responseName: 'friends', fieldName: 'friends', type: new GraphQLList(schema.getType('Character')), fields: [ { responseName: 'name', fieldName: 'name', type: GraphQLString } ] } ] }); expect(generator.output).toMatchSnapshot(); }); test(`should generate a struct declaration for a selection set with a fragment spread that matches the parent type`, function() { addFragment({ fragmentName: 'HeroDetails', typeCondition: schema.getType('Character') }); structDeclarationForSelectionSet(generator, { structName: 'Hero', parentType: schema.getType('Character'), fragmentSpreads: ['HeroDetails'], fields: [ { responseName: 'name', fieldName: 'name', type: GraphQLString } ] }); expect(generator.output).toMatchSnapshot(); }); test(`should generate a struct declaration for a selection set with a fragment spread with a more specific type condition`, function() { addFragment({ fragmentName: 'DroidDetails', typeCondition: schema.getType('Droid') }); structDeclarationForSelectionSet(generator, { structName: 'Hero', parentType: schema.getType('Character'), fragmentSpreads: ['DroidDetails'], fields: [ { responseName: 'name', fieldName: 'name', type: GraphQLString } ] }); expect(generator.output).toMatchSnapshot(); }); test(`should generate a struct declaration for a selection set with an inline fragment`, function() { structDeclarationForSelectionSet(generator, { structName: 'Hero', parentType: schema.getType('Character'), fields: [ { responseName: 'name', fieldName: 'name', type: new GraphQLNonNull(GraphQLString) } ], inlineFragments: [ { typeCondition: schema.getType('Droid'), possibleTypes: ['Droid'], fields: [ { responseName: 'name', fieldName: 'name', type: new GraphQLNonNull(GraphQLString) }, { responseName: 'primaryFunction', fieldName: 'primaryFunction', type: GraphQLString } ] } ] }); expect(generator.output).toMatchSnapshot(); }); test(`should generate a struct declaration for a fragment spread nested in an inline fragment`, function() { addFragment({ fragmentName: 'HeroDetails', typeCondition: schema.getType('Character') }); structDeclarationForSelectionSet(generator, { structName: 'Hero', parentType: schema.getType('Character'), fields: [], inlineFragments: [ { typeCondition: schema.getType('Droid'), possibleTypes: ['Droid'], fields: [], fragmentSpreads: ['HeroDetails'], } ] }); expect(generator.output).toMatchSnapshot(); }); }); describe('#dictionaryLiteralForFieldArguments()', function() { test('should include expressions for input objects with variables', function() { const { operations } = compileFromSource(` mutation FieldArgumentsWithInputObjects($commentary: String!, $red: Int!) { createReview(episode: JEDI, review: { stars: 2, commentary: $commentary, favorite_color: { red: $red, blue: 100, green: 50 } }) { commentary } } `); const fieldArguments = operations['FieldArgumentsWithInputObjects'].fields[0].args; const dictionaryLiteral = dictionaryLiteralForFieldArguments(fieldArguments); expect(dictionaryLiteral).toBe('["episode": "JEDI", "review": ["stars": 2, "commentary": Variable("commentary"), "favorite_color": ["red": Variable("red"), "blue": 100, "green": 50]]]'); }); }); describe('#typeDeclarationForGraphQLType()', function() { test('should generate an enum declaration for a GraphQLEnumType', function() { const generator = new CodeGenerator(); typeDeclarationForGraphQLType(generator, schema.getType('Episode')); expect(generator.output).toMatchSnapshot(); }); test('should escape identifiers in cases of enum declaration for a GraphQLEnumType', function() { const generator = new CodeGenerator(); const albumPrivaciesEnum = new GraphQLEnumType({ name: 'AlbumPrivacies', values: { PUBLIC: { value: "PUBLIC" }, PRIVATE: { value: "PRIVATE" } } }); typeDeclarationForGraphQLType(generator, albumPrivaciesEnum); expect(generator.output).toMatchSnapshot(); }); test('should generate a struct declaration for a GraphQLInputObjectType', function() { const generator = new CodeGenerator(); typeDeclarationForGraphQLType(generator, schema.getType('ReviewInput')); expect(generator.output).toMatchSnapshot(); }); }); });