diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index 8ab3fac..0fecebc 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -256,7 +256,9 @@ class Visitor array_pop($path); continue; } - $editValue = null; + if ($result->removeNode) { + $editValue = null; + } } else { $editValue = $result; } @@ -312,7 +314,6 @@ class Visitor */ static function visitInParallel($visitors) { - // TODO: implement real parallel visiting once PHP supports it $visitorsCount = count($visitors); $skipping = new \SplFixedArray($visitorsCount); @@ -330,6 +331,8 @@ class Visitor $skipping[$i] = $node; } else if ($result->doBreak) { $skipping[$i] = $result; + } else if ($result->removeNode) { + return $result; } } else if ($result !== null) { return $result; @@ -348,6 +351,8 @@ class Visitor if ($result instanceof VisitorOperation) { if ($result->doBreak) { $skipping[$i] = $result; + } else if ($result->removeNode) { + return $result; } } else if ($result !== null) { return $result; diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 987e2de..1ad9d59 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -8,7 +8,11 @@ use GraphQL\Language\AST\Node; use GraphQL\Language\AST\OperationDefinition; use GraphQL\Language\AST\SelectionSet; use GraphQL\Language\Parser; +use GraphQL\Language\Printer; use GraphQL\Language\Visitor; +use GraphQL\Tests\Validator\TestCase; +use GraphQL\Type\Definition\Type; +use GraphQL\Utils\TypeInfo; class VisitorTest extends \PHPUnit_Framework_TestCase { @@ -643,4 +647,661 @@ class VisitorTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expected, $visited); } + + // Describe: visitInParallel + // Note: nearly identical to the above test of the same test but using visitInParallel. + + /** + * @it allows skipping a sub-tree + */ + public function testAllowsSkippingSubTree() + { + $visited = []; + + $ast = Parser::parse('{ a, b { x }, c }'); + Visitor::visit($ast, Visitor::visitInParallel([ + [ + 'enter' => function($node) use (&$visited) { + $visited[] = [ 'enter', $node->kind, isset($node->value) ? $node->value : null]; + + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { + return Visitor::skipNode(); + } + }, + + 'leave' => function($node) use (&$visited) { + $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; + } + ] + ])); + + $this->assertEquals([ + [ 'enter', 'Document', null ], + [ 'enter', 'OperationDefinition', null ], + [ 'enter', 'SelectionSet', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'a' ], + [ 'leave', 'Name', 'a' ], + [ 'leave', 'Field', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'c' ], + [ 'leave', 'Name', 'c' ], + [ 'leave', 'Field', null ], + [ 'leave', 'SelectionSet', null ], + [ 'leave', 'OperationDefinition', null ], + [ 'leave', 'Document', null ], + ], $visited); + } + + /** + * @it allows skipping different sub-trees + */ + public function testAllowsSkippingDifferentSubTrees() + { + $visited = []; + + $ast = Parser::parse('{ a { x }, b { y} }'); + Visitor::visit($ast, Visitor::visitInParallel([ + [ + 'enter' => function($node) use (&$visited) { + $visited[] = ['no-a', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') { + return Visitor::skipNode(); + } + }, + 'leave' => function($node) use (&$visited) { + $visited[] = [ 'no-a', 'leave', $node->kind, isset($node->value) ? $node->value : null ]; + } + ], + [ + 'enter' => function($node) use (&$visited) { + $visited[] = ['no-b', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { + return Visitor::skipNode(); + } + }, + 'leave' => function($node) use (&$visited) { + $visited[] = ['no-b', 'leave', $node->kind, isset($node->value) ? $node->value : null]; + } + ] + ])); + + $this->assertEquals([ + [ 'no-a', 'enter', 'Document', null ], + [ 'no-b', 'enter', 'Document', null ], + [ 'no-a', 'enter', 'OperationDefinition', null ], + [ 'no-b', 'enter', 'OperationDefinition', null ], + [ 'no-a', 'enter', 'SelectionSet', null ], + [ 'no-b', 'enter', 'SelectionSet', null ], + [ 'no-a', 'enter', 'Field', null ], + [ 'no-b', 'enter', 'Field', null ], + [ 'no-b', 'enter', 'Name', 'a' ], + [ 'no-b', 'leave', 'Name', 'a' ], + [ 'no-b', 'enter', 'SelectionSet', null ], + [ 'no-b', 'enter', 'Field', null ], + [ 'no-b', 'enter', 'Name', 'x' ], + [ 'no-b', 'leave', 'Name', 'x' ], + [ 'no-b', 'leave', 'Field', null ], + [ 'no-b', 'leave', 'SelectionSet', null ], + [ 'no-b', 'leave', 'Field', null ], + [ 'no-a', 'enter', 'Field', null ], + [ 'no-b', 'enter', 'Field', null ], + [ 'no-a', 'enter', 'Name', 'b' ], + [ 'no-a', 'leave', 'Name', 'b' ], + [ 'no-a', 'enter', 'SelectionSet', null ], + [ 'no-a', 'enter', 'Field', null ], + [ 'no-a', 'enter', 'Name', 'y' ], + [ 'no-a', 'leave', 'Name', 'y' ], + [ 'no-a', 'leave', 'Field', null ], + [ 'no-a', 'leave', 'SelectionSet', null ], + [ 'no-a', 'leave', 'Field', null ], + [ 'no-a', 'leave', 'SelectionSet', null ], + [ 'no-b', 'leave', 'SelectionSet', null ], + [ 'no-a', 'leave', 'OperationDefinition', null ], + [ 'no-b', 'leave', 'OperationDefinition', null ], + [ 'no-a', 'leave', 'Document', null ], + [ 'no-b', 'leave', 'Document', null ], + ], $visited); + } + + /** + * @it allows early exit while visiting + */ + public function testAllowsEarlyExitWhileVisiting2() + { + $visited = []; + + $ast = Parser::parse('{ a, b { x }, c }'); + Visitor::visit($ast, Visitor::visitInParallel([ [ + 'enter' => function($node) use (&$visited) { + $value = isset($node->value) ? $node->value : null; + $visited[] = ['enter', $node->kind, $value]; + if ($node->kind === 'Name' && $value === 'x') { + return Visitor::stop(); + } + }, + 'leave' => function($node) use (&$visited) { + $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; + } + ] ])); + + $this->assertEquals([ + [ 'enter', 'Document', null ], + [ 'enter', 'OperationDefinition', null ], + [ 'enter', 'SelectionSet', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'a' ], + [ 'leave', 'Name', 'a' ], + [ 'leave', 'Field', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'b' ], + [ 'leave', 'Name', 'b' ], + [ 'enter', 'SelectionSet', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'x' ] + ], $visited); + } + + /** + * @it allows early exit from different points + */ + public function testAllowsEarlyExitFromDifferentPoints() + { + $visited = []; + + $ast = Parser::parse('{ a { y }, b { x } }'); + Visitor::visit($ast, Visitor::visitInParallel([ + [ + 'enter' => function($node) use (&$visited) { + $value = isset($node->value) ? $node->value : null; + $visited[] = ['break-a', 'enter', $node->kind, $value]; + if ($node->kind === 'Name' && $value === 'a') { + return Visitor::stop(); + } + }, + 'leave' => function($node) use (&$visited) { + $visited[] = [ 'break-a', 'leave', $node->kind, isset($node->value) ? $node->value : null ]; + } + ], + [ + 'enter' => function($node) use (&$visited) { + $value = isset($node->value) ? $node->value : null; + $visited[] = ['break-b', 'enter', $node->kind, $value]; + if ($node->kind === 'Name' && $value === 'b') { + return Visitor::stop(); + } + }, + 'leave' => function($node) use (&$visited) { + $visited[] = ['break-b', 'leave', $node->kind, isset($node->value) ? $node->value : null]; + } + ], + ])); + + $this->assertEquals([ + [ 'break-a', 'enter', 'Document', null ], + [ 'break-b', 'enter', 'Document', null ], + [ 'break-a', 'enter', 'OperationDefinition', null ], + [ 'break-b', 'enter', 'OperationDefinition', null ], + [ 'break-a', 'enter', 'SelectionSet', null ], + [ 'break-b', 'enter', 'SelectionSet', null ], + [ 'break-a', 'enter', 'Field', null ], + [ 'break-b', 'enter', 'Field', null ], + [ 'break-a', 'enter', 'Name', 'a' ], + [ 'break-b', 'enter', 'Name', 'a' ], + [ 'break-b', 'leave', 'Name', 'a' ], + [ 'break-b', 'enter', 'SelectionSet', null ], + [ 'break-b', 'enter', 'Field', null ], + [ 'break-b', 'enter', 'Name', 'y' ], + [ 'break-b', 'leave', 'Name', 'y' ], + [ 'break-b', 'leave', 'Field', null ], + [ 'break-b', 'leave', 'SelectionSet', null ], + [ 'break-b', 'leave', 'Field', null ], + [ 'break-b', 'enter', 'Field', null ], + [ 'break-b', 'enter', 'Name', 'b' ] + ], $visited); + } + + /** + * @it allows early exit while leaving + */ + public function testAllowsEarlyExitWhileLeaving2() + { + $visited = []; + + $ast = Parser::parse('{ a, b { x }, c }'); + Visitor::visit($ast, Visitor::visitInParallel([ [ + 'enter' => function($node) use (&$visited) { + $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + }, + 'leave' => function($node) use (&$visited) { + $value = isset($node->value) ? $node->value : null; + $visited[] = ['leave', $node->kind, $value]; + if ($node->kind === 'Name' && $value === 'x') { + return Visitor::stop(); + } + } + ] ])); + + $this->assertEquals([ + [ 'enter', 'Document', null ], + [ 'enter', 'OperationDefinition', null ], + [ 'enter', 'SelectionSet', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'a' ], + [ 'leave', 'Name', 'a' ], + [ 'leave', 'Field', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'b' ], + [ 'leave', 'Name', 'b' ], + [ 'enter', 'SelectionSet', null ], + [ 'enter', 'Field', null ], + [ 'enter', 'Name', 'x' ], + [ 'leave', 'Name', 'x' ] + ], $visited); + } + + /** + * @it allows early exit from leaving different points + */ + public function testAllowsEarlyExitFromLeavingDifferentPoints() + { + $visited = []; + + $ast = Parser::parse('{ a { y }, b { x } }'); + Visitor::visit($ast, Visitor::visitInParallel([ + [ + 'enter' => function($node) use (&$visited) { + $visited[] = ['break-a', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + }, + 'leave' => function($node) use (&$visited) { + $visited[] = ['break-a', 'leave', $node->kind, isset($node->value) ? $node->value : null]; + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') { + return Visitor::stop(); + } + } + ], + [ + 'enter' => function($node) use (&$visited) { + $visited[] = ['break-b', 'enter', $node->kind, isset($node->value) ? $node->value : null]; + }, + 'leave' => function($node) use (&$visited) { + $visited[] = ['break-b', 'leave', $node->kind, isset($node->value) ? $node->value : null]; + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { + return Visitor::stop(); + } + } + ], + ])); + + $this->assertEquals([ + [ 'break-a', 'enter', 'Document', null ], + [ 'break-b', 'enter', 'Document', null ], + [ 'break-a', 'enter', 'OperationDefinition', null ], + [ 'break-b', 'enter', 'OperationDefinition', null ], + [ 'break-a', 'enter', 'SelectionSet', null ], + [ 'break-b', 'enter', 'SelectionSet', null ], + [ 'break-a', 'enter', 'Field', null ], + [ 'break-b', 'enter', 'Field', null ], + [ 'break-a', 'enter', 'Name', 'a' ], + [ 'break-b', 'enter', 'Name', 'a' ], + [ 'break-a', 'leave', 'Name', 'a' ], + [ 'break-b', 'leave', 'Name', 'a' ], + [ 'break-a', 'enter', 'SelectionSet', null ], + [ 'break-b', 'enter', 'SelectionSet', null ], + [ 'break-a', 'enter', 'Field', null ], + [ 'break-b', 'enter', 'Field', null ], + [ 'break-a', 'enter', 'Name', 'y' ], + [ 'break-b', 'enter', 'Name', 'y' ], + [ 'break-a', 'leave', 'Name', 'y' ], + [ 'break-b', 'leave', 'Name', 'y' ], + [ 'break-a', 'leave', 'Field', null ], + [ 'break-b', 'leave', 'Field', null ], + [ 'break-a', 'leave', 'SelectionSet', null ], + [ 'break-b', 'leave', 'SelectionSet', null ], + [ 'break-a', 'leave', 'Field', null ], + [ 'break-b', 'leave', 'Field', null ], + [ 'break-b', 'enter', 'Field', null ], + [ 'break-b', 'enter', 'Name', 'b' ], + [ 'break-b', 'leave', 'Name', 'b' ], + [ 'break-b', 'enter', 'SelectionSet', null ], + [ 'break-b', 'enter', 'Field', null ], + [ 'break-b', 'enter', 'Name', 'x' ], + [ 'break-b', 'leave', 'Name', 'x' ], + [ 'break-b', 'leave', 'Field', null ], + [ 'break-b', 'leave', 'SelectionSet', null ], + [ 'break-b', 'leave', 'Field', null ] + ], $visited); + } + + /** + * @it allows for editing on enter + */ + public function testAllowsForEditingOnEnter2() + { + $visited = []; + + $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); + $editedAst = Visitor::visit($ast, Visitor::visitInParallel([ + [ + 'enter' => function ($node) use (&$visited) { + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { + return Visitor::removeNode(); + } + } + ], + [ + 'enter' => function ($node) use (&$visited) { + $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + }, + 'leave' => function ($node) use (&$visited) { + $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; + } + ], + ])); + + $this->assertEquals( + Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]), + $ast + ); + + $this->assertEquals( + Parser::parse('{ a, c { a, c } }', ['noLocation' => true]), + $editedAst + ); + + $this->assertEquals([ + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'c'], + ['leave', 'Name', 'c'], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'c'], + ['leave', 'Name', 'c'], + ['leave', 'Field', null], + ['leave', 'SelectionSet', null], + ['leave', 'Field', null], + ['leave', 'SelectionSet', null], + ['leave', 'OperationDefinition', null], + ['leave', 'Document', null] + ], $visited); + } + + /** + * @it allows for editing on leave + */ + public function testAllowsForEditingOnLeave2() + { + $visited = []; + + $ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]); + $editedAst = Visitor::visit($ast, Visitor::visitInParallel([ + [ + 'leave' => function ($node) use (&$visited) { + if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { + return Visitor::removeNode(); + } + } + ], + [ + 'enter' => function ($node) use (&$visited) { + $visited[] = ['enter', $node->kind, isset($node->value) ? $node->value : null]; + }, + 'leave' => function ($node) use (&$visited) { + $visited[] = ['leave', $node->kind, isset($node->value) ? $node->value : null]; + } + ], + ])); + + $this->assertEquals( + Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]), + $ast + ); + + $this->assertEquals( + Parser::parse('{ a, c { a, c } }', ['noLocation' => true]), + $editedAst + ); + + $this->assertEquals([ + ['enter', 'Document', null], + ['enter', 'OperationDefinition', null], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'b'], + ['leave', 'Name', 'b'], + ['enter', 'Field', null], + ['enter', 'Name', 'c'], + ['leave', 'Name', 'c'], + ['enter', 'SelectionSet', null], + ['enter', 'Field', null], + ['enter', 'Name', 'a'], + ['leave', 'Name', 'a'], + ['leave', 'Field', null], + ['enter', 'Field', null], + ['enter', 'Name', 'b'], + ['leave', 'Name', 'b'], + ['enter', 'Field', null], + ['enter', 'Name', 'c'], + ['leave', 'Name', 'c'], + ['leave', 'Field', null], + ['leave', 'SelectionSet', null], + ['leave', 'Field', null], + ['leave', 'SelectionSet', null], + ['leave', 'OperationDefinition', null], + ['leave', 'Document', null] + ], $visited); + } + + // Describe: visitWithTypeInfo + + /** + * @it maintains type info during visit + */ + public function testMaintainsTypeInfoDuringVisit() + { + $visited = []; + + $typeInfo = new TypeInfo(TestCase::getDefaultSchema()); + + $ast = Parser::parse('{ human(id: 4) { name, pets { name }, unknown } }'); + 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 + ]; + }, + '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([ + ['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', 'name', 'Pet', 'String', null], + ['leave', 'Name', 'name', 'Pet', 'String', null], + ['leave', 'Field', null, 'Pet', 'String', null], + ['leave', 'SelectionSet', null, 'Pet', '[Pet]', null], + ['leave', 'Field', null, 'Human', '[Pet]', null], + ['enter', 'Field', null, 'Human', null, null], + ['enter', 'Name', 'unknown', 'Human', null, null], + ['leave', 'Name', 'unknown', 'Human', null, null], + ['leave', 'Field', null, 'Human', null, null], + ['leave', 'SelectionSet', null, 'Human', 'Human', null], + ['leave', 'Field', null, 'QueryRoot', 'Human', null], + ['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null], + ['leave', 'OperationDefinition', null, null, 'QueryRoot', null], + ['leave', 'Document', null, null, null, null] + ], $visited); + } + + /** + * @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 Field([ + 'alias' => $node->alias, + 'name' => $node->name, + 'arguments' => $node->arguments, + 'directives' => $node->directives, + 'selectionSet' => new SelectionSet([ + 'kind' => 'SelectionSet', + 'selections' => [ + new Field([ + 'name' => new Name(['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); + } } diff --git a/tests/Validator/TestCase.php b/tests/Validator/TestCase.php index ed77714..23f921b 100644 --- a/tests/Validator/TestCase.php +++ b/tests/Validator/TestCase.php @@ -15,12 +15,10 @@ use GraphQL\Validator\DocumentValidator; abstract class TestCase extends \PHPUnit_Framework_TestCase { - public $humanType; - /** * @return Schema */ - protected function getDefaultSchema() + public static function getDefaultSchema() { $FurColor = null; @@ -124,7 +122,8 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase ] ]); - $Human = $this->humanType = new ObjectType([ + $Human = null; + $Human = new ObjectType([ 'name' => 'Human', 'isTypeOf' => function() {return true;}, 'interfaces' => [$Being, $Intelligent], @@ -134,7 +133,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase 'args' => ['surname' => ['type' => Type::boolean()]] ], 'pets' => ['type' => Type::listOf($Pet)], - 'relatives' => ['type' => function() {return Type::listOf($this->humanType); }], + 'relatives' => ['type' => function() use (&$Human) {return Type::listOf($Human); }], 'iq' => ['type' => Type::int()] ] ]);