function expectInvalid($schema, $rules, $queryString, $errors) { $result = DocumentValidator::validate($schema, Parser::parse($queryString), $rules); $this->assertEquals(false, $result['isValid'], 'GraphQL should not validate'); $this->assertEquals($errors, $result['errors']); return $result; }
/** * @it isTypeOf used to resolve runtime type for Union */ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() { $dogType = new ObjectType(['name' => 'Dog', 'isTypeOf' => function ($obj) { return $obj instanceof Dog; }, 'fields' => ['name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()]]]); $catType = new ObjectType(['name' => 'Cat', 'isTypeOf' => function ($obj) { return $obj instanceof Cat; }, 'fields' => ['name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()]]]); $petType = new UnionType(['name' => 'Pet', 'types' => [$dogType, $catType]]); $schema = new Schema(['query' => new ObjectType(['name' => 'Query', 'fields' => ['pets' => ['type' => Type::listOf($petType), 'resolve' => function () { return [new Dog('Odie', true), new Cat('Garfield', false)]; }]]])]); $query = '{ pets { name ... on Dog { woofs } ... on Cat { meows } } }'; $expected = new ExecutionResult(['pets' => [['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false]]]); $this->assertEquals($expected, Executor::execute($schema, Parser::parse($query))); }
/** * @it evaluates mutations correctly in the presense of a failed mutation */ public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() { $doc = 'mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { theNumber }, second: promiseToChangeTheNumber(newNumber: 2) { theNumber }, third: failToChangeTheNumber(newNumber: 3) { theNumber } fourth: promiseToChangeTheNumber(newNumber: 4) { theNumber }, fifth: immediatelyChangeTheNumber(newNumber: 5) { theNumber } sixth: promiseAndFailToChangeTheNumber(newNumber: 6) { theNumber } }'; $ast = Parser::parse($doc); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $expected = ['data' => ['first' => ['theNumber' => 1], 'second' => ['theNumber' => 2], 'third' => null, 'fourth' => ['theNumber' => 4], 'fifth' => ['theNumber' => 5], 'sixth' => null], 'errors' => [FormattedError::create('Cannot change the number', [new SourceLocation(8, 7)]), FormattedError::create('Cannot change the number', [new SourceLocation(17, 7)])]]; $this->assertArraySubset($expected, self::awaitPromise($mutationResult)); }
function expectInvalid($schema, $rules, $queryString, $expectedErrors) { $errors = DocumentValidator::validate($schema, Parser::parse($queryString), $rules); $this->assertNotEmpty($errors, 'GraphQL should not validate'); $this->assertEquals($expectedErrors, array_map(['GraphQL\\Error\\Error', 'formatError'], $errors)); return $errors; }
/** * Set up the graphql request. * * @param $query string * @return void */ public function setupRequest($query = 'GraphGL request', $operation = 'query') { $source = new Source($query); $ast = GraphQLParser::parse($source); if (isset($ast->definitions[0])) { $d = $ast->definitions[0]; $operation = $d->operation ?: 'query'; $selectionSet = $d->selectionSet->selections; $this->parseSelections($selectionSet, $operation); } }
public function testExecutesUsingASchema() { $BlogArticle = null; $BlogImage = new ObjectType(['name' => 'Image', 'fields' => ['url' => ['type' => Type::string()], 'width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]]]); $BlogAuthor = new ObjectType(['name' => 'Author', 'fields' => ['id' => ['type' => Type::string()], 'name' => ['type' => Type::string()], 'pic' => ['args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]], 'type' => $BlogImage, 'resolve' => function ($obj, $args) { return $obj['pic']($args['width'], $args['height']); }], 'recentArticle' => ['type' => function () use(&$BlogArticle) { return $BlogArticle; }]]]); $BlogArticle = new ObjectType(['name' => 'Article', 'fields' => ['id' => ['type' => Type::nonNull(Type::string())], 'isPublished' => ['type' => Type::boolean()], 'author' => ['type' => $BlogAuthor], 'title' => ['type' => Type::string()], 'body' => ['type' => Type::string()], 'keywords' => ['type' => Type::listOf(Type::string())]]]); $BlogQuery = new ObjectType(['name' => 'Query', 'fields' => ['article' => ['type' => $BlogArticle, 'args' => ['id' => ['type' => Type::id()]], 'resolve' => function ($_, $args) { return $this->article($args['id']); }], 'feed' => ['type' => Type::listOf($BlogArticle), 'resolve' => function () { return [$this->article(1), $this->article(2), $this->article(3), $this->article(4), $this->article(5), $this->article(6), $this->article(7), $this->article(8), $this->article(9), $this->article(10)]; }]]]); $BlogSchema = new Schema($BlogQuery); $request = ' { feed { id, title }, article(id: "1") { ...articleFields, author { id, name, pic(width: 640, height: 480) { url, width, height }, recentArticle { ...articleFields, keywords } } } } fragment articleFields on Article { id, isPublished, title, body, hidden, notdefined } '; $expected = ['data' => ['feed' => [['id' => '1', 'title' => 'My Article 1'], ['id' => '2', 'title' => 'My Article 2'], ['id' => '3', 'title' => 'My Article 3'], ['id' => '4', 'title' => 'My Article 4'], ['id' => '5', 'title' => 'My Article 5'], ['id' => '6', 'title' => 'My Article 6'], ['id' => '7', 'title' => 'My Article 7'], ['id' => '8', 'title' => 'My Article 8'], ['id' => '9', 'title' => 'My Article 9'], ['id' => '10', 'title' => 'My Article 10']], 'article' => ['id' => '1', 'isPublished' => true, 'title' => 'My Article 1', 'body' => 'This is a post', 'author' => ['id' => '123', 'name' => 'John Smith', 'pic' => ['url' => 'cdn://123', 'width' => 640, 'height' => 480], 'recentArticle' => ['id' => '1', 'isPublished' => true, 'title' => 'My Article 1', 'body' => 'This is a post', 'keywords' => ['foo', 'bar', '1', 'true', null]]]]]]; $this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray()); }
public function testParseCreatesAst() { $source = new Source('{ node(id: 4) { id, name } } '); $result = Parser::parse($source); $expected = new Document(array('loc' => new Location(0, 41, $source), 'definitions' => array(new OperationDefinition(array('loc' => new Location(0, 40, $source), 'operation' => 'query', 'name' => null, 'variableDefinitions' => null, 'directives' => array(), 'selectionSet' => new SelectionSet(array('loc' => new Location(0, 40, $source), 'selections' => array(new Field(array('loc' => new Location(4, 38, $source), 'alias' => null, 'name' => new Name(array('loc' => new Location(4, 8, $source), 'value' => 'node')), 'arguments' => array(new Argument(array('name' => new Name(array('loc' => new Location(9, 11, $source), 'value' => 'id')), 'value' => new IntValue(array('loc' => new Location(13, 14, $source), 'value' => '4')), 'loc' => new Location(9, 14, $source)))), 'directives' => [], 'selectionSet' => new SelectionSet(array('loc' => new Location(16, 38, $source), 'selections' => array(new Field(array('loc' => new Location(22, 24, $source), 'alias' => null, 'name' => new Name(array('loc' => new Location(22, 24, $source), 'value' => 'id')), 'arguments' => [], 'directives' => [], 'selectionSet' => null)), new Field(array('loc' => new Location(30, 34, $source), 'alias' => null, 'name' => new Name(array('loc' => new Location(30, 34, $source), 'value' => 'name')), 'arguments' => [], 'directives' => [], 'selectionSet' => null))))))))))))))); $this->assertEquals($expected, $result); }
/** * Handles execution of a lazily created interface */ public function testReturnsFragmentsWithLazyCreatedInterface() { $request = ' { lazyInterface { ... on TestObject { name } } } '; $expected = ['data' => ['lazyInterface' => ['name' => 'testname']]]; $this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray()); }
private function check($testType, $testData, $expected) { $data = ['test' => $testData]; $dataType = null; $dataType = new ObjectType(['name' => 'DataType', 'fields' => function () use(&$testType, &$dataType, $data) { return ['test' => ['type' => $testType], 'nest' => ['type' => $dataType, 'resolve' => function () use($data) { return $data; }]]; }]); $schema = new Schema(['query' => $dataType]); $ast = Parser::parse('{ nest { test } }'); $result = Executor::execute($schema, $ast, $data); $this->assertArraySubset($expected, $result->toArray()); }
/** * @param Schema $schema * @param $requestString * @param mixed $rootValue * @param array <string, string>|null $variableValues * @param string|null $operationName * @return array */ public static function execute(Schema $schema, $requestString, $rootValue = null, $variableValues = null, $operationName = null) { try { $source = new Source($requestString ?: '', 'GraphQL request'); $documentAST = Parser::parse($source); $validationErrors = DocumentValidator::validate($schema, $documentAST); if (!empty($validationErrors)) { return ['errors' => array_map(['GraphQL\\Error', 'formatError'], $validationErrors)]; } else { return Executor::execute($schema, $documentAST, $rootValue, $variableValues, $operationName)->toArray(); } } catch (Error $e) { return ['errors' => [Error::formatError($e)]]; } }
/** * @param Schema $schema * @param $requestString * @param mixed $rootObject * @param array <string, string>|null $variableValues * @param string|null $operationName * @return array */ public static function execute(Schema $schema, $requestString, $rootObject = null, $variableValues = null, $operationName = null) { try { $source = new Source($requestString ?: '', 'GraphQL request'); $ast = Parser::parse($source); $validationResult = DocumentValidator::validate($schema, $ast); if (empty($validationResult['isValid'])) { return ['errors' => $validationResult['errors']]; } else { return Executor::execute($schema, $rootObject, $ast, $operationName, $variableValues); } } catch (\Exception $e) { return ['errors' => Error::formatError($e)]; } }
/** * @param Schema $schema * @param $requestString * @param null $rootValue * @param null $variableValues * @param null $operationName * @return array|ExecutionResult */ public static function executeAndReturnResult(Schema $schema, $requestString, $rootValue = null, $variableValues = null, $operationName = null) { try { $source = new Source($requestString ?: '', 'GraphQL request'); $documentAST = Parser::parse($source); $validationErrors = DocumentValidator::validate($schema, $documentAST); if (!empty($validationErrors)) { return new ExecutionResult(null, $validationErrors); } else { return Executor::execute($schema, $documentAST, $rootValue, $variableValues, $operationName); } } catch (Error $e) { return new ExecutionResult(null, [$e]); } }
public function testPrintsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); $expected = <<<'EOT' query queryName($foo: ComplexType, $site: Site = MOBILE) { whoever123is: node(id: [123, 456]) { id, ... on User @defer { field2 { id, alias: field1(first: 10, after: $foo) @if: $foo { id, ...frag } } } } } mutation likeStory { like(story: 123) @defer { story { id } } } fragment frag on Friend { foo(size: $size, bar: $b, obj: {key: "value"}) } { unnamed(truthy: true, falsey: false), query } EOT; $this->assertEquals($expected, $printed); }
/** * @param Schema $schema * @param $requestString * @param null $rootValue * @param null $variableValues * @param null $operationName * @return array|ExecutionResult */ public static function executeAndReturnResult(Schema $schema, $requestString, $rootValue = null, $contextValue = null, $variableValues = null, $operationName = null) { try { if ($requestString instanceof Document) { $documentAST = $requestString; } else { $source = new Source($requestString ?: '', 'GraphQL request'); $documentAST = Parser::parse($source); } /** @var QueryComplexity $queryComplexity */ $queryComplexity = DocumentValidator::getRule('QueryComplexity'); $queryComplexity->setRawVariableValues($variableValues); $validationErrors = DocumentValidator::validate($schema, $documentAST); if (!empty($validationErrors)) { return new ExecutionResult(null, $validationErrors); } else { return Executor::execute($schema, $documentAST, $rootValue, $contextValue, $variableValues, $operationName); } } catch (Error $e) { return new ExecutionResult(null, [$e]); } }
public function testPrintsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/schema-kitchen-sink.graphql'); $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); $expected = 'schema { query: QueryType mutation: MutationType } type Foo implements Bar { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B scalar CustomScalar scalar AnnotatedScalar @onScalar enum Site { DESKTOP MOBILE } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObjectType { annotatedField: Type @onField } extend type Foo { seven(argument: [String]): Type } extend type Foo @onType {} type NoFields {} directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT '; $this->assertEquals($expected, $printed); }
private function executeTestQuery($doc) { return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray(); }
/** * @see https://github.com/webonyx/graphql-php/issues/59 */ public function testSerializesToEmptyObjectVsEmptyArray() { $iface = null; $a = new ObjectType(['name' => 'A', 'fields' => ['id' => Type::id()], 'interfaces' => function () use(&$iface) { return [$iface]; }]); $b = new ObjectType(['name' => 'B', 'fields' => ['id' => Type::id()], 'interfaces' => function () use(&$iface) { return [$iface]; }]); $iface = new InterfaceType(['name' => 'Iface', 'fields' => ['id' => Type::id()], 'resolveType' => function ($v) use($a, $b) { return $v['type'] === 'A' ? $a : $b; }]); $schema = new Schema(['query' => new ObjectType(['name' => 'Query', 'fields' => ['ab' => Type::listOf($iface)]]), 'types' => [$a, $b]]); $data = ['ab' => [['id' => 1, 'type' => 'A'], ['id' => 2, 'type' => 'A'], ['id' => 3, 'type' => 'B'], ['id' => 4, 'type' => 'B']]]; $query = Parser::parse(' { ab { ... on A{ id } } } '); $result = Executor::execute($schema, $query, $data, null); $this->assertEquals(['data' => ['ab' => [['id' => '1'], ['id' => '2'], new \stdClass(), new \stdClass()]]], $result->toArray()); }
/** * @it when argument provided cannot be parsed */ public function testWhenArgumentProvidedCannotBeParsed() { $ast = Parser::parse('{ fieldWithDefaultArgumentValue(input: WRONG_TYPE) }'); $this->assertEquals(['data' => ['fieldWithDefaultArgumentValue' => '"Hello World"']], Executor::execute($this->schema(), $ast)->toArray()); }
public function testHandlesNonNullListsOfNonNullsWhenTheyReturnNull() { $doc = ' query Q { nest { nonNullListOfNonNullReturnsNull, } } '; $ast = Parser::parse($doc); $expected = ['data' => ['nest' => null], 'errors' => [FormattedError::create('Cannot return null for non-nullable type.', [new SourceLocation(4, 11)])]]; $this->assertEquals($expected, Executor::execute($this->schema(), $ast, $this->data(), [], 'Q')->toArray()); }
public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull() { $doc = ' query Q { nonNullPromise } '; $ast = Parser::parse($doc); $expected = ['data' => null, 'errors' => [FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(2, 17)])]]; Executor::setPromiseAdapter(new ReactPromiseAdapter()); $this->assertArraySubsetPromise($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')); }
public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() { $doc = ' query q($input:[String!]!) { nnListNN(input: $input) } '; $ast = Parser::parse($doc); $expected = ['data' => null, 'errors' => [new FormattedError('Variable $input expected value of type [String!]! but got: ["A",null,"B"].', [new SourceLocation(2, 17)])]]; $this->assertEquals($expected, Executor::execute($this->schema(), null, $ast, null, ['input' => ['A', null, 'B']])); }
public function testResolvedValueIsMemoized() { $doc = ' query Q { a { b { c d } } } '; $memoizedValue = new \ArrayObject(['b' => 'id1']); $A = null; $Test = new ObjectType(['name' => 'Test', 'fields' => ['a' => ['type' => function () use(&$A) { return Type::listOf($A); }, 'resolve' => function () use($memoizedValue) { return [$memoizedValue, new \ArrayObject(['b' => 'id2']), $memoizedValue, new \ArrayObject(['b' => 'id2'])]; }]]]); $callCounts = ['id1' => 0, 'id2' => 0]; $A = new ObjectType(['name' => 'A', 'fields' => ['b' => ['type' => new ObjectType(['name' => 'B', 'fields' => ['c' => ['type' => Type::string()], 'd' => ['type' => Type::string()]]]), 'resolve' => function ($value) use(&$callCounts) { $callCounts[$value['b']]++; switch ($value['b']) { case 'id1': return ['c' => 'c1', 'd' => 'd1']; case 'id2': return ['c' => 'c2', 'd' => 'd2']; } }]]]); // Test that value resolved once is memoized for same query field $schema = new Schema($Test); $query = Parser::parse($doc); $result = Executor::execute($schema, $query); $expected = ['data' => ['a' => [['b' => ['c' => 'c1', 'd' => 'd1']], ['b' => ['c' => 'c2', 'd' => 'd2']], ['b' => ['c' => 'c1', 'd' => 'd1']], ['b' => ['c' => 'c2', 'd' => 'd2']]]]]; $this->assertEquals($expected, $result->toArray()); $this->assertSame($callCounts['id1'], 1); // Result for id1 is expected to be memoized after first call $this->assertSame($callCounts['id2'], 2); }
/** * @it Simple input object with args should fail */ public function testSimpleInputObjectWithArgsShouldFail() { $body = ' input Hello { world(foo: Int): String }'; $this->setExpectedException('GraphQL\\Error\\SyntaxError'); Parser::parse($body); }
public function testDoesNotIncludeArgumentsThatWereNotSet() { $schema = new Schema(new ObjectType(['name' => 'Type', 'fields' => ['field' => ['type' => Type::string(), 'resolve' => function ($data, $args) { return $args ? json_encode($args) : ''; }, 'args' => ['a' => ['type' => Type::boolean()], 'b' => ['type' => Type::boolean()], 'c' => ['type' => Type::boolean()], 'd' => ['type' => Type::int()], 'e' => ['type' => Type::int()]]]]])); $query = Parser::parse('{ field(a: true, c: false, e: 0) }'); $result = Executor::execute($schema, $query); $expected = ['data' => ['field' => '{"a":true,"c":false,"e":0}']]; $this->assertEquals($expected, $result->toArray()); /* var query = parse('{ field(a: true, c: false, e: 0) }'); var result = await execute(schema, query); expect(result).to.deep.equal({ data: { field: '{"a":true,"c":false,"e":0}' } }); }); it('fails when an isTypeOf check is not met', async () => { class Special { constructor(value) { this.value = value; } } class NotSpecial { constructor(value) { this.value = value; } } var SpecialType = new GraphQLObjectType({ name: 'SpecialType', isTypeOf(obj) { return obj instanceof Special; }, fields: { value: { type: GraphQLString } } }); var schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { specials: { type: new GraphQLList(SpecialType), resolve: rootValue => rootValue.specials } } }) }); var query = parse('{ specials { value } }'); var value = { specials: [ new Special('foo'), new NotSpecial('bar') ] }; var result = await execute(schema, query, value); expect(result.data).to.deep.equal({ specials: [ { value: 'foo' }, null ] }); expect(result.errors).to.have.lengthOf(1); expect(result.errors).to.containSubset([ { message: 'Expected value of type "SpecialType" but got: [object Object].', locations: [ { line: 1, column: 3 } ] } ]); }); */ }
/** * Helper function to test a query and the expected response. */ private function validationErrors($query) { $ast = Parser::parse($query); return DocumentValidator::validate(StarWarsSchema::build(), $ast); }
/** * @it prints kitchen sink */ public function testPrintsKitchenSink() { $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $ast = Parser::parse($kitchenSink); $printed = Printer::doPrint($ast); $expected = <<<'EOT' query queryName($foo: ComplexType, $site: Site = MOBILE) { whoever123is: node(id: [123, 456]) { id ... on User @defer { field2 { id alias: field1(first: 10, after: $foo) @include(if: $foo) { id ...frag } } } ... @skip(unless: $foo) { id } ... { id } } } mutation likeStory { like(story: 123) @defer { story { id } } } subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { storyLikeSubscribe(input: $input) { story { likers { count } likeSentence { text } } } } fragment frag on Friend { foo(size: $size, bar: $b, obj: {key: "value"}) } { unnamed(truthy: true, falsey: false) query } EOT; $this->assertEquals($expected, $printed); }
/** * @it serializes to include message and locations */ public function testSerializesToIncludeMessageAndLocations() { $node = Parser::parse('{ field }')->definitions[0]->selectionSet->selections[0]; $e = new Error('msg', [$node]); $this->assertEquals(['message' => 'msg', 'locations' => [['line' => 1, 'column' => 3]]], $e->toSerializableArray()); }
public function testAllowsFragmentConditionsToBeAbstractTypes() { $ast = Parser::parse(' { __typename name pets { ...PetFields } friends { ...FriendFields } } fragment PetFields on Pet { __typename ... on Dog { name barks } ... on Cat { name meows } } fragment FriendFields on Named { __typename name ... on Dog { barks } ... on Cat { meows } } '); $expected = ['data' => ['__typename' => 'Person', 'name' => 'John', 'pets' => [['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]], 'friends' => [['__typename' => 'Person', 'name' => 'Liz'], ['__typename' => 'Dog', 'name' => 'Odie', 'barks' => true]]]]; $this->assertEquals($expected, Executor::execute($this->schema, $this->john, $ast)); }
public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() { // nulls the top level if sync non-nullable field returns null $doc = ' query Q { nonNullSync } '; $expected = ['data' => null, 'errors' => [FormattedError::create('Cannot return null for non-nullable type.', [new SourceLocation(2, 17)])]]; $this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray()); }
/** * @it maintains type info during edit */ public function testMaintainsTypeInfoDuringEdit() { $visited = []; $typeInfo = new TypeInfo(TestCase::getDefaultSchema()); $ast = Parser::parse('{ human(id: 4) { name, pets }, alien }'); $editedAst = Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, ['enter' => function ($node) use($typeInfo, &$visited) { $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); $inputType = $typeInfo->getInputType(); $visited[] = ['enter', $node->kind, $node->kind === 'Name' ? $node->value : null, $parentType ? (string) $parentType : null, $type ? (string) $type : null, $inputType ? (string) $inputType : null]; // Make a query valid by adding missing selection sets. if ($node->kind === 'Field' && !$node->selectionSet && Type::isCompositeType(Type::getNamedType($type))) { return new FieldNode(['alias' => $node->alias, 'name' => $node->name, 'arguments' => $node->arguments, 'directives' => $node->directives, 'selectionSet' => new SelectionSetNode(['kind' => 'SelectionSet', 'selections' => [new FieldNode(['name' => new NameNode(['value' => '__typename'])])]])]); } }, 'leave' => function ($node) use($typeInfo, &$visited) { $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); $inputType = $typeInfo->getInputType(); $visited[] = ['leave', $node->kind, $node->kind === 'Name' ? $node->value : null, $parentType ? (string) $parentType : null, $type ? (string) $type : null, $inputType ? (string) $inputType : null]; }])); $this->assertEquals(Printer::doPrint(Parser::parse('{ human(id: 4) { name, pets }, alien }')), Printer::doPrint($ast)); $this->assertEquals(Printer::doPrint(Parser::parse('{ human(id: 4) { name, pets { __typename } }, alien { __typename } }')), Printer::doPrint($editedAst)); $this->assertEquals([['enter', 'Document', null, null, null, null], ['enter', 'OperationDefinition', null, null, 'QueryRoot', null], ['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['enter', 'Field', null, 'QueryRoot', 'Human', null], ['enter', 'Name', 'human', 'QueryRoot', 'Human', null], ['leave', 'Name', 'human', 'QueryRoot', 'Human', null], ['enter', 'Argument', null, 'QueryRoot', 'Human', 'ID'], ['enter', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['leave', 'Name', 'id', 'QueryRoot', 'Human', 'ID'], ['enter', 'IntValue', null, 'QueryRoot', 'Human', 'ID'], ['leave', 'IntValue', null, 'QueryRoot', 'Human', 'ID'], ['leave', 'Argument', null, 'QueryRoot', 'Human', 'ID'], ['enter', 'SelectionSet', null, 'Human', 'Human', null], ['enter', 'Field', null, 'Human', 'String', null], ['enter', 'Name', 'name', 'Human', 'String', null], ['leave', 'Name', 'name', 'Human', 'String', null], ['leave', 'Field', null, 'Human', 'String', null], ['enter', 'Field', null, 'Human', '[Pet]', null], ['enter', 'Name', 'pets', 'Human', '[Pet]', null], ['leave', 'Name', 'pets', 'Human', '[Pet]', null], ['enter', 'SelectionSet', null, 'Pet', '[Pet]', null], ['enter', 'Field', null, 'Pet', 'String!', null], ['enter', 'Name', '__typename', 'Pet', 'String!', null], ['leave', 'Name', '__typename', 'Pet', 'String!', null], ['leave', 'Field', null, 'Pet', 'String!', null], ['leave', 'SelectionSet', null, 'Pet', '[Pet]', null], ['leave', 'Field', null, 'Human', '[Pet]', null], ['leave', 'SelectionSet', null, 'Human', 'Human', null], ['leave', 'Field', null, 'QueryRoot', 'Human', null], ['enter', 'Field', null, 'QueryRoot', 'Alien', null], ['enter', 'Name', 'alien', 'QueryRoot', 'Alien', null], ['leave', 'Name', 'alien', 'QueryRoot', 'Alien', null], ['enter', 'SelectionSet', null, 'Alien', 'Alien', null], ['enter', 'Field', null, 'Alien', 'String!', null], ['enter', 'Name', '__typename', 'Alien', 'String!', null], ['leave', 'Name', '__typename', 'Alien', 'String!', null], ['leave', 'Field', null, 'Alien', 'String!', null], ['leave', 'SelectionSet', null, 'Alien', 'Alien', null], ['leave', 'Field', null, 'QueryRoot', 'Alien', null], ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], ['leave', 'OperationDefinition', null, null, 'QueryRoot', null], ['leave', 'Document', null, null, null, null]], $visited); }