diff --git a/tests/Executor/AbstractPromiseTest.php b/tests/Executor/AbstractPromiseTest.php index 8a6f1d1..70e2edd 100644 --- a/tests/Executor/AbstractPromiseTest.php +++ b/tests/Executor/AbstractPromiseTest.php @@ -1,79 +1,83 @@ 'Pet', + 'name' => 'Pet', 'fields' => [ - 'name' => [ 'type' => Type::string() ] - ] + 'name' => ['type' => Type::string()], + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', - 'interfaces' => [ $PetType ], - 'isTypeOf' => function($obj) { - return new Deferred(function() use ($obj) { + 'name' => 'Dog', + 'interfaces' => [$PetType], + 'isTypeOf' => function ($obj) { + return new Deferred(function () use ($obj) { return $obj instanceof Dog; }); }, - 'fields' => [ - 'name' => [ 'type' => Type::string() ], - 'woofs' => [ 'type' => Type::boolean() ], - ] + 'fields' => [ + 'name' => ['type' => Type::string()], + 'woofs' => ['type' => Type::boolean()], + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', - 'interfaces' => [ $PetType ], - 'isTypeOf' => function($obj) { - return new Deferred(function() use ($obj) { + 'name' => 'Cat', + 'interfaces' => [$PetType], + 'isTypeOf' => function ($obj) { + return new Deferred(function () use ($obj) { return $obj instanceof Cat; }); }, - 'fields' => [ - 'name' => [ 'type' => Type::string() ], - 'meows' => [ 'type' => Type::boolean() ], - ] + 'fields' => [ + 'name' => ['type' => Type::string()], + 'meows' => ['type' => Type::boolean()], + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), - 'resolve' => function() { + 'type' => Type::listOf($PetType), + 'resolve' => function () { return [ new Dog('Odie', true), - new Cat('Garfield', false) + new Cat('Garfield', false), ]; - } - ] - ] + }, + ], + ], ]), - 'types' => [ $CatType, $DogType ] + 'types' => [$CatType, $DogType], ]); $query = '{ @@ -93,10 +97,10 @@ class AbstractPromiseTest extends TestCase $expected = [ 'data' => [ 'pets' => [ - [ 'name' => 'Odie', 'woofs' => true ], - [ 'name' => 'Garfield', 'meows' => false ] - ] - ] + ['name' => 'Odie', 'woofs' => true], + ['name' => 'Garfield', 'meows' => false], + ], + ], ]; $this->assertEquals($expected, $result); @@ -107,58 +111,57 @@ class AbstractPromiseTest extends TestCase */ public function testIsTypeOfCanBeRejected() : void { - $PetType = new InterfaceType([ - 'name' => 'Pet', + 'name' => 'Pet', 'fields' => [ - 'name' => ['type' => Type::string()] - ] + 'name' => ['type' => Type::string()], + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$PetType], - 'isTypeOf' => function () { + 'isTypeOf' => function () { return new Deferred(function () { throw new UserError('We are testing this error'); }); }, - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$PetType], - 'isTypeOf' => function ($obj) { + 'isTypeOf' => function ($obj) { return new Deferred(function () use ($obj) { return $obj instanceof Cat; }); }, - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return [ new Dog('Odie', true), - new Cat('Garfield', false) + new Cat('Garfield', false), ]; - } - ] - ] + }, + ], + ], ]), - 'types' => [$CatType, $DogType] + 'types' => [$CatType, $DogType], ]); $query = '{ @@ -176,21 +179,21 @@ class AbstractPromiseTest extends TestCase $result = GraphQL::executeQuery($schema, $query)->toArray(); $expected = [ - 'data' => [ - 'pets' => [null, null] + 'data' => [ + 'pets' => [null, null], ], 'errors' => [ [ - 'message' => 'We are testing this error', + 'message' => 'We are testing this error', 'locations' => [['line' => 2, 'column' => 7]], - 'path' => ['pets', 0], + 'path' => ['pets', 0], ], [ - 'message' => 'We are testing this error', + 'message' => 'We are testing this error', 'locations' => [['line' => 2, 'column' => 7]], - 'path' => ['pets', 1] - ] - ] + 'path' => ['pets', 1], + ], + ], ]; $this->assertArraySubset($expected, $result); @@ -201,50 +204,49 @@ class AbstractPromiseTest extends TestCase */ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void { - $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'isTypeOf' => function ($obj) { return new Deferred(function () use ($obj) { return $obj instanceof Dog; }); }, - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'isTypeOf' => function ($obj) { return new Deferred(function () use ($obj) { return $obj instanceof Cat; }); }, - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $PetType = new UnionType([ - 'name' => 'Pet', - 'types' => [$DogType, $CatType] + 'name' => 'Pet', + 'types' => [$DogType, $CatType], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return [new Dog('Odie', true), new Cat('Garfield', false)]; - } - ] - ] - ]) + }, + ], + ], + ]), ]); $query = '{ @@ -266,9 +268,9 @@ class AbstractPromiseTest extends TestCase 'data' => [ 'pets' => [ ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false] - ] - ] + ['name' => 'Garfield', 'meows' => false], + ], + ], ]; $this->assertEquals($expected, $result); @@ -280,7 +282,7 @@ class AbstractPromiseTest extends TestCase public function testResolveTypeOnInterfaceYieldsUsefulError() : void { $PetType = new InterfaceType([ - 'name' => 'Pet', + 'name' => 'Pet', 'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) { return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) { if ($obj instanceof Dog) { @@ -292,58 +294,59 @@ class AbstractPromiseTest extends TestCase if ($obj instanceof Human) { return $HumanType; } + return null; }); }, - 'fields' => [ - 'name' => ['type' => Type::string()] - ] + 'fields' => [ + 'name' => ['type' => Type::string()], + ], ]); $HumanType = new ObjectType([ - 'name' => 'Human', + 'name' => 'Human', 'fields' => [ 'name' => ['type' => Type::string()], - ] + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return new Deferred(function () { return [ new Dog('Odie', true), new Cat('Garfield', false), - new Human('Jon') + new Human('Jon'), ]; }); - } - ] - ] + }, + ], + ], ]), - 'types' => [$CatType, $DogType] + 'types' => [$CatType, $DogType], ]); $query = '{ @@ -361,20 +364,20 @@ class AbstractPromiseTest extends TestCase $result = GraphQL::executeQuery($schema, $query)->toArray(true); $expected = [ - 'data' => [ + 'data' => [ 'pets' => [ ['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false], - null - ] + null, + ], ], 'errors' => [ [ 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', - 'locations' => [['line' => 2, 'column' => 7]], - 'path' => ['pets', 2] + 'locations' => [['line' => 2, 'column' => 7]], + 'path' => ['pets', 2], ], - ] + ], ]; $this->assertArraySubset($expected, $result); @@ -385,32 +388,31 @@ class AbstractPromiseTest extends TestCase */ public function testResolveTypeOnUnionYieldsUsefulError() : void { - $HumanType = new ObjectType([ - 'name' => 'Human', + 'name' => 'Human', 'fields' => [ 'name' => ['type' => Type::string()], - ] + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'fields' => [ - 'name' => ['type' => Type::string()], + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'fields' => [ - 'name' => ['type' => Type::string()], + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $PetType = new UnionType([ - 'name' => 'Pet', + 'name' => 'Pet', 'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) { return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) { if ($obj instanceof Dog) { @@ -422,28 +424,29 @@ class AbstractPromiseTest extends TestCase if ($obj instanceof Human) { return $HumanType; } + return null; }); }, - 'types' => [$DogType, $CatType] + 'types' => [$DogType, $CatType], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return [ new Dog('Odie', true), new Cat('Garfield', false), - new Human('Jon') + new Human('Jon'), ]; - } - ] - ] - ]) + }, + ], + ], + ]), ]); $query = '{ @@ -462,20 +465,20 @@ class AbstractPromiseTest extends TestCase $result = GraphQL::executeQuery($schema, $query)->toArray(true); $expected = [ - 'data' => [ + 'data' => [ 'pets' => [ ['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false], - null - ] + null, + ], ], 'errors' => [ [ 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', - 'locations' => [['line' => 2, 'column' => 7]], - 'path' => ['pets', 2] - ] - ] + 'locations' => [['line' => 2, 'column' => 7]], + 'path' => ['pets', 2], + ], + ], ]; $this->assertArraySubset($expected, $result); @@ -487,7 +490,7 @@ class AbstractPromiseTest extends TestCase public function testResolveTypeAllowsResolvingWithTypeName() : void { $PetType = new InterfaceType([ - 'name' => 'Pet', + 'name' => 'Pet', 'resolveType' => function ($obj) { return new Deferred(function () use ($obj) { if ($obj instanceof Dog) { @@ -496,49 +499,49 @@ class AbstractPromiseTest extends TestCase if ($obj instanceof Cat) { return 'Cat'; } + return null; }); }, - 'fields' => [ - 'name' => ['type' => Type::string()] - ] + 'fields' => [ + 'name' => ['type' => Type::string()], + ], ]); - $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return [ new Dog('Odie', true), - new Cat('Garfield', false) + new Cat('Garfield', false), ]; - } - ] - ] + }, + ], + ], ]), - 'types' => [$CatType, $DogType] + 'types' => [$CatType, $DogType], ]); $query = '{ @@ -560,8 +563,8 @@ class AbstractPromiseTest extends TestCase 'pets' => [ ['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false], - ] - ] + ], + ], ]; $this->assertEquals($expected, $result); } @@ -571,53 +574,52 @@ class AbstractPromiseTest extends TestCase */ public function testResolveTypeCanBeCaught() : void { - $PetType = new InterfaceType([ - 'name' => 'Pet', + 'name' => 'Pet', 'resolveType' => function () { return new Deferred(function () { throw new UserError('We are testing this error'); }); }, - 'fields' => [ - 'name' => ['type' => Type::string()] - ] + 'fields' => [ + 'name' => ['type' => Type::string()], + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return [ new Dog('Odie', true), - new Cat('Garfield', false) + new Cat('Garfield', false), ]; - } - ] - ] + }, + ], + ], ]), - 'types' => [$CatType, $DogType] + 'types' => [$CatType, $DogType], ]); $query = '{ @@ -635,21 +637,21 @@ class AbstractPromiseTest extends TestCase $result = GraphQL::executeQuery($schema, $query)->toArray(); $expected = [ - 'data' => [ - 'pets' => [null, null] + 'data' => [ + 'pets' => [null, null], ], 'errors' => [ [ - 'message' => 'We are testing this error', + 'message' => 'We are testing this error', 'locations' => [['line' => 2, 'column' => 7]], - 'path' => ['pets', 0] + 'path' => ['pets', 0], ], [ - 'message' => 'We are testing this error', + 'message' => 'We are testing this error', 'locations' => [['line' => 2, 'column' => 7]], - 'path' => ['pets', 1] - ] - ] + 'path' => ['pets', 1], + ], + ], ]; $this->assertArraySubset($expected, $result); diff --git a/tests/Executor/AbstractTest.php b/tests/Executor/AbstractTest.php index 38ebd1f..97197cb 100644 --- a/tests/Executor/AbstractTest.php +++ b/tests/Executor/AbstractTest.php @@ -1,23 +1,28 @@ 'Pet', + 'name' => 'Pet', 'fields' => [ - 'name' => ['type' => Type::string()] - ] + 'name' => ['type' => Type::string()], + ], ]); // Added to interface type when defined $dogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$petType], - 'isTypeOf' => function($obj) { return $obj instanceof Dog; }, - 'fields' => [ - 'name' => ['type' => Type::string()], - 'woofs' => ['type' => Type::boolean()] - ] + 'isTypeOf' => function ($obj) { + return $obj instanceof Dog; + }, + 'fields' => [ + 'name' => ['type' => Type::string()], + 'woofs' => ['type' => Type::boolean()], + ], ]); $catType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$petType], - 'isTypeOf' => function ($obj) { + 'isTypeOf' => function ($obj) { return $obj instanceof Cat; }, - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($petType), + 'type' => Type::listOf($petType), 'resolve' => function () { return [new Dog('Odie', true), new Cat('Garfield', false)]; - } - ] - ] + }, + ], + ], ]), - 'types' => [$catType, $dogType] + 'types' => [$catType, $dogType], ]); $query = '{ @@ -84,8 +91,8 @@ class AbstractTest extends TestCase $expected = new ExecutionResult([ 'pets' => [ ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false] - ] + ['name' => 'Garfield', 'meows' => false], + ], ]); $result = Executor::execute($schema, Parser::parse($query)); @@ -98,42 +105,44 @@ class AbstractTest extends TestCase public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void { $dogType = new ObjectType([ - 'name' => 'Dog', - 'isTypeOf' => function($obj) { return $obj instanceof Dog; }, - 'fields' => [ - 'name' => ['type' => Type::string()], - 'woofs' => ['type' => Type::boolean()] - ] + 'name' => 'Dog', + 'isTypeOf' => function ($obj) { + return $obj instanceof Dog; + }, + 'fields' => [ + 'name' => ['type' => Type::string()], + 'woofs' => ['type' => Type::boolean()], + ], ]); $catType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'isTypeOf' => function ($obj) { return $obj instanceof Cat; }, - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $petType = new UnionType([ - 'name' => 'Pet', - 'types' => [$dogType, $catType] + 'name' => 'Pet', + 'types' => [$dogType, $catType], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($petType), - 'resolve' => function() { - return [ new Dog('Odie', true), new Cat('Garfield', false) ]; - } - ] - ] - ]) + 'type' => Type::listOf($petType), + 'resolve' => function () { + return [new Dog('Odie', true), new Cat('Garfield', false)]; + }, + ], + ], + ]), ]); $query = '{ @@ -151,8 +160,8 @@ class AbstractTest extends TestCase $expected = new ExecutionResult([ 'pets' => [ ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false] - ] + ['name' => 'Garfield', 'meows' => false], + ], ]); $this->assertEquals($expected, Executor::execute($schema, Parser::parse($query))); @@ -161,14 +170,14 @@ class AbstractTest extends TestCase /** * @see it('resolveType on Interface yields useful error') */ - function testResolveTypeOnInterfaceYieldsUsefulError() + public function testResolveTypeOnInterfaceYieldsUsefulError() : void { - $DogType = null; - $CatType = null; + $DogType = null; + $CatType = null; $HumanType = null; $PetType = new InterfaceType([ - 'name' => 'Pet', + 'name' => 'Pet', 'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) { if ($obj instanceof Dog) { return $DogType; @@ -179,58 +188,58 @@ class AbstractTest extends TestCase if ($obj instanceof Human) { return $HumanType; } + return null; }, - 'fields' => [ - 'name' => ['type' => Type::string()] - ] + 'fields' => [ + 'name' => ['type' => Type::string()], + ], ]); $HumanType = new ObjectType([ - 'name' => 'Human', + 'name' => 'Human', 'fields' => [ 'name' => ['type' => Type::string()], - ] + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$PetType], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return [ new Dog('Odie', true), new Cat('Garfield', false), - new Human('Jon') + new Human('Jon'), ]; - } - ] + }, + ], ], ]), - 'types' => [$DogType, $CatType] + 'types' => [$DogType, $CatType], ]); - $query = '{ pets { name @@ -244,20 +253,21 @@ class AbstractTest extends TestCase }'; $expected = [ - 'data' => [ + 'data' => [ 'pets' => [ ['name' => 'Odie', 'woofs' => true], ['name' => 'Garfield', 'meows' => false], - null - ] + null, + ], ], 'errors' => [[ 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', - 'locations' => [['line' => 2, 'column' => 11]], - 'path' => ['pets', 2] - ]] + 'locations' => [['line' => 2, 'column' => 11]], + 'path' => ['pets', 2], + ], + ], ]; - $actual = GraphQL::executeQuery($schema, $query)->toArray(true); + $actual = GraphQL::executeQuery($schema, $query)->toArray(true); $this->assertArraySubset($expected, $actual); } @@ -268,30 +278,30 @@ class AbstractTest extends TestCase public function testResolveTypeOnUnionYieldsUsefulError() : void { $HumanType = new ObjectType([ - 'name' => 'Human', + 'name' => 'Human', 'fields' => [ 'name' => ['type' => Type::string()], - ] + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'fields' => [ - 'name' => ['type' => Type::string()], + 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], - ] + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'fields' => [ - 'name' => ['type' => Type::string()], + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], - ] + ], ]); $PetType = new UnionType([ - 'name' => 'Pet', + 'name' => 'Pet', 'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) { if ($obj instanceof Dog) { return $DogType; @@ -303,25 +313,25 @@ class AbstractTest extends TestCase return $HumanType; } }, - 'types' => [$DogType, $CatType] + 'types' => [$DogType, $CatType], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($PetType), 'resolve' => function () { return [ new Dog('Odie', true), new Cat('Garfield', false), - new Human('Jon') + new Human('Jon'), ]; - } - ] - ] - ]) + }, + ], + ], + ]), ]); $query = '{ @@ -337,22 +347,27 @@ class AbstractTest extends TestCase } }'; - $result = GraphQL::executeQuery($schema, $query)->toArray(true); + $result = GraphQL::executeQuery($schema, $query)->toArray(true); $expected = [ - 'data' => [ + 'data' => [ 'pets' => [ - ['name' => 'Odie', - 'woofs' => true], - ['name' => 'Garfield', - 'meows' => false], - null - ] + [ + 'name' => 'Odie', + 'woofs' => true, + ], + [ + 'name' => 'Garfield', + 'meows' => false, + ], + null, + ], ], 'errors' => [[ 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', - 'locations' => [['line' => 2, 'column' => 11]], - 'path' => ['pets', 2] - ]] + 'locations' => [['line' => 2, 'column' => 11]], + 'path' => ['pets', 2], + ], + ], ]; $this->assertArraySubset($expected, $result); } @@ -363,25 +378,25 @@ class AbstractTest extends TestCase public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : void { $fooInterface = new InterfaceType([ - 'name' => 'FooInterface', - 'fields' => ['bar' => ['type' => Type::string()]], + 'name' => 'FooInterface', + 'fields' => ['bar' => ['type' => Type::string()]], 'resolveType' => function () { return []; }, ]); $fooObject = new ObjectType([ - 'name' => 'FooObject', - 'fields' => ['bar' => ['type' => Type::string()]], + 'name' => 'FooObject', + 'fields' => ['bar' => ['type' => Type::string()]], 'interfaces' => [$fooInterface], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'foo' => [ - 'type' => $fooInterface, + 'type' => $fooInterface, 'resolve' => function () { return 'dummy'; }, @@ -394,18 +409,18 @@ class AbstractTest extends TestCase $result = GraphQL::executeQuery($schema, '{ foo { bar } }'); $expected = [ - 'data' => ['foo' => null], + 'data' => ['foo' => null], 'errors' => [ [ - 'message' => 'Internal server error', + 'message' => 'Internal server error', 'debugMessage' => 'Abstract type FooInterface must resolve to an Object type at ' . 'runtime for field Query.foo with value "dummy", received "[]". ' . 'Either the FooInterface type should provide a "resolveType" ' . 'function or each possible type should provide an "isTypeOf" function.', - 'locations' => [['line' => 1, 'column' => 3]], - 'path' => ['foo'], - 'category' => 'internal', + 'locations' => [['line' => 1, 'column' => 3]], + 'path' => ['foo'], + 'category' => 'internal', ], ], ]; @@ -418,51 +433,56 @@ class AbstractTest extends TestCase public function testResolveTypeAllowsResolvingWithTypeName() : void { $PetType = new InterfaceType([ - 'name' => 'Pet', - 'resolveType' => function($obj) { - if ($obj instanceof Dog) return 'Dog'; - if ($obj instanceof Cat) return 'Cat'; + 'name' => 'Pet', + 'resolveType' => function ($obj) { + if ($obj instanceof Dog) { + return 'Dog'; + } + if ($obj instanceof Cat) { + return 'Cat'; + } + return null; }, - 'fields' => [ - 'name' => [ 'type' => Type::string() ] - ] + 'fields' => [ + 'name' => ['type' => Type::string()], + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', - 'interfaces' => [ $PetType ], - 'fields' => [ - 'name' => [ 'type' => Type::string() ], - 'woofs' => [ 'type' => Type::boolean() ], - ] + 'name' => 'Dog', + 'interfaces' => [$PetType], + 'fields' => [ + 'name' => ['type' => Type::string()], + 'woofs' => ['type' => Type::boolean()], + ], ]); $CatType = new ObjectType([ - 'name' => 'Cat', - 'interfaces' => [ $PetType ], - 'fields' => [ - 'name' => [ 'type' => Type::string() ], - 'meows' => [ 'type' => Type::boolean() ], - ] + 'name' => 'Cat', + 'interfaces' => [$PetType], + 'fields' => [ + 'name' => ['type' => Type::string()], + 'meows' => ['type' => Type::boolean()], + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), - 'resolve' => function() { + 'type' => Type::listOf($PetType), + 'resolve' => function () { return [ new Dog('Odie', true), - new Cat('Garfield', false) + new Cat('Garfield', false), ]; - } - ] - ] + }, + ], + ], ]), - 'types' => [ $CatType, $DogType ] + 'types' => [$CatType, $DogType], ]); $query = '{ @@ -479,51 +499,52 @@ class AbstractTest extends TestCase $result = GraphQL::executeQuery($schema, $query)->toArray(); - $this->assertEquals([ - 'data' => [ - 'pets' => [ - ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false] - ] - ] - ], $result); + $this->assertEquals( + [ + 'data' => [ + 'pets' => [ + ['name' => 'Odie', 'woofs' => true], + ['name' => 'Garfield', 'meows' => false], + ], + ], + ], + $result + ); } public function testHintsOnConflictingTypeInstancesInResolveType() : void { - $createTest = function() use (&$iface) { + $createTest = function () use (&$iface) { return new ObjectType([ - 'name' => 'Test', - 'fields' => [ - 'a' => Type::string() + 'name' => 'Test', + 'fields' => [ + 'a' => Type::string(), ], - 'interfaces' => function() use ($iface) { + 'interfaces' => function () use ($iface) { return [$iface]; - } + }, ]); }; $iface = new InterfaceType([ - 'name' => 'Node', - 'fields' => [ - 'a' => Type::string() + 'name' => 'Node', + 'fields' => [ + 'a' => Type::string(), ], - 'resolveType' => function() use (&$createTest) { + 'resolveType' => function () use (&$createTest) { return $createTest(); - } + }, ]); $query = new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'node' => $iface, - 'test' => $createTest() - ] + 'test' => $createTest(), + ], ]); - $schema = new Schema([ - 'query' => $query, - ]); + $schema = new Schema(['query' => $query]); $schema->assertValid(); $query = ' @@ -537,9 +558,9 @@ class AbstractTest extends TestCase $result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]); $this->assertEquals( - 'Schema must contain unique named types but contains multiple types named "Test". '. - 'Make sure that `resolveType` function of abstract type "Node" returns the same type instance '. - 'as referenced anywhere else within the schema '. + 'Schema must contain unique named types but contains multiple types named "Test". ' . + 'Make sure that `resolveType` function of abstract type "Node" returns the same type instance ' . + 'as referenced anywhere else within the schema ' . '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).', $result->errors[0]->getMessage() ); diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index 775b5ab..511be53 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -1,33 +1,44 @@ categoryDataSource = [ ['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8], ['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3], - ['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9] + ['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9], ]; - $this->path = []; + $this->path = []; $this->userType = new ObjectType([ - 'name' => 'User', - 'fields' => function() { + 'name' => 'User', + 'fields' => function () { return [ - 'name' => [ - 'type' => Type::string(), + 'name' => [ + 'type' => Type::string(), 'resolve' => function ($user, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; + return $user['name']; - } + }, ], 'bestFriend' => [ - 'type' => $this->userType, - 'resolve' => function($user, $args, $context, ResolveInfo $info) { + 'type' => $this->userType, + 'resolve' => function ($user, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; - return new Deferred(function() use ($user) { + return new Deferred(function () use ($user) { $this->path[] = 'deferred-for-best-friend-of-' . $user['id']; - return Utils::find($this->userDataSource, function($entry) use ($user) { - return $entry['id'] === $user['bestFriendId']; - }); + + return Utils::find( + $this->userDataSource, + function ($entry) use ($user) { + return $entry['id'] === $user['bestFriendId']; + } + ); }); - } - ] + }, + ], ]; - } + }, ]); $this->storyType = new ObjectType([ - 'name' => 'Story', + 'name' => 'Story', 'fields' => [ - 'title' => [ - 'type' => Type::string(), - 'resolve' => function($entry, $args, $context, ResolveInfo $info) { - $this->path[] = $info->path; - return $entry['title']; - } - ], - 'author' => [ - 'type' => $this->userType, - 'resolve' => function($story, $args, $context, ResolveInfo $info) { + 'title' => [ + 'type' => Type::string(), + 'resolve' => function ($entry, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; - return new Deferred(function() use ($story) { + return $entry['title']; + }, + ], + 'author' => [ + 'type' => $this->userType, + 'resolve' => function ($story, $args, $context, ResolveInfo $info) { + $this->path[] = $info->path; + + return new Deferred(function () use ($story) { $this->path[] = 'deferred-for-story-' . $story['id'] . '-author'; - return Utils::find($this->userDataSource, function($entry) use ($story) { - return $entry['id'] === $story['authorId']; - }); + + return Utils::find( + $this->userDataSource, + function ($entry) use ($story) { + return $entry['id'] === $story['authorId']; + } + ); }); - } - ] - ] + }, + ], + ], ]); $this->categoryType = new ObjectType([ - 'name' => 'Category', + 'name' => 'Category', 'fields' => [ 'name' => [ - 'type' => Type::string(), - 'resolve' => function($category, $args, $context, ResolveInfo $info) { + 'type' => Type::string(), + 'resolve' => function ($category, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; + return $category['name']; - } + }, ], - 'stories' => [ - 'type' => Type::listOf($this->storyType), - 'resolve' => function($category, $args, $context, ResolveInfo $info) { + 'stories' => [ + 'type' => Type::listOf($this->storyType), + 'resolve' => function ($category, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; - return Utils::filter($this->storyDataSource, function($story) use ($category) { - return in_array($category['id'], $story['categoryIds']); - }); - } + + return Utils::filter( + $this->storyDataSource, + function ($story) use ($category) { + return in_array($category['id'], $story['categoryIds']); + } + ); + }, ], 'topStory' => [ - 'type' => $this->storyType, - 'resolve' => function($category, $args, $context, ResolveInfo $info) { + 'type' => $this->storyType, + 'resolve' => function ($category, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; return new Deferred(function () use ($category) { $this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory'; - return Utils::find($this->storyDataSource, function($story) use ($category) { - return $story['id'] === $category['topStoryId']; - }); + + return Utils::find( + $this->storyDataSource, + function ($story) use ($category) { + return $story['id'] === $category['topStoryId']; + } + ); }); - } - ] - ] + }, + ], + ], ]); $this->queryType = new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ - 'topStories' => [ - 'type' => Type::listOf($this->storyType), - 'resolve' => function($val, $args, $context, ResolveInfo $info) { + 'topStories' => [ + 'type' => Type::listOf($this->storyType), + 'resolve' => function ($val, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; - return Utils::filter($this->storyDataSource, function($story) { - return $story['id'] % 2 === 1; - }); - } + + return Utils::filter( + $this->storyDataSource, + function ($story) { + return $story['id'] % 2 === 1; + } + ); + }, ], 'featuredCategory' => [ - 'type' => $this->categoryType, - 'resolve' => function($val, $args, $context, ResolveInfo $info) { + 'type' => $this->categoryType, + 'resolve' => function ($val, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; + return $this->categoryDataSource[0]; - } + }, ], - 'categories' => [ - 'type' => Type::listOf($this->categoryType), - 'resolve' => function($val, $args, $context, ResolveInfo $info) { + 'categories' => [ + 'type' => Type::listOf($this->categoryType), + 'resolve' => function ($val, $args, $context, ResolveInfo $info) { $this->path[] = $info->path; + return $this->categoryDataSource; - } - ] - ] + }, + ], + ], ]); parent::setUp(); @@ -202,12 +238,12 @@ class DeferredFieldsTest extends TestCase '); $schema = new Schema([ - 'query' => $this->queryType + 'query' => $this->queryType, ]); $expected = [ 'data' => [ - 'topStories' => [ + 'topStories' => [ ['title' => 'Story #1', 'author' => ['name' => 'John']], ['title' => 'Story #3', 'author' => ['name' => 'Joe']], ['title' => 'Story #5', 'author' => ['name' => 'John']], @@ -220,9 +256,9 @@ class DeferredFieldsTest extends TestCase ['title' => 'Story #4', 'author' => ['name' => 'Joe']], ['title' => 'Story #6', 'author' => ['name' => 'Jane']], ['title' => 'Story #8', 'author' => ['name' => 'John']], - ] - ] - ] + ], + ], + ], ]; $result = Executor::execute($schema, $query); @@ -292,12 +328,12 @@ class DeferredFieldsTest extends TestCase '); $schema = new Schema([ - 'query' => $this->queryType + 'query' => $this->queryType, ]); $author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']]; $author2 = ['name' => 'Jane', 'bestFriend' => ['name' => 'Joe']]; - $author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']]; + $author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']]; $author4 = ['name' => 'Dirk', 'bestFriend' => ['name' => 'John']]; $expected = [ @@ -306,8 +342,8 @@ class DeferredFieldsTest extends TestCase ['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]], ['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]], ['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]], - ] - ] + ], + ], ]; $result = Executor::execute($schema, $query); @@ -352,54 +388,56 @@ class DeferredFieldsTest extends TestCase public function testComplexRecursiveDeferredFields() : void { $complexType = new ObjectType([ - 'name' => 'ComplexType', - 'fields' => function() use (&$complexType) { + 'name' => 'ComplexType', + 'fields' => function () use (&$complexType) { return [ - 'sync' => [ - 'type' => Type::string(), - 'resolve' => function($v, $a, $c, ResolveInfo $info) { - $this->path[] = $info->path; - return 'sync'; - } - ], - 'deferred' => [ - 'type' => Type::string(), - 'resolve' => function($v, $a, $c, ResolveInfo $info) { + 'sync' => [ + 'type' => Type::string(), + 'resolve' => function ($v, $a, $c, ResolveInfo $info) { $this->path[] = $info->path; - return new Deferred(function() use ($info) { + return 'sync'; + }, + ], + 'deferred' => [ + 'type' => Type::string(), + 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + $this->path[] = $info->path; + + return new Deferred(function () use ($info) { $this->path[] = ['!dfd for: ', $info->path]; + return 'deferred'; }); - } + }, ], - 'nest' => [ - 'type' => $complexType, - 'resolve' => function($v, $a, $c, ResolveInfo $info) { + 'nest' => [ + 'type' => $complexType, + 'resolve' => function ($v, $a, $c, ResolveInfo $info) { $this->path[] = $info->path; + return []; - } + }, ], 'deferredNest' => [ - 'type' => $complexType, - 'resolve' => function($v, $a, $c, ResolveInfo $info) { + 'type' => $complexType, + 'resolve' => function ($v, $a, $c, ResolveInfo $info) { $this->path[] = $info->path; - return new Deferred(function() use ($info) { + return new Deferred(function () use ($info) { $this->path[] = ['!dfd nest for: ', $info->path]; + return []; }); - } - ] + }, + ], ]; - } + }, ]); - $schema = new Schema([ - 'query' => $complexType - ]); + $schema = new Schema(['query' => $complexType]); - $query = Parser::parse(' + $query = Parser::parse(' { nest { sync @@ -427,34 +465,34 @@ class DeferredFieldsTest extends TestCase } } '); - $result = Executor::execute($schema, $query); + $result = Executor::execute($schema, $query); $expected = [ 'data' => [ - 'nest' => [ - 'sync' => 'sync', - 'deferred' => 'deferred', - 'nest' => [ - 'sync' => 'sync', - 'deferred' => 'deferred' + 'nest' => [ + 'sync' => 'sync', + 'deferred' => 'deferred', + 'nest' => [ + 'sync' => 'sync', + 'deferred' => 'deferred', ], 'deferredNest' => [ - 'sync' => 'sync', - 'deferred' => 'deferred' - ] + 'sync' => 'sync', + 'deferred' => 'deferred', + ], ], 'deferredNest' => [ - 'sync' => 'sync', - 'deferred' => 'deferred', - 'nest' => [ - 'sync' => 'sync', - 'deferred' => 'deferred' + 'sync' => 'sync', + 'deferred' => 'deferred', + 'nest' => [ + 'sync' => 'sync', + 'deferred' => 'deferred', ], 'deferredNest' => [ - 'sync' => 'sync', - 'deferred' => 'deferred' - ] - ] - ] + 'sync' => 'sync', + 'deferred' => 'deferred', + ], + ], + ], ]; $this->assertEquals($expected, $result->toArray()); diff --git a/tests/Executor/DirectivesTest.php b/tests/Executor/DirectivesTest.php index 295e2f1..7150c2e 100644 --- a/tests/Executor/DirectivesTest.php +++ b/tests/Executor/DirectivesTest.php @@ -1,16 +1,27 @@ assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b }')); } + /** + * @param Source|string $doc + * @return mixed[] + */ + private function executeTestQuery($doc) : array + { + return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray(); + } + + private static function getSchema() : Schema + { + if (! self::$schema) { + self::$schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'TestType', + 'fields' => [ + 'a' => ['type' => Type::string()], + 'b' => ['type' => Type::string()], + ], + ]), + ]); + } + + return self::$schema; + } + + /** + * @return string[] + */ + private static function getData() : array + { + return self::$data ?: (self::$data = [ + 'a' => 'a', + 'b' => 'b', + ]); + } + public function testWorksOnScalars() : void { // if true includes scalar - $this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b @include(if: true) }')); + $this->assertEquals( + ['data' => ['a' => 'a', 'b' => 'b']], + $this->executeTestQuery('{ a, b @include(if: true) }') + ); // if false omits on scalar $this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @include(if: false) }')); @@ -200,37 +251,4 @@ class DirectivesTest extends TestCase $this->executeTestQuery('{ a, b @include(if: false) @skip(if: false) }') ); } - - private static $schema; - - private static $data; - - private static function getSchema() - { - if (!self::$schema) { - self::$schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'TestType', - 'fields' => [ - 'a' => ['type' => Type::string()], - 'b' => ['type' => Type::string()] - ] - ]) - ]); - } - return self::$schema; - } - - private static function getData() - { - return self::$data ?: (self::$data = [ - 'a' => 'a', - 'b' => 'b' - ]); - } - - private function executeTestQuery($doc) - { - return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray(); - } } diff --git a/tests/Executor/ExecutionResultTest.php b/tests/Executor/ExecutionResultTest.php index 69fbd78..ce20221 100644 --- a/tests/Executor/ExecutionResultTest.php +++ b/tests/Executor/ExecutionResultTest.php @@ -1,4 +1,7 @@ 'Pet', - 'fields' => function() { + 'name' => 'Pet', + 'fields' => function () { return [ - 'name' => ['type' => Type::string()] + 'name' => ['type' => Type::string()], ]; - } + }, ]); // Added to interface type when defined $dogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$petType], - 'isTypeOf' => function($obj) { return $obj instanceof Dog; }, - 'fields' => function() { + 'isTypeOf' => function ($obj) { + return $obj instanceof Dog; + }, + 'fields' => function () { return [ - 'name' => ['type' => Type::string()], - 'woofs' => ['type' => Type::boolean()] + 'name' => ['type' => Type::string()], + 'woofs' => ['type' => Type::boolean()], ]; - } + }, ]); $catType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$petType], - 'isTypeOf' => function ($obj) { + 'isTypeOf' => function ($obj) { return $obj instanceof Cat; }, - 'fields' => function() { + 'fields' => function () { return [ - 'name' => ['type' => Type::string()], + 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], ]; - } + }, ]); $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Query', + 'query' => new ObjectType([ + 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($petType), + 'type' => Type::listOf($petType), 'resolve' => function () { return [new Dog('Odie', true), new Cat('Garfield', false)]; - } - ] - ] + }, + ], + ], ]), - 'types' => [$catType, $dogType], - 'typeLoader' => function($name) use ($dogType, $petType, $catType) { + 'types' => [$catType, $dogType], + 'typeLoader' => function ($name) use ($dogType, $petType, $catType) { switch ($name) { case 'Dog': return $dogType; @@ -102,7 +122,7 @@ class ExecutorLazySchemaTest extends TestCase case 'Cat': return $catType; } - } + }, ]); $query = '{ @@ -120,7 +140,7 @@ class ExecutorLazySchemaTest extends TestCase $expected = new ExecutionResult([ 'pets' => [ ['name' => 'Odie', 'woofs' => true], - ['name' => 'Garfield', 'meows' => false] + ['name' => 'Garfield', 'meows' => false], ], ]); @@ -134,43 +154,46 @@ class ExecutorLazySchemaTest extends TestCase $this->assertInstanceOf(Error::class, $result->errors[0]->getPrevious()); $this->assertEquals( - 'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of '. - 'GraphQL\Tests\Executor\Dog. Switching to slow resolution method using `isTypeOf` of all possible '. - 'implementations. It requires full schema scan and degrades query performance significantly. '. + 'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of ' . + 'GraphQL\Tests\Executor\TestClasses\Dog. Switching to slow resolution method using `isTypeOf` of all possible ' . + 'implementations. It requires full schema scan and degrades query performance significantly. ' . 'Make sure your `resolveType` always returns valid implementation or throws.', - $result->errors[0]->getMessage()); + $result->errors[0]->getMessage() + ); } public function testHintsOnConflictingTypeInstancesInDefinitions() : void { - $calls = []; - $typeLoader = function($name) use (&$calls) { + $calls = []; + $typeLoader = function ($name) use (&$calls) { $calls[] = $name; switch ($name) { case 'Test': return new ObjectType([ - 'name' => 'Test', - 'fields' => function() { + 'name' => 'Test', + 'fields' => function () { return [ 'test' => Type::string(), ]; - } + }, ]); default: return null; } }; + $query = new ObjectType([ - 'name' => 'Query', - 'fields' => function() use ($typeLoader) { + 'name' => 'Query', + 'fields' => function () use ($typeLoader) { return [ - 'test' => $typeLoader('Test') + 'test' => $typeLoader('Test'), ]; - } + }, ]); + $schema = new Schema([ - 'query' => $query, - 'typeLoader' => $typeLoader + 'query' => $query, + 'typeLoader' => $typeLoader, ]); $query = ' @@ -186,8 +209,8 @@ class ExecutorLazySchemaTest extends TestCase $this->assertEquals(['Test', 'Test'], $calls); $this->assertEquals( - 'Schema must contain unique named types but contains multiple types named "Test". '. - 'Make sure that type loader returns the same instance as defined in Query.test '. + 'Schema must contain unique named types but contains multiple types named "Test". ' . + 'Make sure that type loader returns the same instance as defined in Query.test ' . '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).', $result->errors[0]->getMessage() ); @@ -200,54 +223,161 @@ class ExecutorLazySchemaTest extends TestCase public function testSimpleQuery() : void { $schema = new Schema([ - 'query' => $this->loadType('Query'), - 'typeLoader' => function($name) { + 'query' => $this->loadType('Query'), + 'typeLoader' => function ($name) { return $this->loadType($name, true); - } + }, ]); - $query = '{ object { string } }'; + $query = '{ object { string } }'; $result = Executor::execute( $schema, Parser::parse($query), ['object' => ['string' => 'test']] ); - $expected = [ + $expected = [ 'data' => ['object' => ['string' => 'test']], ]; $expectedExecutorCalls = [ 'Query.fields', 'SomeObject', - 'SomeObject.fields' + 'SomeObject.fields', ]; $this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expectedExecutorCalls, $this->calls); } + public function loadType($name, $isExecutorCall = false) + { + if ($isExecutorCall) { + $this->calls[] = $name; + } + $this->loadedTypes[$name] = true; + + switch ($name) { + case 'Query': + return $this->queryType ?: $this->queryType = new ObjectType([ + 'name' => 'Query', + 'fields' => function () { + $this->calls[] = 'Query.fields'; + + return [ + 'object' => ['type' => $this->loadType('SomeObject')], + 'other' => ['type' => $this->loadType('OtherObject')], + ]; + }, + ]); + case 'SomeObject': + return $this->someObjectType ?: $this->someObjectType = new ObjectType([ + 'name' => 'SomeObject', + 'fields' => function () { + $this->calls[] = 'SomeObject.fields'; + + return [ + 'string' => ['type' => Type::string()], + 'object' => ['type' => $this->someObjectType], + ]; + }, + 'interfaces' => function () { + $this->calls[] = 'SomeObject.interfaces'; + + return [ + $this->loadType('SomeInterface'), + ]; + }, + ]); + case 'OtherObject': + return $this->otherObjectType ?: $this->otherObjectType = new ObjectType([ + 'name' => 'OtherObject', + 'fields' => function () { + $this->calls[] = 'OtherObject.fields'; + + return [ + 'union' => ['type' => $this->loadType('SomeUnion')], + 'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))], + ]; + }, + ]); + case 'DeeperObject': + return $this->deeperObjectType ?: $this->deeperObjectType = new ObjectType([ + 'name' => 'DeeperObject', + 'fields' => function () { + return [ + 'scalar' => ['type' => $this->loadType('SomeScalar')], + ]; + }, + ]); + case 'SomeScalar': + return $this->someScalarType ?: $this->someScalarType = new CustomScalarType([ + 'name' => 'SomeScalar', + 'serialize' => function ($value) { + return $value; + }, + 'parseValue' => function ($value) { + return $value; + }, + 'parseLiteral' => function () { + }, + ]); + case 'SomeUnion': + return $this->someUnionType ?: $this->someUnionType = new UnionType([ + 'name' => 'SomeUnion', + 'resolveType' => function () { + $this->calls[] = 'SomeUnion.resolveType'; + + return $this->loadType('DeeperObject'); + }, + 'types' => function () { + $this->calls[] = 'SomeUnion.types'; + + return [$this->loadType('DeeperObject')]; + }, + ]); + case 'SomeInterface': + return $this->someInterfaceType ?: $this->someInterfaceType = new InterfaceType([ + 'name' => 'SomeInterface', + 'resolveType' => function () { + $this->calls[] = 'SomeInterface.resolveType'; + + return $this->loadType('SomeObject'); + }, + 'fields' => function () { + $this->calls[] = 'SomeInterface.fields'; + + return [ + 'string' => ['type' => Type::string()], + ]; + }, + ]); + default: + return null; + } + } + public function testDeepQuery() : void { $schema = new Schema([ - 'query' => $this->loadType('Query'), - 'typeLoader' => function($name) { + 'query' => $this->loadType('Query'), + 'typeLoader' => function ($name) { return $this->loadType($name, true); - } + }, ]); - $query = '{ object { object { object { string } } } }'; + $query = '{ object { object { object { string } } } }'; $result = Executor::execute( $schema, Parser::parse($query), ['object' => ['object' => ['object' => ['string' => 'test']]]] ); - $expected = [ - 'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]] + $expected = [ + 'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]], ]; $expectedLoadedTypes = [ - 'Query' => true, - 'SomeObject' => true, - 'OtherObject' => true + 'Query' => true, + 'SomeObject' => true, + 'OtherObject' => true, ]; $this->assertEquals($expected, $result->toArray(true)); @@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase $expectedExecutorCalls = [ 'Query.fields', 'SomeObject', - 'SomeObject.fields' + 'SomeObject.fields', ]; $this->assertEquals($expectedExecutorCalls, $this->calls); } @@ -264,13 +394,13 @@ class ExecutorLazySchemaTest extends TestCase public function testResolveUnion() : void { $schema = new Schema([ - 'query' => $this->loadType('Query'), - 'typeLoader' => function($name) { + 'query' => $this->loadType('Query'), + 'typeLoader' => function ($name) { return $this->loadType($name, true); - } + }, ]); - $query = ' + $query = ' { other { union { @@ -285,17 +415,17 @@ class ExecutorLazySchemaTest extends TestCase ['other' => ['union' => ['scalar' => 'test']]] ); - $expected = [ + $expected = [ 'data' => ['other' => ['union' => ['scalar' => 'test']]], ]; $expectedLoadedTypes = [ - 'Query' => true, - 'SomeObject' => true, - 'OtherObject' => true, - 'SomeUnion' => true, + 'Query' => true, + 'SomeObject' => true, + 'OtherObject' => true, + 'SomeUnion' => true, 'SomeInterface' => true, - 'DeeperObject' => true, - 'SomeScalar' => true, + 'DeeperObject' => true, + 'SomeScalar' => true, ]; $this->assertEquals($expected, $result->toArray(true)); @@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase ]; $this->assertEquals($expectedCalls, $this->calls); } - - public function loadType($name, $isExecutorCall = false) - { - if ($isExecutorCall) { - $this->calls[] = $name; - } - $this->loadedTypes[$name] = true; - - switch ($name) { - case 'Query': - return $this->QueryType ?: $this->QueryType = new ObjectType([ - 'name' => 'Query', - 'fields' => function() { - $this->calls[] = 'Query.fields'; - return [ - 'object' => ['type' => $this->loadType('SomeObject')], - 'other' => ['type' => $this->loadType('OtherObject')], - ]; - } - ]); - case 'SomeObject': - return $this->SomeObjectType ?: $this->SomeObjectType = new ObjectType([ - 'name' => 'SomeObject', - 'fields' => function() { - $this->calls[] = 'SomeObject.fields'; - return [ - 'string' => ['type' => Type::string()], - 'object' => ['type' => $this->SomeObjectType] - ]; - }, - 'interfaces' => function() { - $this->calls[] = 'SomeObject.interfaces'; - return [ - $this->loadType('SomeInterface') - ]; - } - ]); - case 'OtherObject': - return $this->OtherObjectType ?: $this->OtherObjectType = new ObjectType([ - 'name' => 'OtherObject', - 'fields' => function() { - $this->calls[] = 'OtherObject.fields'; - return [ - 'union' => ['type' => $this->loadType('SomeUnion')], - 'iface' => ['type' => Type::nonNull($this->loadType('SomeInterface'))], - ]; - } - ]); - case 'DeeperObject': - return $this->DeeperObjectType ?: $this->DeeperObjectType = new ObjectType([ - 'name' => 'DeeperObject', - 'fields' => function() { - return [ - 'scalar' => ['type' => $this->loadType('SomeScalar')], - ]; - } - ]); - case 'SomeScalar'; - return $this->SomeScalarType ?: $this->SomeScalarType = new CustomScalarType([ - 'name' => 'SomeScalar', - 'serialize' => function($value) {return $value;}, - 'parseValue' => function($value) {return $value;}, - 'parseLiteral' => function() {} - ]); - case 'SomeUnion': - return $this->SomeUnionType ?: $this->SomeUnionType = new UnionType([ - 'name' => 'SomeUnion', - 'resolveType' => function() { - $this->calls[] = 'SomeUnion.resolveType'; - return $this->loadType('DeeperObject'); - }, - 'types' => function() { - $this->calls[] = 'SomeUnion.types'; - return [ $this->loadType('DeeperObject') ]; - } - ]); - case 'SomeInterface': - return $this->SomeInterfaceType ?: $this->SomeInterfaceType = new InterfaceType([ - 'name' => 'SomeInterface', - 'resolveType' => function() { - $this->calls[] = 'SomeInterface.resolveType'; - return $this->loadType('SomeObject'); - }, - 'fields' => function() { - $this->calls[] = 'SomeInterface.fields'; - return [ - 'string' => ['type' => Type::string() ] - ]; - } - ]); - default: - return null; - } - } } diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 5c62b09..9ea79fb 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -1,74 +1,77 @@ 'Image', + $BlogImage = new ObjectType([ + 'name' => 'Image', 'fields' => [ - 'url' => ['type' => Type::string()], - 'width' => ['type' => Type::int()], + 'url' => ['type' => Type::string()], + 'width' => ['type' => Type::int()], 'height' => ['type' => Type::int()], - ] + ], ]); $BlogAuthor = new ObjectType([ - 'name' => 'Author', - 'fields' => function() use (&$BlogArticle, &$BlogImage) { + 'name' => 'Author', + 'fields' => function () use (&$BlogArticle, &$BlogImage) { return [ - 'id' => ['type' => Type::string()], - 'name' => ['type' => Type::string()], - 'pic' => [ - 'args' => ['width' => ['type' => Type::int()], 'height' => ['type' => Type::int()]], - 'type' => $BlogImage, + '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' => $BlogArticle + 'recentArticle' => $BlogArticle, ]; - } + }, ]); $BlogArticle = new ObjectType([ - 'name' => 'Article', + 'name' => 'Article', 'fields' => [ - 'id' => ['type' => Type::nonNull(Type::string())], + '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())] - ] + 'author' => ['type' => $BlogAuthor], + 'title' => ['type' => Type::string()], + 'body' => ['type' => Type::string()], + 'keywords' => ['type' => Type::listOf(Type::string())], + ], ]); $BlogQuery = new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'article' => [ - 'type' => $BlogArticle, - 'args' => ['id' => ['type' => Type::id()]], + 'type' => $BlogArticle, + 'args' => ['id' => ['type' => Type::id()]], 'resolve' => function ($_, $args) { return $this->article($args['id']); - } + }, ], - 'feed' => [ - 'type' => Type::listOf($BlogArticle), + 'feed' => [ + 'type' => Type::listOf($BlogArticle), 'resolve' => function () { return [ $this->article(1), @@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase $this->article(7), $this->article(8), $this->article(9), - $this->article(10) + $this->article(10), ]; - } - ] - ] + }, + ], + ], ]); $BlogSchema = new Schema(['query' => $BlogQuery]); - $request = ' { feed { @@ -126,51 +128,71 @@ class ExecutorSchemaTest extends TestCase $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'] + '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', + '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 + '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', + 'id' => '1', 'isPublished' => true, - 'title' => 'My Article 1', - 'body' => 'This is a post', - 'keywords' => ['foo', 'bar', '1', 'true', null] - ] - ] - ] - ] + '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()); @@ -179,30 +201,32 @@ class ExecutorSchemaTest extends TestCase private function article($id) { $johnSmith = null; - $article = function($id) use (&$johnSmith) { + $article = function ($id) use (&$johnSmith) { return [ - 'id' => $id, + 'id' => $id, 'isPublished' => 'true', - 'author' => $johnSmith, - 'title' => 'My Article ' . $id, - 'body' => 'This is a post', - 'hidden' => 'This data is not exposed in the schema', - 'keywords' => ['foo', 'bar', 1, true, null] + 'author' => $johnSmith, + 'title' => 'My Article ' . $id, + 'body' => 'This is a post', + 'hidden' => 'This data is not exposed in the schema', + 'keywords' => ['foo', 'bar', 1, true, null], ]; }; - $getPic = function($uid, $width, $height) { + $getPic = function ($uid, $width, $height) { return [ - 'url' => "cdn://$uid", - 'width' => $width, - 'height' => $height + 'url' => sprintf('cdn://%s', $uid), + 'width' => $width, + 'height' => $height, ]; }; $johnSmith = [ - 'id' => 123, - 'name' => 'John Smith', - 'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);}, + 'id' => 123, + 'name' => 'John Smith', + 'pic' => function ($width, $height) use ($getPic) { + return $getPic(123, $width, $height); + }, 'recentArticle' => $article(1), ]; diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index de63db2..f462c15 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -1,20 +1,25 @@ function () { return 'Apple';}, - 'b' => function () {return 'Banana';}, - 'c' => function () {return 'Cookie';}, - 'd' => function () {return 'Donut';}, - 'e' => function () {return 'Egg';}, - 'f' => 'Fish', - 'pic' => function ($size = 50) { + 'a' => function () { + return 'Apple'; + }, + 'b' => function () { + return 'Banana'; + }, + 'c' => function () { + return 'Cookie'; + }, + 'd' => function () { + return 'Donut'; + }, + 'e' => function () { + return 'Egg'; + }, + 'f' => 'Fish', + 'pic' => function ($size = 50) { return 'Pic of size: ' . $size; }, - 'promise' => function() use ($promiseData) { + 'promise' => function () use ($promiseData) { return $promiseData(); }, - 'deep' => function () use (&$deepData) { + 'deep' => function () use (&$deepData) { return $deepData; - } + }, ]; + // Required for that & reference above $deepData = [ - 'a' => function () { return 'Already Been Done'; }, - 'b' => function () { return 'Boring'; }, - 'c' => function () { + 'a' => function () { + return 'Already Been Done'; + }, + 'b' => function () { + return 'Boring'; + }, + 'c' => function () { return ['Contrived', null, 'Confusing']; }, 'deeper' => function () use (&$data) { return [$data, null, $data]; - } + }, ]; - $doc = ' query Example($size: Int) { a, @@ -99,68 +118,70 @@ class ExecutorTest extends TestCase } '; - $ast = Parser::parse($doc); + $ast = Parser::parse($doc); $expected = [ 'data' => [ - 'a' => 'Apple', - 'b' => 'Banana', - 'x' => 'Cookie', - 'd' => 'Donut', - 'e' => 'Egg', - 'f' => 'Fish', - 'pic' => 'Pic of size: 100', - 'promise' => [ - 'a' => 'Apple' - ], - 'deep' => [ - 'a' => 'Already Been Done', - 'b' => 'Boring', - 'c' => [ 'Contrived', null, 'Confusing' ], + 'a' => 'Apple', + 'b' => 'Banana', + 'x' => 'Cookie', + 'd' => 'Donut', + 'e' => 'Egg', + 'f' => 'Fish', + 'pic' => 'Pic of size: 100', + 'promise' => ['a' => 'Apple'], + 'deep' => [ + 'a' => 'Already Been Done', + 'b' => 'Boring', + 'c' => ['Contrived', null, 'Confusing'], 'deeper' => [ - [ 'a' => 'Apple', 'b' => 'Banana' ], + ['a' => 'Apple', 'b' => 'Banana'], null, - [ 'a' => 'Apple', 'b' => 'Banana' ] - ] - ] - ] + ['a' => 'Apple', 'b' => 'Banana'], + ], + ], + ], ]; $deepDataType = null; - $dataType = new ObjectType([ - 'name' => 'DataType', - 'fields' => function() use (&$dataType, &$deepDataType) { + $dataType = new ObjectType([ + 'name' => 'DataType', + 'fields' => function () use (&$dataType, &$deepDataType) { return [ - 'a' => [ 'type' => Type::string() ], - 'b' => [ 'type' => Type::string() ], - 'c' => [ 'type' => Type::string() ], - 'd' => [ 'type' => Type::string() ], - 'e' => [ 'type' => Type::string() ], - 'f' => [ 'type' => Type::string() ], - 'pic' => [ - 'args' => [ 'size' => ['type' => Type::int() ] ], - 'type' => Type::string(), - 'resolve' => function($obj, $args) { + 'a' => ['type' => Type::string()], + 'b' => ['type' => Type::string()], + 'c' => ['type' => Type::string()], + 'd' => ['type' => Type::string()], + 'e' => ['type' => Type::string()], + 'f' => ['type' => Type::string()], + 'pic' => [ + 'args' => ['size' => ['type' => Type::int()]], + 'type' => Type::string(), + 'resolve' => function ($obj, $args) { return $obj['pic']($args['size']); - } + }, ], 'promise' => ['type' => $dataType], - 'deep' => ['type' => $deepDataType], + 'deep' => ['type' => $deepDataType], ]; - } + }, ]); + // Required for that & reference above $deepDataType = new ObjectType([ - 'name' => 'DeepDataType', + 'name' => 'DeepDataType', 'fields' => [ - 'a' => [ 'type' => Type::string() ], - 'b' => [ 'type' => Type::string() ], - 'c' => [ 'type' => Type::listOf(Type::string()) ], - 'deeper' => [ 'type' => Type::listOf($dataType) ] - ] + 'a' => ['type' => Type::string()], + 'b' => ['type' => Type::string()], + 'c' => ['type' => Type::listOf(Type::string())], + 'deeper' => ['type' => Type::listOf($dataType)], + ], ]); - $schema = new Schema(['query' => $dataType]); + $schema = new Schema(['query' => $dataType]); - $this->assertEquals($expected, Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->toArray()); + $this->assertEquals( + $expected, + Executor::execute($schema, $ast, $data, null, ['size' => 100], 'Example')->toArray() + ); } /** @@ -183,42 +204,52 @@ class ExecutorTest extends TestCase '); $Type = new ObjectType([ - 'name' => 'Type', - 'fields' => function() use (&$Type) { + 'name' => 'Type', + 'fields' => function () use (&$Type) { return [ - 'a' => ['type' => Type::string(), 'resolve' => function () { - return 'Apple'; - }], - 'b' => ['type' => Type::string(), 'resolve' => function () { - return 'Banana'; - }], - 'c' => ['type' => Type::string(), 'resolve' => function () { - return 'Cherry'; - }], + 'a' => [ + 'type' => Type::string(), + 'resolve' => function () { + return 'Apple'; + }, + ], + 'b' => [ + 'type' => Type::string(), + 'resolve' => function () { + return 'Banana'; + }, + ], + 'c' => [ + 'type' => Type::string(), + 'resolve' => function () { + return 'Cherry'; + }, + ], 'deep' => [ - 'type' => $Type, + 'type' => $Type, 'resolve' => function () { return []; - } - ] + }, + ], ]; - } + }, ]); - $schema = new Schema(['query' => $Type]); + + $schema = new Schema(['query' => $Type]); $expected = [ 'data' => [ - 'a' => 'Apple', - 'b' => 'Banana', - 'c' => 'Cherry', + 'a' => 'Apple', + 'b' => 'Banana', + 'c' => 'Cherry', 'deep' => [ - 'b' => 'Banana', - 'c' => 'Cherry', + 'b' => 'Banana', + 'c' => 'Cherry', 'deeper' => [ 'b' => 'Banana', - 'c' => 'Cherry' - ] - ] - ] + 'c' => 'Cherry', + ], + ], + ], ]; $this->assertEquals($expected, Executor::execute($schema, $ast)->toArray()); @@ -232,37 +263,40 @@ class ExecutorTest extends TestCase $ast = Parser::parse('query ($var: String) { result: test }'); /** @var ResolveInfo $info */ - $info = null; + $info = null; $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Test', + 'name' => 'Test', 'fields' => [ 'test' => [ - 'type' => Type::string(), - 'resolve' => function($val, $args, $ctx, $_info) use (&$info) { + 'type' => Type::string(), + 'resolve' => function ($val, $args, $ctx, $_info) use (&$info) { $info = $_info; - } - ] - ] - ]) + }, + ], + ], + ]), ]); - $rootValue = [ 'root' => 'val' ]; + $rootValue = ['root' => 'val']; - Executor::execute($schema, $ast, $rootValue, null, [ 'var' => '123' ]); + Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']); - $this->assertEquals([ - 'fieldName', - 'fieldNodes', - 'returnType', - 'parentType', - 'path', - 'schema', - 'fragments', - 'rootValue', - 'operation', - 'variableValues', - ], array_keys((array) $info)); + $this->assertEquals( + [ + 'fieldName', + 'fieldNodes', + 'returnType', + 'parentType', + 'path', + 'schema', + 'fragments', + 'rootValue', + 'operation', + 'variableValues', + ], + array_keys((array) $info) + ); $this->assertEquals('test', $info->fieldName); $this->assertEquals(1, count($info->fieldNodes)); @@ -286,24 +320,22 @@ class ExecutorTest extends TestCase $gotHere = false; - $data = [ - 'contextThing' => 'thing', - ]; + $data = ['contextThing' => 'thing']; - $ast = Parser::parse($doc); + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'a' => [ - 'type' => Type::string(), + 'type' => Type::string(), 'resolve' => function ($context) use ($doc, &$gotHere) { $this->assertEquals('thing', $context['contextThing']); $gotHere = true; - } - ] - ] - ]) + }, + ], + ], + ]), ]); Executor::execute($schema, $ast, $data, null, [], 'Example'); @@ -326,22 +358,22 @@ class ExecutorTest extends TestCase $docAst = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'b' => [ - 'args' => [ - 'numArg' => ['type' => Type::int()], - 'stringArg' => ['type' => Type::string()] + 'args' => [ + 'numArg' => ['type' => Type::int()], + 'stringArg' => ['type' => Type::string()], ], - 'type' => Type::string(), + 'type' => Type::string(), 'resolve' => function ($_, $args) use (&$gotHere) { $this->assertEquals(123, $args['numArg']); $this->assertEquals('foo', $args['stringArg']); $gotHere = true; - } - ] - ] - ]) + }, + ], + ], + ]), ]); Executor::execute($schema, $docAst, null, null, [], 'Example'); $this->assertSame($gotHere, true); @@ -368,18 +400,18 @@ class ExecutorTest extends TestCase }'; $data = [ - 'sync' => function () { + 'sync' => function () { return 'sync'; }, - 'syncError' => function () { + 'syncError' => function () { throw new UserError('Error getting syncError'); }, - 'syncRawError' => function() { + 'syncRawError' => function () { throw new UserError('Error getting syncRawError'); }, // inherited from JS reference implementation, but make no sense in this PHP impl // leaving it just to simplify migrations from newer js versions - 'syncReturnError' => function() { + 'syncReturnError' => function () { return new UserError('Error getting syncReturnError'); }, 'syncReturnErrorList' => function () { @@ -387,39 +419,43 @@ class ExecutorTest extends TestCase 'sync0', new UserError('Error getting syncReturnErrorList1'), 'sync2', - new UserError('Error getting syncReturnErrorList3') + new UserError('Error getting syncReturnErrorList3'), ]; }, - 'async' => function() { - return new Deferred(function() { return 'async'; }); + 'async' => function () { + return new Deferred(function () { + return 'async'; + }); }, - 'asyncReject' => function() { - return new Deferred(function() { throw new UserError('Error getting asyncReject'); }); + 'asyncReject' => function () { + return new Deferred(function () { + throw new UserError('Error getting asyncReject'); + }); }, - 'asyncRawReject' => function () { - return new Deferred(function() { + 'asyncRawReject' => function () { + return new Deferred(function () { throw new UserError('Error getting asyncRawReject'); }); }, - 'asyncEmptyReject' => function () { - return new Deferred(function() { + 'asyncEmptyReject' => function () { + return new Deferred(function () { throw new UserError(); }); }, - 'asyncError' => function() { - return new Deferred(function() { + 'asyncError' => function () { + return new Deferred(function () { throw new UserError('Error getting asyncError'); }); }, // inherited from JS reference implementation, but make no sense in this PHP impl // leaving it just to simplify migrations from newer js versions - 'asyncRawError' => function() { - return new Deferred(function() { + 'asyncRawError' => function () { + return new Deferred(function () { throw new UserError('Error getting asyncRawError'); }); }, - 'asyncReturnError' => function() { - return new Deferred(function() { + 'asyncReturnError' => function () { + return new Deferred(function () { throw new UserError('Error getting asyncReturnError'); }); }, @@ -428,96 +464,96 @@ class ExecutorTest extends TestCase $docAst = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ - 'sync' => ['type' => Type::string()], - 'syncError' => ['type' => Type::string()], - 'syncRawError' => ['type' => Type::string()], - 'syncReturnError' => ['type' => Type::string()], + 'sync' => ['type' => Type::string()], + 'syncError' => ['type' => Type::string()], + 'syncRawError' => ['type' => Type::string()], + 'syncReturnError' => ['type' => Type::string()], 'syncReturnErrorList' => ['type' => Type::listOf(Type::string())], - 'async' => ['type' => Type::string()], - 'asyncReject' => ['type' => Type::string() ], - 'asyncRawReject' => ['type' => Type::string() ], - 'asyncEmptyReject' => ['type' => Type::string() ], - 'asyncError' => ['type' => Type::string()], - 'asyncRawError' => ['type' => Type::string()], - 'asyncReturnError' => ['type' => Type::string()], - ] - ]) + 'async' => ['type' => Type::string()], + 'asyncReject' => ['type' => Type::string()], + 'asyncRawReject' => ['type' => Type::string()], + 'asyncEmptyReject' => ['type' => Type::string()], + 'asyncError' => ['type' => Type::string()], + 'asyncRawError' => ['type' => Type::string()], + 'asyncReturnError' => ['type' => Type::string()], + ], + ]), ]); $expected = [ - 'data' => [ - 'sync' => 'sync', - 'syncError' => null, - 'syncRawError' => null, - 'syncReturnError' => null, + 'data' => [ + 'sync' => 'sync', + 'syncError' => null, + 'syncRawError' => null, + 'syncReturnError' => null, 'syncReturnErrorList' => ['sync0', null, 'sync2', null], - 'async' => 'async', - 'asyncReject' => null, - 'asyncRawReject' => null, - 'asyncEmptyReject' => null, - 'asyncError' => null, - 'asyncRawError' => null, - 'asyncReturnError' => null, + 'async' => 'async', + 'asyncReject' => null, + 'asyncRawReject' => null, + 'asyncEmptyReject' => null, + 'asyncError' => null, + 'asyncRawError' => null, + 'asyncReturnError' => null, ], 'errors' => [ [ - 'message' => 'Error getting syncError', + 'message' => 'Error getting syncError', 'locations' => [['line' => 3, 'column' => 7]], - 'path' => ['syncError'] + 'path' => ['syncError'], ], [ - 'message' => 'Error getting syncRawError', - 'locations' => [ [ 'line' => 4, 'column' => 7 ] ], - 'path'=> [ 'syncRawError' ] + 'message' => 'Error getting syncRawError', + 'locations' => [['line' => 4, 'column' => 7]], + 'path' => ['syncRawError'], ], [ - 'message' => 'Error getting syncReturnError', + 'message' => 'Error getting syncReturnError', 'locations' => [['line' => 5, 'column' => 7]], - 'path' => ['syncReturnError'] + 'path' => ['syncReturnError'], ], [ - 'message' => 'Error getting syncReturnErrorList1', + 'message' => 'Error getting syncReturnErrorList1', 'locations' => [['line' => 6, 'column' => 7]], - 'path' => ['syncReturnErrorList', 1] + 'path' => ['syncReturnErrorList', 1], ], [ - 'message' => 'Error getting syncReturnErrorList3', + 'message' => 'Error getting syncReturnErrorList3', 'locations' => [['line' => 6, 'column' => 7]], - 'path' => ['syncReturnErrorList', 3] + 'path' => ['syncReturnErrorList', 3], ], [ - 'message' => 'Error getting asyncReject', + 'message' => 'Error getting asyncReject', 'locations' => [['line' => 8, 'column' => 7]], - 'path' => ['asyncReject'] + 'path' => ['asyncReject'], ], [ - 'message' => 'Error getting asyncRawReject', + 'message' => 'Error getting asyncRawReject', 'locations' => [['line' => 9, 'column' => 7]], - 'path' => ['asyncRawReject'] + 'path' => ['asyncRawReject'], ], [ - 'message' => 'An unknown error occurred.', + 'message' => 'An unknown error occurred.', 'locations' => [['line' => 10, 'column' => 7]], - 'path' => ['asyncEmptyReject'] + 'path' => ['asyncEmptyReject'], ], [ - 'message' => 'Error getting asyncError', + 'message' => 'Error getting asyncError', 'locations' => [['line' => 11, 'column' => 7]], - 'path' => ['asyncError'] + 'path' => ['asyncError'], ], [ - 'message' => 'Error getting asyncRawError', - 'locations' => [ [ 'line' => 12, 'column' => 7 ] ], - 'path' => [ 'asyncRawError' ] + 'message' => 'Error getting asyncRawError', + 'locations' => [['line' => 12, 'column' => 7]], + 'path' => ['asyncRawError'], ], [ - 'message' => 'Error getting asyncReturnError', + 'message' => 'Error getting asyncReturnError', 'locations' => [['line' => 13, 'column' => 7]], - 'path' => ['asyncReturnError'] + 'path' => ['asyncReturnError'], ], - ] + ], ]; $result = Executor::execute($schema, $docAst, $data)->toArray(); @@ -530,16 +566,16 @@ class ExecutorTest extends TestCase */ public function testUsesTheInlineOperationIfNoOperationIsProvided() : void { - $doc = '{ a }'; - $data = ['a' => 'b']; - $ast = Parser::parse($doc); + $doc = '{ a }'; + $data = ['a' => 'b']; + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'a' => ['type' => Type::string()], - ] - ]) + ], + ]), ]); $ex = Executor::execute($schema, $ast, $data); @@ -552,16 +588,16 @@ class ExecutorTest extends TestCase */ public function testUsesTheOnlyOperationIfNoOperationIsProvided() : void { - $doc = 'query Example { a }'; - $data = [ 'a' => 'b' ]; - $ast = Parser::parse($doc); + $doc = 'query Example { a }'; + $data = ['a' => 'b']; + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ - 'a' => [ 'type' => Type::string() ], - ] - ]) + 'a' => ['type' => Type::string()], + ], + ]), ]); $ex = Executor::execute($schema, $ast, $data); @@ -573,16 +609,16 @@ class ExecutorTest extends TestCase */ public function testUsesTheNamedOperationIfOperationNameIsProvided() : void { - $doc = 'query Example { first: a } query OtherExample { second: a }'; - $data = [ 'a' => 'b' ]; - $ast = Parser::parse($doc); + $doc = 'query Example { first: a } query OtherExample { second: a }'; + $data = ['a' => 'b']; + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ - 'a' => [ 'type' => Type::string() ], - ] - ]) + 'a' => ['type' => Type::string()], + ], + ]), ]); $result = Executor::execute($schema, $ast, $data, null, null, 'OtherExample'); @@ -594,25 +630,23 @@ class ExecutorTest extends TestCase */ public function testProvidesErrorIfNoOperationIsProvided() : void { - $doc = 'fragment Example on Type { a }'; - $data = [ 'a' => 'b' ]; - $ast = Parser::parse($doc); + $doc = 'fragment Example on Type { a }'; + $data = ['a' => 'b']; + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ - 'a' => [ 'type' => Type::string() ], - ] - ]) + 'a' => ['type' => Type::string()], + ], + ]), ]); - $result = Executor::execute($schema, $ast, $data); + $result = Executor::execute($schema, $ast, $data); $expected = [ 'errors' => [ - [ - 'message' => 'Must provide an operation.', - ] - ] + ['message' => 'Must provide an operation.'], + ], ]; $this->assertArraySubset($expected, $result->toArray()); @@ -623,26 +657,24 @@ class ExecutorTest extends TestCase */ public function testErrorsIfNoOperationIsProvidedWithMultipleOperations() : void { - $doc = 'query Example { a } query OtherExample { a }'; - $data = ['a' => 'b']; - $ast = Parser::parse($doc); + $doc = 'query Example { a } query OtherExample { a }'; + $data = ['a' => 'b']; + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'a' => ['type' => Type::string()], - ] - ]) + ], + ]), ]); $result = Executor::execute($schema, $ast, $data); $expected = [ 'errors' => [ - [ - 'message' => 'Must provide operation name if query contains multiple operations.', - ] - ] + ['message' => 'Must provide operation name if query contains multiple operations.'], + ], ]; $this->assertArraySubset($expected, $result->toArray()); @@ -653,18 +685,17 @@ class ExecutorTest extends TestCase */ public function testErrorsIfUnknownOperationNameIsProvided() : void { - $doc = 'query Example { a } query OtherExample { a }'; - $ast = Parser::parse($doc); + $doc = 'query Example { a } query OtherExample { a }'; + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'a' => ['type' => Type::string()], - ] - ]) + ], + ]), ]); - $result = Executor::execute( $schema, $ast, @@ -676,10 +707,8 @@ class ExecutorTest extends TestCase $expected = [ 'errors' => [ - [ - 'message' => 'Unknown operation named "UnknownExample".', - ] - ] + ['message' => 'Unknown operation named "UnknownExample".'], + ], ]; @@ -691,22 +720,22 @@ class ExecutorTest extends TestCase */ public function testUsesTheQuerySchemaForQueries() : void { - $doc = 'query Q { a } mutation M { c }'; - $data = ['a' => 'b', 'c' => 'd']; - $ast = Parser::parse($doc); + $doc = 'query Q { a } mutation M { c }'; + $data = ['a' => 'b', 'c' => 'd']; + $ast = Parser::parse($doc); $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Q', + 'query' => new ObjectType([ + 'name' => 'Q', 'fields' => [ 'a' => ['type' => Type::string()], - ] + ], ]), 'mutation' => new ObjectType([ - 'name' => 'M', + 'name' => 'M', 'fields' => [ 'c' => ['type' => Type::string()], - ] - ]) + ], + ]), ]); $queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q'); @@ -718,22 +747,22 @@ class ExecutorTest extends TestCase */ public function testUsesTheMutationSchemaForMutations() : void { - $doc = 'query Q { a } mutation M { c }'; - $data = [ 'a' => 'b', 'c' => 'd' ]; - $ast = Parser::parse($doc); - $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Q', + $doc = 'query Q { a } mutation M { c }'; + $data = ['a' => 'b', 'c' => 'd']; + $ast = Parser::parse($doc); + $schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'Q', 'fields' => [ 'a' => ['type' => Type::string()], - ] + ], ]), 'mutation' => new ObjectType([ - 'name' => 'M', + 'name' => 'M', 'fields' => [ - 'c' => [ 'type' => Type::string() ], - ] - ]) + 'c' => ['type' => Type::string()], + ], + ]), ]); $mutationResult = Executor::execute($schema, $ast, $data, null, [], 'M'); $this->assertEquals(['data' => ['c' => 'd']], $mutationResult->toArray()); @@ -744,22 +773,22 @@ class ExecutorTest extends TestCase */ public function testUsesTheSubscriptionSchemaForSubscriptions() : void { - $doc = 'query Q { a } subscription S { a }'; - $data = [ 'a' => 'b', 'c' => 'd' ]; - $ast = Parser::parse($doc); + $doc = 'query Q { a } subscription S { a }'; + $data = ['a' => 'b', 'c' => 'd']; + $ast = Parser::parse($doc); $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Q', + 'query' => new ObjectType([ + 'name' => 'Q', 'fields' => [ - 'a' => [ 'type' => Type::string() ], - ] + 'a' => ['type' => Type::string()], + ], ]), 'subscription' => new ObjectType([ - 'name' => 'S', + 'name' => 'S', 'fields' => [ - 'a' => [ 'type' => Type::string() ], - ] - ]) + 'a' => ['type' => Type::string()], + ], + ]), ]); $subscriptionResult = Executor::execute($schema, $ast, $data, null, [], 'S'); @@ -768,7 +797,7 @@ class ExecutorTest extends TestCase public function testCorrectFieldOrderingDespiteExecutionOrder() : void { - $doc = '{ + $doc = '{ a, b, c, @@ -780,13 +809,17 @@ class ExecutorTest extends TestCase return 'a'; }, 'b' => function () { - return new Deferred(function () { return 'b'; }); + return new Deferred(function () { + return 'b'; + }); }, 'c' => function () { return 'c'; }, 'd' => function () { - return new Deferred(function () { return 'd'; }); + return new Deferred(function () { + return 'd'; + }); }, 'e' => function () { return 'e'; @@ -796,16 +829,16 @@ class ExecutorTest extends TestCase $ast = Parser::parse($doc); $queryType = new ObjectType([ - 'name' => 'DeepDataType', + 'name' => 'DeepDataType', 'fields' => [ - 'a' => [ 'type' => Type::string() ], - 'b' => [ 'type' => Type::string() ], - 'c' => [ 'type' => Type::string() ], - 'd' => [ 'type' => Type::string() ], - 'e' => [ 'type' => Type::string() ], - ] + 'a' => ['type' => Type::string()], + 'b' => ['type' => Type::string()], + 'c' => ['type' => Type::string()], + 'd' => ['type' => Type::string()], + 'e' => ['type' => Type::string()], + ], ]); - $schema = new Schema(['query' => $queryType]); + $schema = new Schema(['query' => $queryType]); $expected = [ 'data' => [ @@ -814,7 +847,7 @@ class ExecutorTest extends TestCase 'c' => 'c', 'd' => 'd', 'e' => 'e', - ] + ], ]; $this->assertEquals($expected, Executor::execute($schema, $ast, $data)->toArray()); @@ -825,7 +858,7 @@ class ExecutorTest extends TestCase */ public function testAvoidsRecursion() : void { - $doc = ' + $doc = ' query Q { a ...Frag @@ -837,15 +870,15 @@ class ExecutorTest extends TestCase ...Frag } '; - $data = ['a' => 'b']; - $ast = Parser::parse($doc); + $data = ['a' => 'b']; + $ast = Parser::parse($doc); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'a' => ['type' => Type::string()], - ] - ]) + ], + ]), ]); $queryResult = Executor::execute($schema, $ast, $data, null, [], 'Q'); @@ -857,23 +890,23 @@ class ExecutorTest extends TestCase */ public function testDoesNotIncludeIllegalFieldsInOutput() : void { - $doc = 'mutation M { + $doc = 'mutation M { thisIsIllegalDontIncludeMe }'; - $ast = Parser::parse($doc); - $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Q', + $ast = Parser::parse($doc); + $schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'Q', 'fields' => [ 'a' => ['type' => Type::string()], - ] + ], ]), 'mutation' => new ObjectType([ - 'name' => 'M', + 'name' => 'M', 'fields' => [ 'c' => ['type' => Type::string()], - ] - ]) + ], + ]), ]); $mutationResult = Executor::execute($schema, $ast); $this->assertEquals(['data' => []], $mutationResult->toArray()); @@ -886,29 +919,29 @@ class ExecutorTest extends TestCase { $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'field' => [ - 'type' => Type::string(), - 'resolve' => function($data, $args) {return $args ? json_encode($args) : '';}, - 'args' => [ + '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()] - ] - ] - ] - ]) + 'e' => ['type' => Type::int()], + ], + ], + ], + ]), ]); - $query = Parser::parse('{ field(a: true, c: false, e: 0) }'); - $result = Executor::execute($schema, $query); + $query = Parser::parse('{ field(a: true, c: false, e: 0) }'); + $result = Executor::execute($schema, $query); $expected = [ - 'data' => [ - 'field' => '{"a":true,"c":false,"e":0}' - ] + 'data' => ['field' => '{"a":true,"c":false,"e":0}'], ]; $this->assertEquals($expected, $result->toArray()); @@ -920,48 +953,54 @@ class ExecutorTest extends TestCase public function testFailsWhenAnIsTypeOfCheckIsNotMet() : void { $SpecialType = new ObjectType([ - 'name' => 'SpecialType', - 'isTypeOf' => function($obj) { + 'name' => 'SpecialType', + 'isTypeOf' => function ($obj) { return $obj instanceof Special; }, - 'fields' => [ - 'value' => ['type' => Type::string()] - ] + 'fields' => [ + 'value' => ['type' => Type::string()], + ], ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ 'specials' => [ - 'type' => Type::listOf($SpecialType), - 'resolve' => function($rootValue) { + 'type' => Type::listOf($SpecialType), + 'resolve' => function ($rootValue) { return $rootValue['specials']; - } - ] - ] - ]) + }, + ], + ], + ]), ]); - $query = Parser::parse('{ specials { value } }'); - $value = [ - 'specials' => [ new Special('foo'), new NotSpecial('bar') ] + $query = Parser::parse('{ specials { value } }'); + $value = [ + 'specials' => [new Special('foo'), new NotSpecial('bar')], ]; $result = Executor::execute($schema, $query, $value); - $this->assertEquals([ - 'specials' => [ - ['value' => 'foo'], - null - ] - ], $result->data); + $this->assertEquals( + [ + 'specials' => [ + ['value' => 'foo'], + null, + ], + ], + $result->data + ); $this->assertEquals(1, count($result->errors)); - $this->assertEquals([ - 'message' => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\NotSpecial.', - 'locations' => [['line' => 1, 'column' => 3]], - 'path' => ['specials', 1] - ], $result->errors[0]->toSerializableArray()); + $this->assertEquals( + [ + 'message' => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\TestClasses\NotSpecial.', + 'locations' => [['line' => 1, 'column' => 3]], + 'path' => ['specials', 1], + ], + $result->errors[0]->toSerializableArray() + ); } /** @@ -977,20 +1016,17 @@ class ExecutorTest extends TestCase $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ - 'foo' => ['type' => Type::string()] - ] - ]) + 'foo' => ['type' => Type::string()], + ], + ]), ]); - $result = Executor::execute($schema, $query); $expected = [ - 'data' => [ - 'foo' => null, - ], + 'data' => ['foo' => null], ]; $this->assertArraySubset($expected, $result->toArray()); @@ -1005,11 +1041,11 @@ class ExecutorTest extends TestCase $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ - 'foo' => ['type' => Type::string()] - ] - ]) + 'foo' => ['type' => Type::string()], + ], + ]), ]); // For the purposes of test, just return the name of the field! @@ -1028,7 +1064,7 @@ class ExecutorTest extends TestCase ); $expected = [ - 'data' => ['foo' => 'foo'] + 'data' => ['foo' => 'foo'], ]; $this->assertEquals($expected, $result->toArray()); @@ -1038,12 +1074,14 @@ class ExecutorTest extends TestCase { $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Type', + 'name' => 'Type', 'fields' => [ 'field' => [ - 'type' => Type::string(), - 'resolve' => function($data, $args) {return $args ? json_encode($args) : '';}, - 'args' => [ + 'type' => Type::string(), + 'resolve' => function ($data, $args) { + return $args ? json_encode($args) : ''; + }, + 'args' => [ 'a' => ['type' => Type::boolean(), 'defaultValue' => 1], 'b' => ['type' => Type::boolean(), 'defaultValue' => null], 'c' => ['type' => Type::boolean(), 'defaultValue' => 0], @@ -1051,25 +1089,25 @@ class ExecutorTest extends TestCase 'e' => ['type' => Type::int(), 'defaultValue' => '0'], 'f' => ['type' => Type::int(), 'defaultValue' => 'some-string'], 'g' => ['type' => Type::boolean()], - 'h' => ['type' => new InputObjectType([ - 'name' => 'ComplexType', - 'fields' => [ - 'a' => ['type' => Type::int()], - 'b' => ['type' => Type::string()] - ] - ]), 'defaultValue' => ['a' => 1, 'b' => 'test']] - ] - ] - ] - ]) + 'h' => [ + 'type' => new InputObjectType([ + 'name' => 'ComplexType', + 'fields' => [ + 'a' => ['type' => Type::int()], + 'b' => ['type' => Type::string()], + ], + ]), 'defaultValue' => ['a' => 1, 'b' => 'test'], + ], + ], + ], + ], + ]), ]); - $query = Parser::parse('{ field }'); - $result = Executor::execute($schema, $query); + $query = Parser::parse('{ field }'); + $result = Executor::execute($schema, $query); $expected = [ - 'data' => [ - 'field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}' - ] + 'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'], ]; $this->assertEquals($expected, $result->toArray()); @@ -1083,43 +1121,43 @@ class ExecutorTest extends TestCase $iface = null; $a = new ObjectType([ - 'name' => 'A', - 'fields' => [ - 'id' => Type::id() + 'name' => 'A', + 'fields' => [ + 'id' => Type::id(), ], - 'interfaces' => function() use (&$iface) { + 'interfaces' => function () use (&$iface) { return [$iface]; - } + }, ]); $b = new ObjectType([ - 'name' => 'B', - 'fields' => [ - 'id' => Type::id() + 'name' => 'B', + 'fields' => [ + 'id' => Type::id(), ], - 'interfaces' => function() use (&$iface) { + 'interfaces' => function () use (&$iface) { return [$iface]; - } + }, ]); $iface = new InterfaceType([ - 'name' => 'Iface', - 'fields' => [ - 'id' => Type::id() + 'name' => 'Iface', + 'fields' => [ + 'id' => Type::id(), ], - 'resolveType' => function($v) use ($a, $b) { + 'resolveType' => function ($v) use ($a, $b) { return $v['type'] === 'A' ? $a : $b; - } + }, ]); $schema = new Schema([ 'query' => new ObjectType([ - 'name' => 'Query', + 'name' => 'Query', 'fields' => [ - 'ab' => Type::listOf($iface) - ] + 'ab' => Type::listOf($iface), + ], ]), - 'types' => [$a, $b] + 'types' => [$a, $b], ]); $data = [ @@ -1127,8 +1165,8 @@ class ExecutorTest extends TestCase ['id' => 1, 'type' => 'A'], ['id' => 2, 'type' => 'A'], ['id' => 3, 'type' => 'B'], - ['id' => 4, 'type' => 'B'] - ] + ['id' => 4, 'type' => 'B'], + ], ]; $query = Parser::parse(' @@ -1143,15 +1181,18 @@ class ExecutorTest extends TestCase $result = Executor::execute($schema, $query, $data, null); - $this->assertEquals([ - 'data' => [ - 'ab' => [ - ['id' => '1'], - ['id' => '2'], - new \stdClass(), - new \stdClass() - ] - ] - ], $result->toArray()); + $this->assertEquals( + [ + 'data' => [ + 'ab' => [ + ['id' => '1'], + ['id' => '2'], + new \stdClass(), + new \stdClass(), + ], + ], + ], + $result->toArray() + ); } } diff --git a/tests/Executor/LazyInterfaceTest.php b/tests/Executor/LazyInterfaceTest.php index a099187..f9fcc31 100644 --- a/tests/Executor/LazyInterfaceTest.php +++ b/tests/Executor/LazyInterfaceTest.php @@ -1,104 +1,34 @@ 'query', - 'fields' => function () { - return [ - 'lazyInterface' => [ - 'type' => $this->getLazyInterfaceType(), - 'resolve' => function() { - return []; - } - ] - ]; - } - ]); - - $this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]); - } - - /** - * Returns the LazyInterface - * - * @return InterfaceType - */ - protected function getLazyInterfaceType() - { - if (!$this->lazyInterface) { - $this->lazyInterface = new InterfaceType([ - 'name' => 'LazyInterface', - 'fields' => [ - 'a' => Type::string() - ], - 'resolveType' => function() { - return $this->getTestObjectType(); - }, - ]); - } - - return $this->lazyInterface; - } - - /** - * Returns the test ObjectType - * @return ObjectType - */ - protected function getTestObjectType() - { - if (!$this->testObject) { - $this->testObject = new ObjectType([ - 'name' => 'TestObject', - 'fields' => [ - 'name' => [ - 'type' => Type::string(), - 'resolve' => function() { - return 'testname'; - } - ] - ], - 'interfaces' => [$this->getLazyInterfaceType()] - ]); - } - - return $this->testObject; - } - /** * Handles execution of a lazily created interface */ @@ -116,12 +46,78 @@ class LazyInterfaceTest extends TestCase $expected = [ 'data' => [ - 'lazyInterface' => [ - 'name' => 'testname' - ] - ] + 'lazyInterface' => ['name' => 'testname'], + ], ]; $this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray()); } + + /** + * Setup schema + */ + protected function setUp() + { + $query = new ObjectType([ + 'name' => 'query', + 'fields' => function () { + return [ + 'lazyInterface' => [ + 'type' => $this->getLazyInterfaceType(), + 'resolve' => function () { + return []; + }, + ], + ]; + }, + ]); + + $this->schema = new Schema(['query' => $query, 'types' => [$this->getTestObjectType()]]); + } + + /** + * Returns the LazyInterface + * + * @return InterfaceType + */ + protected function getLazyInterfaceType() + { + if (! $this->lazyInterface) { + $this->lazyInterface = new InterfaceType([ + 'name' => 'LazyInterface', + 'fields' => [ + 'a' => Type::string(), + ], + 'resolveType' => function () { + return $this->getTestObjectType(); + }, + ]); + } + + return $this->lazyInterface; + } + + /** + * Returns the test ObjectType + * @return ObjectType + */ + protected function getTestObjectType() + { + if (! $this->testObject) { + $this->testObject = new ObjectType([ + 'name' => 'TestObject', + 'fields' => [ + 'name' => [ + 'type' => Type::string(), + 'resolve' => function () { + return 'testname'; + }, + ], + ], + 'interfaces' => [$this->getLazyInterfaceType()], + ]); + } + + return $this->testObject; + } } diff --git a/tests/Executor/ListsTest.php b/tests/Executor/ListsTest.php index 3ab1406..f9906f9 100644 --- a/tests/Executor/ListsTest.php +++ b/tests/Executor/ListsTest.php @@ -1,22 +1,21 @@ checkHandlesNullableLists( - [ 1, 2 ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + [1, 2], + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNullableLists( - [ 1, null, 2 ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] + [1, null, 2], + ['data' => ['nest' => ['test' => [1, null, 2]]]] ); // Returns null $this->checkHandlesNullableLists( null, - [ 'data' => [ 'nest' => [ 'test' => null ] ] ] + ['data' => ['nest' => ['test' => null]]] ); } + private function checkHandlesNullableLists($testData, $expected) + { + $testType = Type::listOf(Type::int()); + $this->check($testType, $testData, $expected); + } + + private function check($testType, $testData, $expected, $debug = false) + { + $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($debug)); + } + /** * [T] */ @@ -48,26 +81,26 @@ class ListsTest extends TestCase { // Contains values $this->checkHandlesNullableLists( - new Deferred(function() { - return [1,2]; + new Deferred(function () { + return [1, 2]; }), - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNullableLists( - new Deferred(function() { + new Deferred(function () { return [1, null, 2]; }), - [ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, null, 2]]]] ); // Returns null $this->checkHandlesNullableLists( - new Deferred(function() { + new Deferred(function () { return null; }), - [ 'data' => [ 'nest' => [ 'test' => null ] ] ] + ['data' => ['nest' => ['test' => null]]] ); // Rejected @@ -78,14 +111,14 @@ class ListsTest extends TestCase }); }, [ - 'data' => ['nest' => ['test' => null]], + 'data' => ['nest' => ['test' => null]], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test'] - ] - ] + 'path' => ['nest', 'test'], + ], + ], ] ); } @@ -98,57 +131,64 @@ class ListsTest extends TestCase // Contains values $this->checkHandlesNullableLists( [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { return 2; - })], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + }), + ], + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNullableLists( [ - new Deferred(function() {return 1;}), - new Deferred(function() {return null;}), - new Deferred(function() {return 2;}) + new Deferred(function () { + return 1; + }), + new Deferred(function () { + return null; + }), + new Deferred(function () { + return 2; + }), ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, null, 2]]]] ); // Returns null $this->checkHandlesNullableLists( - new Deferred(function() { + new Deferred(function () { return null; }), - [ 'data' => [ 'nest' => [ 'test' => null ] ] ] + ['data' => ['nest' => ['test' => null]]] ); // Contains reject $this->checkHandlesNullableLists( function () { return [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { throw new UserError('bad'); }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ]; }, [ - 'data' => ['nest' => ['test' => [1, null, 2]]], + 'data' => ['nest' => ['test' => [1, null, 2]]], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test', 1] - ] - ] + 'path' => ['nest', 'test', 1], + ], + ], ] ); } @@ -160,32 +200,38 @@ class ListsTest extends TestCase { // Contains values $this->checkHandlesNonNullableLists( - [ 1, 2 ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + [1, 2], + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNonNullableLists( - [ 1, null, 2 ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] + [1, null, 2], + ['data' => ['nest' => ['test' => [1, null, 2]]]] ); // Returns null $this->checkHandlesNonNullableLists( null, [ - 'data' => [ 'nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [['line' => 1, 'column' => 10]] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); } + private function checkHandlesNonNullableLists($testData, $expected, $debug = false) + { + $testType = Type::nonNull(Type::listOf(Type::int())); + $this->check($testType, $testData, $expected, $debug); + } + /** * [T]! */ @@ -193,31 +239,31 @@ class ListsTest extends TestCase { // Contains values $this->checkHandlesNonNullableLists( - new Deferred(function() { - return [1,2]; + new Deferred(function () { + return [1, 2]; }), - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNonNullableLists( - new Deferred(function() { + new Deferred(function () { return [1, null, 2]; }), - [ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, null, 2]]]] ); // Returns null $this->checkHandlesNonNullableLists( null, [ - 'data' => [ 'nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [['line' => 1, 'column' => 10]] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); @@ -225,19 +271,19 @@ class ListsTest extends TestCase // Rejected $this->checkHandlesNonNullableLists( function () { - return new Deferred(function() { + return new Deferred(function () { throw new UserError('bad'); }); }, [ - 'data' => ['nest' => null], + 'data' => ['nest' => null], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test'] - ] - ] + 'path' => ['nest', 'test'], + ], + ], ] ); } @@ -250,56 +296,56 @@ class ListsTest extends TestCase // Contains values $this->checkHandlesNonNullableLists( [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNonNullableLists( [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { return null; }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, null, 2]]]] ); // Contains reject $this->checkHandlesNonNullableLists( function () { return [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { throw new UserError('bad'); }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ]; }, [ - 'data' => ['nest' => ['test' => [1, null, 2]]], + 'data' => ['nest' => ['test' => [1, null, 2]]], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test', 1] - ] - ] + 'path' => ['nest', 'test', 1], + ], + ], ] ); } @@ -311,21 +357,21 @@ class ListsTest extends TestCase { // Contains values $this->checkHandlesListOfNonNulls( - [ 1, 2 ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + [1, 2], + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesListOfNonNulls( - [ 1, null, 2 ], + [1, null, 2], [ - 'data' => [ 'nest' => [ 'test' => null ] ], + 'data' => ['nest' => ['test' => null]], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [ ['line' => 1, 'column' => 10] ] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); @@ -333,10 +379,16 @@ class ListsTest extends TestCase // Returns null $this->checkHandlesListOfNonNulls( null, - [ 'data' => [ 'nest' => [ 'test' => null ] ] ] + ['data' => ['nest' => ['test' => null]]] ); } + private function checkHandlesListOfNonNulls($testData, $expected, $debug = false) + { + $testType = Type::listOf(Type::nonNull(Type::int())); + $this->check($testType, $testData, $expected, $debug); + } + /** * [T!] */ @@ -344,53 +396,53 @@ class ListsTest extends TestCase { // Contains values $this->checkHandlesListOfNonNulls( - new Deferred(function() { + new Deferred(function () { return [1, 2]; }), - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesListOfNonNulls( - new Deferred(function() { + new Deferred(function () { return [1, null, 2]; }), [ - 'data' => [ 'nest' => [ 'test' => null ] ], + 'data' => ['nest' => ['test' => null]], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [['line' => 1, 'column' => 10]] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); // Returns null $this->checkHandlesListOfNonNulls( - new Deferred(function() { + new Deferred(function () { return null; }), - [ 'data' => [ 'nest' => [ 'test' => null ] ] ] + ['data' => ['nest' => ['test' => null]]] ); // Rejected $this->checkHandlesListOfNonNulls( function () { - return new Deferred(function() { + return new Deferred(function () { throw new UserError('bad'); }); }, [ - 'data' => ['nest' => ['test' => null]], + 'data' => ['nest' => ['test' => null]], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test'] - ] - ] + 'path' => ['nest', 'test'], + ], + ], ] ); } @@ -403,50 +455,56 @@ class ListsTest extends TestCase // Contains values $this->checkHandlesListOfNonNulls( [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesListOfNonNulls( [ - new Deferred(function() {return 1;}), - new Deferred(function() {return null;}), - new Deferred(function() {return 2;}) + new Deferred(function () { + return 1; + }), + new Deferred(function () { + return null; + }), + new Deferred(function () { + return 2; + }), ], - [ 'data' => [ 'nest' => [ 'test' => null ] ] ] + ['data' => ['nest' => ['test' => null]]] ); // Contains reject $this->checkHandlesListOfNonNulls( function () { return [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { throw new UserError('bad'); }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ]; }, [ - 'data' => ['nest' => ['test' => null]], + 'data' => ['nest' => ['test' => null]], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test', 1] - ] - ] + 'path' => ['nest', 'test', 1], + ], + ], ] ); } @@ -458,22 +516,21 @@ class ListsTest extends TestCase { // Contains values $this->checkHandlesNonNullListOfNonNulls( - [ 1, 2 ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + [1, 2], + ['data' => ['nest' => ['test' => [1, 2]]]] ); - // Contains null $this->checkHandlesNonNullListOfNonNulls( - [ 1, null, 2 ], + [1, null, 2], [ - 'data' => [ 'nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [['line' => 1, 'column' => 10 ]] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); @@ -482,18 +539,24 @@ class ListsTest extends TestCase $this->checkHandlesNonNullListOfNonNulls( null, [ - 'data' => [ 'nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [ ['line' => 1, 'column' => 10] ] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); } + public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false) + { + $testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int()))); + $this->check($testType, $testData, $expected, $debug); + } + /** * [T!]! */ @@ -501,42 +564,42 @@ class ListsTest extends TestCase { // Contains values $this->checkHandlesNonNullListOfNonNulls( - new Deferred(function() { + new Deferred(function () { return [1, 2]; }), - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNonNullListOfNonNulls( - new Deferred(function() { + new Deferred(function () { return [1, null, 2]; }), [ - 'data' => [ 'nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [ ['line' => 1, 'column' => 10] ] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); // Returns null $this->checkHandlesNonNullListOfNonNulls( - new Deferred(function() { + new Deferred(function () { return null; }), [ - 'data' => [ 'nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [ ['line' => 1, 'column' => 10] ] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); @@ -544,19 +607,19 @@ class ListsTest extends TestCase // Rejected $this->checkHandlesNonNullListOfNonNulls( function () { - return new Deferred(function() { + return new Deferred(function () { throw new UserError('bad'); }); }, [ - 'data' => ['nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test'] - ] - ] + 'path' => ['nest', 'test'], + ], + ], ] ); } @@ -569,38 +632,38 @@ class ListsTest extends TestCase // Contains values $this->checkHandlesNonNullListOfNonNulls( [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ], - [ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] + ['data' => ['nest' => ['test' => [1, 2]]]] ); // Contains null $this->checkHandlesNonNullListOfNonNulls( [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { return null; }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ], [ - 'data' => [ 'nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', - 'locations' => [['line' => 1, 'column' => 10]] - ] - ] + 'locations' => [['line' => 1, 'column' => 10]], + ], + ], ], true ); @@ -609,83 +672,27 @@ class ListsTest extends TestCase $this->checkHandlesNonNullListOfNonNulls( function () { return [ - new Deferred(function() { + new Deferred(function () { return 1; }), - new Deferred(function() { + new Deferred(function () { throw new UserError('bad'); }), - new Deferred(function() { + new Deferred(function () { return 2; - }) + }), ]; }, [ - 'data' => ['nest' => null ], + 'data' => ['nest' => null], 'errors' => [ [ - 'message' => 'bad', + 'message' => 'bad', 'locations' => [['line' => 1, 'column' => 10]], - 'path' => ['nest', 'test'] - ] - ] + 'path' => ['nest', 'test'], + ], + ], ] ); } - - private function checkHandlesNullableLists($testData, $expected) - { - $testType = Type::listOf(Type::int()); - $this->check($testType, $testData, $expected); - } - - private function checkHandlesNonNullableLists($testData, $expected, $debug = false) - { - $testType = Type::nonNull(Type::listOf(Type::int())); - $this->check($testType, $testData, $expected, $debug); - } - - private function checkHandlesListOfNonNulls($testData, $expected, $debug = false) - { - $testType = Type::listOf(Type::nonNull(Type::int())); - $this->check($testType, $testData, $expected, $debug); - } - - public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false) - { - $testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int()))); - $this->check($testType, $testData, $expected, $debug); - } - - private function check($testType, $testData, $expected, $debug = false) - { - $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($debug)); - } } diff --git a/tests/Executor/MutationsTest.php b/tests/Executor/MutationsTest.php index bc83b5d..157de57 100644 --- a/tests/Executor/MutationsTest.php +++ b/tests/Executor/MutationsTest.php @@ -1,27 +1,26 @@ schema(), $ast, new Root(6)); - $expected = [ + $expected = [ 'data' => [ - 'first' => [ - 'theNumber' => 1 - ], - 'second' => [ - 'theNumber' => 2 - ], - 'third' => [ - 'theNumber' => 3 - ], - 'fourth' => [ - 'theNumber' => 4 - ], - 'fifth' => [ - 'theNumber' => 5 - ] - ] + 'first' => ['theNumber' => 1], + 'second' => ['theNumber' => 2], + 'third' => ['theNumber' => 3], + 'fourth' => ['theNumber' => 4], + 'fifth' => ['theNumber' => 5], + ], ]; $this->assertEquals($expected, $mutationResult->toArray()); } + private function schema() : Schema + { + $numberHolderType = new ObjectType([ + 'fields' => [ + 'theNumber' => ['type' => Type::int()], + ], + 'name' => 'NumberHolder', + ]); + $schema = new Schema([ + 'query' => new ObjectType([ + 'fields' => [ + 'numberHolder' => ['type' => $numberHolderType], + ], + 'name' => 'Query', + ]), + 'mutation' => new ObjectType([ + 'fields' => [ + 'immediatelyChangeTheNumber' => [ + 'type' => $numberHolderType, + 'args' => ['newNumber' => ['type' => Type::int()]], + 'resolve' => function (Root $obj, $args) { + return $obj->immediatelyChangeTheNumber($args['newNumber']); + }, + ], + 'promiseToChangeTheNumber' => [ + 'type' => $numberHolderType, + 'args' => ['newNumber' => ['type' => Type::int()]], + 'resolve' => function (Root $obj, $args) { + return $obj->promiseToChangeTheNumber($args['newNumber']); + }, + ], + 'failToChangeTheNumber' => [ + 'type' => $numberHolderType, + 'args' => ['newNumber' => ['type' => Type::int()]], + 'resolve' => function (Root $obj, $args) { + $obj->failToChangeTheNumber(); + }, + ], + 'promiseAndFailToChangeTheNumber' => [ + 'type' => $numberHolderType, + 'args' => ['newNumber' => ['type' => Type::int()]], + 'resolve' => function (Root $obj, $args) { + return $obj->promiseAndFailToChangeTheNumber(); + }, + ], + ], + 'name' => 'Mutation', + ]), + ]); + + return $schema; + } + /** * @see it('evaluates mutations correctly in the presense of a failed mutation') */ public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() : void { - $doc = 'mutation M { + $doc = 'mutation M { first: immediatelyChangeTheNumber(newNumber: 1) { theNumber }, @@ -87,147 +129,28 @@ class MutationsTest extends TestCase theNumber } }'; - $ast = Parser::parse($doc); + $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, + $expected = [ + 'data' => [ + 'first' => ['theNumber' => 1], + 'second' => ['theNumber' => 2], + 'third' => null, + 'fourth' => ['theNumber' => 4], + 'fifth' => ['theNumber' => 5], + 'sixth' => null, ], 'errors' => [ [ 'debugMessage' => 'Cannot change the number', - 'locations' => [['line' => 8, 'column' => 7]] + 'locations' => [['line' => 8, 'column' => 7]], ], [ 'debugMessage' => 'Cannot change the number', - 'locations' => [['line' => 17, 'column' => 7]] - ] - ] + 'locations' => [['line' => 17, 'column' => 7]], + ], + ], ]; $this->assertArraySubset($expected, $mutationResult->toArray(true)); } - - private function schema() - { - $numberHolderType = new ObjectType([ - 'fields' => [ - 'theNumber' => ['type' => Type::int()], - ], - 'name' => 'NumberHolder', - ]); - $schema = new Schema([ - 'query' => new ObjectType([ - 'fields' => [ - 'numberHolder' => ['type' => $numberHolderType], - ], - 'name' => 'Query', - ]), - 'mutation' => new ObjectType([ - 'fields' => [ - 'immediatelyChangeTheNumber' => [ - 'type' => $numberHolderType, - 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => (function (Root $obj, $args) { - return $obj->immediatelyChangeTheNumber($args['newNumber']); - }) - ], - 'promiseToChangeTheNumber' => [ - 'type' => $numberHolderType, - 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => (function (Root $obj, $args) { - return $obj->promiseToChangeTheNumber($args['newNumber']); - }) - ], - 'failToChangeTheNumber' => [ - 'type' => $numberHolderType, - 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => (function (Root $obj, $args) { - return $obj->failToChangeTheNumber($args['newNumber']); - }) - ], - 'promiseAndFailToChangeTheNumber' => [ - 'type' => $numberHolderType, - 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => (function (Root $obj, $args) { - return $obj->promiseAndFailToChangeTheNumber($args['newNumber']); - }) - ] - ], - 'name' => 'Mutation', - ]) - ]); - return $schema; - } -} - -class NumberHolder -{ - public $theNumber; - - public function __construct($originalNumber) - { - $this->theNumber = $originalNumber; - } -} - -class Root { - public $numberHolder; - - public function __construct($originalNumber) - { - $this->numberHolder = new NumberHolder($originalNumber); - } - - /** - * @param $newNumber - * @return NumberHolder - */ - public function immediatelyChangeTheNumber($newNumber) - { - $this->numberHolder->theNumber = $newNumber; - return $this->numberHolder; - } - - /** - * @param $newNumber - * - * @return Deferred - */ - public function promiseToChangeTheNumber($newNumber) - { - return new Deferred(function () use ($newNumber) { - return $this->immediatelyChangeTheNumber($newNumber); - }); - } - - /** - * @throws \Exception - */ - public function failToChangeTheNumber() - { - throw new \Exception('Cannot change the number'); - } - - /** - * @return Deferred - */ - public function promiseAndFailToChangeTheNumber() - { - return new Deferred(function () { - throw new \Exception("Cannot change the number"); - }); - } } diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index 2d2a528..5727d5c 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -1,16 +1,18 @@ syncError = new UserError('sync'); - $this->syncNonNullError = new UserError('syncNonNull'); - $this->promiseError = new UserError('promise'); + $this->syncError = new UserError('sync'); + $this->syncNonNullError = new UserError('syncNonNull'); + $this->promiseError = new UserError('promise'); $this->promiseNonNullError = new UserError('promiseNonNull'); $this->throwingData = [ - 'sync' => function () { + 'sync' => function () { throw $this->syncError; }, - 'syncNonNull' => function () { + 'syncNonNull' => function () { throw $this->syncNonNullError; }, - 'promise' => function () { + 'promise' => function () { return new Deferred(function () { throw $this->promiseError; }); }, - 'promiseNonNull' => function () { + 'promiseNonNull' => function () { return new Deferred(function () { throw $this->promiseNonNullError; }); }, - 'syncNest' => function () { + 'syncNest' => function () { return $this->throwingData; }, - 'syncNonNullNest' => function () { + 'syncNonNullNest' => function () { return $this->throwingData; }, - 'promiseNest' => function () { + 'promiseNest' => function () { return new Deferred(function () { return $this->throwingData; }); @@ -74,29 +81,29 @@ class NonNullTest extends TestCase ]; $this->nullingData = [ - 'sync' => function () { + 'sync' => function () { return null; }, - 'syncNonNull' => function () { + 'syncNonNull' => function () { return null; }, - 'promise' => function () { + 'promise' => function () { return new Deferred(function () { return null; }); }, - 'promiseNonNull' => function () { + 'promiseNonNull' => function () { return new Deferred(function () { return null; }); }, - 'syncNest' => function () { + 'syncNest' => function () { return $this->nullingData; }, - 'syncNonNullNest' => function () { + 'syncNonNullNest' => function () { return $this->nullingData; }, - 'promiseNest' => function () { + 'promiseNest' => function () { return new Deferred(function () { return $this->nullingData; }); @@ -109,19 +116,19 @@ class NonNullTest extends TestCase ]; $dataType = new ObjectType([ - 'name' => 'DataType', - 'fields' => function() use (&$dataType) { + 'name' => 'DataType', + 'fields' => function () use (&$dataType) { return [ - 'sync' => ['type' => Type::string()], - 'syncNonNull' => ['type' => Type::nonNull(Type::string())], - 'promise' => Type::string(), - 'promiseNonNull' => Type::nonNull(Type::string()), - 'syncNest' => $dataType, - 'syncNonNullNest' => Type::nonNull($dataType), - 'promiseNest' => $dataType, + 'sync' => ['type' => Type::string()], + 'syncNonNull' => ['type' => Type::nonNull(Type::string())], + 'promise' => Type::string(), + 'promiseNonNull' => Type::nonNull(Type::string()), + 'syncNest' => $dataType, + 'syncNonNullNest' => Type::nonNull($dataType), + 'promiseNest' => $dataType, 'promiseNonNullNest' => Type::nonNull($dataType), ]; - } + }, ]); $this->schema = new Schema(['query' => $dataType]); @@ -143,17 +150,18 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'sync' => null, - ], + 'data' => ['sync' => null], 'errors' => [ FormattedError::create( $this->syncError->getMessage(), [new SourceLocation(3, 9)] - ) - ] + ), + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsANullableFieldThatThrowsInAPromise() : void @@ -167,18 +175,19 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'promise' => null, - ], + 'data' => ['promise' => null], 'errors' => [ FormattedError::create( $this->promiseError->getMessage(), [new SourceLocation(3, 9)] - ) - ] + ), + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously() : void @@ -195,14 +204,15 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'syncNest' => null - ], + 'data' => ['syncNest' => null], 'errors' => [ - FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]) - ] + FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]), + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise() : void @@ -218,15 +228,16 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'syncNest' => null - ], + 'data' => ['syncNest' => null], 'errors' => [ - FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]) - ] + FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]), + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously() : void @@ -242,15 +253,16 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'promiseNest' => null - ], + 'data' => ['promiseNest' => null], 'errors' => [ - FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]) - ] + FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(4, 11)]), + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise() : void @@ -266,15 +278,16 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'promiseNest' => null - ], + 'data' => ['promiseNest' => null], 'errors' => [ - FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]) - ] + FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(4, 11)]), + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } /** @@ -314,28 +327,28 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'syncNest' => [ - 'sync' => null, - 'promise' => null, - 'syncNest' => [ - 'sync' => null, + 'data' => [ + 'syncNest' => [ + 'sync' => null, + 'promise' => null, + 'syncNest' => [ + 'sync' => null, 'promise' => null, ], 'promiseNest' => [ - 'sync' => null, + 'sync' => null, 'promise' => null, ], ], 'promiseNest' => [ - 'sync' => null, - 'promise' => null, - 'syncNest' => [ - 'sync' => null, + 'sync' => null, + 'promise' => null, + 'syncNest' => [ + 'sync' => null, 'promise' => null, ], 'promiseNest' => [ - 'sync' => null, + 'sync' => null, 'promise' => null, ], ], @@ -353,10 +366,13 @@ class NonNullTest extends TestCase FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]), FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(20, 13)]), FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 13)]), - ] + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull() : void @@ -413,10 +429,10 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'syncNest' => null, - 'promiseNest' => null, - 'anotherNest' => null, + 'data' => [ + 'syncNest' => null, + 'promiseNest' => null, + 'anotherNest' => null, 'anotherPromiseNest' => null, ], 'errors' => [ @@ -424,10 +440,13 @@ class NonNullTest extends TestCase FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(19, 19)]), FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(30, 19)]), FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(41, 19)]), - ] + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsANullableFieldThatSynchronouslyReturnsNull() : void @@ -441,11 +460,12 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'sync' => null, - ] + 'data' => ['sync' => null], ]; - $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertEquals( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray() + ); } public function testNullsANullableFieldThatReturnsNullInAPromise() : void @@ -459,12 +479,13 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'promise' => null, - ] + 'data' => ['promise' => null], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray() + ); } public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously() : void @@ -480,17 +501,18 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'syncNest' => null - ], + 'data' => ['syncNest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', - 'locations' => [['line' => 4, 'column' => 11]] - ] - ] + 'locations' => [['line' => 4, 'column' => 11]], + ], + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true)); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + ); } public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() : void @@ -506,15 +528,13 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'syncNest' => null, - ], + 'data' => ['syncNest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', - 'locations' => [['line' => 4, 'column' => 11]] + 'locations' => [['line' => 4, 'column' => 11]], ], - ] + ], ]; $this->assertArraySubset( @@ -536,15 +556,13 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'promiseNest' => null, - ], + 'data' => ['promiseNest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', - 'locations' => [['line' => 4, 'column' => 11]] + 'locations' => [['line' => 4, 'column' => 11]], ], - ] + ], ]; $this->assertArraySubset( @@ -566,15 +584,13 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'promiseNest' => null, - ], + 'data' => ['promiseNest' => null], 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', - 'locations' => [['line' => 4, 'column' => 11]] + 'locations' => [['line' => 4, 'column' => 11]], ], - ] + ], ]; $this->assertArraySubset( @@ -618,31 +634,31 @@ class NonNullTest extends TestCase $expected = [ 'data' => [ - 'syncNest' => [ - 'sync' => null, - 'promise' => null, - 'syncNest' => [ - 'sync' => null, + 'syncNest' => [ + 'sync' => null, + 'promise' => null, + 'syncNest' => [ + 'sync' => null, 'promise' => null, ], 'promiseNest' => [ - 'sync' => null, + 'sync' => null, 'promise' => null, - ] + ], ], 'promiseNest' => [ - 'sync' => null, - 'promise' => null, - 'syncNest' => [ - 'sync' => null, + 'sync' => null, + 'promise' => null, + 'syncNest' => [ + 'sync' => null, 'promise' => null, ], 'promiseNest' => [ - 'sync' => null, + 'sync' => null, 'promise' => null, - ] - ] - ] + ], + ], + ], ]; $actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(); @@ -703,18 +719,18 @@ class NonNullTest extends TestCase $ast = Parser::parse($doc); $expected = [ - 'data' => [ - 'syncNest' => null, - 'promiseNest' => null, - 'anotherNest' => null, + 'data' => [ + 'syncNest' => null, + 'promiseNest' => null, + 'anotherNest' => null, 'anotherPromiseNest' => null, ], 'errors' => [ - ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 8, 'column' => 19]]], - ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [ ['line' => 19, 'column' => 19]]], - ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [ ['line' => 30, 'column' => 19]]], - ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [ ['line' => 41, 'column' => 19]]], - ] + ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 8, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 19, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 30, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]], + ], ]; $this->assertArraySubset( @@ -734,10 +750,10 @@ class NonNullTest extends TestCase $expected = [ 'errors' => [ - FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(2, 17)]) - ] + FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(2, 17)]), + ], ]; - $actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray(); + $actual = Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray(); $this->assertArraySubset($expected, $actual); } @@ -752,10 +768,13 @@ class NonNullTest extends TestCase $expected = [ 'errors' => [ FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(2, 17)]), - ] + ], ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray() + ); } public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void @@ -769,9 +788,9 @@ class NonNullTest extends TestCase 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', - 'locations' => [['line' => 2, 'column' => 17]] + 'locations' => [['line' => 2, 'column' => 17]], ], - ] + ], ]; $this->assertArraySubset( $expected, @@ -791,9 +810,9 @@ class NonNullTest extends TestCase 'errors' => [ [ 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', - 'locations' => [['line' => 2, 'column' => 17]] + 'locations' => [['line' => 2, 'column' => 17]], ], - ] + ], ]; $this->assertArraySubset( diff --git a/tests/Executor/Promise/ReactPromiseAdapterTest.php b/tests/Executor/Promise/ReactPromiseAdapterTest.php index 6859aeb..e28ba3c 100644 --- a/tests/Executor/Promise/ReactPromiseAdapterTest.php +++ b/tests/Executor/Promise/ReactPromiseAdapterTest.php @@ -1,17 +1,17 @@ markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest'); + if (class_exists('React\Promise\Promise')) { + return; } + + $this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest'); } public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void { $reactAdapter = new ReactPromiseAdapter(); - $this->assertSame(true, $reactAdapter->isThenable(new ReactPromise(function() {}))); + $this->assertSame( + true, + $reactAdapter->isThenable(new ReactPromise(function () { + })) + ); $this->assertSame(true, $reactAdapter->isThenable(new FulfilledPromise())); $this->assertSame(true, $reactAdapter->isThenable(new RejectedPromise())); - $this->assertSame(true, $reactAdapter->isThenable(new LazyPromise(function() {}))); + $this->assertSame( + true, + $reactAdapter->isThenable(new LazyPromise(function () { + })) + ); $this->assertSame(false, $reactAdapter->isThenable(false)); $this->assertSame(false, $reactAdapter->isThenable(true)); $this->assertSame(false, $reactAdapter->isThenable(1)); @@ -58,13 +68,16 @@ class ReactPromiseAdapterTest extends TestCase { $reactAdapter = new ReactPromiseAdapter(); $reactPromise = new FulfilledPromise(1); - $promise = $reactAdapter->convertThenable($reactPromise); + $promise = $reactAdapter->convertThenable($reactPromise); $result = null; - $resultPromise = $reactAdapter->then($promise, function ($value) use (&$result) { - $result = $value; - }); + $resultPromise = $reactAdapter->then( + $promise, + function ($value) use (&$result) { + $result = $value; + } + ); $this->assertSame(1, $result); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise); @@ -73,9 +86,9 @@ class ReactPromiseAdapterTest extends TestCase public function testCreate() : void { - $reactAdapter = new ReactPromiseAdapter(); + $reactAdapter = new ReactPromiseAdapter(); $resolvedPromise = $reactAdapter->create(function ($resolve) { - $resolve(1); + $resolve(1); }); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resolvedPromise); @@ -84,7 +97,7 @@ class ReactPromiseAdapterTest extends TestCase $result = null; $resolvedPromise->then(function ($value) use (&$result) { - $result = $value; + $result = $value; }); $this->assertSame(1, $result); @@ -92,7 +105,7 @@ class ReactPromiseAdapterTest extends TestCase public function testCreateFulfilled() : void { - $reactAdapter = new ReactPromiseAdapter(); + $reactAdapter = new ReactPromiseAdapter(); $fulfilledPromise = $reactAdapter->createFulfilled(1); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $fulfilledPromise); @@ -109,7 +122,7 @@ class ReactPromiseAdapterTest extends TestCase public function testCreateRejected() : void { - $reactAdapter = new ReactPromiseAdapter(); + $reactAdapter = new ReactPromiseAdapter(); $rejectedPromise = $reactAdapter->createRejected(new \Exception('I am a bad promise')); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $rejectedPromise); @@ -117,9 +130,12 @@ class ReactPromiseAdapterTest extends TestCase $exception = null; - $rejectedPromise->then(null, function ($error) use (&$exception) { - $exception = $error; - }); + $rejectedPromise->then( + null, + function ($error) use (&$exception) { + $exception = $error; + } + ); $this->assertInstanceOf('\Exception', $exception); $this->assertEquals('I am a bad promise', $exception->getMessage()); @@ -128,7 +144,7 @@ class ReactPromiseAdapterTest extends TestCase public function testAll() : void { $reactAdapter = new ReactPromiseAdapter(); - $promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)]; + $promises = [new FulfilledPromise(1), new FulfilledPromise(2), new FulfilledPromise(3)]; $allPromise = $reactAdapter->all($promises); @@ -138,7 +154,7 @@ class ReactPromiseAdapterTest extends TestCase $result = null; $allPromise->then(function ($values) use (&$result) { - $result = $values; + $result = $values; }); $this->assertSame([1, 2, 3], $result); @@ -147,9 +163,9 @@ class ReactPromiseAdapterTest extends TestCase public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises() : void { $reactAdapter = new ReactPromiseAdapter(); - $deferred = new Deferred(); - $promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)]; - $result = null; + $deferred = new Deferred(); + $promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)]; + $result = null; $reactAdapter->all($promises)->then(function ($values) use (&$result) { $result = $values; diff --git a/tests/Executor/Promise/SyncPromiseAdapterTest.php b/tests/Executor/Promise/SyncPromiseAdapterTest.php index bbe838b..edb0682 100644 --- a/tests/Executor/Promise/SyncPromiseAdapterTest.php +++ b/tests/Executor/Promise/SyncPromiseAdapterTest.php @@ -1,4 +1,7 @@ assertEquals(true, $this->promises->isThenable(new Deferred(function() {}))); + $this->assertEquals( + true, + $this->promises->isThenable(new Deferred(function () { + })) + ); $this->assertEquals(false, $this->promises->isThenable(false)); $this->assertEquals(false, $this->promises->isThenable(true)); $this->assertEquals(false, $this->promises->isThenable(1)); @@ -35,7 +40,8 @@ class SyncPromiseAdapterTest extends TestCase public function testConvert() : void { - $dfd = new Deferred(function() {}); + $dfd = new Deferred(function () { + }); $result = $this->promises->convertThenable($dfd); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result); @@ -48,7 +54,8 @@ class SyncPromiseAdapterTest extends TestCase public function testThen() : void { - $dfd = new Deferred(function() {}); + $dfd = new Deferred(function () { + }); $promise = $this->promises->convertThenable($dfd); $result = $this->promises->then($promise); @@ -59,18 +66,55 @@ class SyncPromiseAdapterTest extends TestCase public function testCreatePromise() : void { - $promise = $this->promises->create(function($resolve, $reject) {}); + $promise = $this->promises->create(function ($resolve, $reject) { + }); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise); $this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise); - $promise = $this->promises->create(function($resolve, $reject) { + $promise = $this->promises->create(function ($resolve, $reject) { $resolve('A'); }); $this->assertValidPromise($promise, null, 'A', SyncPromise::FULFILLED); } + private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState) + { + $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise); + $this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise); + + $actualNextValue = null; + $actualNextReason = null; + $onFulfilledCalled = false; + $onRejectedCalled = false; + + $promise->then( + function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) { + $onFulfilledCalled = true; + $actualNextValue = $nextValue; + }, + function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) { + $onRejectedCalled = true; + $actualNextReason = $reason->getMessage(); + } + ); + + $this->assertSame($onFulfilledCalled, false); + $this->assertSame($onRejectedCalled, false); + + SyncPromise::runQueue(); + + if ($expectedNextState !== SyncPromise::PENDING) { + $this->assertSame(! $expectedNextReason, $onFulfilledCalled); + $this->assertSame(! ! $expectedNextReason, $onRejectedCalled); + } + + $this->assertSame($expectedNextValue, $actualNextValue); + $this->assertSame($expectedNextReason, $actualNextReason); + $this->assertSame($expectedNextState, $promise->adoptedPromise->state); + } + public function testCreateFulfilledPromise() : void { $promise = $this->promises->createFulfilled('test'); @@ -94,8 +138,8 @@ class SyncPromiseAdapterTest extends TestCase $promise1 = new SyncPromise(); $promise2 = new SyncPromise(); $promise3 = $promise2->then( - function($value) { - return $value .'-value3'; + function ($value) { + return $value . '-value3'; } ); @@ -105,7 +149,7 @@ class SyncPromiseAdapterTest extends TestCase new Promise($promise2, $this->promises), 3, new Promise($promise3, $this->promises), - [] + [], ]; $promise = $this->promises->all($data); @@ -114,36 +158,46 @@ class SyncPromiseAdapterTest extends TestCase $promise1->resolve('value1'); $this->assertValidPromise($promise, null, null, SyncPromise::PENDING); $promise2->resolve('value2'); - $this->assertValidPromise($promise, null, ['1', 'value1', 'value2', 3, 'value2-value3', []], SyncPromise::FULFILLED); + $this->assertValidPromise( + $promise, + null, + ['1', 'value1', 'value2', 3, 'value2-value3', []], + SyncPromise::FULFILLED + ); } public function testWait() : void { $called = []; - $deferred1 = new Deferred(function() use (&$called) { + $deferred1 = new Deferred(function () use (&$called) { $called[] = 1; + return 1; }); - $deferred2 = new Deferred(function() use (&$called) { + $deferred2 = new Deferred(function () use (&$called) { $called[] = 2; + return 2; }); $p1 = $this->promises->convertThenable($deferred1); $p2 = $this->promises->convertThenable($deferred2); - $p3 = $p2->then(function() use (&$called) { - $dfd = new Deferred(function() use (&$called) { + $p3 = $p2->then(function () use (&$called) { + $dfd = new Deferred(function () use (&$called) { $called[] = 3; + return 3; }); + return $this->promises->convertThenable($dfd); }); - $p4 = $p3->then(function() use (&$called) { - return new Deferred(function() use (&$called) { + $p4 = $p3->then(function () use (&$called) { + return new Deferred(function () use (&$called) { $called[] = 4; + return 4; }); }); @@ -156,46 +210,10 @@ class SyncPromiseAdapterTest extends TestCase $this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state); $this->assertEquals([1, 2], $called); - $expectedResult = [0,1,2,3,4]; - $result = $this->promises->wait($all); + $expectedResult = [0, 1, 2, 3, 4]; + $result = $this->promises->wait($all); $this->assertEquals($expectedResult, $result); $this->assertEquals([1, 2, 3, 4], $called); - $this->assertValidPromise($all, null, [0,1,2,3,4], SyncPromise::FULFILLED); - } - - private function assertValidPromise($promise, $expectedNextReason, $expectedNextValue, $expectedNextState) - { - $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise); - $this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise); - - $actualNextValue = null; - $actualNextReason = null; - $onFulfilledCalled = false; - $onRejectedCalled = false; - - $promise->then( - function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) { - $onFulfilledCalled = true; - $actualNextValue = $nextValue; - }, - function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) { - $onRejectedCalled = true; - $actualNextReason = $reason->getMessage(); - } - ); - - $this->assertSame($onFulfilledCalled, false); - $this->assertSame($onRejectedCalled, false); - - SyncPromise::runQueue(); - - if ($expectedNextState !== SyncPromise::PENDING) { - $this->assertSame(!$expectedNextReason, $onFulfilledCalled); - $this->assertSame(!!$expectedNextReason, $onRejectedCalled); - } - - $this->assertSame($expectedNextValue, $actualNextValue); - $this->assertSame($expectedNextReason, $actualNextReason); - $this->assertSame($expectedNextState, $promise->adoptedPromise->state); + $this->assertValidPromise($all, null, [0, 1, 2, 3, 4], SyncPromise::FULFILLED); } } diff --git a/tests/Executor/Promise/SyncPromiseTest.php b/tests/Executor/Promise/SyncPromiseTest.php index 6c878aa..fefba45 100644 --- a/tests/Executor/Promise/SyncPromiseTest.php +++ b/tests/Executor/Promise/SyncPromiseTest.php @@ -1,25 +1,32 @@ assertEquals(SyncPromise::PENDING, $promise->state); @@ -63,8 +69,7 @@ class SyncPromiseTest extends TestCase $expectedNextValue, $expectedNextReason, $expectedNextState - ) - { + ) { $promise = new SyncPromise(); $this->assertEquals(SyncPromise::PENDING, $promise->state); @@ -85,21 +90,27 @@ class SyncPromiseTest extends TestCase $expectedNextValue, $expectedNextReason, $expectedNextState - ) - { + ) { $promise = new SyncPromise(); $this->assertEquals(SyncPromise::PENDING, $promise->state); $promise->resolve($resolvedValue); $this->assertEquals(SyncPromise::FULFILLED, $promise->state); - $nextPromise = $promise->then(null, function() {}); + $nextPromise = $promise->then( + null, + function () { + } + ); $this->assertSame($promise, $nextPromise); $onRejectedCalled = false; - $nextPromise = $promise->then($onFulfilled, function () use (&$onRejectedCalled) { - $onRejectedCalled = true; - }); + $nextPromise = $promise->then( + $onFulfilled, + function () use (&$onRejectedCalled) { + $onRejectedCalled = true; + } + ); if ($onFulfilled) { $this->assertNotSame($promise, $nextPromise); @@ -124,19 +135,57 @@ class SyncPromiseTest extends TestCase $this->assertValidPromise($nextPromise3, $expectedNextReason, $expectedNextValue, $expectedNextState); } + private function assertValidPromise( + SyncPromise $promise, + $expectedNextReason, + $expectedNextValue, + $expectedNextState + ) { + $actualNextValue = null; + $actualNextReason = null; + $onFulfilledCalled = false; + $onRejectedCalled = false; + + $promise->then( + function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) { + $onFulfilledCalled = true; + $actualNextValue = $nextValue; + }, + function (\Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) { + $onRejectedCalled = true; + $actualNextReason = $reason->getMessage(); + } + ); + + $this->assertEquals($onFulfilledCalled, false); + $this->assertEquals($onRejectedCalled, false); + + SyncPromise::runQueue(); + + $this->assertEquals(! $expectedNextReason, $onFulfilledCalled); + $this->assertEquals(! ! $expectedNextReason, $onRejectedCalled); + + $this->assertEquals($expectedNextValue, $actualNextValue); + $this->assertEquals($expectedNextReason, $actualNextReason); + $this->assertEquals($expectedNextState, $promise->state); + } + public function getRejectedPromiseData() { - $onRejectedReturnsNull = function() { + $onRejectedReturnsNull = function () { return null; }; - $onRejectedReturnsSomeValue = function($reason) { + + $onRejectedReturnsSomeValue = function ($reason) { return 'some-value'; }; - $onRejectedThrowsSameReason = function($reason) { + + $onRejectedThrowsSameReason = function ($reason) { throw $reason; }; - $onRejectedThrowsOtherReason = function($value) { - throw new \Exception("onRejected throws other!"); + + $onRejectedThrowsOtherReason = function ($value) { + throw new \Exception('onRejected throws other!'); }; return [ @@ -158,8 +207,7 @@ class SyncPromiseTest extends TestCase $expectedNextValue, $expectedNextReason, $expectedNextState - ) - { + ) { $promise = new SyncPromise(); $this->assertEquals(SyncPromise::PENDING, $promise->state); @@ -169,7 +217,6 @@ class SyncPromiseTest extends TestCase $this->expectException(\Throwable::class); $this->expectExceptionMessage('Cannot change rejection reason'); $promise->reject(new \Exception('other-reason')); - } /** @@ -181,8 +228,7 @@ class SyncPromiseTest extends TestCase $expectedNextValue, $expectedNextReason, $expectedNextState - ) - { + ) { $promise = new SyncPromise(); $this->assertEquals(SyncPromise::PENDING, $promise->state); @@ -203,8 +249,7 @@ class SyncPromiseTest extends TestCase $expectedNextValue, $expectedNextReason, $expectedNextState - ) - { + ) { $promise = new SyncPromise(); $this->assertEquals(SyncPromise::PENDING, $promise->state); @@ -214,22 +259,26 @@ class SyncPromiseTest extends TestCase try { $promise->reject(new \Exception('other-reason')); $this->fail('Expected exception not thrown'); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->assertEquals('Cannot change rejection reason', $e->getMessage()); } try { $promise->resolve('anything'); $this->fail('Expected exception not thrown'); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->assertEquals('Cannot resolve rejected promise', $e->getMessage()); } - $nextPromise = $promise->then(function() {}, null); + $nextPromise = $promise->then( + function () { + }, + null + ); $this->assertSame($promise, $nextPromise); $onFulfilledCalled = false; - $nextPromise = $promise->then( + $nextPromise = $promise->then( function () use (&$onFulfilledCalled) { $onFulfilledCalled = true; }, @@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase try { $promise->resolve($promise); $this->fail('Expected exception not thrown'); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->assertEquals('Cannot resolve promise with self', $e->getMessage()); $this->assertEquals(SyncPromise::PENDING, $promise->state); } @@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase throw $e; } catch (\Throwable $e) { $this->assertEquals(SyncPromise::PENDING, $promise->state); - } catch (\Exception $e) { - $this->assertEquals(SyncPromise::PENDING, $promise->state); } - $promise->reject(new \Exception("Rejected Reason")); - $this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED); + $promise->reject(new \Exception('Rejected Reason')); + $this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED); - $promise = new SyncPromise(); - $promise2 = $promise->then(null, function() { - return 'value'; - }); - $promise->reject(new \Exception("Rejected Again")); + $promise = new SyncPromise(); + $promise2 = $promise->then( + null, + function () { + return 'value'; + } + ); + $promise->reject(new \Exception('Rejected Again')); $this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED); - $promise = new SyncPromise(); + $promise = new SyncPromise(); $promise2 = $promise->then(); - $promise->reject(new \Exception("Rejected Once Again")); - $this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED); + $promise->reject(new \Exception('Rejected Once Again')); + $this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED); } public function testPendingPromiseThen() : void @@ -331,12 +381,14 @@ class SyncPromiseTest extends TestCase // Make sure that it queues derivative promises until resolution: $onFulfilledCount = 0; - $onRejectedCount = 0; - $onFulfilled = function($value) use (&$onFulfilledCount) { + $onRejectedCount = 0; + $onFulfilled = function ($value) use (&$onFulfilledCount) { $onFulfilledCount++; + return $onFulfilledCount; }; - $onRejected = function($reason) use (&$onRejectedCount) { + + $onRejected = function ($reason) use (&$onRejectedCount) { $onRejectedCount++; throw $reason; }; @@ -367,35 +419,4 @@ class SyncPromiseTest extends TestCase $this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED); $this->assertValidPromise($nextPromise4, null, 3, SyncPromise::FULFILLED); } - - private function assertValidPromise(SyncPromise $promise, $expectedNextReason, $expectedNextValue, $expectedNextState) - { - $actualNextValue = null; - $actualNextReason = null; - $onFulfilledCalled = false; - $onRejectedCalled = false; - - $promise->then( - function($nextValue) use (&$actualNextValue, &$onFulfilledCalled) { - $onFulfilledCalled = true; - $actualNextValue = $nextValue; - }, - function(\Exception $reason) use (&$actualNextReason, &$onRejectedCalled) { - $onRejectedCalled = true; - $actualNextReason = $reason->getMessage(); - } - ); - - $this->assertEquals($onFulfilledCalled, false); - $this->assertEquals($onRejectedCalled, false); - - SyncPromise::runQueue(); - - $this->assertEquals(!$expectedNextReason, $onFulfilledCalled); - $this->assertEquals(!!$expectedNextReason, $onRejectedCalled); - - $this->assertEquals($expectedNextValue, $actualNextValue); - $this->assertEquals($expectedNextReason, $actualNextReason); - $this->assertEquals($expectedNextState, $promise->state); - } } diff --git a/tests/Executor/ResolveTest.php b/tests/Executor/ResolveTest.php index 0d05e53..f838695 100644 --- a/tests/Executor/ResolveTest.php +++ b/tests/Executor/ResolveTest.php @@ -1,30 +1,22 @@ new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'test' => $testField - ] - ]) - ]); - } - /** * @see it('default function accesses properties') */ @@ -32,9 +24,7 @@ class ResolveTest extends TestCase { $schema = $this->buildSchema(['type' => Type::string()]); - $source = [ - 'test' => 'testValue' - ]; + $source = ['test' => 'testValue']; $this->assertEquals( ['data' => ['test' => 'testValue']], @@ -42,18 +32,28 @@ class ResolveTest extends TestCase ); } + private function buildSchema($testField) + { + return new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => ['test' => $testField], + ]), + ]); + } + /** * @see it('default function calls methods') */ public function testDefaultFunctionCallsClosures() : void { - $schema = $this->buildSchema(['type' => Type::string()]); + $schema = $this->buildSchema(['type' => Type::string()]); $_secret = 'secretValue' . uniqid(); $source = [ - 'test' => function() use ($_secret) { + 'test' => function () use ($_secret) { return $_secret; - } + }, ]; $this->assertEquals( ['data' => ['test' => $_secret]], @@ -69,7 +69,7 @@ class ResolveTest extends TestCase $schema = $this->buildSchema([ 'type' => Type::int(), 'args' => [ - 'addend1' => [ 'type' => Type::int() ], + 'addend1' => ['type' => Type::int()], ], ]); @@ -85,14 +85,14 @@ class ResolveTest extends TestCase public function testUsesProvidedResolveFunction() : void { $schema = $this->buildSchema([ - 'type' => Type::string(), - 'args' => [ + 'type' => Type::string(), + 'args' => [ 'aStr' => ['type' => Type::string()], 'aInt' => ['type' => Type::int()], ], 'resolve' => function ($source, $args) { return json_encode([$source, $args]); - } + }, ]); $this->assertEquals( diff --git a/tests/Executor/SyncTest.php b/tests/Executor/SyncTest.php index 3c1c989..a0ccbaa 100644 --- a/tests/Executor/SyncTest.php +++ b/tests/Executor/SyncTest.php @@ -1,8 +1,10 @@ schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Query', + 'query' => new ObjectType([ + 'name' => 'Query', 'fields' => [ - 'syncField' => [ - 'type' => Type::string(), + 'syncField' => [ + 'type' => Type::string(), 'resolve' => function ($rootValue) { return $rootValue; - } + }, ], 'asyncField' => [ - 'type' => Type::string(), + 'type' => Type::string(), 'resolve' => function ($rootValue) { return new Deferred(function () use ($rootValue) { return $rootValue; }); - } - ] - ] + }, + ], + ], ]), 'mutation' => new ObjectType([ - 'name' => 'Mutation', + 'name' => 'Mutation', 'fields' => [ 'syncMutationField' => [ - 'type' => Type::string(), + 'type' => Type::string(), 'resolve' => function ($rootValue) { return $rootValue; - } - ] - ] - ]) + }, + ], + ], + ]), ]); + $this->promiseAdapter = new SyncPromiseAdapter(); } @@ -70,7 +73,7 @@ class SyncTest extends TestCase */ public function testDoesNotReturnAPromiseForInitialErrors() : void { - $doc = 'fragment Example on Query { syncField }'; + $doc = 'fragment Example on Query { syncField }'; $result = $this->execute( $this->schema, Parser::parse($doc), @@ -79,106 +82,6 @@ class SyncTest extends TestCase $this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result); } - /** - * @see it('does not return a Promise if fields are all synchronous') - */ - public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void - { - $doc = 'query Example { syncField }'; - $result = $this->execute( - $this->schema, - Parser::parse($doc), - 'rootValue' - ); - $this->assertSync(['data' => ['syncField' => 'rootValue']], $result); - } - - /** - * @see it('does not return a Promise if mutation fields are all synchronous') - */ - public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void - { - $doc = 'mutation Example { syncMutationField }'; - $result = $this->execute( - $this->schema, - Parser::parse($doc), - 'rootValue' - ); - $this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result); - } - - /** - * @see it('returns a Promise if any field is asynchronous') - */ - public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void - { - $doc = 'query Example { syncField, asyncField }'; - $result = $this->execute( - $this->schema, - Parser::parse($doc), - 'rootValue' - ); - $this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result); - } - - // Describe: graphqlSync - - /** - * @see it('does not return a Promise for syntax errors') - */ - public function testDoesNotReturnAPromiseForSyntaxErrors() : void - { - $doc = 'fragment Example on Query { { { syncField }'; - $result = $this->graphqlSync( - $this->schema, - $doc - ); - $this->assertSync([ - 'errors' => [ - ['message' => 'Syntax Error: Expected Name, found {', - 'locations' => [['line' => 1, 'column' => 29]]] - ] - ], $result); - } - - /** - * @see it('does not return a Promise for validation errors') - */ - public function testDoesNotReturnAPromiseForValidationErrors() : void - { - $doc = 'fragment Example on Query { unknownField }'; - $validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc)); - $result = $this->graphqlSync( - $this->schema, - $doc - ); - $expected = [ - 'errors' => Utils::map($validationErrors, function ($e) { - return FormattedError::createFromException($e); - }) - ]; - $this->assertSync($expected, $result); - } - - /** - * @see it('does not return a Promise for sync execution') - */ - public function testDoesNotReturnAPromiseForSyncExecution() : void - { - $doc = 'query Example { syncField }'; - $result = $this->graphqlSync( - $this->schema, - $doc, - 'rootValue' - ); - $this->assertSync(['data' => ['syncField' => 'rootValue']], $result); - } - - private function graphqlSync($schema, $doc, $rootValue = null) - { - return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue); - } - private function execute($schema, $doc, $rootValue = null) { return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue); @@ -191,7 +94,56 @@ class SyncTest extends TestCase $this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message); $this->assertEquals(SyncPromise::FULFILLED, $actualResult->adoptedPromise->state, $message); $this->assertInstanceOf(ExecutionResult::class, $actualResult->adoptedPromise->result, $message); - $this->assertArraySubset($expectedFinalArray, $actualResult->adoptedPromise->result->toArray(), $message); + $this->assertArraySubset( + $expectedFinalArray, + $actualResult->adoptedPromise->result->toArray(), + false, + $message + ); + } + + /** + * @see it('does not return a Promise if fields are all synchronous') + */ + public function testDoesNotReturnAPromiseIfFieldsAreAllSynchronous() : void + { + $doc = 'query Example { syncField }'; + $result = $this->execute( + $this->schema, + Parser::parse($doc), + 'rootValue' + ); + $this->assertSync(['data' => ['syncField' => 'rootValue']], $result); + } + + // Describe: graphqlSync + + /** + * @see it('does not return a Promise if mutation fields are all synchronous') + */ + public function testDoesNotReturnAPromiseIfMutationFieldsAreAllSynchronous() : void + { + $doc = 'mutation Example { syncMutationField }'; + $result = $this->execute( + $this->schema, + Parser::parse($doc), + 'rootValue' + ); + $this->assertSync(['data' => ['syncMutationField' => 'rootValue']], $result); + } + + /** + * @see it('returns a Promise if any field is asynchronous') + */ + public function testReturnsAPromiseIfAnyFieldIsAsynchronous() : void + { + $doc = 'query Example { syncField, asyncField }'; + $result = $this->execute( + $this->schema, + Parser::parse($doc), + 'rootValue' + ); + $this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result); } private function assertAsync($expectedFinalArray, $actualResult) @@ -202,6 +154,70 @@ class SyncTest extends TestCase $this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message); $resolvedResult = $this->promiseAdapter->wait($actualResult); $this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message); - $this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), $message); + $this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), false, $message); + } + + /** + * @see it('does not return a Promise for syntax errors') + */ + public function testDoesNotReturnAPromiseForSyntaxErrors() : void + { + $doc = 'fragment Example on Query { { { syncField }'; + $result = $this->graphqlSync( + $this->schema, + $doc + ); + $this->assertSync( + [ + 'errors' => [ + [ + 'message' => 'Syntax Error: Expected Name, found {', + 'locations' => [['line' => 1, 'column' => 29]], + ], + ], + ], + $result + ); + } + + private function graphqlSync($schema, $doc, $rootValue = null) + { + return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue); + } + + /** + * @see it('does not return a Promise for validation errors') + */ + public function testDoesNotReturnAPromiseForValidationErrors() : void + { + $doc = 'fragment Example on Query { unknownField }'; + $validationErrors = DocumentValidator::validate($this->schema, Parser::parse($doc)); + $result = $this->graphqlSync( + $this->schema, + $doc + ); + $expected = [ + 'errors' => Utils::map( + $validationErrors, + function ($e) { + return FormattedError::createFromException($e); + } + ), + ]; + $this->assertSync($expected, $result); + } + + /** + * @see it('does not return a Promise for sync execution') + */ + public function testDoesNotReturnAPromiseForSyncExecution() : void + { + $doc = 'query Example { syncField }'; + $result = $this->graphqlSync( + $this->schema, + $doc, + 'rootValue' + ); + $this->assertSync(['data' => ['syncField' => 'rootValue']], $result); } } diff --git a/tests/Executor/TestClasses.php b/tests/Executor/TestClasses.php deleted file mode 100644 index 0ae6ca6..0000000 --- a/tests/Executor/TestClasses.php +++ /dev/null @@ -1,128 +0,0 @@ -name = $name; - $this->woofs = $woofs; - } -} - -class Cat -{ - function __construct($name, $meows) - { - $this->name = $name; - $this->meows = $meows; - } -} - -class Human -{ - function __construct($name) - { - $this->name = $name; - } -} - -class Person -{ - public $name; - public $pets; - public $friends; - - function __construct($name, $pets = null, $friends = null) - { - $this->name = $name; - $this->pets = $pets; - $this->friends = $friends; - } -} - -class ComplexScalar extends ScalarType -{ - public static function create() - { - return new self(); - } - - public $name = 'ComplexScalar'; - - /** - * {@inheritdoc} - */ - public function serialize($value) - { - if ($value === 'DeserializedValue') { - return 'SerializedValue'; - } - - throw new Error("Cannot serialize value as ComplexScalar: " . Utils::printSafe($value)); - } - - /** - * {@inheritdoc} - */ - public function parseValue($value) - { - if ($value === 'SerializedValue') { - return 'DeserializedValue'; - } - - throw new Error("Cannot represent value as ComplexScalar: " . Utils::printSafe($value)); - } - - /** - * {@inheritdoc} - */ - public function parseLiteral($valueNode, array $variables = null) - { - if ($valueNode->value === 'SerializedValue') { - return 'DeserializedValue'; - } - - throw new Error("Cannot represent literal as ComplexScalar: " . Utils::printSafe($valueNode->value)); - } -} - -class Special -{ - public $value; - - public function __construct($value) - { - $this->value = $value; - } -} - -class NotSpecial -{ - public $value; - - public function __construct($value) - { - $this->value = $value; - } -} - -class Adder -{ - public $num; - - public $test; - - public function __construct($num) - { - $this->num = $num; - - $this->test = function($source, $args, $context) { - return $this->num + $args['addend1'] + $context['addend2']; - }; - } -} diff --git a/tests/Executor/TestClasses/Adder.php b/tests/Executor/TestClasses/Adder.php new file mode 100644 index 0000000..b086b51 --- /dev/null +++ b/tests/Executor/TestClasses/Adder.php @@ -0,0 +1,23 @@ +num = $num; + + $this->test = function ($source, $args, $context) { + return $this->num + $args['addend1'] + $context['addend2']; + }; + } +} diff --git a/tests/Executor/TestClasses/Cat.php b/tests/Executor/TestClasses/Cat.php new file mode 100644 index 0000000..500e6a9 --- /dev/null +++ b/tests/Executor/TestClasses/Cat.php @@ -0,0 +1,20 @@ +name = $name; + $this->meows = $meows; + } +} diff --git a/tests/Executor/TestClasses/ComplexScalar.php b/tests/Executor/TestClasses/ComplexScalar.php new file mode 100644 index 0000000..91197a3 --- /dev/null +++ b/tests/Executor/TestClasses/ComplexScalar.php @@ -0,0 +1,56 @@ +value === 'SerializedValue') { + return 'DeserializedValue'; + } + + throw new Error('Cannot represent literal as ComplexScalar: ' . Utils::printSafe($valueNode->value)); + } +} diff --git a/tests/Executor/TestClasses/Dog.php b/tests/Executor/TestClasses/Dog.php new file mode 100644 index 0000000..46861c3 --- /dev/null +++ b/tests/Executor/TestClasses/Dog.php @@ -0,0 +1,20 @@ +name = $name; + $this->woofs = $woofs; + } +} diff --git a/tests/Executor/TestClasses/Human.php b/tests/Executor/TestClasses/Human.php new file mode 100644 index 0000000..b4ae7d8 --- /dev/null +++ b/tests/Executor/TestClasses/Human.php @@ -0,0 +1,16 @@ +name = $name; + } +} diff --git a/tests/Executor/TestClasses/NotSpecial.php b/tests/Executor/TestClasses/NotSpecial.php new file mode 100644 index 0000000..b54f991 --- /dev/null +++ b/tests/Executor/TestClasses/NotSpecial.php @@ -0,0 +1,19 @@ +value = $value; + } +} diff --git a/tests/Executor/TestClasses/NumberHolder.php b/tests/Executor/TestClasses/NumberHolder.php new file mode 100644 index 0000000..72d0072 --- /dev/null +++ b/tests/Executor/TestClasses/NumberHolder.php @@ -0,0 +1,16 @@ +theNumber = $originalNumber; + } +} diff --git a/tests/Executor/TestClasses/Person.php b/tests/Executor/TestClasses/Person.php new file mode 100644 index 0000000..210c625 --- /dev/null +++ b/tests/Executor/TestClasses/Person.php @@ -0,0 +1,28 @@ +name = $name; + $this->pets = $pets; + $this->friends = $friends; + } +} diff --git a/tests/Executor/TestClasses/Root.php b/tests/Executor/TestClasses/Root.php new file mode 100644 index 0000000..812db16 --- /dev/null +++ b/tests/Executor/TestClasses/Root.php @@ -0,0 +1,44 @@ +numberHolder = new NumberHolder($originalNumber); + } + + public function promiseToChangeTheNumber($newNumber) : Deferred + { + return new Deferred(function () use ($newNumber) { + return $this->immediatelyChangeTheNumber($newNumber); + }); + } + + public function immediatelyChangeTheNumber($newNumber) : NumberHolder + { + $this->numberHolder->theNumber = $newNumber; + + return $this->numberHolder; + } + + public function failToChangeTheNumber() : void + { + throw new \Exception('Cannot change the number'); + } + + public function promiseAndFailToChangeTheNumber() : Deferred + { + return new Deferred(function () { + $this->failToChangeTheNumber(); + }); + } +} diff --git a/tests/Executor/TestClasses/Special.php b/tests/Executor/TestClasses/Special.php new file mode 100644 index 0000000..e8293d0 --- /dev/null +++ b/tests/Executor/TestClasses/Special.php @@ -0,0 +1,19 @@ +value = $value; + } +} diff --git a/tests/Executor/UnionInterfaceTest.php b/tests/Executor/UnionInterfaceTest.php index 4d1d934..3e21692 100644 --- a/tests/Executor/UnionInterfaceTest.php +++ b/tests/Executor/UnionInterfaceTest.php @@ -1,64 +1,76 @@ 'Named', + 'name' => 'Named', 'fields' => [ - 'name' => ['type' => Type::string()] - ] + 'name' => ['type' => Type::string()], + ], ]); $DogType = new ObjectType([ - 'name' => 'Dog', + 'name' => 'Dog', 'interfaces' => [$NamedType], - 'fields' => [ - 'name' => ['type' => Type::string()], - 'woofs' => ['type' => Type::boolean()] + 'fields' => [ + 'name' => ['type' => Type::string()], + 'woofs' => ['type' => Type::boolean()], ], - 'isTypeOf' => function ($value) { + 'isTypeOf' => function ($value) { return $value instanceof Dog; - } + }, ]); $CatType = new ObjectType([ - 'name' => 'Cat', + 'name' => 'Cat', 'interfaces' => [$NamedType], - 'fields' => [ - 'name' => ['type' => Type::string()], - 'meows' => ['type' => Type::boolean()] + 'fields' => [ + 'name' => ['type' => Type::string()], + 'meows' => ['type' => Type::boolean()], ], - 'isTypeOf' => function ($value) { + 'isTypeOf' => function ($value) { return $value instanceof Cat; - } + }, ]); $PetType = new UnionType([ - 'name' => 'Pet', - 'types' => [$DogType, $CatType], + 'name' => 'Pet', + 'types' => [$DogType, $CatType], 'resolveType' => function ($value) use ($DogType, $CatType) { if ($value instanceof Dog) { return $DogType; @@ -66,32 +78,31 @@ class UnionInterfaceTest extends TestCase if ($value instanceof Cat) { return $CatType; } - } + }, ]); $PersonType = new ObjectType([ - 'name' => 'Person', + 'name' => 'Person', 'interfaces' => [$NamedType], - 'fields' => [ - 'name' => ['type' => Type::string()], - 'pets' => ['type' => Type::listOf($PetType)], - 'friends' => ['type' => Type::listOf($NamedType)] + 'fields' => [ + 'name' => ['type' => Type::string()], + 'pets' => ['type' => Type::listOf($PetType)], + 'friends' => ['type' => Type::listOf($NamedType)], ], - 'isTypeOf' => function ($value) { + 'isTypeOf' => function ($value) { return $value instanceof Person; - } + }, ]); $this->schema = new Schema([ 'query' => $PersonType, - 'types' => [ $PetType ] + 'types' => [$PetType], ]); $this->garfield = new Cat('Garfield', false); - $this->odie = new Dog('Odie', true); - $this->liz = new Person('Liz'); - $this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]); - + $this->odie = new Dog('Odie', true); + $this->liz = new Person('Liz'); + $this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]); } // Execute: Union and intersection types @@ -101,7 +112,6 @@ class UnionInterfaceTest extends TestCase */ public function testCanIntrospectOnUnionAndIntersectionTypes() : void { - $ast = Parser::parse(' { Named: __type(name: "Named") { @@ -128,33 +138,33 @@ class UnionInterfaceTest extends TestCase $expected = [ 'data' => [ 'Named' => [ - 'kind' => 'INTERFACE', - 'name' => 'Named', - 'fields' => [ - ['name' => 'name'] + 'kind' => 'INTERFACE', + 'name' => 'Named', + 'fields' => [ + ['name' => 'name'], ], - 'interfaces' => null, + 'interfaces' => null, 'possibleTypes' => [ ['name' => 'Person'], ['name' => 'Dog'], - ['name' => 'Cat'] + ['name' => 'Cat'], ], - 'enumValues' => null, - 'inputFields' => null + 'enumValues' => null, + 'inputFields' => null, ], - 'Pet' => [ - 'kind' => 'UNION', - 'name' => 'Pet', - 'fields' => null, - 'interfaces' => null, + 'Pet' => [ + 'kind' => 'UNION', + 'name' => 'Pet', + 'fields' => null, + 'interfaces' => null, 'possibleTypes' => [ ['name' => 'Dog'], - ['name' => 'Cat'] + ['name' => 'Cat'], ], - 'enumValues' => null, - 'inputFields' => null - ] - ] + 'enumValues' => null, + 'inputFields' => null, + ], + ], ]; $this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray()); } @@ -165,7 +175,7 @@ class UnionInterfaceTest extends TestCase public function testExecutesUsingUnionTypes() : void { // NOTE: This is an *invalid* query, but it should be an *executable* query. - $ast = Parser::parse(' + $ast = Parser::parse(' { __typename name @@ -180,12 +190,12 @@ class UnionInterfaceTest extends TestCase $expected = [ 'data' => [ '__typename' => 'Person', - 'name' => 'John', - 'pets' => [ + 'name' => 'John', + 'pets' => [ ['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], - ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] - ] - ] + ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true], + ], + ], ]; $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); @@ -197,7 +207,7 @@ class UnionInterfaceTest extends TestCase public function testExecutesUnionTypesWithInlineFragments() : void { // This is the valid version of the query in the above test. - $ast = Parser::parse(' + $ast = Parser::parse(' { __typename name @@ -217,13 +227,13 @@ class UnionInterfaceTest extends TestCase $expected = [ 'data' => [ '__typename' => 'Person', - 'name' => 'John', - 'pets' => [ + 'name' => 'John', + 'pets' => [ ['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], - ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] - ] + ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true], + ], - ] + ], ]; $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); } @@ -234,7 +244,7 @@ class UnionInterfaceTest extends TestCase public function testExecutesUsingInterfaceTypes() : void { // NOTE: This is an *invalid* query, but it should be an *executable* query. - $ast = Parser::parse(' + $ast = Parser::parse(' { __typename name @@ -249,12 +259,12 @@ class UnionInterfaceTest extends TestCase $expected = [ 'data' => [ '__typename' => 'Person', - 'name' => 'John', - 'friends' => [ + 'name' => 'John', + 'friends' => [ ['__typename' => 'Person', 'name' => 'Liz'], - ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] - ] - ] + ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true], + ], + ], ]; $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); @@ -266,7 +276,7 @@ class UnionInterfaceTest extends TestCase public function testExecutesInterfaceTypesWithInlineFragments() : void { // This is the valid version of the query in the above test. - $ast = Parser::parse(' + $ast = Parser::parse(' { __typename name @@ -285,12 +295,12 @@ class UnionInterfaceTest extends TestCase $expected = [ 'data' => [ '__typename' => 'Person', - 'name' => 'John', - 'friends' => [ + 'name' => 'John', + 'friends' => [ ['__typename' => 'Person', 'name' => 'Liz'], - ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] - ] - ] + ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true], + ], + ], ]; $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true)); @@ -336,16 +346,16 @@ class UnionInterfaceTest extends TestCase $expected = [ 'data' => [ '__typename' => 'Person', - 'name' => 'John', - 'pets' => [ + 'name' => 'John', + 'pets' => [ ['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], - ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] + ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true], ], - 'friends' => [ + 'friends' => [ ['__typename' => 'Person', 'name' => 'Liz'], - ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true] - ] - ] + ['__typename' => 'Dog', 'name' => 'Odie', 'woofs' => true], + ], + ], ]; $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); @@ -356,36 +366,45 @@ class UnionInterfaceTest extends TestCase */ public function testGetsExecutionInfoInResolver() : void { - $encounteredContext = null; - $encounteredSchema = null; + $encounteredContext = null; + $encounteredSchema = null; $encounteredRootValue = null; - $PersonType2 = null; + $PersonType2 = null; $NamedType2 = new InterfaceType([ - 'name' => 'Named', - 'fields' => [ - 'name' => ['type' => Type::string()] + 'name' => 'Named', + 'fields' => [ + 'name' => ['type' => Type::string()], ], - 'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) { - $encounteredContext = $context; - $encounteredSchema = $info->schema; + 'resolveType' => function ( + $obj, + $context, + ResolveInfo $info + ) use ( + &$encounteredContext, + & + $encounteredSchema, + &$encounteredRootValue, + &$PersonType2 + ) { + $encounteredContext = $context; + $encounteredSchema = $info->schema; $encounteredRootValue = $info->rootValue; + return $PersonType2; - } + }, ]); $PersonType2 = new ObjectType([ - 'name' => 'Person', + 'name' => 'Person', 'interfaces' => [$NamedType2], - 'fields' => [ - 'name' => ['type' => Type::string()], + 'fields' => [ + 'name' => ['type' => Type::string()], 'friends' => ['type' => Type::listOf($NamedType2)], ], ]); - $schema2 = new Schema([ - 'query' => $PersonType2 - ]); + $schema2 = new Schema(['query' => $PersonType2]); $john2 = new Person('John', [], [$this->liz]); diff --git a/tests/Executor/ValuesTest.php b/tests/Executor/ValuesTest.php index 4d7bf2d..f2713e7 100644 --- a/tests/Executor/ValuesTest.php +++ b/tests/Executor/ValuesTest.php @@ -1,4 +1,7 @@ expectInputVariablesMatchOutputVariables(['idInput' => '123456789']); $this->assertEquals( - ['errors'=> [], 'coerced' => ['idInput' => '123456789']], - self::runTestCase(['idInput' => 123456789]), + ['errors' => [], 'coerced' => ['idInput' => '123456789']], + $this->runTestCase(['idInput' => 123456789]), 'Integer ID was not converted to string' ); } + private function expectInputVariablesMatchOutputVariables($variables) : void + { + $this->assertEquals( + $variables, + $this->runTestCase($variables)['coerced'], + 'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL + ); + } + + /** + * @param mixed[] $variables + * @return mixed[] + */ + private function runTestCase($variables) : array + { + return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables); + } + + private static function getSchema() : Schema + { + if (! self::$schema) { + self::$schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'test' => [ + 'type' => Type::boolean(), + 'args' => [ + 'idInput' => Type::id(), + 'boolInput' => Type::boolean(), + 'intInput' => Type::int(), + 'stringInput' => Type::string(), + 'floatInput' => Type::float(), + ], + ], + ], + ]), + ]); + } + + return self::$schema; + } + + /** + * @return VariableDefinitionNode[] + */ + private static function getVariableDefinitionNodes() : array + { + $idInputDefinition = new VariableDefinitionNode([ + 'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]), + 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])]), + ]); + $boolInputDefinition = new VariableDefinitionNode([ + 'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]), + 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])]), + ]); + $intInputDefinition = new VariableDefinitionNode([ + 'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]), + 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])]), + ]); + $stringInputDefintion = new VariableDefinitionNode([ + 'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]), + 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])]), + ]); + $floatInputDefinition = new VariableDefinitionNode([ + 'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]), + 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])]), + ]); + + return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition]; + } + public function testGetBooleanVariableValues() : void { $this->expectInputVariablesMatchOutputVariables(['boolInput' => true]); @@ -64,11 +145,20 @@ class ValuesTest extends TestCase $this->expectGraphQLError(['idInput' => true]); } + private function expectGraphQLError($variables) : void + { + $result = $this->runTestCase($variables); + $this->assertGreaterThan(0, count($result['errors'])); + } + public function testFloatForIDVariableThrowsError() : void { $this->expectGraphQLError(['idInput' => 1.0]); } + /** + * Helpers for running test cases and making assertions + */ public function testStringForBooleanVariableThrowsError() : void { $this->expectGraphQLError(['boolInput' => 'true']); @@ -98,77 +188,4 @@ class ValuesTest extends TestCase { $this->expectGraphQLError(['intInput' => -2147483649]); } - - // Helpers for running test cases and making assertions - - private function expectInputVariablesMatchOutputVariables($variables) - { - $this->assertEquals( - $variables, - self::runTestCase($variables)['coerced'], - 'Output variables did not match input variables' . PHP_EOL . var_export($variables, true) . PHP_EOL - ); - } - - private function expectGraphQLError($variables) - { - $result = self::runTestCase($variables); - $this->assertGreaterThan(0, count($result['errors'])); - } - - private static $schema; - - private static function getSchema() - { - if (!self::$schema) { - self::$schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'test' => [ - 'type' => Type::boolean(), - 'args' => [ - 'idInput' => Type::id(), - 'boolInput' => Type::boolean(), - 'intInput' => Type::int(), - 'stringInput' => Type::string(), - 'floatInput' => Type::float() - ] - ], - ] - ]) - ]); - } - return self::$schema; - } - - private static function getVariableDefinitionNodes() - { - $idInputDefinition = new VariableDefinitionNode([ - 'variable' => new VariableNode(['name' => new NameNode(['value' => 'idInput'])]), - 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'ID'])]) - ]); - $boolInputDefinition = new VariableDefinitionNode([ - 'variable' => new VariableNode(['name' => new NameNode(['value' => 'boolInput'])]), - 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Boolean'])]) - ]); - $intInputDefinition = new VariableDefinitionNode([ - 'variable' => new VariableNode(['name' => new NameNode(['value' => 'intInput'])]), - 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Int'])]) - ]); - $stringInputDefintion = new VariableDefinitionNode([ - 'variable' => new VariableNode(['name' => new NameNode(['value' => 'stringInput'])]), - 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'String'])]) - ]); - $floatInputDefinition = new VariableDefinitionNode([ - 'variable' => new VariableNode(['name' => new NameNode(['value' => 'floatInput'])]), - 'type' => new NamedTypeNode(['name' => new NameNode(['value' => 'Float'])]) - ]); - return [$idInputDefinition, $boolInputDefinition, $intInputDefinition, $stringInputDefintion, $floatInputDefinition]; - } - - private function runTestCase($variables) - { - return Values::getVariableValues(self::getSchema(), self::getVariableDefinitionNodes(), $variables); - } } diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 89392d2..5f374ad 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -1,16 +1,19 @@ [ - 'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}' - ] + 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'], ]; $this->assertEquals($expected, $result->toArray()); // properly parses single value to list: - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) } @@ -46,7 +46,7 @@ class VariablesTest extends TestCase $this->assertEquals($expected, $result->toArray()); // properly parses null value to null - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null}) } @@ -56,7 +56,7 @@ class VariablesTest extends TestCase $this->assertEquals($expected, $result->toArray()); // properly parses null value in list - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"}) } @@ -73,12 +73,13 @@ class VariablesTest extends TestCase '); $expected = [ - 'data' => ['fieldWithObjectInput' => null], + 'data' => ['fieldWithObjectInput' => null], 'errors' => [[ - 'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].', - 'path' => ['fieldWithObjectInput'], - 'locations' => [['line' => 3, 'column' => 39]] - ]] + 'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].', + 'path' => ['fieldWithObjectInput'], + 'locations' => [['line' => 3, 'column' => 39]], + ], + ], ]; $this->assertArraySubset($expected, $result->toArray()); @@ -94,6 +95,77 @@ class VariablesTest extends TestCase ); } + private function executeQuery($query, $variableValues = null) + { + $document = Parser::parse($query); + + return Executor::execute($this->schema(), $document, null, null, $variableValues); + } + + /** + * Describe: Handles nullable scalars + */ + public function schema() : Schema + { + $ComplexScalarType = ComplexScalar::create(); + + $TestInputObject = new InputObjectType([ + 'name' => 'TestInputObject', + 'fields' => [ + 'a' => ['type' => Type::string()], + 'b' => ['type' => Type::listOf(Type::string())], + 'c' => ['type' => Type::nonNull(Type::string())], + 'd' => ['type' => $ComplexScalarType], + ], + ]); + + $TestNestedInputObject = new InputObjectType([ + 'name' => 'TestNestedInputObject', + 'fields' => [ + 'na' => ['type' => Type::nonNull($TestInputObject)], + 'nb' => ['type' => Type::nonNull(Type::string())], + ], + ]); + + $TestType = new ObjectType([ + 'name' => 'TestType', + 'fields' => [ + 'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]), + 'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]), + 'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]), + 'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([ + 'type' => Type::string(), + 'defaultValue' => 'Hello World', + ]), + 'fieldWithNestedInputObject' => $this->fieldWithInputArg([ + 'type' => $TestNestedInputObject, + 'defaultValue' => 'Hello World', + ]), + 'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]), + 'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]), + 'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]), + 'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]), + ], + ]); + + return new Schema(['query' => $TestType]); + } + + private function fieldWithInputArg($inputArg) + { + return [ + 'type' => Type::string(), + 'args' => ['input' => $inputArg], + 'resolve' => function ($_, $args) { + if (isset($args['input'])) { + return json_encode($args['input']); + } + + return null; + }, + ]; + } + public function testUsingVariables() : void { $doc = ' @@ -119,7 +191,7 @@ class VariablesTest extends TestCase '); $expected = [ - 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'] + 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'], ]; $this->assertEquals($expected, $result->toArray()); @@ -132,63 +204,61 @@ class VariablesTest extends TestCase ); // executes with complex scalar input: - $params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ]; - $result = $this->executeQuery($doc, $params); + $params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']]; + $result = $this->executeQuery($doc, $params); $expected = [ - 'data' => [ - 'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}' - ] + 'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'], ]; $this->assertEquals($expected, $result->toArray()); // errors on null for nested non-null: - $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]]; - $result = $this->executeQuery($doc, $params); + $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => null]]; + $result = $this->executeQuery($doc, $params); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value ' . '{"a":"foo","b":"bar","c":null}; ' . 'Expected non-nullable type String! not to be null at value.c.', 'locations' => [['line' => 2, 'column' => 21]], - 'category' => 'graphql' - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); // errors on incorrect type: - $params = [ 'input' => 'foo bar' ]; - $result = $this->executeQuery($doc, $params); + $params = ['input' => 'foo bar']; + $result = $this->executeQuery($doc, $params); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value "foo bar"; ' . 'Expected type TestInputObject to be an object.', - 'locations' => [ [ 'line' => 2, 'column' => 21 ] ], - 'category' => 'graphql', - ] - ] + 'locations' => [['line' => 2, 'column' => 21]], + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); // errors on omission of nested non-null: $params = ['input' => ['a' => 'foo', 'b' => 'bar']]; - $result = $this->executeQuery($doc, $params); + $result = $this->executeQuery($doc, $params); $expected = [ 'errors' => [ [ - 'message' => - 'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '. + 'message' => + 'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' . 'Field value.c of required type String! was not provided.', 'locations' => [['line' => 2, 'column' => 21]], - 'category' => 'graphql', - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); @@ -198,62 +268,59 @@ class VariablesTest extends TestCase fieldWithNestedObjectInput(input: $input) } '; - $params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ]; + $params = ['input' => ['na' => ['a' => 'foo']]]; - $result = $this->executeQuery($nestedDoc, $params); + $result = $this->executeQuery($nestedDoc, $params); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' . 'Field value.na.c of required type String! was not provided.', 'locations' => [['line' => 2, 'column' => 19]], - 'category' => 'graphql', + 'category' => 'graphql', ], [ - 'message' => + 'message' => 'Variable "$input" got invalid value {"na":{"a":"foo"}}; ' . 'Field value.nb of required type String! was not provided.', 'locations' => [['line' => 2, 'column' => 19]], - 'category' => 'graphql', - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); - // errors on addition of unknown input field - $params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog' ]]; - $result = $this->executeQuery($doc, $params); + $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']]; + $result = $this->executeQuery($doc, $params); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value ' . '{"a":"foo","b":"bar","c":"baz","extra":"dog"}; ' . 'Field "extra" is not defined by type TestInputObject.', 'locations' => [['line' => 2, 'column' => 21]], - 'category' => 'graphql', - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } - // Describe: Handles nullable scalars - /** * @see it('allows nullable inputs to be omitted') */ public function testAllowsNullableInputsToBeOmitted() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithNullableStringInput } '); $expected = [ - 'data' => ['fieldWithNullableStringInput' => null] + 'data' => ['fieldWithNullableStringInput' => null], ]; $this->assertEquals($expected, $result->toArray()); @@ -264,7 +331,7 @@ class VariablesTest extends TestCase */ public function testAllowsNullableInputsToBeOmittedInAVariable() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } @@ -279,7 +346,7 @@ class VariablesTest extends TestCase */ public function testAllowsNullableInputsToBeOmittedInAnUnlistedVariable() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' query SetsNullable { fieldWithNullableStringInput(input: $value) } @@ -288,12 +355,15 @@ class VariablesTest extends TestCase $this->assertEquals($expected, $result->toArray()); } + + // Describe: Handles non-nullable scalars + /** * @see it('allows nullable inputs to be set to null in a variable') */ public function testAllowsNullableInputsToBeSetToNullInAVariable() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } @@ -308,12 +378,12 @@ class VariablesTest extends TestCase */ public function testAllowsNullableInputsToBeSetToAValueInAVariable() : void { - $doc = ' + $doc = ' query SetsNullable($value: String) { fieldWithNullableStringInput(input: $value) } '; - $result = $this->executeQuery($doc, ['value' => 'a']); + $result = $this->executeQuery($doc, ['value' => 'a']); $expected = ['data' => ['fieldWithNullableStringInput' => '"a"']]; $this->assertEquals($expected, $result->toArray()); } @@ -323,7 +393,7 @@ class VariablesTest extends TestCase */ public function testAllowsNullableInputsToBeSetToAValueDirectly() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithNullableStringInput(input: "a") } @@ -332,21 +402,18 @@ class VariablesTest extends TestCase $this->assertEquals($expected, $result->toArray()); } - - // Describe: Handles non-nullable scalars - /** * @see it('allows non-nullable inputs to be omitted given a default') */ public function testAllowsNonNullableInputsToBeOmittedGivenADefault() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' query SetsNonNullable($value: String = "default") { fieldWithNonNullableStringInput(input: $value) } '); $expected = [ - 'data' => ['fieldWithNonNullableStringInput' => '"default"'] + 'data' => ['fieldWithNonNullableStringInput' => '"default"'], ]; $this->assertEquals($expected, $result->toArray()); } @@ -365,11 +432,11 @@ class VariablesTest extends TestCase $expected = [ 'errors' => [ [ - 'message' => 'Variable "$value" of required type "String!" was not provided.', + 'message' => 'Variable "$value" of required type "String!" was not provided.', 'locations' => [['line' => 2, 'column' => 31]], - 'category' => 'graphql' - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -379,22 +446,22 @@ class VariablesTest extends TestCase */ public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : void { - $doc = ' + $doc = ' query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } '; - $result = $this->executeQuery($doc, ['value' => null]); + $result = $this->executeQuery($doc, ['value' => null]); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$value" got invalid value null; ' . 'Expected non-nullable type String! not to be null.', 'locations' => [['line' => 2, 'column' => 31]], - 'category' => 'graphql', - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -404,12 +471,12 @@ class VariablesTest extends TestCase */ public function testAllowsNonNullableInputsToBeSetToAValueInAVariable() : void { - $doc = ' + $doc = ' query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } '; - $result = $this->executeQuery($doc, ['value' => 'a']); + $result = $this->executeQuery($doc, ['value' => 'a']); $expected = ['data' => ['fieldWithNonNullableStringInput' => '"a"']]; $this->assertEquals($expected, $result->toArray()); } @@ -419,7 +486,7 @@ class VariablesTest extends TestCase */ public function testAllowsNonNullableInputsToBeSetToAValueDirectly() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithNonNullableStringInput(input: "a") } @@ -433,47 +500,50 @@ class VariablesTest extends TestCase */ public function testReportsErrorForMissingNonNullableInputs() : void { - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithNonNullableStringInput } '); $expected = [ - 'data' => ['fieldWithNonNullableStringInput' => null], + 'data' => ['fieldWithNonNullableStringInput' => null], 'errors' => [[ - 'message' => 'Argument "input" of required type "String!" was not provided.', - 'locations' => [ [ 'line' => 3, 'column' => 9 ] ], - 'path' => [ 'fieldWithNonNullableStringInput' ], - 'category' => 'graphql', - ]] + 'message' => 'Argument "input" of required type "String!" was not provided.', + 'locations' => [['line' => 3, 'column' => 9]], + 'path' => ['fieldWithNonNullableStringInput'], + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } + // Describe: Handles lists and nullability + /** * @see it('reports error for array passed into string input') */ public function testReportsErrorForArrayPassedIntoStringInput() : void { - - $doc = ' + $doc = ' query SetsNonNullable($value: String!) { fieldWithNonNullableStringInput(input: $value) } '; $variables = ['value' => [1, 2, 3]]; - $result = $this->executeQuery($doc, $variables); + $result = $this->executeQuery($doc, $variables); $expected = [ 'errors' => [[ - 'message' => + 'message' => 'Variable "$value" got invalid value [1,2,3]; Expected type ' . 'String; String cannot represent an array value: [1,2,3]', - 'category' => 'graphql', + 'category' => 'graphql', 'locations' => [ - ['line' => 2, 'column' => 31] - ] - ]] + ['line' => 2, 'column' => 31], + ], + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -498,38 +568,37 @@ class VariablesTest extends TestCase // and are being run against a new schema which have introduced a breaking // change to make a formerly non-required argument required, this asserts // failure before allowing the underlying code to receive a non-null value. - $result = $this->executeQuery(' + $result = $this->executeQuery(' { fieldWithNonNullableStringInput(input: $foo) } '); $expected = [ - 'data' => ['fieldWithNonNullableStringInput' => null], + 'data' => ['fieldWithNonNullableStringInput' => null], 'errors' => [[ - 'message' => + 'message' => 'Argument "input" of required type "String!" was provided the ' . 'variable "$foo" which was not provided a runtime value.', 'locations' => [['line' => 3, 'column' => 48]], - 'path' => ['fieldWithNonNullableStringInput'], - 'category' => 'graphql', - ]] + 'path' => ['fieldWithNonNullableStringInput'], + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } - // Describe: Handles lists and nullability - /** * @see it('allows lists to be null') */ public function testAllowsListsToBeNull() : void { - $doc = ' + $doc = ' query q($input:[String]) { list(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => null]); + $result = $this->executeQuery($doc, ['input' => null]); $expected = ['data' => ['list' => null]]; $this->assertEquals($expected, $result->toArray()); @@ -540,12 +609,12 @@ class VariablesTest extends TestCase */ public function testAllowsListsToContainValues() : void { - $doc = ' + $doc = ' query q($input:[String]) { list(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A']]); + $result = $this->executeQuery($doc, ['input' => ['A']]); $expected = ['data' => ['list' => '["A"]']]; $this->assertEquals($expected, $result->toArray()); } @@ -555,12 +624,12 @@ class VariablesTest extends TestCase */ public function testAllowsListsToContainNull() : void { - $doc = ' + $doc = ' query q($input:[String]) { list(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A',null,'B']]); + $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $expected = ['data' => ['list' => '["A",null,"B"]']]; $this->assertEquals($expected, $result->toArray()); } @@ -570,22 +639,22 @@ class VariablesTest extends TestCase */ public function testDoesNotAllowNonNullListsToBeNull() : void { - $doc = ' + $doc = ' query q($input:[String]!) { nnList(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => null]); + $result = $this->executeQuery($doc, ['input' => null]); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value null; ' . 'Expected non-nullable type [String]! not to be null.', 'locations' => [['line' => 2, 'column' => 17]], - 'category' => 'graphql', - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -595,12 +664,12 @@ class VariablesTest extends TestCase */ public function testAllowsNonNullListsToContainValues() : void { - $doc = ' + $doc = ' query q($input:[String]!) { nnList(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A']]); + $result = $this->executeQuery($doc, ['input' => ['A']]); $expected = ['data' => ['nnList' => '["A"]']]; $this->assertEquals($expected, $result->toArray()); } @@ -610,12 +679,12 @@ class VariablesTest extends TestCase */ public function testAllowsNonNullListsToContainNull() : void { - $doc = ' + $doc = ' query q($input:[String]!) { nnList(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A',null,'B']]); + $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $expected = ['data' => ['nnList' => '["A",null,"B"]']]; $this->assertEquals($expected, $result->toArray()); } @@ -625,12 +694,12 @@ class VariablesTest extends TestCase */ public function testAllowsListsOfNonNullsToBeNull() : void { - $doc = ' + $doc = ' query q($input:[String!]) { listNN(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => null]); + $result = $this->executeQuery($doc, ['input' => null]); $expected = ['data' => ['listNN' => null]]; $this->assertEquals($expected, $result->toArray()); } @@ -640,12 +709,12 @@ class VariablesTest extends TestCase */ public function testAllowsListsOfNonNullsToContainValues() : void { - $doc = ' + $doc = ' query q($input:[String!]) { listNN(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A']]); + $result = $this->executeQuery($doc, ['input' => ['A']]); $expected = ['data' => ['listNN' => '["A"]']]; $this->assertEquals($expected, $result->toArray()); } @@ -655,22 +724,22 @@ class VariablesTest extends TestCase */ public function testDoesNotAllowListsOfNonNullsToContainNull() : void { - $doc = ' + $doc = ' query q($input:[String!]) { listNN(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); + $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Expected non-nullable type String! not to be null at value[1].', - 'locations' => [ ['line' => 2, 'column' => 17] ], - 'category' => 'graphql', - ] - ] + 'locations' => [['line' => 2, 'column' => 17]], + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -680,22 +749,22 @@ class VariablesTest extends TestCase */ public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void { - $doc = ' + $doc = ' query q($input:[String!]!) { nnListNN(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => null]); + $result = $this->executeQuery($doc, ['input' => null]); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value null; ' . 'Expected non-nullable type [String!]! not to be null.', - 'locations' => [ ['line' => 2, 'column' => 17] ], - 'category' => 'graphql', - ] - ] + 'locations' => [['line' => 2, 'column' => 17]], + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -705,37 +774,39 @@ class VariablesTest extends TestCase */ public function testAllowsNonNullListsOfNonNullsToContainValues() : void { - $doc = ' + $doc = ' query q($input:[String!]!) { nnListNN(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A']]); + $result = $this->executeQuery($doc, ['input' => ['A']]); $expected = ['data' => ['nnListNN' => '["A"]']]; $this->assertEquals($expected, $result->toArray()); } + // Describe: Execute: Uses argument default values + /** * @see it('does not allow non-null lists of non-nulls to contain null') */ public function testDoesNotAllowNonNullListsOfNonNullsToContainNull() : void { - $doc = ' + $doc = ' query q($input:[String!]!) { nnListNN(input: $input) } '; - $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); + $result = $this->executeQuery($doc, ['input' => ['A', null, 'B']]); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Expected non-nullable type String! not to be null at value[1].', - 'locations' => [ ['line' => 2, 'column' => 17] ], - 'category' => 'graphql', - ] - ] + 'locations' => [['line' => 2, 'column' => 17]], + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -745,23 +816,23 @@ class VariablesTest extends TestCase */ public function testDoesNotAllowInvalidTypesToBeUsedAsValues() : void { - $doc = ' + $doc = ' query q($input: TestType!) { fieldWithObjectInput(input: $input) } '; - $vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ]; - $result = $this->executeQuery($doc, $vars); + $vars = ['input' => ['list' => ['A', 'B']]]; + $result = $this->executeQuery($doc, $vars); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" expected value of type "TestType!" which cannot ' . 'be used as an input type.', 'locations' => [['line' => 2, 'column' => 25]], - 'category' => 'graphql', - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } @@ -771,29 +842,28 @@ class VariablesTest extends TestCase */ public function testDoesNotAllowUnknownTypesToBeUsedAsValues() : void { - $doc = ' + $doc = ' query q($input: UnknownType!) { fieldWithObjectInput(input: $input) } '; $vars = ['input' => 'whoknows']; - $result = $this->executeQuery($doc, $vars); + $result = $this->executeQuery($doc, $vars); $expected = [ 'errors' => [ [ - 'message' => + 'message' => 'Variable "$input" expected value of type "UnknownType!" which ' . 'cannot be used as an input type.', 'locations' => [['line' => 2, 'column' => 25]], - 'category' => 'graphql', - ] - ] + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } - // Describe: Execute: Uses argument default values /** * @see it('when no argument provided') */ @@ -834,84 +904,17 @@ class VariablesTest extends TestCase }'); $expected = [ - 'data' => ['fieldWithDefaultArgumentValue' => null], + 'data' => ['fieldWithDefaultArgumentValue' => null], 'errors' => [[ - 'message' => + 'message' => 'Argument "input" has invalid value WRONG_TYPE.', - 'locations' => [ [ 'line' => 2, 'column' => 50 ] ], - 'path' => [ 'fieldWithDefaultArgumentValue' ], - 'category' => 'graphql', - ]] + 'locations' => [['line' => 2, 'column' => 50]], + 'path' => ['fieldWithDefaultArgumentValue'], + 'category' => 'graphql', + ], + ], ]; $this->assertEquals($expected, $result->toArray()); } - - - public function schema() - { - $ComplexScalarType = ComplexScalar::create(); - - $TestInputObject = new InputObjectType([ - 'name' => 'TestInputObject', - 'fields' => [ - 'a' => ['type' => Type::string()], - 'b' => ['type' => Type::listOf(Type::string())], - 'c' => ['type' => Type::nonNull(Type::string())], - 'd' => ['type' => $ComplexScalarType], - ] - ]); - - $TestNestedInputObject = new InputObjectType([ - 'name' => 'TestNestedInputObject', - 'fields' => [ - 'na' => [ 'type' => Type::nonNull($TestInputObject) ], - 'nb' => [ 'type' => Type::nonNull(Type::string()) ], - ], - ]); - - $TestType = new ObjectType([ - 'name' => 'TestType', - 'fields' => [ - 'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]), - 'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]), - 'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]), - 'fieldWithDefaultArgumentValue' => $this->fieldWithInputArg([ - 'type' => Type::string(), - 'defaultValue' => 'Hello World', - ]), - 'fieldWithNestedInputObject' => $this->fieldWithInputArg([ - 'type' => $TestNestedInputObject, - 'defaultValue' => 'Hello World' - ]), - 'list' => $this->fieldWithInputArg(['type' => Type::listOf(Type::string())]), - 'nnList' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::string()))]), - 'listNN' => $this->fieldWithInputArg(['type' => Type::listOf(Type::nonNull(Type::string()))]), - 'nnListNN' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string())))]), - ] - ]); - - $schema = new Schema(['query' => $TestType]); - return $schema; - } - - private function fieldWithInputArg($inputArg) - { - return [ - 'type' => Type::string(), - 'args' => ['input' => $inputArg], - 'resolve' => function ($_, $args) { - if (isset($args['input'])) { - return json_encode($args['input']); - } - return null; - }, - ]; - } - - private function executeQuery($query, $variableValues = null) - { - $document = Parser::parse($query); - return Executor::execute($this->schema(), $document, null, null, $variableValues); - } }