Merge branch 'master' into patch-1

This commit is contained in:
Benedikt Franke 2018-09-03 21:25:36 +02:00 committed by GitHub
commit e7513e356a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
135 changed files with 14159 additions and 11797 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
.phpcs-cache
composer.phar composer.phar
composer.lock composer.lock
phpcs.xml phpcs.xml
phpstan.neon
vendor/ vendor/

View File

@ -6,10 +6,6 @@ php:
- 7.2 - 7.2
- nightly - nightly
matrix:
allow_failures:
- php: nightly
cache: cache:
directories: directories:
- $HOME/.composer/cache - $HOME/.composer/cache
@ -18,10 +14,7 @@ before_install:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available" - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
- travis_retry composer self-update - travis_retry composer self-update
install: install: travis_retry composer update --prefer-dist
- composer require react/promise:2.*
- composer require psr/http-message:1.*
- travis_retry composer update --prefer-dist
script: ./vendor/bin/phpunit --group default,ReactPromise script: ./vendor/bin/phpunit --group default,ReactPromise
@ -45,21 +38,16 @@ jobs:
after_script: after_script:
- wget https://scrutinizer-ci.com/ocular.phar - wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover clover.xml - php ocular.phar code-coverage:upload --format=php-clover clover.xml
- stage: Pull request coding standard
if: type = pull_request - stage: Code Quality
php: 7.1
env: CODING_STANDARD
install: travis_retry composer install --prefer-dist install: travis_retry composer install --prefer-dist
script: script:
- | - ./vendor/bin/phpcs
if [ $TRAVIS_BRANCH != "master" ]; then
git remote set-branches --add origin $TRAVIS_BRANCH; - stage: Code Quality
git fetch origin $TRAVIS_BRANCH; php: 7.1
fi env: STATIC_ANALYSIS
- git merge-base origin/$TRAVIS_BRANCH $TRAVIS_PULL_REQUEST_SHA || git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge --unshallow install: travis_retry composer install --prefer-dist
- wget https://github.com/diff-sniffer/git/releases/download/0.1.0/git-phpcs.phar script: composer static-analysis
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
# - stage: Coding standard
# if: NOT type = pull_request
# php: 7.1
# install: travis_retry composer install --prefer-dist
# script:
# - ./vendor/bin/phpcs

View File

@ -25,6 +25,8 @@ composer install
./vendor/bin/phpunit ./vendor/bin/phpunit
``` ```
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
## Coding Standard ## Coding Standard
Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection: Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection:

View File

@ -15,8 +15,11 @@
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^4.0", "doctrine/coding-standard": "^4.0",
"phpstan/phpstan": "^0.10.3",
"phpstan/phpstan-phpunit": "^0.10.0",
"phpunit/phpunit": "^7.2", "phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0" "psr/http-message": "^1.0",
"react/promise": "2.*"
}, },
"config": { "config": {
"preferred-install": "dist", "preferred-install": "dist",
@ -39,6 +42,7 @@
"psr/http-message": "To use standard GraphQL server" "psr/http-message": "To use standard GraphQL server"
}, },
"scripts": { "scripts": {
"lint" : "phpcs" "lint" : "phpcs",
"static-analysis": "phpstan analyse --ansi -l 1 -c phpstan.neon.dist src tests"
} }
} }

View File

@ -4,7 +4,7 @@ first learn about GraphQL on [the official website](http://graphql.org/learn/).
# Installation # Installation
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run: Using [composer](https://getcomposer.org/doc/00-intro.md), run:
```sh ```sh
composer require webonyx/graphql-php composer require webonyx/graphql-php

3
phpstan.neon.dist Normal file
View File

@ -0,0 +1,3 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon

View File

@ -1,23 +1,20 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL; namespace GraphQL;
use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Executor\Promise\Adapter\SyncPromise;
class Deferred class Deferred
{ {
/** /** @var \SplQueue */
* @var \SplQueue
*/
private static $queue; private static $queue;
/** /** @var callable */
* @var callable
*/
private $callback; private $callback;
/** /** @var SyncPromise */
* @var SyncPromise
*/
public $promise; public $promise;
public static function getQueue() public static function getQueue()
@ -28,7 +25,7 @@ class Deferred
public static function runQueue() public static function runQueue()
{ {
$q = self::$queue; $q = self::$queue;
while ($q && !$q->isEmpty()) { while ($q && ! $q->isEmpty()) {
/** @var self $dfd */ /** @var self $dfd */
$dfd = $q->dequeue(); $dfd = $q->dequeue();
$dfd->run(); $dfd->run();
@ -47,7 +44,7 @@ class Deferred
return $this->promise->then($onFulfilled, $onRejected); return $this->promise->then($onFulfilled, $onRejected);
} }
private function run() public function run() : void
{ {
try { try {
$cb = $this->callback; $cb = $this->callback;

View File

@ -35,6 +35,7 @@ use GraphQL\Utils\TypeInfo;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function array_keys; use function array_keys;
use function array_merge; use function array_merge;
use function array_reduce;
use function array_values; use function array_values;
use function get_class; use function get_class;
use function is_array; use function is_array;
@ -996,7 +997,6 @@ class Executor
* return a Promise. * return a Promise.
* *
* @param mixed[] $values * @param mixed[] $values
* @param \Closure $callback
* @param Promise|mixed|null $initialValue * @param Promise|mixed|null $initialValue
* @return mixed[] * @return mixed[]
*/ */
@ -1291,23 +1291,18 @@ class Executor
return $this->executeFields($returnType, $result, $path, $subFieldNodes); return $this->executeFields($returnType, $result, $path, $subFieldNodes);
} }
/** private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayObject
* @param ObjectType $returnType
* @param $fieldNodes
* @return ArrayObject
*/
private function collectSubFields(ObjectType $returnType, $fieldNodes): ArrayObject
{ {
if (!isset($this->subFieldCache[$returnType])) { if (! isset($this->subFieldCache[$returnType])) {
$this->subFieldCache[$returnType] = new \SplObjectStorage(); $this->subFieldCache[$returnType] = new \SplObjectStorage();
} }
if (!isset($this->subFieldCache[$returnType][$fieldNodes])) { if (! isset($this->subFieldCache[$returnType][$fieldNodes])) {
// Collect sub-fields to execute to complete this value. // Collect sub-fields to execute to complete this value.
$subFieldNodes = new \ArrayObject(); $subFieldNodes = new \ArrayObject();
$visitedFragmentNames = new \ArrayObject(); $visitedFragmentNames = new \ArrayObject();
foreach ($fieldNodes as $fieldNode) { foreach ($fieldNodes as $fieldNode) {
if (!isset($fieldNode->selectionSet)) { if (! isset($fieldNode->selectionSet)) {
continue; continue;
} }

View File

@ -6,6 +6,7 @@ namespace GraphQL\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Executor\Promise\Adapter\SyncPromise;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use React\Promise\Promise as ReactPromise;
/** /**
* Convenience wrapper for promises represented by Promise Adapter * Convenience wrapper for promises represented by Promise Adapter

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL; namespace GraphQL;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -6,15 +9,19 @@ use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise; use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as SchemaType;
use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\ValidationRule;
use GraphQL\Validator\Rules\QueryComplexity; use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\ValidationRule;
use function array_values;
use function trigger_error;
use const E_USER_DEPRECATED;
/** /**
* This is the primary facade for fulfilling GraphQL operations. * This is the primary facade for fulfilling GraphQL operations.
@ -58,32 +65,35 @@ class GraphQL
* queries which are validated before persisting and assumed valid during execution) * queries which are validated before persisting and assumed valid during execution)
* *
* @api * @api
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source * @param string|DocumentNode $source
* @param mixed $rootValue * @param mixed $rootValue
* @param mixed $context * @param mixed $context
* @param array|null $variableValues * @param mixed[]|null $variableValues
* @param string|null $operationName * @param ValidationRule[] $validationRules
* @param callable $fieldResolver
* @param array $validationRules
*
* @return ExecutionResult
*/ */
public static function executeQuery( public static function executeQuery(
\GraphQL\Type\Schema $schema, SchemaType $schema,
$source, $source,
$rootValue = null, $rootValue = null,
$context = null, $context = null,
$variableValues = null, $variableValues = null,
$operationName = null, ?string $operationName = null,
callable $fieldResolver = null, ?callable $fieldResolver = null,
array $validationRules = null ?array $validationRules = null
) ) : ExecutionResult {
{
$promiseAdapter = new SyncPromiseAdapter(); $promiseAdapter = new SyncPromiseAdapter();
$promise = self::promiseToExecute($promiseAdapter, $schema, $source, $rootValue, $context, $promise = self::promiseToExecute(
$variableValues, $operationName, $fieldResolver, $validationRules); $promiseAdapter,
$schema,
$source,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver,
$validationRules
);
return $promiseAdapter->wait($promise); return $promiseAdapter->wait($promise);
} }
@ -93,30 +103,23 @@ class GraphQL
* Useful for Async PHP platforms. * Useful for Async PHP platforms.
* *
* @api * @api
* @param PromiseAdapter $promiseAdapter
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source * @param string|DocumentNode $source
* @param mixed $rootValue * @param mixed $rootValue
* @param mixed $context * @param mixed $context
* @param array|null $variableValues * @param mixed[]|null $variableValues
* @param string|null $operationName * @param ValidationRule[]|null $validationRules
* @param callable $fieldResolver
* @param array $validationRules
*
* @return Promise
*/ */
public static function promiseToExecute( public static function promiseToExecute(
PromiseAdapter $promiseAdapter, PromiseAdapter $promiseAdapter,
\GraphQL\Type\Schema $schema, SchemaType $schema,
$source, $source,
$rootValue = null, $rootValue = null,
$context = null, $context = null,
$variableValues = null, $variableValues = null,
$operationName = null, ?string $operationName = null,
callable $fieldResolver = null, ?callable $fieldResolver = null,
array $validationRules = null ?array $validationRules = null
) ) : Promise {
{
try { try {
if ($source instanceof DocumentNode) { if ($source instanceof DocumentNode) {
$documentNode = $source; $documentNode = $source;
@ -125,25 +128,28 @@ class GraphQL
} }
// FIXME // FIXME
if (!empty($validationRules)) { if (empty($validationRules)) {
foreach ($validationRules as $rule) {
if ($rule instanceof QueryComplexity) {
$rule->setRawVariableValues($variableValues);
}
}
} else {
/** @var QueryComplexity $queryComplexity */ /** @var QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class); $queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
$queryComplexity->setRawVariableValues($variableValues); $queryComplexity->setRawVariableValues($variableValues);
} else {
foreach ($validationRules as $rule) {
if (! ($rule instanceof QueryComplexity)) {
continue;
}
$rule->setRawVariableValues($variableValues);
}
} }
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules); $validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
if (!empty($validationErrors)) { if (! empty($validationErrors)) {
return $promiseAdapter->createFulfilled( return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $validationErrors) new ExecutionResult(null, $validationErrors)
); );
} else { }
return Executor::promiseToExecute( return Executor::promiseToExecute(
$promiseAdapter, $promiseAdapter,
$schema, $schema,
@ -154,7 +160,6 @@ class GraphQL
$operationName, $operationName,
$fieldResolver $fieldResolver
); );
}
} catch (Error $e) { } catch (Error $e) {
return $promiseAdapter->createFulfilled( return $promiseAdapter->createFulfilled(
new ExecutionResult(null, [$e]) new ExecutionResult(null, [$e])
@ -165,29 +170,28 @@ class GraphQL
/** /**
* @deprecated Use executeQuery()->toArray() instead * @deprecated Use executeQuery()->toArray() instead
* *
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source * @param string|DocumentNode $source
* @param mixed $rootValue * @param mixed $rootValue
* @param mixed $contextValue * @param mixed $contextValue
* @param array|null $variableValues * @param mixed[]|null $variableValues
* @param string|null $operationName * @return Promise|mixed[]
* @return Promise|array
*/ */
public static function execute( public static function execute(
\GraphQL\Type\Schema $schema, SchemaType $schema,
$source, $source,
$rootValue = null, $rootValue = null,
$contextValue = null, $contextValue = null,
$variableValues = null, $variableValues = null,
$operationName = null ?string $operationName = null
) ) {
{
trigger_error( trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement', __METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
E_USER_DEPRECATED E_USER_DEPRECATED
); );
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute( $result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(), $promiseAdapter,
$schema, $schema,
$source, $source,
$rootValue, $rootValue,
@ -199,40 +203,40 @@ class GraphQL
if ($promiseAdapter instanceof SyncPromiseAdapter) { if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result)->toArray(); $result = $promiseAdapter->wait($result)->toArray();
} else { } else {
$result = $result->then(function(ExecutionResult $r) { $result = $result->then(function (ExecutionResult $r) {
return $r->toArray(); return $r->toArray();
}); });
} }
return $result; return $result;
} }
/** /**
* @deprecated renamed to executeQuery() * @deprecated renamed to executeQuery()
* *
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source * @param string|DocumentNode $source
* @param mixed $rootValue * @param mixed $rootValue
* @param mixed $contextValue * @param mixed $contextValue
* @param array|null $variableValues * @param mixed[]|null $variableValues
* @param string|null $operationName
* *
* @return ExecutionResult|Promise * @return ExecutionResult|Promise
*/ */
public static function executeAndReturnResult( public static function executeAndReturnResult(
\GraphQL\Type\Schema $schema, SchemaType $schema,
$source, $source,
$rootValue = null, $rootValue = null,
$contextValue = null, $contextValue = null,
$variableValues = null, $variableValues = null,
$operationName = null ?string $operationName = null
) ) {
{
trigger_error( trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement', __METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
E_USER_DEPRECATED E_USER_DEPRECATED
); );
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute( $result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(), $promiseAdapter,
$schema, $schema,
$source, $source,
$rootValue, $rootValue,
@ -240,9 +244,11 @@ class GraphQL
$variableValues, $variableValues,
$operationName $operationName
); );
if ($promiseAdapter instanceof SyncPromiseAdapter) { if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result); $result = $promiseAdapter->wait($result);
} }
return $result; return $result;
} }
@ -252,7 +258,7 @@ class GraphQL
* @api * @api
* @return Directive[] * @return Directive[]
*/ */
public static function getStandardDirectives() public static function getStandardDirectives() : array
{ {
return array_values(Directive::getInternalDirectives()); return array_values(Directive::getInternalDirectives());
} }
@ -263,7 +269,7 @@ class GraphQL
* @api * @api
* @return Type[] * @return Type[]
*/ */
public static function getStandardTypes() public static function getStandardTypes() : array
{ {
return array_values(Type::getInternalTypes()); return array_values(Type::getInternalTypes());
} }
@ -274,23 +280,17 @@ class GraphQL
* @api * @api
* @return ValidationRule[] * @return ValidationRule[]
*/ */
public static function getStandardValidationRules() public static function getStandardValidationRules() : array
{ {
return array_values(DocumentValidator::defaultRules()); return array_values(DocumentValidator::defaultRules());
} }
/** public static function setDefaultFieldResolver(callable $fn) : void
* @param callable $fn
*/
public static function setDefaultFieldResolver(callable $fn)
{ {
Executor::setDefaultFieldResolver($fn); Executor::setDefaultFieldResolver($fn);
} }
/** public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
* @param PromiseAdapter|null $promiseAdapter
*/
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
{ {
Executor::setPromiseAdapter($promiseAdapter); Executor::setPromiseAdapter($promiseAdapter);
} }
@ -301,7 +301,7 @@ class GraphQL
* @deprecated Renamed to getStandardDirectives * @deprecated Renamed to getStandardDirectives
* @return Directive[] * @return Directive[]
*/ */
public static function getInternalDirectives() public static function getInternalDirectives() : array
{ {
return self::getStandardDirectives(); return self::getStandardDirectives();
} }

View File

@ -264,6 +264,7 @@ class Visitor
if ($visitFn) { if ($visitFn) {
$result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors); $result = call_user_func($visitFn, $node, $key, $parent, $path, $ancestors);
$editValue = null;
if ($result !== null) { if ($result !== null) {
if ($result instanceof VisitorOperation) { if ($result instanceof VisitorOperation) {

View File

@ -1,6 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL; namespace GraphQL;
use function trigger_error;
use const E_USER_DEPRECATED;
trigger_error( trigger_error(
'GraphQL\Schema is moved to GraphQL\Type\Schema', 'GraphQL\Schema is moved to GraphQL\Type\Schema',
E_USER_DEPRECATED E_USER_DEPRECATED

View File

@ -4,11 +4,11 @@ declare(strict_types=1);
namespace GraphQL\Server; namespace GraphQL\Server;
use const CASE_LOWER;
use function array_change_key_case; use function array_change_key_case;
use function is_string; use function is_string;
use function json_decode; use function json_decode;
use function json_last_error; use function json_last_error;
use const CASE_LOWER;
/** /**
* Structure representing parsed HTTP parameters for GraphQL operation * Structure representing parsed HTTP parameters for GraphQL operation

View File

@ -8,7 +8,7 @@ use GraphQL\Error\FormattedError;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Promise; use GraphQL\Executor\Promise\Promise;
use GraphQL\Utils; use GraphQL\Utils\Utils;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;

View File

@ -21,7 +21,7 @@ class InputObjectField
/** @var string|null */ /** @var string|null */
public $description; public $description;
/** @var callback|InputType */ /** @var callable|InputType */
public $type; public $type;
/** @var InputValueDefinitionNode|null */ /** @var InputValueDefinitionNode|null */

View File

@ -12,7 +12,6 @@ use function is_array;
use function is_callable; use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
use function spritnf;
/** /**
* Class InputObjectType * Class InputObjectType
@ -70,7 +69,7 @@ class InputObjectType extends Type implements InputType, NamedType
if (! is_array($fields)) { if (! is_array($fields)) {
throw new InvariantViolation( throw new InvariantViolation(
spritnf('%s fields must be an array or a callable which returns such an array.', $this->name) sprintf('%s fields must be an array or a callable which returns such an array.', $this->name)
); );
} }

View File

@ -65,7 +65,7 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
} }
$int = intval($num); $int = intval($num);
// int cast with == used for performance reasons // int cast with == used for performance reasons
// @codingStandardsIgnoreLine // phpcs:ignore
if ($int != $num) { if ($int != $num) {
throw new Error( throw new Error(
'Int cannot represent non-integer value: ' . 'Int cannot represent non-integer value: ' .

View File

@ -255,7 +255,7 @@ class AST
} }
if (is_float($serialized)) { if (is_float($serialized)) {
// int cast with == used for performance reasons // int cast with == used for performance reasons
// @codingStandardsIgnoreLine // phpcs:ignore
if ((int) $serialized == $serialized) { if ((int) $serialized == $serialized) {
return new IntValueNode(['value' => $serialized]); return new IntValueNode(['value' => $serialized]);
} }

View File

@ -7,7 +7,9 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext; use GraphQL\Validator\ValidationContext;
use function count; use function count;
@ -59,7 +61,9 @@ class KnownDirectives extends ValidationRule
} }
/** /**
* @param (Node|NodeList)[] $ancestors * @param Node[]|NodeList[] $ancestors The type is actually (Node|NodeList)[] but this PSR-5 syntax is so far not supported by most of the tools
*
* @return string
*/ */
private function getDirectiveLocationForASTPath(array $ancestors) private function getDirectiveLocationForASTPath(array $ancestors)
{ {

View File

@ -44,10 +44,10 @@ class VariablesDefaultValueAllowed extends ValidationRule
return Visitor::skipNode(); return Visitor::skipNode();
}, },
NodeKind::SELECTION_SET => function (SelectionSetNode $node) use ($context) { NodeKind::SELECTION_SET => function (SelectionSetNode $node) {
return Visitor::skipNode(); return Visitor::skipNode();
}, },
NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) { NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) {
return Visitor::skipNode(); return Visitor::skipNode();
}, },
]; ];

View File

@ -1,23 +1,27 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Error\Warning;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Human;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
require_once __DIR__ . '/TestClasses.php';
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* DESCRIBE: Execute: Handles execution of abstract types with promises
*/
class AbstractPromiseTest extends TestCase class AbstractPromiseTest extends TestCase
{ {
// DESCRIBE: Execute: Handles execution of abstract types with promises
/** /**
* @see it('isTypeOf used to resolve runtime type for Interface') * @see it('isTypeOf used to resolve runtime type for Interface')
*/ */
@ -26,36 +30,36 @@ class AbstractPromiseTest extends TestCase
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'isTypeOf' => function($obj) { 'isTypeOf' => function ($obj) {
return new Deferred(function() use ($obj) { return new Deferred(function () use ($obj) {
return $obj instanceof Dog; return $obj instanceof Dog;
}); });
}, },
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'woofs' => [ 'type' => Type::boolean() ], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'isTypeOf' => function($obj) { 'isTypeOf' => function ($obj) {
return new Deferred(function() use ($obj) { return new Deferred(function () use ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}); });
}, },
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'meows' => [ 'type' => Type::boolean() ], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -64,16 +68,16 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function() { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [ $CatType, $DogType ] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -93,10 +97,10 @@ class AbstractPromiseTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [ 'pets' => [
[ 'name' => 'Odie', 'woofs' => true ], ['name' => 'Odie', 'woofs' => true],
[ 'name' => 'Garfield', 'meows' => false ] ['name' => 'Garfield', 'meows' => false],
] ],
] ],
]; ];
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
@ -107,12 +111,11 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testIsTypeOfCanBeRejected() : void public function testIsTypeOfCanBeRejected() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
@ -126,7 +129,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -140,7 +143,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -152,13 +155,13 @@ class AbstractPromiseTest extends TestCase
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -177,7 +180,7 @@ class AbstractPromiseTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [null, null] 'pets' => [null, null],
], ],
'errors' => [ 'errors' => [
[ [
@ -188,9 +191,9 @@ class AbstractPromiseTest extends TestCase
[ [
'message' => 'We are testing this error', 'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1] 'path' => ['pets', 1],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
@ -201,7 +204,6 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void
{ {
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
@ -212,7 +214,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -225,12 +227,12 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -241,10 +243,10 @@ class AbstractPromiseTest extends TestCase
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function () { 'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -266,9 +268,9 @@ class AbstractPromiseTest extends TestCase
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
] ],
] ],
]; ];
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
@ -292,19 +294,20 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) { if ($obj instanceof Human) {
return $HumanType; return $HumanType;
} }
return null; return null;
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$HumanType = new ObjectType([ $HumanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
@ -313,7 +316,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -322,7 +325,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -336,14 +339,14 @@ class AbstractPromiseTest extends TestCase
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
}); });
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -365,16 +368,16 @@ class AbstractPromiseTest extends TestCase
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
null null,
] ],
], ],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2] 'path' => ['pets', 2],
],
], ],
]
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
@ -385,12 +388,11 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testResolveTypeOnUnionYieldsUsefulError() : void public function testResolveTypeOnUnionYieldsUsefulError() : void
{ {
$HumanType = new ObjectType([ $HumanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
@ -398,7 +400,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -406,7 +408,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
@ -422,10 +424,11 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Human) { if ($obj instanceof Human) {
return $HumanType; return $HumanType;
} }
return null; return null;
}); });
}, },
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -438,12 +441,12 @@ class AbstractPromiseTest extends TestCase
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -466,16 +469,16 @@ class AbstractPromiseTest extends TestCase
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
null null,
] ],
], ],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 7]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 2] 'path' => ['pets', 2],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
@ -496,22 +499,22 @@ class AbstractPromiseTest extends TestCase
if ($obj instanceof Cat) { if ($obj instanceof Cat) {
return 'Cat'; return 'Cat';
} }
return null; return null;
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$PetType], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -520,7 +523,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -532,13 +535,13 @@ class AbstractPromiseTest extends TestCase
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -560,8 +563,8 @@ class AbstractPromiseTest extends TestCase
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
] ],
] ],
]; ];
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
} }
@ -571,7 +574,6 @@ class AbstractPromiseTest extends TestCase
*/ */
public function testResolveTypeCanBeCaught() : void public function testResolveTypeCanBeCaught() : void
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function () { 'resolveType' => function () {
@ -580,8 +582,8 @@ class AbstractPromiseTest extends TestCase
}); });
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
@ -590,7 +592,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -599,7 +601,7 @@ class AbstractPromiseTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -611,13 +613,13 @@ class AbstractPromiseTest extends TestCase
'resolve' => function () { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [$CatType, $DogType] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -636,20 +638,20 @@ class AbstractPromiseTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [null, null] 'pets' => [null, null],
], ],
'errors' => [ 'errors' => [
[ [
'message' => 'We are testing this error', 'message' => 'We are testing this error',
'locations' => [['line' => 2, 'column' => 7]], '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]], 'locations' => [['line' => 2, 'column' => 7]],
'path' => ['pets', 1] 'path' => ['pets', 1],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);

View File

@ -1,23 +1,28 @@
<?php <?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php'; declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Human;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* Execute: Handles execution of abstract types
*/
class AbstractTest extends TestCase class AbstractTest extends TestCase
{ {
// Execute: Handles execution of abstract types
/** /**
* @see it('isTypeOf used to resolve runtime type for Interface') * @see it('isTypeOf used to resolve runtime type for Interface')
*/ */
@ -27,19 +32,21 @@ class AbstractTest extends TestCase
$petType = new InterfaceType([ $petType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
// Added to interface type when defined // Added to interface type when defined
$dogType = new ObjectType([ $dogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; }, 'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$catType = new ObjectType([ $catType = new ObjectType([
@ -51,7 +58,7 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -62,11 +69,11 @@ class AbstractTest extends TestCase
'type' => Type::listOf($petType), 'type' => Type::listOf($petType),
'resolve' => function () { 'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]), ]),
'types' => [$catType, $dogType] 'types' => [$catType, $dogType],
]); ]);
$query = '{ $query = '{
@ -84,8 +91,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([ $expected = new ExecutionResult([
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
] ],
]); ]);
$result = Executor::execute($schema, Parser::parse($query)); $result = Executor::execute($schema, Parser::parse($query));
@ -99,11 +106,13 @@ class AbstractTest extends TestCase
{ {
$dogType = new ObjectType([ $dogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'isTypeOf' => function($obj) { return $obj instanceof Dog; }, 'isTypeOf' => function ($obj) {
return $obj instanceof Dog;
},
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$catType = new ObjectType([ $catType = new ObjectType([
@ -114,12 +123,12 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$petType = new UnionType([ $petType = new UnionType([
'name' => 'Pet', 'name' => 'Pet',
'types' => [$dogType, $catType] 'types' => [$dogType, $catType],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -128,12 +137,12 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($petType), 'type' => Type::listOf($petType),
'resolve' => function() { 'resolve' => function () {
return [ new Dog('Odie', true), new Cat('Garfield', false) ]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -151,8 +160,8 @@ class AbstractTest extends TestCase
$expected = new ExecutionResult([ $expected = new ExecutionResult([
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
] ],
]); ]);
$this->assertEquals($expected, Executor::execute($schema, Parser::parse($query))); $this->assertEquals($expected, Executor::execute($schema, Parser::parse($query)));
@ -161,7 +170,7 @@ class AbstractTest extends TestCase
/** /**
* @see it('resolveType on Interface yields useful error') * @see it('resolveType on Interface yields useful error')
*/ */
function testResolveTypeOnInterfaceYieldsUsefulError() public function testResolveTypeOnInterfaceYieldsUsefulError() : void
{ {
$DogType = null; $DogType = null;
$CatType = null; $CatType = null;
@ -179,18 +188,19 @@ class AbstractTest extends TestCase
if ($obj instanceof Human) { if ($obj instanceof Human) {
return $HumanType; return $HumanType;
} }
return null; return null;
}, },
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$HumanType = new ObjectType([ $HumanType = new ObjectType([
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
@ -199,7 +209,7 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -208,7 +218,7 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -221,16 +231,15 @@ class AbstractTest extends TestCase
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
} },
] ],
], ],
]), ]),
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$query = '{ $query = '{
pets { pets {
name name
@ -248,14 +257,15 @@ class AbstractTest extends TestCase
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false], ['name' => 'Garfield', 'meows' => false],
null null,
] ],
], ],
'errors' => [[ 'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]], 'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2] 'path' => ['pets', 2],
]] ],
],
]; ];
$actual = GraphQL::executeQuery($schema, $query)->toArray(true); $actual = GraphQL::executeQuery($schema, $query)->toArray(true);
@ -271,7 +281,7 @@ class AbstractTest extends TestCase
'name' => 'Human', 'name' => 'Human',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
@ -279,7 +289,7 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -287,7 +297,7 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
@ -303,7 +313,7 @@ class AbstractTest extends TestCase
return $HumanType; return $HumanType;
} }
}, },
'types' => [$DogType, $CatType] 'types' => [$DogType, $CatType],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -316,12 +326,12 @@ class AbstractTest extends TestCase
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false), new Cat('Garfield', false),
new Human('Jon') new Human('Jon'),
]; ];
} },
] ],
] ],
]) ]),
]); ]);
$query = '{ $query = '{
@ -341,18 +351,23 @@ class AbstractTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', [
'woofs' => true], 'name' => 'Odie',
['name' => 'Garfield', 'woofs' => true,
'meows' => false], ],
null [
] 'name' => 'Garfield',
'meows' => false,
],
null,
],
], ],
'errors' => [[ 'errors' => [[
'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".', 'debugMessage' => 'Runtime Object type "Human" is not a possible type for "Pet".',
'locations' => [['line' => 2, 'column' => 11]], 'locations' => [['line' => 2, 'column' => 11]],
'path' => ['pets', 2] 'path' => ['pets', 2],
]] ],
],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
} }
@ -419,32 +434,37 @@ class AbstractTest extends TestCase
{ {
$PetType = new InterfaceType([ $PetType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'resolveType' => function($obj) { 'resolveType' => function ($obj) {
if ($obj instanceof Dog) return 'Dog'; if ($obj instanceof Dog) {
if ($obj instanceof Cat) return 'Cat'; return 'Dog';
}
if ($obj instanceof Cat) {
return 'Cat';
}
return null; return null;
}, },
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'woofs' => [ 'type' => Type::boolean() ], 'woofs' => ['type' => Type::boolean()],
] ],
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
'name' => 'Cat', 'name' => 'Cat',
'interfaces' => [ $PetType ], 'interfaces' => [$PetType],
'fields' => [ 'fields' => [
'name' => [ 'type' => Type::string() ], 'name' => ['type' => Type::string()],
'meows' => [ 'type' => Type::boolean() ], 'meows' => ['type' => Type::boolean()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -453,16 +473,16 @@ class AbstractTest extends TestCase
'fields' => [ 'fields' => [
'pets' => [ 'pets' => [
'type' => Type::listOf($PetType), 'type' => Type::listOf($PetType),
'resolve' => function() { 'resolve' => function () {
return [ return [
new Dog('Odie', true), new Dog('Odie', true),
new Cat('Garfield', false) new Cat('Garfield', false),
]; ];
} },
] ],
] ],
]), ]),
'types' => [ $CatType, $DogType ] 'types' => [$CatType, $DogType],
]); ]);
$query = '{ $query = '{
@ -479,51 +499,52 @@ class AbstractTest extends TestCase
$result = GraphQL::executeQuery($schema, $query)->toArray(); $result = GraphQL::executeQuery($schema, $query)->toArray();
$this->assertEquals([ $this->assertEquals(
[
'data' => [ 'data' => [
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['name' => 'Odie', 'woofs' => true],
['name' => 'Garfield', 'meows' => false] ['name' => 'Garfield', 'meows' => false],
] ],
] ],
], $result); ],
$result
);
} }
public function testHintsOnConflictingTypeInstancesInResolveType() : void public function testHintsOnConflictingTypeInstancesInResolveType() : void
{ {
$createTest = function() use (&$iface) { $createTest = function () use (&$iface) {
return new ObjectType([ return new ObjectType([
'name' => 'Test', 'name' => 'Test',
'fields' => [ 'fields' => [
'a' => Type::string() 'a' => Type::string(),
], ],
'interfaces' => function() use ($iface) { 'interfaces' => function () use ($iface) {
return [$iface]; return [$iface];
} },
]); ]);
}; };
$iface = new InterfaceType([ $iface = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => [ 'fields' => [
'a' => Type::string() 'a' => Type::string(),
], ],
'resolveType' => function() use (&$createTest) { 'resolveType' => function () use (&$createTest) {
return $createTest(); return $createTest();
} },
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'node' => $iface, 'node' => $iface,
'test' => $createTest() 'test' => $createTest(),
] ],
]); ]);
$schema = new Schema([ $schema = new Schema(['query' => $query]);
'query' => $query,
]);
$schema->assertValid(); $schema->assertValid();
$query = ' $query = '
@ -537,9 +558,9 @@ class AbstractTest extends TestCase
$result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]); $result = Executor::execute($schema, Parser::parse($query), ['node' => ['a' => 'value']]);
$this->assertEquals( $this->assertEquals(
'Schema must contain unique named types but contains multiple types named "Test". '. '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 '. 'Make sure that `resolveType` function of abstract type "Node" returns the same type instance ' .
'as referenced anywhere else within the schema '. 'as referenced anywhere else within the schema ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).', '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage() $result->errors[0]->getMessage()
); );

View File

@ -1,33 +1,44 @@
<?php <?php
namespace GraphQL\Tests\Executor;
declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function in_array;
class DeferredFieldsTest extends TestCase class DeferredFieldsTest extends TestCase
{ {
/** @var ObjectType */
private $userType; private $userType;
/** @var ObjectType */
private $storyType; private $storyType;
/** @var ObjectType */
private $categoryType; private $categoryType;
/** @var */
private $path; private $path;
/** @var mixed[][] */
private $storyDataSource; private $storyDataSource;
/** @var mixed[][] */
private $userDataSource; private $userDataSource;
/** @var mixed[][] */
private $categoryDataSource; private $categoryDataSource;
/** @var ObjectType */
private $queryType; private $queryType;
public function setUp() public function setUp()
@ -54,36 +65,41 @@ class DeferredFieldsTest extends TestCase
$this->categoryDataSource = [ $this->categoryDataSource = [
['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8], ['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8],
['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3], ['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([ $this->userType = new ObjectType([
'name' => 'User', 'name' => 'User',
'fields' => function() { 'fields' => function () {
return [ return [
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($user, $args, $context, ResolveInfo $info) { 'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $user['name']; return $user['name'];
} },
], ],
'bestFriend' => [ 'bestFriend' => [
'type' => $this->userType, 'type' => $this->userType,
'resolve' => function($user, $args, $context, ResolveInfo $info) { 'resolve' => function ($user, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $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']; $this->path[] = 'deferred-for-best-friend-of-' . $user['id'];
return Utils::find($this->userDataSource, function($entry) use ($user) {
return Utils::find(
$this->userDataSource,
function ($entry) use ($user) {
return $entry['id'] === $user['bestFriendId']; return $entry['id'] === $user['bestFriendId'];
});
});
} }
] );
});
},
],
]; ];
} },
]); ]);
$this->storyType = new ObjectType([ $this->storyType = new ObjectType([
@ -91,25 +107,30 @@ class DeferredFieldsTest extends TestCase
'fields' => [ 'fields' => [
'title' => [ 'title' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($entry, $args, $context, ResolveInfo $info) { 'resolve' => function ($entry, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $entry['title']; return $entry['title'];
} },
], ],
'author' => [ 'author' => [
'type' => $this->userType, 'type' => $this->userType,
'resolve' => function($story, $args, $context, ResolveInfo $info) { 'resolve' => function ($story, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function() use ($story) { return new Deferred(function () use ($story) {
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author'; $this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
return Utils::find($this->userDataSource, function($entry) use ($story) {
return Utils::find(
$this->userDataSource,
function ($entry) use ($story) {
return $entry['id'] === $story['authorId']; return $entry['id'] === $story['authorId'];
});
});
} }
] );
] });
},
],
],
]); ]);
$this->categoryType = new ObjectType([ $this->categoryType = new ObjectType([
@ -117,35 +138,44 @@ class DeferredFieldsTest extends TestCase
'fields' => [ 'fields' => [
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($category, $args, $context, ResolveInfo $info) { 'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $category['name']; return $category['name'];
} },
], ],
'stories' => [ 'stories' => [
'type' => Type::listOf($this->storyType), 'type' => Type::listOf($this->storyType),
'resolve' => function($category, $args, $context, ResolveInfo $info) { 'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) use ($category) {
return Utils::filter(
$this->storyDataSource,
function ($story) use ($category) {
return in_array($category['id'], $story['categoryIds']); return in_array($category['id'], $story['categoryIds']);
});
} }
);
},
], ],
'topStory' => [ 'topStory' => [
'type' => $this->storyType, 'type' => $this->storyType,
'resolve' => function($category, $args, $context, ResolveInfo $info) { 'resolve' => function ($category, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function () use ($category) { return new Deferred(function () use ($category) {
$this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory'; $this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory';
return Utils::find($this->storyDataSource, function($story) use ($category) {
return Utils::find(
$this->storyDataSource,
function ($story) use ($category) {
return $story['id'] === $category['topStoryId']; return $story['id'] === $category['topStoryId'];
});
});
} }
] );
] });
},
],
],
]); ]);
$this->queryType = new ObjectType([ $this->queryType = new ObjectType([
@ -153,28 +183,34 @@ class DeferredFieldsTest extends TestCase
'fields' => [ 'fields' => [
'topStories' => [ 'topStories' => [
'type' => Type::listOf($this->storyType), 'type' => Type::listOf($this->storyType),
'resolve' => function($val, $args, $context, ResolveInfo $info) { 'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return Utils::filter($this->storyDataSource, function($story) {
return Utils::filter(
$this->storyDataSource,
function ($story) {
return $story['id'] % 2 === 1; return $story['id'] % 2 === 1;
});
} }
);
},
], ],
'featuredCategory' => [ 'featuredCategory' => [
'type' => $this->categoryType, 'type' => $this->categoryType,
'resolve' => function($val, $args, $context, ResolveInfo $info) { 'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $this->categoryDataSource[0]; return $this->categoryDataSource[0];
} },
], ],
'categories' => [ 'categories' => [
'type' => Type::listOf($this->categoryType), 'type' => Type::listOf($this->categoryType),
'resolve' => function($val, $args, $context, ResolveInfo $info) { 'resolve' => function ($val, $args, $context, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return $this->categoryDataSource; return $this->categoryDataSource;
} },
] ],
] ],
]); ]);
parent::setUp(); parent::setUp();
@ -202,7 +238,7 @@ class DeferredFieldsTest extends TestCase
'); ');
$schema = new Schema([ $schema = new Schema([
'query' => $this->queryType 'query' => $this->queryType,
]); ]);
$expected = [ $expected = [
@ -220,9 +256,9 @@ class DeferredFieldsTest extends TestCase
['title' => 'Story #4', 'author' => ['name' => 'Joe']], ['title' => 'Story #4', 'author' => ['name' => 'Joe']],
['title' => 'Story #6', 'author' => ['name' => 'Jane']], ['title' => 'Story #6', 'author' => ['name' => 'Jane']],
['title' => 'Story #8', 'author' => ['name' => 'John']], ['title' => 'Story #8', 'author' => ['name' => 'John']],
] ],
] ],
] ],
]; ];
$result = Executor::execute($schema, $query); $result = Executor::execute($schema, $query);
@ -292,7 +328,7 @@ class DeferredFieldsTest extends TestCase
'); ');
$schema = new Schema([ $schema = new Schema([
'query' => $this->queryType 'query' => $this->queryType,
]); ]);
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']]; $author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
@ -306,8 +342,8 @@ class DeferredFieldsTest extends TestCase
['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]], ['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]],
['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]], ['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]],
['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]], ['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]],
] ],
] ],
]; ];
$result = Executor::execute($schema, $query); $result = Executor::execute($schema, $query);
@ -353,51 +389,53 @@ class DeferredFieldsTest extends TestCase
{ {
$complexType = new ObjectType([ $complexType = new ObjectType([
'name' => 'ComplexType', 'name' => 'ComplexType',
'fields' => function() use (&$complexType) { 'fields' => function () use (&$complexType) {
return [ return [
'sync' => [ 'sync' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) { 'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return 'sync'; return 'sync';
} },
], ],
'deferred' => [ 'deferred' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($v, $a, $c, ResolveInfo $info) { 'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function() use ($info) { return new Deferred(function () use ($info) {
$this->path[] = ['!dfd for: ', $info->path]; $this->path[] = ['!dfd for: ', $info->path];
return 'deferred'; return 'deferred';
}); });
} },
], ],
'nest' => [ 'nest' => [
'type' => $complexType, 'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) { 'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return []; return [];
} },
], ],
'deferredNest' => [ 'deferredNest' => [
'type' => $complexType, 'type' => $complexType,
'resolve' => function($v, $a, $c, ResolveInfo $info) { 'resolve' => function ($v, $a, $c, ResolveInfo $info) {
$this->path[] = $info->path; $this->path[] = $info->path;
return new Deferred(function() use ($info) { return new Deferred(function () use ($info) {
$this->path[] = ['!dfd nest for: ', $info->path]; $this->path[] = ['!dfd nest for: ', $info->path];
return []; return [];
}); });
} },
] ],
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema(['query' => $complexType]);
'query' => $complexType
]);
$query = Parser::parse(' $query = Parser::parse('
{ {
@ -435,26 +473,26 @@ class DeferredFieldsTest extends TestCase
'deferred' => 'deferred', 'deferred' => 'deferred',
'nest' => [ 'nest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
], ],
'deferredNest' => [ 'deferredNest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
] ],
], ],
'deferredNest' => [ 'deferredNest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred', 'deferred' => 'deferred',
'nest' => [ 'nest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
], ],
'deferredNest' => [ 'deferredNest' => [
'sync' => 'sync', 'sync' => 'sync',
'deferred' => 'deferred' 'deferred' => 'deferred',
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());

View File

@ -1,16 +1,27 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Language\Source;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/**
* Describe: Execute: handles directives
*/
class DirectivesTest extends TestCase class DirectivesTest extends TestCase
{ {
// Describe: Execute: handles directives /** @var Schema */
private static $schema;
/** @var string[] */
private static $data;
/** /**
* @see it('basic query works') * @see it('basic query works')
@ -20,10 +31,50 @@ class DirectivesTest extends TestCase
$this->assertEquals(['data' => ['a' => 'a', 'b' => 'b']], $this->executeTestQuery('{ a, b }')); $this->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 public function testWorksOnScalars() : void
{ {
// if true includes scalar // 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 // if false omits on scalar
$this->assertEquals(['data' => ['a' => 'a']], $this->executeTestQuery('{ a, b @include(if: false) }')); $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) }') $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();
}
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;

View File

@ -1,44 +1,62 @@
<?php <?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php'; declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning; use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
class ExecutorLazySchemaTest extends TestCase class ExecutorLazySchemaTest extends TestCase
{ {
public $SomeScalarType; /** @var ScalarType */
public $someScalarType;
public $SomeObjectType; /** @var ObjectType */
public $someObjectType;
public $OtherObjectType; /** @var ObjectType */
public $otherObjectType;
public $DeeperObjectType; /** @var ObjectType */
public $deeperObjectType;
public $SomeUnionType; /** @var UnionType */
public $someUnionType;
public $SomeInterfaceType; /** @var InterfaceType */
public $someInterfaceType;
public $SomeEnumType; /** @var EnumType */
public $someEnumType;
public $SomeInputObjectType; /** @var InputObjectType */
public $someInputObjectType;
public $QueryType; /** @var ObjectType */
public $queryType;
/** @var string[] */
public $calls = []; public $calls = [];
/** @var bool[] */
public $loadedTypes = []; public $loadedTypes = [];
public function testWarnsAboutSlowIsTypeOfForLazySchema() : void public function testWarnsAboutSlowIsTypeOfForLazySchema() : void
@ -46,24 +64,26 @@ class ExecutorLazySchemaTest extends TestCase
// isTypeOf used to resolve runtime type for Interface // isTypeOf used to resolve runtime type for Interface
$petType = new InterfaceType([ $petType = new InterfaceType([
'name' => 'Pet', 'name' => 'Pet',
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
]; ];
} },
]); ]);
// Added to interface type when defined // Added to interface type when defined
$dogType = new ObjectType([ $dogType = new ObjectType([
'name' => 'Dog', 'name' => 'Dog',
'interfaces' => [$petType], 'interfaces' => [$petType],
'isTypeOf' => function($obj) { return $obj instanceof Dog; }, 'isTypeOf' => function ($obj) {
'fields' => function() { return $obj instanceof Dog;
},
'fields' => function () {
return [ return [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
]; ];
} },
]); ]);
$catType = new ObjectType([ $catType = new ObjectType([
@ -72,12 +92,12 @@ class ExecutorLazySchemaTest extends TestCase
'isTypeOf' => function ($obj) { 'isTypeOf' => function ($obj) {
return $obj instanceof Cat; return $obj instanceof Cat;
}, },
'fields' => function() { 'fields' => function () {
return [ return [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()], 'meows' => ['type' => Type::boolean()],
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -88,12 +108,12 @@ class ExecutorLazySchemaTest extends TestCase
'type' => Type::listOf($petType), 'type' => Type::listOf($petType),
'resolve' => function () { 'resolve' => function () {
return [new Dog('Odie', true), new Cat('Garfield', false)]; return [new Dog('Odie', true), new Cat('Garfield', false)];
} },
] ],
] ],
]), ]),
'types' => [$catType, $dogType], 'types' => [$catType, $dogType],
'typeLoader' => function($name) use ($dogType, $petType, $catType) { 'typeLoader' => function ($name) use ($dogType, $petType, $catType) {
switch ($name) { switch ($name) {
case 'Dog': case 'Dog':
return $dogType; return $dogType;
@ -102,7 +122,7 @@ class ExecutorLazySchemaTest extends TestCase
case 'Cat': case 'Cat':
return $catType; return $catType;
} }
} },
]); ]);
$query = '{ $query = '{
@ -120,7 +140,7 @@ class ExecutorLazySchemaTest extends TestCase
$expected = new ExecutionResult([ $expected = new ExecutionResult([
'pets' => [ 'pets' => [
['name' => 'Odie', 'woofs' => true], ['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->assertInstanceOf(Error::class, $result->errors[0]->getPrevious());
$this->assertEquals( $this->assertEquals(
'GraphQL Interface Type `Pet` returned `null` from it`s `resolveType` function for value: instance of '. '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 '. '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. '. 'implementations. It requires full schema scan and degrades query performance significantly. ' .
'Make sure your `resolveType` always returns valid implementation or throws.', 'Make sure your `resolveType` always returns valid implementation or throws.',
$result->errors[0]->getMessage()); $result->errors[0]->getMessage()
);
} }
public function testHintsOnConflictingTypeInstancesInDefinitions() : void public function testHintsOnConflictingTypeInstancesInDefinitions() : void
{ {
$calls = []; $calls = [];
$typeLoader = function($name) use (&$calls) { $typeLoader = function ($name) use (&$calls) {
$calls[] = $name; $calls[] = $name;
switch ($name) { switch ($name) {
case 'Test': case 'Test':
return new ObjectType([ return new ObjectType([
'name' => 'Test', 'name' => 'Test',
'fields' => function() { 'fields' => function () {
return [ return [
'test' => Type::string(), 'test' => Type::string(),
]; ];
} },
]); ]);
default: default:
return null; return null;
} }
}; };
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => function() use ($typeLoader) { 'fields' => function () use ($typeLoader) {
return [ return [
'test' => $typeLoader('Test') 'test' => $typeLoader('Test'),
]; ];
} },
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => $query, 'query' => $query,
'typeLoader' => $typeLoader 'typeLoader' => $typeLoader,
]); ]);
$query = ' $query = '
@ -186,8 +209,8 @@ class ExecutorLazySchemaTest extends TestCase
$this->assertEquals(['Test', 'Test'], $calls); $this->assertEquals(['Test', 'Test'], $calls);
$this->assertEquals( $this->assertEquals(
'Schema must contain unique named types but contains multiple types named "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 '. '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).', '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$result->errors[0]->getMessage() $result->errors[0]->getMessage()
); );
@ -201,9 +224,9 @@ class ExecutorLazySchemaTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = '{ object { string } }'; $query = '{ object { string } }';
@ -219,19 +242,126 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [ $expectedExecutorCalls = [
'Query.fields', 'Query.fields',
'SomeObject', 'SomeObject',
'SomeObject.fields' 'SomeObject.fields',
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
$this->assertEquals($expectedExecutorCalls, $this->calls); $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 public function testDeepQuery() : void
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = '{ object { object { object { string } } } }'; $query = '{ object { object { object { string } } } }';
@ -242,12 +372,12 @@ class ExecutorLazySchemaTest extends TestCase
); );
$expected = [ $expected = [
'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]] 'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]],
]; ];
$expectedLoadedTypes = [ $expectedLoadedTypes = [
'Query' => true, 'Query' => true,
'SomeObject' => true, 'SomeObject' => true,
'OtherObject' => true 'OtherObject' => true,
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
@ -256,7 +386,7 @@ class ExecutorLazySchemaTest extends TestCase
$expectedExecutorCalls = [ $expectedExecutorCalls = [
'Query.fields', 'Query.fields',
'SomeObject', 'SomeObject',
'SomeObject.fields' 'SomeObject.fields',
]; ];
$this->assertEquals($expectedExecutorCalls, $this->calls); $this->assertEquals($expectedExecutorCalls, $this->calls);
} }
@ -265,9 +395,9 @@ class ExecutorLazySchemaTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->loadType('Query'), 'query' => $this->loadType('Query'),
'typeLoader' => function($name) { 'typeLoader' => function ($name) {
return $this->loadType($name, true); return $this->loadType($name, true);
} },
]); ]);
$query = ' $query = '
@ -313,98 +443,4 @@ class ExecutorLazySchemaTest extends TestCase
]; ];
$this->assertEquals($expectedCalls, $this->calls); $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;
}
}
} }

View File

@ -1,17 +1,20 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function sprintf;
class ExecutorSchemaTest extends TestCase class ExecutorSchemaTest extends TestCase
{ {
// Execute: Handles execution with a complex schema // Execute: Handles execution with a complex schema
/** /**
* @see it('executes using a schema') * @see it('executes using a schema')
*/ */
@ -24,12 +27,12 @@ class ExecutorSchemaTest extends TestCase
'url' => ['type' => Type::string()], 'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()], 'height' => ['type' => Type::int()],
] ],
]); ]);
$BlogAuthor = new ObjectType([ $BlogAuthor = new ObjectType([
'name' => 'Author', 'name' => 'Author',
'fields' => function() use (&$BlogArticle, &$BlogImage) { 'fields' => function () use (&$BlogArticle, &$BlogImage) {
return [ return [
'id' => ['type' => Type::string()], 'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
@ -38,11 +41,11 @@ class ExecutorSchemaTest extends TestCase
'type' => $BlogImage, 'type' => $BlogImage,
'resolve' => function ($obj, $args) { 'resolve' => function ($obj, $args) {
return $obj['pic']($args['width'], $args['height']); return $obj['pic']($args['width'], $args['height']);
} },
], ],
'recentArticle' => $BlogArticle 'recentArticle' => $BlogArticle,
]; ];
} },
]); ]);
$BlogArticle = new ObjectType([ $BlogArticle = new ObjectType([
@ -53,8 +56,8 @@ class ExecutorSchemaTest extends TestCase
'author' => ['type' => $BlogAuthor], 'author' => ['type' => $BlogAuthor],
'title' => ['type' => Type::string()], 'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()], 'body' => ['type' => Type::string()],
'keywords' => ['type' => Type::listOf(Type::string())] 'keywords' => ['type' => Type::listOf(Type::string())],
] ],
]); ]);
$BlogQuery = new ObjectType([ $BlogQuery = new ObjectType([
@ -65,7 +68,7 @@ class ExecutorSchemaTest extends TestCase
'args' => ['id' => ['type' => Type::id()]], 'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($_, $args) { 'resolve' => function ($_, $args) {
return $this->article($args['id']); return $this->article($args['id']);
} },
], ],
'feed' => [ 'feed' => [
'type' => Type::listOf($BlogArticle), 'type' => Type::listOf($BlogArticle),
@ -80,16 +83,15 @@ class ExecutorSchemaTest extends TestCase
$this->article(7), $this->article(7),
$this->article(8), $this->article(8),
$this->article(9), $this->article(9),
$this->article(10) $this->article(10),
]; ];
} },
] ],
] ],
]); ]);
$BlogSchema = new Schema(['query' => $BlogQuery]); $BlogSchema = new Schema(['query' => $BlogQuery]);
$request = ' $request = '
{ {
feed { feed {
@ -127,26 +129,46 @@ class ExecutorSchemaTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'feed' => [ 'feed' => [
['id' => '1', [
'title' => 'My Article 1'], 'id' => '1',
['id' => '2', 'title' => 'My Article 1',
'title' => 'My Article 2'], ],
['id' => '3', [
'title' => 'My Article 3'], 'id' => '2',
['id' => '4', 'title' => 'My Article 2',
'title' => 'My Article 4'], ],
['id' => '5', [
'title' => 'My Article 5'], 'id' => '3',
['id' => '6', 'title' => 'My Article 3',
'title' => 'My Article 6'], ],
['id' => '7', [
'title' => 'My Article 7'], 'id' => '4',
['id' => '8', 'title' => 'My Article 4',
'title' => 'My Article 8'], ],
['id' => '9', [
'title' => 'My Article 9'], 'id' => '5',
['id' => '10', 'title' => 'My Article 5',
'title' => 'My Article 10'] ],
[
'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' => [ 'article' => [
'id' => '1', 'id' => '1',
@ -159,18 +181,18 @@ class ExecutorSchemaTest extends TestCase
'pic' => [ 'pic' => [
'url' => 'cdn://123', 'url' => 'cdn://123',
'width' => 640, 'width' => 640,
'height' => 480 'height' => 480,
], ],
'recentArticle' => [ 'recentArticle' => [
'id' => '1', 'id' => '1',
'isPublished' => true, 'isPublished' => true,
'title' => 'My Article 1', 'title' => 'My Article 1',
'body' => 'This is a post', 'body' => 'This is a post',
'keywords' => ['foo', 'bar', '1', 'true', null] 'keywords' => ['foo', 'bar', '1', 'true', null],
] ],
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray()); $this->assertEquals($expected, Executor::execute($BlogSchema, Parser::parse($request))->toArray());
@ -179,7 +201,7 @@ class ExecutorSchemaTest extends TestCase
private function article($id) private function article($id)
{ {
$johnSmith = null; $johnSmith = null;
$article = function($id) use (&$johnSmith) { $article = function ($id) use (&$johnSmith) {
return [ return [
'id' => $id, 'id' => $id,
'isPublished' => 'true', 'isPublished' => 'true',
@ -187,22 +209,24 @@ class ExecutorSchemaTest extends TestCase
'title' => 'My Article ' . $id, 'title' => 'My Article ' . $id,
'body' => 'This is a post', 'body' => 'This is a post',
'hidden' => 'This data is not exposed in the schema', 'hidden' => 'This data is not exposed in the schema',
'keywords' => ['foo', 'bar', 1, true, null] 'keywords' => ['foo', 'bar', 1, true, null],
]; ];
}; };
$getPic = function($uid, $width, $height) { $getPic = function ($uid, $width, $height) {
return [ return [
'url' => "cdn://$uid", 'url' => sprintf('cdn://%s', $uid),
'width' => $width, 'width' => $width,
'height' => $height 'height' => $height,
]; ];
}; };
$johnSmith = [ $johnSmith = [
'id' => 123, 'id' => 123,
'name' => 'John Smith', 'name' => 'John Smith',
'pic' => function($width, $height) use ($getPic) {return $getPic(123, $width, $height);}, 'pic' => function ($width, $height) use ($getPic) {
return $getPic(123, $width, $height);
},
'recentArticle' => $article(1), 'recentArticle' => $article(1),
]; ];

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +1,34 @@
<?php <?php
declare(strict_types=1);
/** /**
* @author: Ivo Meißner * @author: Ivo Meißner
* Date: 03.05.16 * Date: 03.05.16
* Time: 13:14 * Time: 13:14
*/ */
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class LazyInterfaceTest extends TestCase class LazyInterfaceTest extends TestCase
{ {
/** /** @var Schema */
* @var Schema
*/
protected $schema; protected $schema;
/** /** @var InterfaceType */
* @var InterfaceType
*/
protected $lazyInterface; protected $lazyInterface;
/** /** @var ObjectType */
* @var ObjectType
*/
protected $testObject; protected $testObject;
/**
* 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;
}
/** /**
* Handles execution of a lazily created interface * Handles execution of a lazily created interface
*/ */
@ -116,12 +46,78 @@ class LazyInterfaceTest extends TestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'lazyInterface' => [ 'lazyInterface' => ['name' => 'testname'],
'name' => 'testname' ],
]
]
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($request))->toArray()); $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;
}
} }

View File

@ -1,22 +1,21 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ListsTest extends TestCase class ListsTest extends TestCase
{ {
// Describe: Execute: Handles list nullability // Describe: Execute: Handles list nullability
/** /**
* [T] * [T]
*/ */
@ -24,23 +23,57 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ 1, null, 2 ], [1, null, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
null, 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] * [T]
*/ */
@ -48,26 +81,26 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return [1,2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Rejected // Rejected
@ -83,9 +116,9 @@ class ListsTest extends TestCase
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -98,46 +131,53 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
})], }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ],
['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
[ [
new Deferred(function() {return 1;}), new Deferred(function () {
new Deferred(function() {return null;}), return 1;
new Deferred(function() {return 2;}) }),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Contains reject // Contains reject
$this->checkHandlesNullableLists( $this->checkHandlesNullableLists(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
@ -146,9 +186,9 @@ class ListsTest extends TestCase
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -160,32 +200,38 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ 1, null, 2 ], [1, null, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
} }
private function checkHandlesNonNullableLists($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::int()));
$this->check($testType, $testData, $expected, $debug);
}
/** /**
* [T]! * [T]!
*/ */
@ -193,31 +239,31 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
new Deferred(function() { new Deferred(function () {
return [1,2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Returns null // Returns null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -225,7 +271,7 @@ class ListsTest extends TestCase
// Rejected // Rejected
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
@ -235,9 +281,9 @@ class ListsTest extends TestCase
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -250,45 +296,45 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, null, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, null, 2]]]]
); );
// Contains reject // Contains reject
$this->checkHandlesNonNullableLists( $this->checkHandlesNonNullableLists(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
@ -297,9 +343,9 @@ class ListsTest extends TestCase
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -311,21 +357,21 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ 1, null, 2 ], [1, null, 2],
[ [
'data' => [ 'nest' => [ 'test' => null ] ], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -333,10 +379,16 @@ class ListsTest extends TestCase
// Returns null // Returns null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
null, 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!] * [T!]
*/ */
@ -344,41 +396,41 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, 2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ [
'data' => [ 'nest' => [ 'test' => null ] ], 'data' => ['nest' => ['test' => null]],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
// Returns null // Returns null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Rejected // Rejected
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
@ -388,9 +440,9 @@ class ListsTest extends TestCase
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -403,39 +455,45 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
[ [
new Deferred(function() {return 1;}), new Deferred(function () {
new Deferred(function() {return null;}), return 1;
new Deferred(function() {return 2;}) }),
new Deferred(function () {
return null;
}),
new Deferred(function () {
return 2;
}),
], ],
[ 'data' => [ 'nest' => [ 'test' => null ] ] ] ['data' => ['nest' => ['test' => null]]]
); );
// Contains reject // Contains reject
$this->checkHandlesListOfNonNulls( $this->checkHandlesListOfNonNulls(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
@ -444,9 +502,9 @@ class ListsTest extends TestCase
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test', 1] 'path' => ['nest', 'test', 1],
] ],
] ],
] ]
); );
} }
@ -458,22 +516,21 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ 1, 2 ], [1, 2],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ 1, null, 2 ], [1, null, 2],
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10 ]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -482,18 +539,24 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
null, null,
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
} }
public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false)
{
$testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int())));
$this->check($testType, $testData, $expected, $debug);
}
/** /**
* [T!]! * [T!]!
*/ */
@ -501,42 +564,42 @@ class ListsTest extends TestCase
{ {
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, 2]; return [1, 2];
}), }),
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return [1, null, 2]; return [1, null, 2];
}), }),
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
// Returns null // Returns null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [ ['line' => 1, 'column' => 10] ] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -544,19 +607,19 @@ class ListsTest extends TestCase
// Rejected // Rejected
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
function () { function () {
return new Deferred(function() { return new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}); });
}, },
[ [
'data' => ['nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], 'locations' => [['line' => 1, 'column' => 10]],
'path' => ['nest', 'test'] 'path' => ['nest', 'test'],
] ],
] ],
] ]
); );
} }
@ -569,38 +632,38 @@ class ListsTest extends TestCase
// Contains values // Contains values
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ 'data' => [ 'nest' => [ 'test' => [ 1, 2 ] ] ] ] ['data' => ['nest' => ['test' => [1, 2]]]]
); );
// Contains null // Contains null
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
[ [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
return null; return null;
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
], ],
[ [
'data' => [ 'nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.',
'locations' => [['line' => 1, 'column' => 10]] 'locations' => [['line' => 1, 'column' => 10]],
] ],
] ],
], ],
true true
); );
@ -609,83 +672,27 @@ class ListsTest extends TestCase
$this->checkHandlesNonNullListOfNonNulls( $this->checkHandlesNonNullListOfNonNulls(
function () { function () {
return [ return [
new Deferred(function() { new Deferred(function () {
return 1; return 1;
}), }),
new Deferred(function() { new Deferred(function () {
throw new UserError('bad'); throw new UserError('bad');
}), }),
new Deferred(function() { new Deferred(function () {
return 2; return 2;
}) }),
]; ];
}, },
[ [
'data' => ['nest' => null ], 'data' => ['nest' => null],
'errors' => [ 'errors' => [
[ [
'message' => 'bad', 'message' => 'bad',
'locations' => [['line' => 1, 'column' => 10]], '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));
}
} }

View File

@ -1,21 +1,20 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation; use GraphQL\Tests\Executor\TestClasses\Root;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class MutationsTest extends TestCase class MutationsTest extends TestCase
{ {
// Execute: Handles mutation execution ordering // Execute: Handles mutation execution ordering
/** /**
* @see it('evaluates mutations serially') * @see it('evaluates mutations serially')
*/ */
@ -42,26 +41,69 @@ class MutationsTest extends TestCase
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => [ 'first' => ['theNumber' => 1],
'theNumber' => 1 'second' => ['theNumber' => 2],
'third' => ['theNumber' => 3],
'fourth' => ['theNumber' => 4],
'fifth' => ['theNumber' => 5],
], ],
'second' => [
'theNumber' => 2
],
'third' => [
'theNumber' => 3
],
'fourth' => [
'theNumber' => 4
],
'fifth' => [
'theNumber' => 5
]
]
]; ];
$this->assertEquals($expected, $mutationResult->toArray()); $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') * @see it('evaluates mutations correctly in the presense of a failed mutation')
*/ */
@ -91,143 +133,24 @@ class MutationsTest extends TestCase
$mutationResult = Executor::execute($this->schema(), $ast, new Root(6)); $mutationResult = Executor::execute($this->schema(), $ast, new Root(6));
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => [ 'first' => ['theNumber' => 1],
'theNumber' => 1 'second' => ['theNumber' => 2],
],
'second' => [
'theNumber' => 2
],
'third' => null, 'third' => null,
'fourth' => [ 'fourth' => ['theNumber' => 4],
'theNumber' => 4 'fifth' => ['theNumber' => 5],
],
'fifth' => [
'theNumber' => 5
],
'sixth' => null, 'sixth' => null,
], ],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot change the number', 'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 8, 'column' => 7]] 'locations' => [['line' => 8, 'column' => 7]],
], ],
[ [
'debugMessage' => 'Cannot change the number', 'debugMessage' => 'Cannot change the number',
'locations' => [['line' => 17, 'column' => 7]] 'locations' => [['line' => 17, 'column' => 7]],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $mutationResult->toArray(true)); $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");
});
}
} }

View File

@ -1,16 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\FormattedError;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Error\FormattedError;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class NonNullTest extends TestCase class NonNullTest extends TestCase
@ -27,8 +29,13 @@ class NonNullTest extends TestCase
/** @var \Exception */ /** @var \Exception */
public $promiseNonNullError; public $promiseNonNullError;
/** @var callable[] */
public $throwingData; public $throwingData;
/** @var callable[] */
public $nullingData; public $nullingData;
/** @var Schema */
public $schema; public $schema;
public function setUp() public function setUp()
@ -110,7 +117,7 @@ class NonNullTest extends TestCase
$dataType = new ObjectType([ $dataType = new ObjectType([
'name' => 'DataType', 'name' => 'DataType',
'fields' => function() use (&$dataType) { 'fields' => function () use (&$dataType) {
return [ return [
'sync' => ['type' => Type::string()], 'sync' => ['type' => Type::string()],
'syncNonNull' => ['type' => Type::nonNull(Type::string())], 'syncNonNull' => ['type' => Type::nonNull(Type::string())],
@ -121,7 +128,7 @@ class NonNullTest extends TestCase
'promiseNest' => $dataType, 'promiseNest' => $dataType,
'promiseNonNullNest' => Type::nonNull($dataType), 'promiseNonNullNest' => Type::nonNull($dataType),
]; ];
} },
]); ]);
$this->schema = new Schema(['query' => $dataType]); $this->schema = new Schema(['query' => $dataType]);
@ -143,17 +150,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['sync' => null],
'sync' => null,
],
'errors' => [ 'errors' => [
FormattedError::create( FormattedError::create(
$this->syncError->getMessage(), $this->syncError->getMessage(),
[new SourceLocation(3, 9)] [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 public function testNullsANullableFieldThatThrowsInAPromise() : void
@ -167,18 +175,19 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promise' => null],
'promise' => null,
],
'errors' => [ 'errors' => [
FormattedError::create( FormattedError::create(
$this->promiseError->getMessage(), $this->promiseError->getMessage(),
[new SourceLocation(3, 9)] [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 public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsSynchronously() : void
@ -195,14 +204,15 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ '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 public function testNullsAsynchronouslyReturnedObjectThatContainsANonNullableFieldThatThrowsInAPromise() : void
@ -218,15 +228,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ '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 public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsSynchronously() : void
@ -242,15 +253,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null
],
'errors' => [ '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 public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatThrowsInAPromise() : void
@ -266,15 +278,16 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null
],
'errors' => [ '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()
);
} }
/** /**
@ -353,10 +366,13 @@ class NonNullTest extends TestCase
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(17, 11)]), 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(20, 13)]),
FormattedError::create($this->promiseError->getMessage(), [new SourceLocation(24, 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 public function testNullsTheFirstNullableObjectAfterAFieldThrowsInALongChainOfFieldsThatAreNonNull() : void
@ -424,10 +440,13 @@ class NonNullTest extends TestCase
FormattedError::create($this->syncNonNullError->getMessage(), [new SourceLocation(19, 19)]), 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(30, 19)]),
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(41, 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 public function testNullsANullableFieldThatSynchronouslyReturnsNull() : void
@ -441,11 +460,12 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['sync' => null],
'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 public function testNullsANullableFieldThatReturnsNullInAPromise() : void
@ -459,12 +479,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promise' => null],
'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 public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullSynchronously() : void
@ -480,17 +501,18 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', '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 public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() : void
@ -506,15 +528,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['syncNest' => null],
'syncNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
],
], ],
]
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -536,15 +556,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
],
], ],
]
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -566,15 +584,13 @@ class NonNullTest extends TestCase
$ast = Parser::parse($doc); $ast = Parser::parse($doc);
$expected = [ $expected = [
'data' => [ 'data' => ['promiseNest' => null],
'promiseNest' => null,
],
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 4, 'column' => 11]] 'locations' => [['line' => 4, 'column' => 11]],
],
], ],
]
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -628,7 +644,7 @@ class NonNullTest extends TestCase
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
] ],
], ],
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
@ -640,9 +656,9 @@ class NonNullTest extends TestCase
'promiseNest' => [ 'promiseNest' => [
'sync' => null, 'sync' => null,
'promise' => null, 'promise' => null,
] ],
] ],
] ],
]; ];
$actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(); $actual = Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray();
@ -710,11 +726,11 @@ class NonNullTest extends TestCase
'anotherPromiseNest' => null, 'anotherPromiseNest' => null,
], ],
'errors' => [ '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' => 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.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' => 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.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]],
] ],
]; ];
$this->assertArraySubset( $this->assertArraySubset(
@ -734,8 +750,8 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'errors' => [ '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); $this->assertArraySubset($expected, $actual);
@ -752,10 +768,13 @@ class NonNullTest extends TestCase
$expected = [ $expected = [
'errors' => [ 'errors' => [
FormattedError::create($this->promiseNonNullError->getMessage(), [new SourceLocation(2, 17)]), 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 public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void
@ -769,9 +788,9 @@ class NonNullTest extends TestCase
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.',
'locations' => [['line' => 2, 'column' => 17]] 'locations' => [['line' => 2, 'column' => 17]],
],
], ],
]
]; ];
$this->assertArraySubset( $this->assertArraySubset(
$expected, $expected,
@ -791,9 +810,9 @@ class NonNullTest extends TestCase
'errors' => [ 'errors' => [
[ [
'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.',
'locations' => [['line' => 2, 'column' => 17]] 'locations' => [['line' => 2, 'column' => 17]],
],
], ],
]
]; ];
$this->assertArraySubset( $this->assertArraySubset(

View File

@ -1,17 +1,17 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise; namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter; use GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use React\Promise\Deferred; use React\Promise\Deferred;
use React\Promise\FulfilledPromise; use React\Promise\FulfilledPromise;
use React\Promise\LazyPromise; use React\Promise\LazyPromise;
use React\Promise\Promise as ReactPromise; use React\Promise\Promise as ReactPromise;
use React\Promise\RejectedPromise; use React\Promise\RejectedPromise;
use function class_exists;
/** /**
* @group ReactPromise * @group ReactPromise
@ -20,27 +20,35 @@ class ReactPromiseAdapterTest extends TestCase
{ {
public function setUp() public function setUp()
{ {
if(! class_exists('React\Promise\Promise')) { if (class_exists('React\Promise\Promise')) {
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest'); return;
} }
$this->markTestSkipped('react/promise package must be installed to run GraphQL\Tests\Executor\Promise\ReactPromiseAdapterTest');
} }
public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void
{ {
$reactAdapter = new ReactPromiseAdapter(); $reactAdapter = new ReactPromiseAdapter();
$this->assertSame(true, $reactAdapter->isThenable(new ReactPromise(function() {}))); $this->assertTrue(
$this->assertSame(true, $reactAdapter->isThenable(new FulfilledPromise())); $reactAdapter->isThenable(new ReactPromise(function () {
$this->assertSame(true, $reactAdapter->isThenable(new RejectedPromise())); }))
$this->assertSame(true, $reactAdapter->isThenable(new LazyPromise(function() {}))); );
$this->assertSame(false, $reactAdapter->isThenable(false)); $this->assertTrue($reactAdapter->isThenable(new FulfilledPromise()));
$this->assertSame(false, $reactAdapter->isThenable(true)); $this->assertTrue($reactAdapter->isThenable(new RejectedPromise()));
$this->assertSame(false, $reactAdapter->isThenable(1)); $this->assertTrue(
$this->assertSame(false, $reactAdapter->isThenable(0)); $reactAdapter->isThenable(new LazyPromise(function () {
$this->assertSame(false, $reactAdapter->isThenable('test')); }))
$this->assertSame(false, $reactAdapter->isThenable('')); );
$this->assertSame(false, $reactAdapter->isThenable([])); $this->assertFalse($reactAdapter->isThenable(false));
$this->assertSame(false, $reactAdapter->isThenable(new \stdClass())); $this->assertFalse($reactAdapter->isThenable(true));
$this->assertFalse($reactAdapter->isThenable(1));
$this->assertFalse($reactAdapter->isThenable(0));
$this->assertFalse($reactAdapter->isThenable('test'));
$this->assertFalse($reactAdapter->isThenable(''));
$this->assertFalse($reactAdapter->isThenable([]));
$this->assertFalse($reactAdapter->isThenable(new \stdClass()));
} }
public function testConvertsReactPromisesToGraphQlOnes() : void public function testConvertsReactPromisesToGraphQlOnes() : void
@ -62,9 +70,12 @@ class ReactPromiseAdapterTest extends TestCase
$result = null; $result = null;
$resultPromise = $reactAdapter->then($promise, function ($value) use (&$result) { $resultPromise = $reactAdapter->then(
$promise,
function ($value) use (&$result) {
$result = $value; $result = $value;
}); }
);
$this->assertSame(1, $result); $this->assertSame(1, $result);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise);
@ -117,9 +128,12 @@ class ReactPromiseAdapterTest extends TestCase
$exception = null; $exception = null;
$rejectedPromise->then(null, function ($error) use (&$exception) { $rejectedPromise->then(
null,
function ($error) use (&$exception) {
$exception = $error; $exception = $error;
}); }
);
$this->assertInstanceOf('\Exception', $exception); $this->assertInstanceOf('\Exception', $exception);
$this->assertEquals('I am a bad promise', $exception->getMessage()); $this->assertEquals('I am a bad promise', $exception->getMessage());

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise; namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Deferred; use GraphQL\Deferred;
@ -10,9 +13,7 @@ use PHPUnit\Framework\TestCase;
class SyncPromiseAdapterTest extends TestCase class SyncPromiseAdapterTest extends TestCase
{ {
/** /** @var SyncPromiseAdapter */
* @var SyncPromiseAdapter
*/
private $promises; private $promises;
public function setUp() public function setUp()
@ -22,7 +23,11 @@ class SyncPromiseAdapterTest extends TestCase
public function testIsThenable() : void public function testIsThenable() : void
{ {
$this->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(false));
$this->assertEquals(false, $this->promises->isThenable(true)); $this->assertEquals(false, $this->promises->isThenable(true));
$this->assertEquals(false, $this->promises->isThenable(1)); $this->assertEquals(false, $this->promises->isThenable(1));
@ -35,7 +40,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testConvert() : void public function testConvert() : void
{ {
$dfd = new Deferred(function() {}); $dfd = new Deferred(function () {
});
$result = $this->promises->convertThenable($dfd); $result = $this->promises->convertThenable($dfd);
$this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result); $this->assertInstanceOf('GraphQL\Executor\Promise\Promise', $result);
@ -48,7 +54,8 @@ class SyncPromiseAdapterTest extends TestCase
public function testThen() : void public function testThen() : void
{ {
$dfd = new Deferred(function() {}); $dfd = new Deferred(function () {
});
$promise = $this->promises->convertThenable($dfd); $promise = $this->promises->convertThenable($dfd);
$result = $this->promises->then($promise); $result = $this->promises->then($promise);
@ -59,18 +66,55 @@ class SyncPromiseAdapterTest extends TestCase
public function testCreatePromise() : void 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\Promise', $promise);
$this->assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise); $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'); $resolve('A');
}); });
$this->assertValidPromise($promise, null, 'A', SyncPromise::FULFILLED); $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 public function testCreateFulfilledPromise() : void
{ {
$promise = $this->promises->createFulfilled('test'); $promise = $this->promises->createFulfilled('test');
@ -94,8 +138,8 @@ class SyncPromiseAdapterTest extends TestCase
$promise1 = new SyncPromise(); $promise1 = new SyncPromise();
$promise2 = new SyncPromise(); $promise2 = new SyncPromise();
$promise3 = $promise2->then( $promise3 = $promise2->then(
function($value) { function ($value) {
return $value .'-value3'; return $value . '-value3';
} }
); );
@ -105,7 +149,7 @@ class SyncPromiseAdapterTest extends TestCase
new Promise($promise2, $this->promises), new Promise($promise2, $this->promises),
3, 3,
new Promise($promise3, $this->promises), new Promise($promise3, $this->promises),
[] [],
]; ];
$promise = $this->promises->all($data); $promise = $this->promises->all($data);
@ -114,36 +158,46 @@ class SyncPromiseAdapterTest extends TestCase
$promise1->resolve('value1'); $promise1->resolve('value1');
$this->assertValidPromise($promise, null, null, SyncPromise::PENDING); $this->assertValidPromise($promise, null, null, SyncPromise::PENDING);
$promise2->resolve('value2'); $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 public function testWait() : void
{ {
$called = []; $called = [];
$deferred1 = new Deferred(function() use (&$called) { $deferred1 = new Deferred(function () use (&$called) {
$called[] = 1; $called[] = 1;
return 1; return 1;
}); });
$deferred2 = new Deferred(function() use (&$called) { $deferred2 = new Deferred(function () use (&$called) {
$called[] = 2; $called[] = 2;
return 2; return 2;
}); });
$p1 = $this->promises->convertThenable($deferred1); $p1 = $this->promises->convertThenable($deferred1);
$p2 = $this->promises->convertThenable($deferred2); $p2 = $this->promises->convertThenable($deferred2);
$p3 = $p2->then(function() use (&$called) { $p3 = $p2->then(function () use (&$called) {
$dfd = new Deferred(function() use (&$called) { $dfd = new Deferred(function () use (&$called) {
$called[] = 3; $called[] = 3;
return 3; return 3;
}); });
return $this->promises->convertThenable($dfd); return $this->promises->convertThenable($dfd);
}); });
$p4 = $p3->then(function() use (&$called) { $p4 = $p3->then(function () use (&$called) {
return new Deferred(function() use (&$called) { return new Deferred(function () use (&$called) {
$called[] = 4; $called[] = 4;
return 4; return 4;
}); });
}); });
@ -156,46 +210,10 @@ class SyncPromiseAdapterTest extends TestCase
$this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state); $this->assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state);
$this->assertEquals([1, 2], $called); $this->assertEquals([1, 2], $called);
$expectedResult = [0,1,2,3,4]; $expectedResult = [0, 1, 2, 3, 4];
$result = $this->promises->wait($all); $result = $this->promises->wait($all);
$this->assertEquals($expectedResult, $result); $this->assertEquals($expectedResult, $result);
$this->assertEquals([1, 2, 3, 4], $called); $this->assertEquals([1, 2, 3, 4], $called);
$this->assertValidPromise($all, null, [0,1,2,3,4], SyncPromise::FULFILLED); $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);
} }
} }

View File

@ -1,25 +1,32 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\Promise; namespace GraphQL\Tests\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise; use GraphQL\Executor\Promise\Adapter\SyncPromise;
use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\Error\Error;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function uniqid;
class SyncPromiseTest extends TestCase class SyncPromiseTest extends TestCase
{ {
public function getFulfilledPromiseResolveData() public function getFulfilledPromiseResolveData()
{ {
$onFulfilledReturnsNull = function() { $onFulfilledReturnsNull = function () {
return null; return null;
}; };
$onFulfilledReturnsSameValue = function($value) {
$onFulfilledReturnsSameValue = function ($value) {
return $value; return $value;
}; };
$onFulfilledReturnsOtherValue = function($value) {
$onFulfilledReturnsOtherValue = function ($value) {
return 'other-' . $value; return 'other-' . $value;
}; };
$onFulfilledThrows = function($value) {
throw new \Exception("onFulfilled throws this!"); $onFulfilledThrows = function ($value) {
throw new \Exception('onFulfilled throws this!');
}; };
return [ return [
@ -28,7 +35,7 @@ class SyncPromiseTest extends TestCase
[uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED], [uniqid(), $onFulfilledReturnsNull, null, null, SyncPromise::FULFILLED],
['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED], ['test-value', $onFulfilledReturnsSameValue, 'test-value', null, SyncPromise::FULFILLED],
['test-value-2', $onFulfilledReturnsOtherValue, 'other-test-value-2', null, SyncPromise::FULFILLED], ['test-value-2', $onFulfilledReturnsOtherValue, 'other-test-value-2', null, SyncPromise::FULFILLED],
['test-value-3', $onFulfilledThrows, null, "onFulfilled throws this!", SyncPromise::REJECTED], ['test-value-3', $onFulfilledThrows, null, 'onFulfilled throws this!', SyncPromise::REJECTED],
]; ];
} }
@ -41,8 +48,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -63,8 +69,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -85,21 +90,27 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
$promise->resolve($resolvedValue); $promise->resolve($resolvedValue);
$this->assertEquals(SyncPromise::FULFILLED, $promise->state); $this->assertEquals(SyncPromise::FULFILLED, $promise->state);
$nextPromise = $promise->then(null, function() {}); $nextPromise = $promise->then(
null,
function () {
}
);
$this->assertSame($promise, $nextPromise); $this->assertSame($promise, $nextPromise);
$onRejectedCalled = false; $onRejectedCalled = false;
$nextPromise = $promise->then($onFulfilled, function () use (&$onRejectedCalled) { $nextPromise = $promise->then(
$onFulfilled,
function () use (&$onRejectedCalled) {
$onRejectedCalled = true; $onRejectedCalled = true;
}); }
);
if ($onFulfilled) { if ($onFulfilled) {
$this->assertNotSame($promise, $nextPromise); $this->assertNotSame($promise, $nextPromise);
@ -124,19 +135,57 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, $expectedNextReason, $expectedNextValue, $expectedNextState); $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() public function getRejectedPromiseData()
{ {
$onRejectedReturnsNull = function() { $onRejectedReturnsNull = function () {
return null; return null;
}; };
$onRejectedReturnsSomeValue = function($reason) {
$onRejectedReturnsSomeValue = function ($reason) {
return 'some-value'; return 'some-value';
}; };
$onRejectedThrowsSameReason = function($reason) {
$onRejectedThrowsSameReason = function ($reason) {
throw $reason; throw $reason;
}; };
$onRejectedThrowsOtherReason = function($value) {
throw new \Exception("onRejected throws other!"); $onRejectedThrowsOtherReason = function ($value) {
throw new \Exception('onRejected throws other!');
}; };
return [ return [
@ -158,8 +207,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -169,7 +217,6 @@ class SyncPromiseTest extends TestCase
$this->expectException(\Throwable::class); $this->expectException(\Throwable::class);
$this->expectExceptionMessage('Cannot change rejection reason'); $this->expectExceptionMessage('Cannot change rejection reason');
$promise->reject(new \Exception('other-reason')); $promise->reject(new \Exception('other-reason'));
} }
/** /**
@ -181,8 +228,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -203,8 +249,7 @@ class SyncPromiseTest extends TestCase
$expectedNextValue, $expectedNextValue,
$expectedNextReason, $expectedNextReason,
$expectedNextState $expectedNextState
) ) {
{
$promise = new SyncPromise(); $promise = new SyncPromise();
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
@ -214,18 +259,22 @@ class SyncPromiseTest extends TestCase
try { try {
$promise->reject(new \Exception('other-reason')); $promise->reject(new \Exception('other-reason'));
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot change rejection reason', $e->getMessage()); $this->assertEquals('Cannot change rejection reason', $e->getMessage());
} }
try { try {
$promise->resolve('anything'); $promise->resolve('anything');
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot resolve rejected promise', $e->getMessage()); $this->assertEquals('Cannot resolve rejected promise', $e->getMessage());
} }
$nextPromise = $promise->then(function() {}, null); $nextPromise = $promise->then(
function () {
},
null
);
$this->assertSame($promise, $nextPromise); $this->assertSame($promise, $nextPromise);
$onFulfilledCalled = false; $onFulfilledCalled = false;
@ -266,7 +315,7 @@ class SyncPromiseTest extends TestCase
try { try {
$promise->resolve($promise); $promise->resolve($promise);
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->assertEquals('Cannot resolve promise with self', $e->getMessage()); $this->assertEquals('Cannot resolve promise with self', $e->getMessage());
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
} }
@ -299,24 +348,25 @@ class SyncPromiseTest extends TestCase
throw $e; throw $e;
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state); $this->assertEquals(SyncPromise::PENDING, $promise->state);
} catch (\Exception $e) {
$this->assertEquals(SyncPromise::PENDING, $promise->state);
} }
$promise->reject(new \Exception("Rejected Reason")); $promise->reject(new \Exception('Rejected Reason'));
$this->assertValidPromise($promise, "Rejected Reason", null, SyncPromise::REJECTED); $this->assertValidPromise($promise, 'Rejected Reason', null, SyncPromise::REJECTED);
$promise = new SyncPromise(); $promise = new SyncPromise();
$promise2 = $promise->then(null, function() { $promise2 = $promise->then(
null,
function () {
return 'value'; return 'value';
}); }
$promise->reject(new \Exception("Rejected Again")); );
$promise->reject(new \Exception('Rejected Again'));
$this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED); $this->assertValidPromise($promise2, null, 'value', SyncPromise::FULFILLED);
$promise = new SyncPromise(); $promise = new SyncPromise();
$promise2 = $promise->then(); $promise2 = $promise->then();
$promise->reject(new \Exception("Rejected Once Again")); $promise->reject(new \Exception('Rejected Once Again'));
$this->assertValidPromise($promise2, "Rejected Once Again", null, SyncPromise::REJECTED); $this->assertValidPromise($promise2, 'Rejected Once Again', null, SyncPromise::REJECTED);
} }
public function testPendingPromiseThen() : void public function testPendingPromiseThen() : void
@ -332,11 +382,13 @@ class SyncPromiseTest extends TestCase
// Make sure that it queues derivative promises until resolution: // Make sure that it queues derivative promises until resolution:
$onFulfilledCount = 0; $onFulfilledCount = 0;
$onRejectedCount = 0; $onRejectedCount = 0;
$onFulfilled = function($value) use (&$onFulfilledCount) { $onFulfilled = function ($value) use (&$onFulfilledCount) {
$onFulfilledCount++; $onFulfilledCount++;
return $onFulfilledCount; return $onFulfilledCount;
}; };
$onRejected = function($reason) use (&$onRejectedCount) {
$onRejected = function ($reason) use (&$onRejectedCount) {
$onRejectedCount++; $onRejectedCount++;
throw $reason; throw $reason;
}; };
@ -367,35 +419,4 @@ class SyncPromiseTest extends TestCase
$this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED); $this->assertValidPromise($nextPromise3, null, 2, SyncPromise::FULFILLED);
$this->assertValidPromise($nextPromise4, null, 3, 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);
}
} }

View File

@ -1,30 +1,22 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Adder;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
require_once __DIR__ . '/TestClasses.php';
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_encode;
use function uniqid;
class ResolveTest extends TestCase class ResolveTest extends TestCase
{ {
// Execute: resolve function // Execute: resolve function
private function buildSchema($testField)
{
return new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'test' => $testField
]
])
]);
}
/** /**
* @see it('default function accesses properties') * @see it('default function accesses properties')
*/ */
@ -32,9 +24,7 @@ class ResolveTest extends TestCase
{ {
$schema = $this->buildSchema(['type' => Type::string()]); $schema = $this->buildSchema(['type' => Type::string()]);
$source = [ $source = ['test' => 'testValue'];
'test' => 'testValue'
];
$this->assertEquals( $this->assertEquals(
['data' => ['test' => 'testValue']], ['data' => ['test' => 'testValue']],
@ -42,6 +32,16 @@ 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') * @see it('default function calls methods')
*/ */
@ -51,9 +51,9 @@ class ResolveTest extends TestCase
$_secret = 'secretValue' . uniqid(); $_secret = 'secretValue' . uniqid();
$source = [ $source = [
'test' => function() use ($_secret) { 'test' => function () use ($_secret) {
return $_secret; return $_secret;
} },
]; ];
$this->assertEquals( $this->assertEquals(
['data' => ['test' => $_secret]], ['data' => ['test' => $_secret]],
@ -69,7 +69,7 @@ class ResolveTest extends TestCase
$schema = $this->buildSchema([ $schema = $this->buildSchema([
'type' => Type::int(), 'type' => Type::int(),
'args' => [ 'args' => [
'addend1' => [ 'type' => Type::int() ], 'addend1' => ['type' => Type::int()],
], ],
]); ]);
@ -92,7 +92,7 @@ class ResolveTest extends TestCase
], ],
'resolve' => function ($source, $args) { 'resolve' => function ($source, $args) {
return json_encode([$source, $args]); return json_encode([$source, $args]);
} },
]); ]);
$this->assertEquals( $this->assertEquals(

View File

@ -1,8 +1,10 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError; use GraphQL\Error\FormattedError;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
@ -36,7 +38,7 @@ class SyncTest extends TestCase
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return $rootValue; return $rootValue;
} },
], ],
'asyncField' => [ 'asyncField' => [
'type' => Type::string(), 'type' => Type::string(),
@ -44,9 +46,9 @@ class SyncTest extends TestCase
return new Deferred(function () use ($rootValue) { return new Deferred(function () use ($rootValue) {
return $rootValue; return $rootValue;
}); });
} },
] ],
] ],
]), ]),
'mutation' => new ObjectType([ 'mutation' => new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
@ -55,11 +57,12 @@ class SyncTest extends TestCase
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($rootValue) { 'resolve' => function ($rootValue) {
return $rootValue; return $rootValue;
} },
] ],
] ],
]) ]),
]); ]);
$this->promiseAdapter = new SyncPromiseAdapter(); $this->promiseAdapter = new SyncPromiseAdapter();
} }
@ -79,6 +82,26 @@ class SyncTest extends TestCase
$this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result); $this->assertSync(['errors' => [['message' => 'Must provide an operation.']]], $result);
} }
private function execute($schema, $doc, $rootValue = null)
{
return Executor::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
}
private function assertSync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was synchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$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(),
false,
$message
);
}
/** /**
* @see it('does not return a Promise if fields are all synchronous') * @see it('does not return a Promise if fields are all synchronous')
*/ */
@ -93,6 +116,8 @@ class SyncTest extends TestCase
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result); $this->assertSync(['data' => ['syncField' => 'rootValue']], $result);
} }
// Describe: graphqlSync
/** /**
* @see it('does not return a Promise if mutation fields are all synchronous') * @see it('does not return a Promise if mutation fields are all synchronous')
*/ */
@ -121,7 +146,16 @@ class SyncTest extends TestCase
$this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result); $this->assertAsync(['data' => ['syncField' => 'rootValue', 'asyncField' => 'rootValue']], $result);
} }
// Describe: graphqlSync private function assertAsync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was asynchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$this->assertEquals(SyncPromise::PENDING, $actualResult->adoptedPromise->state, $message);
$resolvedResult = $this->promiseAdapter->wait($actualResult);
$this->assertInstanceOf(ExecutionResult::class, $resolvedResult, $message);
$this->assertArraySubset($expectedFinalArray, $resolvedResult->toArray(), false, $message);
}
/** /**
* @see it('does not return a Promise for syntax errors') * @see it('does not return a Promise for syntax errors')
@ -133,12 +167,22 @@ class SyncTest extends TestCase
$this->schema, $this->schema,
$doc $doc
); );
$this->assertSync([ $this->assertSync(
[
'errors' => [ 'errors' => [
['message' => 'Syntax Error: Expected Name, found {', [
'locations' => [['line' => 1, 'column' => 29]]] 'message' => 'Syntax Error: Expected Name, found {',
] 'locations' => [['line' => 1, 'column' => 29]],
], $result); ],
],
],
$result
);
}
private function graphqlSync($schema, $doc, $rootValue = null)
{
return GraphQL::promiseToExecute($this->promiseAdapter, $schema, $doc, $rootValue);
} }
/** /**
@ -153,9 +197,12 @@ class SyncTest extends TestCase
$doc $doc
); );
$expected = [ $expected = [
'errors' => Utils::map($validationErrors, function ($e) { 'errors' => Utils::map(
$validationErrors,
function ($e) {
return FormattedError::createFromException($e); return FormattedError::createFromException($e);
}) }
),
]; ];
$this->assertSync($expected, $result); $this->assertSync($expected, $result);
} }
@ -173,35 +220,4 @@ class SyncTest extends TestCase
); );
$this->assertSync(['data' => ['syncField' => 'rootValue']], $result); $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);
}
private function assertSync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was synchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$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);
}
private function assertAsync($expectedFinalArray, $actualResult)
{
$message = 'Failed assertion that execution was asynchronous';
$this->assertInstanceOf(Promise::class, $actualResult, $message);
$this->assertInstanceOf(SyncPromise::class, $actualResult->adoptedPromise, $message);
$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);
}
} }

View File

@ -1,128 +0,0 @@
<?php
namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
class Dog
{
function __construct($name, $woofs)
{
$this->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'];
};
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Adder
{
/** @var float */
public $num;
/** @var callable */
public $test;
public function __construct(float $num)
{
$this->num = $num;
$this->test = function ($source, $args, $context) {
return $this->num + $args['addend1'] + $context['addend2'];
};
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Cat
{
/** @var string */
public $name;
/** @var bool */
public $meows;
public function __construct(string $name, bool $meows)
{
$this->name = $name;
$this->meows = $meows;
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
use GraphQL\Error\Error;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
class ComplexScalar extends ScalarType
{
/** @var string */
public $name = 'ComplexScalar';
public static function create() : self
{
return new self();
}
/**
* {@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));
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Dog
{
/** @var string */
public $name;
/** @var bool */
public $woofs;
public function __construct(string $name, bool $woofs)
{
$this->name = $name;
$this->woofs = $woofs;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Human
{
/** @var string */
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class NotSpecial
{
/** @var string */
public $value;
/**
* @param string $value
*/
public function __construct($value)
{
$this->value = $value;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class NumberHolder
{
/** @var float */
public $theNumber;
public function __construct(float $originalNumber)
{
$this->theNumber = $originalNumber;
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Person
{
/** @var string */
public $name;
/** @var (Dog|Cat)[]|null */
public $pets;
/** @var (Dog|Cat|Person)[]|null */
public $friends;
/**
* @param (Cat|Dog)[]|null $pets
* @param (Cat|Dog|Person)[]|null $friends
*/
public function __construct(string $name, $pets = null, $friends = null)
{
$this->name = $name;
$this->pets = $pets;
$this->friends = $friends;
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
use GraphQL\Deferred;
class Root
{
/** @var NumberHolder */
public $numberHolder;
public function __construct(float $originalNumber)
{
$this->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();
});
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor\TestClasses;
class Special
{
/** @var string */
public $value;
/**
* @param string $value
*/
public function __construct($value)
{
$this->value = $value;
}
}

View File

@ -1,26 +1,38 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Error\Warning;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\Cat;
use GraphQL\Tests\Executor\TestClasses\Dog;
use GraphQL\Tests\Executor\TestClasses\Person;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class UnionInterfaceTest extends TestCase class UnionInterfaceTest extends TestCase
{ {
/** @var */
public $schema; public $schema;
/** @var Cat */
public $garfield; public $garfield;
/** @var Dog */
public $odie; public $odie;
/** @var Person */
public $liz; public $liz;
/** @var Person */
public $john; public $john;
public function setUp() public function setUp()
@ -28,8 +40,8 @@ class UnionInterfaceTest extends TestCase
$NamedType = new InterfaceType([ $NamedType = new InterfaceType([
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
] ],
]); ]);
$DogType = new ObjectType([ $DogType = new ObjectType([
@ -37,11 +49,11 @@ class UnionInterfaceTest extends TestCase
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'woofs' => ['type' => Type::boolean()] 'woofs' => ['type' => Type::boolean()],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Dog; return $value instanceof Dog;
} },
]); ]);
$CatType = new ObjectType([ $CatType = new ObjectType([
@ -49,11 +61,11 @@ class UnionInterfaceTest extends TestCase
'interfaces' => [$NamedType], 'interfaces' => [$NamedType],
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'meows' => ['type' => Type::boolean()] 'meows' => ['type' => Type::boolean()],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Cat; return $value instanceof Cat;
} },
]); ]);
$PetType = new UnionType([ $PetType = new UnionType([
@ -66,7 +78,7 @@ class UnionInterfaceTest extends TestCase
if ($value instanceof Cat) { if ($value instanceof Cat) {
return $CatType; return $CatType;
} }
} },
]); ]);
$PersonType = new ObjectType([ $PersonType = new ObjectType([
@ -75,23 +87,22 @@ class UnionInterfaceTest extends TestCase
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pets' => ['type' => Type::listOf($PetType)], 'pets' => ['type' => Type::listOf($PetType)],
'friends' => ['type' => Type::listOf($NamedType)] 'friends' => ['type' => Type::listOf($NamedType)],
], ],
'isTypeOf' => function ($value) { 'isTypeOf' => function ($value) {
return $value instanceof Person; return $value instanceof Person;
} },
]); ]);
$this->schema = new Schema([ $this->schema = new Schema([
'query' => $PersonType, 'query' => $PersonType,
'types' => [ $PetType ] 'types' => [$PetType],
]); ]);
$this->garfield = new Cat('Garfield', false); $this->garfield = new Cat('Garfield', false);
$this->odie = new Dog('Odie', true); $this->odie = new Dog('Odie', true);
$this->liz = new Person('Liz'); $this->liz = new Person('Liz');
$this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]); $this->john = new Person('John', [$this->garfield, $this->odie], [$this->liz, $this->odie]);
} }
// Execute: Union and intersection types // Execute: Union and intersection types
@ -101,7 +112,6 @@ class UnionInterfaceTest extends TestCase
*/ */
public function testCanIntrospectOnUnionAndIntersectionTypes() : void public function testCanIntrospectOnUnionAndIntersectionTypes() : void
{ {
$ast = Parser::parse(' $ast = Parser::parse('
{ {
Named: __type(name: "Named") { Named: __type(name: "Named") {
@ -131,16 +141,16 @@ class UnionInterfaceTest extends TestCase
'kind' => 'INTERFACE', 'kind' => 'INTERFACE',
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
['name' => 'name'] ['name' => 'name'],
], ],
'interfaces' => null, 'interfaces' => null,
'possibleTypes' => [ 'possibleTypes' => [
['name' => 'Person'], ['name' => 'Person'],
['name' => 'Dog'], ['name' => 'Dog'],
['name' => 'Cat'] ['name' => 'Cat'],
], ],
'enumValues' => null, 'enumValues' => null,
'inputFields' => null 'inputFields' => null,
], ],
'Pet' => [ 'Pet' => [
'kind' => 'UNION', 'kind' => 'UNION',
@ -149,12 +159,12 @@ class UnionInterfaceTest extends TestCase
'interfaces' => null, 'interfaces' => null,
'possibleTypes' => [ 'possibleTypes' => [
['name' => 'Dog'], ['name' => 'Dog'],
['name' => 'Cat'] ['name' => 'Cat'],
], ],
'enumValues' => null, 'enumValues' => null,
'inputFields' => null 'inputFields' => null,
] ],
] ],
]; ];
$this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray()); $this->assertEquals($expected, Executor::execute($this->schema, $ast)->toArray());
} }
@ -183,9 +193,9 @@ class UnionInterfaceTest extends TestCase
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -220,10 +230,10 @@ class UnionInterfaceTest extends TestCase
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
} }
@ -252,9 +262,9 @@ class UnionInterfaceTest extends TestCase
'name' => 'John', 'name' => 'John',
'friends' => [ 'friends' => [
['__typename' => 'Person', 'name' => 'Liz'], ['__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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -288,9 +298,9 @@ class UnionInterfaceTest extends TestCase
'name' => 'John', 'name' => 'John',
'friends' => [ 'friends' => [
['__typename' => 'Person', 'name' => 'Liz'], ['__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)); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true));
@ -339,13 +349,13 @@ class UnionInterfaceTest extends TestCase
'name' => 'John', 'name' => 'John',
'pets' => [ 'pets' => [
['__typename' => 'Cat', 'name' => 'Garfield', 'meows' => false], ['__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' => '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()); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray());
@ -364,14 +374,25 @@ class UnionInterfaceTest extends TestCase
$NamedType2 = new InterfaceType([ $NamedType2 = new InterfaceType([
'name' => 'Named', 'name' => 'Named',
'fields' => [ 'fields' => [
'name' => ['type' => Type::string()] 'name' => ['type' => Type::string()],
], ],
'resolveType' => function ($obj, $context, ResolveInfo $info) use (&$encounteredContext, &$encounteredSchema, &$encounteredRootValue, &$PersonType2) { 'resolveType' => function (
$obj,
$context,
ResolveInfo $info
) use (
&$encounteredContext,
&
$encounteredSchema,
&$encounteredRootValue,
&$PersonType2
) {
$encounteredContext = $context; $encounteredContext = $context;
$encounteredSchema = $info->schema; $encounteredSchema = $info->schema;
$encounteredRootValue = $info->rootValue; $encounteredRootValue = $info->rootValue;
return $PersonType2; return $PersonType2;
} },
]); ]);
$PersonType2 = new ObjectType([ $PersonType2 = new ObjectType([
@ -383,9 +404,7 @@ class UnionInterfaceTest extends TestCase
], ],
]); ]);
$schema2 = new Schema([ $schema2 = new Schema(['query' => $PersonType2]);
'query' => $PersonType2
]);
$john2 = new Person('John', [], [$this->liz]); $john2 = new Person('John', [], [$this->liz]);

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Executor\Values; use GraphQL\Executor\Values;
@ -10,19 +13,97 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function var_export;
use const PHP_EOL;
class ValuesTest extends TestCase class ValuesTest extends TestCase
{ {
/** @var Schema */
private static $schema;
public function testGetIDVariableValues() : void public function testGetIDVariableValues() : void
{ {
$this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']); $this->expectInputVariablesMatchOutputVariables(['idInput' => '123456789']);
$this->assertEquals( $this->assertEquals(
['errors'=> [], 'coerced' => ['idInput' => '123456789']], ['errors' => [], 'coerced' => ['idInput' => '123456789']],
self::runTestCase(['idInput' => 123456789]), $this->runTestCase(['idInput' => 123456789]),
'Integer ID was not converted to string' '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 public function testGetBooleanVariableValues() : void
{ {
$this->expectInputVariablesMatchOutputVariables(['boolInput' => true]); $this->expectInputVariablesMatchOutputVariables(['boolInput' => true]);
@ -64,11 +145,20 @@ class ValuesTest extends TestCase
$this->expectGraphQLError(['idInput' => true]); $this->expectGraphQLError(['idInput' => true]);
} }
private function expectGraphQLError($variables) : void
{
$result = $this->runTestCase($variables);
$this->assertGreaterThan(0, count($result['errors']));
}
public function testFloatForIDVariableThrowsError() : void public function testFloatForIDVariableThrowsError() : void
{ {
$this->expectGraphQLError(['idInput' => 1.0]); $this->expectGraphQLError(['idInput' => 1.0]);
} }
/**
* Helpers for running test cases and making assertions
*/
public function testStringForBooleanVariableThrowsError() : void public function testStringForBooleanVariableThrowsError() : void
{ {
$this->expectGraphQLError(['boolInput' => 'true']); $this->expectGraphQLError(['boolInput' => 'true']);
@ -98,77 +188,4 @@ class ValuesTest extends TestCase
{ {
$this->expectGraphQLError(['intInput' => -2147483649]); $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);
}
} }

View File

@ -1,16 +1,19 @@
<?php <?php
namespace GraphQL\Tests\Executor;
require_once __DIR__ . '/TestClasses.php'; declare(strict_types=1);
namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Executor\Executor; use GraphQL\Executor\Executor;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Type\Schema; use GraphQL\Tests\Executor\TestClasses\ComplexScalar;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_encode;
/** /**
* Execute: Handles inputs * Execute: Handles inputs
@ -18,7 +21,6 @@ use PHPUnit\Framework\TestCase;
*/ */
class VariablesTest extends TestCase class VariablesTest extends TestCase
{ {
public function testUsingInlineStructs() : void public function testUsingInlineStructs() : void
{ {
// executes with complex input: // executes with complex input:
@ -29,9 +31,7 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => [ 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
'fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -77,8 +77,9 @@ class VariablesTest extends TestCase
'errors' => [[ 'errors' => [[
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].', 'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
'path' => ['fieldWithObjectInput'], 'path' => ['fieldWithObjectInput'],
'locations' => [['line' => 3, 'column' => 39]] 'locations' => [['line' => 3, 'column' => 39]],
]] ],
],
]; ];
$this->assertArraySubset($expected, $result->toArray()); $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 public function testUsingVariables() : void
{ {
$doc = ' $doc = '
@ -119,7 +191,7 @@ class VariablesTest extends TestCase
'); ');
$expected = [ $expected = [
'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'] 'data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}'],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -132,12 +204,10 @@ class VariablesTest extends TestCase
); );
// executes with complex scalar input: // executes with complex scalar input:
$params = [ 'input' => [ 'c' => 'foo', 'd' => 'SerializedValue' ] ]; $params = ['input' => ['c' => 'foo', 'd' => 'SerializedValue']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'data' => [ 'data' => ['fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'],
'fieldWithObjectInput' => '{"c":"foo","d":"DeserializedValue"}'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -152,15 +222,15 @@ class VariablesTest extends TestCase
'{"a":"foo","b":"bar","c":null}; ' . '{"a":"foo","b":"bar","c":null}; ' .
'Expected non-nullable type String! not to be null at value.c.', 'Expected non-nullable type String! not to be null at value.c.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on incorrect type: // errors on incorrect type:
$params = [ 'input' => 'foo bar' ]; $params = ['input' => 'foo bar'];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
@ -168,10 +238,10 @@ class VariablesTest extends TestCase
'message' => 'message' =>
'Variable "$input" got invalid value "foo bar"; ' . 'Variable "$input" got invalid value "foo bar"; ' .
'Expected type TestInputObject to be an object.', 'Expected type TestInputObject to be an object.',
'locations' => [ [ 'line' => 2, 'column' => 21 ] ], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -183,12 +253,12 @@ class VariablesTest extends TestCase
'errors' => [ 'errors' => [
[ [
'message' => 'message' =>
'Variable "$input" got invalid value {"a":"foo","b":"bar"}; '. 'Variable "$input" got invalid value {"a":"foo","b":"bar"}; ' .
'Field value.c of required type String! was not provided.', 'Field value.c of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -198,7 +268,7 @@ class VariablesTest extends TestCase
fieldWithNestedObjectInput(input: $input) fieldWithNestedObjectInput(input: $input)
} }
'; ';
$params = [ 'input' => [ 'na' => [ 'a' => 'foo' ] ] ]; $params = ['input' => ['na' => ['a' => 'foo']]];
$result = $this->executeQuery($nestedDoc, $params); $result = $this->executeQuery($nestedDoc, $params);
$expected = [ $expected = [
@ -216,14 +286,13 @@ class VariablesTest extends TestCase
'Field value.nb of required type String! was not provided.', 'Field value.nb of required type String! was not provided.',
'locations' => [['line' => 2, 'column' => 19]], 'locations' => [['line' => 2, 'column' => 19]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// errors on addition of unknown input field // errors on addition of unknown input field
$params = ['input' => [ 'a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog' ]]; $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz', 'extra' => 'dog']];
$result = $this->executeQuery($doc, $params); $result = $this->executeQuery($doc, $params);
$expected = [ $expected = [
'errors' => [ 'errors' => [
@ -234,14 +303,12 @@ class VariablesTest extends TestCase
'Field "extra" is not defined by type TestInputObject.', 'Field "extra" is not defined by type TestInputObject.',
'locations' => [['line' => 2, 'column' => 21]], 'locations' => [['line' => 2, 'column' => 21]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles nullable scalars
/** /**
* @see it('allows nullable inputs to be omitted') * @see it('allows nullable inputs to be omitted')
*/ */
@ -253,7 +320,7 @@ class VariablesTest extends TestCase
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNullableStringInput' => null] 'data' => ['fieldWithNullableStringInput' => null],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -288,6 +355,9 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles non-nullable scalars
/** /**
* @see it('allows nullable inputs to be set to null in a variable') * @see it('allows nullable inputs to be set to null in a variable')
*/ */
@ -332,9 +402,6 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles non-nullable scalars
/** /**
* @see it('allows non-nullable inputs to be omitted given a default') * @see it('allows non-nullable inputs to be omitted given a default')
*/ */
@ -346,7 +413,7 @@ class VariablesTest extends TestCase
} }
'); ');
$expected = [ $expected = [
'data' => ['fieldWithNonNullableStringInput' => '"default"'] 'data' => ['fieldWithNonNullableStringInput' => '"default"'],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -367,9 +434,9 @@ class VariablesTest extends TestCase
[ [
'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]], 'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -393,8 +460,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type String! not to be null.', 'Expected non-nullable type String! not to be null.',
'locations' => [['line' => 2, 'column' => 31]], 'locations' => [['line' => 2, 'column' => 31]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -442,20 +509,22 @@ class VariablesTest extends TestCase
'data' => ['fieldWithNonNullableStringInput' => null], 'data' => ['fieldWithNonNullableStringInput' => null],
'errors' => [[ 'errors' => [[
'message' => 'Argument "input" of required type "String!" was not provided.', 'message' => 'Argument "input" of required type "String!" was not provided.',
'locations' => [ [ 'line' => 3, 'column' => 9 ] ], 'locations' => [['line' => 3, 'column' => 9]],
'path' => [ 'fieldWithNonNullableStringInput' ], 'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles lists and nullability
/** /**
* @see it('reports error for array passed into string input') * @see it('reports error for array passed into string input')
*/ */
public function testReportsErrorForArrayPassedIntoStringInput() : void public function testReportsErrorForArrayPassedIntoStringInput() : void
{ {
$doc = ' $doc = '
query SetsNonNullable($value: String!) { query SetsNonNullable($value: String!) {
fieldWithNonNullableStringInput(input: $value) fieldWithNonNullableStringInput(input: $value)
@ -471,9 +540,10 @@ class VariablesTest extends TestCase
'String; String cannot represent an array value: [1,2,3]', 'String; String cannot represent an array value: [1,2,3]',
'category' => 'graphql', 'category' => 'graphql',
'locations' => [ 'locations' => [
['line' => 2, 'column' => 31] ['line' => 2, 'column' => 31],
] ],
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -512,13 +582,12 @@ class VariablesTest extends TestCase
'locations' => [['line' => 3, 'column' => 48]], 'locations' => [['line' => 3, 'column' => 48]],
'path' => ['fieldWithNonNullableStringInput'], 'path' => ['fieldWithNonNullableStringInput'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Handles lists and nullability
/** /**
* @see it('allows lists to be null') * @see it('allows lists to be null')
*/ */
@ -560,7 +629,7 @@ class VariablesTest extends TestCase
list(input: $input) 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"]']]; $expected = ['data' => ['list' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -584,8 +653,8 @@ class VariablesTest extends TestCase
'Expected non-nullable type [String]! not to be null.', 'Expected non-nullable type [String]! not to be null.',
'locations' => [['line' => 2, 'column' => 17]], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -615,7 +684,7 @@ class VariablesTest extends TestCase
nnList(input: $input) 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"]']]; $expected = ['data' => ['nnList' => '["A",null,"B"]']];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -667,10 +736,10 @@ class VariablesTest extends TestCase
'message' => 'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].', 'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -692,10 +761,10 @@ class VariablesTest extends TestCase
'message' => 'message' =>
'Variable "$input" got invalid value null; ' . 'Variable "$input" got invalid value null; ' .
'Expected non-nullable type [String!]! not to be null.', 'Expected non-nullable type [String!]! not to be null.',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -715,6 +784,8 @@ class VariablesTest extends TestCase
$this->assertEquals($expected, $result->toArray()); $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') * @see it('does not allow non-null lists of non-nulls to contain null')
*/ */
@ -732,10 +803,10 @@ class VariablesTest extends TestCase
'message' => 'message' =>
'Variable "$input" got invalid value ["A",null,"B"]; ' . 'Variable "$input" got invalid value ["A",null,"B"]; ' .
'Expected non-nullable type String! not to be null at value[1].', 'Expected non-nullable type String! not to be null at value[1].',
'locations' => [ ['line' => 2, 'column' => 17] ], 'locations' => [['line' => 2, 'column' => 17]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -750,7 +821,7 @@ class VariablesTest extends TestCase
fieldWithObjectInput(input: $input) fieldWithObjectInput(input: $input)
} }
'; ';
$vars = [ 'input' => [ 'list' => [ 'A', 'B' ] ] ]; $vars = ['input' => ['list' => ['A', 'B']]];
$result = $this->executeQuery($doc, $vars); $result = $this->executeQuery($doc, $vars);
$expected = [ $expected = [
'errors' => [ 'errors' => [
@ -760,8 +831,8 @@ class VariablesTest extends TestCase
'be used as an input type.', 'be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]], 'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -787,13 +858,12 @@ class VariablesTest extends TestCase
'cannot be used as an input type.', 'cannot be used as an input type.',
'locations' => [['line' => 2, 'column' => 25]], 'locations' => [['line' => 2, 'column' => 25]],
'category' => 'graphql', 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
// Describe: Execute: Uses argument default values
/** /**
* @see it('when no argument provided') * @see it('when no argument provided')
*/ */
@ -838,80 +908,13 @@ class VariablesTest extends TestCase
'errors' => [[ 'errors' => [[
'message' => 'message' =>
'Argument "input" has invalid value WRONG_TYPE.', 'Argument "input" has invalid value WRONG_TYPE.',
'locations' => [ [ 'line' => 2, 'column' => 50 ] ], 'locations' => [['line' => 2, 'column' => 50]],
'path' => [ 'fieldWithDefaultArgumentValue' ], 'path' => ['fieldWithDefaultArgumentValue'],
'category' => 'graphql', 'category' => 'graphql',
]] ],
],
]; ];
$this->assertEquals($expected, $result->toArray()); $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);
}
} }

View File

@ -1,13 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\Lexer; use GraphQL\Language\Lexer;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Language\Token; use GraphQL\Language\Token;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function json_decode;
class LexerTest extends TestCase class LexerTest extends TestCase
{ {
@ -23,6 +28,34 @@ class LexerTest extends TestCase
); );
} }
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
$this->lexOne($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
}
/**
* @param string $body
* @return Token
*/
private function lexOne($body)
{
$lexer = new Lexer(new Source($body));
return $lexer->advance();
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
/** /**
* @see it('accepts BOM header') * @see it('accepts BOM header')
*/ */
@ -33,7 +66,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 2, 'start' => 2,
'end' => 5, 'end' => 5,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo')); $this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo'));
@ -50,7 +83,7 @@ class LexerTest extends TestCase
'end' => 11, 'end' => 11,
'line' => 4, 'line' => 4,
'column' => 3, 'column' => 3,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n")); $this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n"));
} }
@ -70,7 +103,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 6, 'start' => 6,
'end' => 9, 'end' => 9,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($example1)); $this->assertArraySubset($expected, (array) $this->lexOne($example1));
@ -83,7 +116,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 18, 'start' => 18,
'end' => 21, 'end' => 21,
'value' => 'foo' 'value' => 'foo',
]; ];
$this->assertArraySubset($expected, (array) $this->lexOne($example2)); $this->assertArraySubset($expected, (array) $this->lexOne($example2));
@ -91,7 +124,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME, 'kind' => Token::NAME,
'start' => 3, 'start' => 3,
'end' => 6, 'end' => 6,
'value' => 'foo' 'value' => 'foo',
]; ];
$example3 = ',,,foo,,,'; $example3 = ',,,foo,,,';
@ -181,63 +214,86 @@ class LexerTest extends TestCase
*/ */
public function testLexesStrings() : void public function testLexesStrings() : void
{ {
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 8, 'end' => 8,
'value' => 'simple' 'value' => 'simple',
], (array) $this->lexOne('"simple"')); ],
(array) $this->lexOne('"simple"')
);
$this->assertArraySubset(
$this->assertArraySubset([ [
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 15, 'end' => 15,
'value' => ' white space ' 'value' => ' white space ',
], (array) $this->lexOne('" white space "')); ],
(array) $this->lexOne('" white space "')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 10, 'end' => 10,
'value' => 'quote "' 'value' => 'quote "',
], (array) $this->lexOne('"quote \\""')); ],
(array) $this->lexOne('"quote \\""')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 25, 'end' => 25,
'value' => 'escaped \n\r\b\t\f' 'value' => 'escaped \n\r\b\t\f',
], (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')); ],
(array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 16, 'end' => 16,
'value' => 'slashes \\ \/' 'value' => 'slashes \\ \/',
], (array) $this->lexOne('"slashes \\\\ \\\\/"')); ],
(array) $this->lexOne('"slashes \\\\ \\\\/"')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 13, 'end' => 13,
'value' => 'unicode яуц' 'value' => 'unicode яуц',
], (array) $this->lexOne('"unicode яуц"')); ],
(array) $this->lexOne('"unicode яуц"')
);
$unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"'); $unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"');
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 34, 'end' => 34,
'value' => 'unicode ' . $unicode 'value' => 'unicode ' . $unicode,
], (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')); ],
(array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::STRING, 'kind' => Token::STRING,
'start' => 0, 'start' => 0,
'end' => 26, 'end' => 26,
'value' => $unicode 'value' => $unicode,
], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')); ],
(array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')
);
} }
/** /**
@ -245,86 +301,128 @@ class LexerTest extends TestCase
*/ */
public function testLexesBlockString() : void public function testLexesBlockString() : void
{ {
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 12, 'end' => 12,
'value' => 'simple' 'value' => 'simple',
], (array) $this->lexOne('"""simple"""')); ],
(array) $this->lexOne('"""simple"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 19, 'end' => 19,
'value' => ' white space ' 'value' => ' white space ',
], (array) $this->lexOne('""" white space """')); ],
(array) $this->lexOne('""" white space """')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 22, 'end' => 22,
'value' => 'contains " quote' 'value' => 'contains " quote',
], (array) $this->lexOne('"""contains " quote"""')); ],
(array) $this->lexOne('"""contains " quote"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 31, 'end' => 31,
'value' => 'contains """ triplequote' 'value' => 'contains """ triplequote',
], (array) $this->lexOne('"""contains \\""" triplequote"""')); ],
(array) $this->lexOne('"""contains \\""" triplequote"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 16, 'end' => 16,
'value' => "multi\nline" 'value' => "multi\nline",
], (array) $this->lexOne("\"\"\"multi\nline\"\"\"")); ],
(array) $this->lexOne("\"\"\"multi\nline\"\"\"")
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 28, 'end' => 28,
'value' => "multi\nline\nnormalized" 'value' => "multi\nline\nnormalized",
], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")); ],
(array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 32, 'end' => 32,
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234' 'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234',
], (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')); ],
(array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 19, 'end' => 19,
'value' => 'slashes \\\\ \\/' 'value' => 'slashes \\\\ \\/',
], (array) $this->lexOne('"""slashes \\\\ \\/"""')); ],
(array) $this->lexOne('"""slashes \\\\ \\/"""')
);
$this->assertArraySubset([ $this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING, 'kind' => Token::BLOCK_STRING,
'start' => 0, 'start' => 0,
'end' => 68, 'end' => 68,
'value' => "spans\n multiple\n lines" 'value' => "spans\n multiple\n lines",
], (array) $this->lexOne("\"\"\" ],
(array) $this->lexOne('"""
spans spans
multiple multiple
lines lines
\"\"\"")); """')
);
} }
public function reportsUsefulStringErrors() { public function reportsUsefulStringErrors()
{
return [ return [
['"', "Unterminated string.", $this->loc(1, 2)], ['"', 'Unterminated string.', $this->loc(1, 2)],
['"no end quote', "Unterminated string.", $this->loc(1, 14)], ['"no end quote', 'Unterminated string.', $this->loc(1, 14)],
["'single quotes'", "Unexpected single quote character ('), did you mean to use a double quote (\")?", $this->loc(1, 1)], [
['"contains unescaped \u0007 control char"', "Invalid character within String: \"\\u0007\"", $this->loc(1, 21)], "'single quotes'",
"Unexpected single quote character ('), did you mean to use a double quote (\")?",
$this->loc(
1,
1
),
],
[
'"contains unescaped \u0007 control char"',
"Invalid character within String: \"\\u0007\"",
$this->loc(
1,
21
),
],
['"null-byte is not \u0000 end of file"', 'Invalid character within String: "\\u0000"', $this->loc(1, 19)], ['"null-byte is not \u0000 end of file"', 'Invalid character within String: "\\u0000"', $this->loc(1, 19)],
['"multi' . "\n" . 'line"', "Unterminated string.", $this->loc(1, 7)], ['"multi' . "\n" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
['"multi' . "\r" . 'line"', "Unterminated string.", $this->loc(1, 7)], ['"multi' . "\r" . 'line"', 'Unterminated string.', $this->loc(1, 7)],
['"bad \\z esc"', "Invalid character escape sequence: \\z", $this->loc(1, 7)], ['"bad \\z esc"', 'Invalid character escape sequence: \\z', $this->loc(1, 7)],
['"bad \\x esc"', "Invalid character escape sequence: \\x", $this->loc(1, 7)], ['"bad \\x esc"', "Invalid character escape sequence: \\x", $this->loc(1, 7)],
['"bad \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)], ['"bad \\u1 esc"', "Invalid character escape sequence: \\u1 es", $this->loc(1, 7)],
['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $this->loc(1, 7)], ['"bad \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $this->loc(1, 7)],
@ -343,12 +441,27 @@ class LexerTest extends TestCase
$this->expectSyntaxError($str, $expectedMessage, $location); $this->expectSyntaxError($str, $expectedMessage, $location);
} }
public function reportsUsefulBlockStringErrors() { public function reportsUsefulBlockStringErrors()
{
return [ return [
['"""', "Unterminated string.", $this->loc(1, 4)], ['"""', 'Unterminated string.', $this->loc(1, 4)],
['"""no end quote', "Unterminated string.", $this->loc(1, 16)], ['"""no end quote', 'Unterminated string.', $this->loc(1, 16)],
['"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', "Invalid character within String: \"\\u0007\"", $this->loc(1, 23)], [
['"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', "Invalid character within String: \"\\u0000\"", $this->loc(1, 21)], '"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""',
"Invalid character within String: \"\\u0007\"",
$this->loc(
1,
23
),
],
[
'"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""',
"Invalid character within String: \"\\u0000\"",
$this->loc(
1,
21
),
],
]; ];
} }
@ -435,15 +548,15 @@ class LexerTest extends TestCase
public function reportsUsefulNumberErrors() public function reportsUsefulNumberErrors()
{ {
return [ return [
[ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)], ['00', 'Invalid number, unexpected digit after 0: "0"', $this->loc(1, 2)],
[ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)], ['+1', 'Cannot parse the unexpected character "+".', $this->loc(1, 1)],
[ '1.', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 3)], ['1.', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 3)],
[ '1.e1', "Invalid number, expected digit but got: \"e\"", $this->loc(1, 3)], ['1.e1', 'Invalid number, expected digit but got: "e"', $this->loc(1, 3)],
[ '.123', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)], ['.123', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
[ '1.A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 3)], ['1.A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 3)],
[ '-A', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 2)], ['-A', 'Invalid number, expected digit but got: "A"', $this->loc(1, 2)],
[ '1.0e', "Invalid number, expected digit but got: <EOF>", $this->loc(1, 5)], ['1.0e', 'Invalid number, expected digit but got: <EOF>', $this->loc(1, 5)],
[ '1.0eA', "Invalid number, expected digit but got: \"A\"", $this->loc(1, 5)], ['1.0eA', 'Invalid number, expected digit but got: "A"', $this->loc(1, 5)],
]; ];
} }
@ -521,8 +634,8 @@ class LexerTest extends TestCase
$unicode2 = json_decode('"\u200b"'); $unicode2 = json_decode('"\u200b"');
return [ return [
['..', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)], ['..', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
['?', "Cannot parse the unexpected character \"?\".", $this->loc(1, 1)], ['?', 'Cannot parse the unexpected character "?".', $this->loc(1, 1)],
[$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)], [$unicode1, "Cannot parse the unexpected character \"\\u203b\".", $this->loc(1, 1)],
[$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $this->loc(1, 1)], [$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $this->loc(1, 1)],
]; ];
@ -544,15 +657,18 @@ class LexerTest extends TestCase
{ {
$q = 'a-b'; $q = 'a-b';
$lexer = new Lexer(new Source($q)); $lexer = new Lexer(new Source($q));
$this->assertArraySubset(['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'], (array) $lexer->advance()); $this->assertArraySubset(
['kind' => Token::NAME, 'start' => 0, 'end' => 1, 'value' => 'a'],
(array) $lexer->advance()
);
$this->expectException(SyntaxError::class); $this->expectException(SyntaxError::class);
$this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"'); $this->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"');
try { try {
$lexer->advance(); $lexer->advance();
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
} catch(SyntaxError $error) { } catch (SyntaxError $error) {
$this->assertEquals([$this->loc(1,3)], $error->getLocations()); $this->assertEquals([$this->loc(1, 3)], $error->getLocations());
throw $error; throw $error;
} }
} }
@ -580,49 +696,28 @@ class LexerTest extends TestCase
$tokens = []; $tokens = [];
for ($tok = $startToken; $tok; $tok = $tok->next) { for ($tok = $startToken; $tok; $tok = $tok->next) {
if (!empty($tokens)) { if (! empty($tokens)) {
// Tokens are double-linked, prev should point to last seen token. // Tokens are double-linked, prev should point to last seen token.
$this->assertSame($tokens[count($tokens) - 1], $tok->prev); $this->assertSame($tokens[count($tokens) - 1], $tok->prev);
} }
$tokens[] = $tok; $tokens[] = $tok;
} }
$this->assertEquals([ $this->assertEquals(
[
'<SOF>', '<SOF>',
'{', '{',
'Comment', 'Comment',
'Name', 'Name',
'}', '}',
'<EOF>' '<EOF>',
], Utils::map($tokens, function ($tok) { ],
Utils::map(
$tokens,
function ($tok) {
return $tok->kind; return $tok->kind;
}));
}
/**
* @param string $body
* @return Token
*/
private function lexOne($body)
{
$lexer = new Lexer(new Source($body));
return $lexer->advance();
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
$this->lexOne($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
} }
)
);
} }
} }

View File

@ -1,7 +1,11 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NameNode;
@ -13,9 +17,10 @@ use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function file_get_contents;
use function sprintf;
class ParserTest extends TestCase class ParserTest extends TestCase
{ {
@ -43,13 +48,26 @@ class ParserTest extends TestCase
public function parseProvidesUsefulErrors() public function parseProvidesUsefulErrors()
{ {
return [ return [
['{', "Syntax Error: Expected Name, found <EOF>", "Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n", [1], [new SourceLocation(1, 2)]], [
['{ ...MissingOn } '{',
'Syntax Error: Expected Name, found <EOF>',
"Syntax Error: Expected Name, found <EOF>\n\nGraphQL request (1:2)\n1: {\n ^\n",
[1],
[new SourceLocation(
1,
2
),
],
],
[
'{ ...MissingOn }
fragment MissingOn Type fragment MissingOn Type
', "Syntax Error: Expected \"on\", found Name \"Type\"", "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",], ', 'Syntax Error: Expected "on", found Name "Type"',
['{ field: {} }', "Syntax Error: Expected Name, found {", "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"], "Syntax Error: Expected \"on\", found Name \"Type\"\n\nGraphQL request (2:20)\n1: { ...MissingOn }\n2: fragment MissingOn Type\n ^\n3: \n",
['notanoperation Foo { field }', "Syntax Error: Unexpected Name \"notanoperation\"", "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"], ],
['...', "Syntax Error: Unexpected ...", "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"], ['{ field: {} }', 'Syntax Error: Expected Name, found {', "Syntax Error: Expected Name, found {\n\nGraphQL request (1:10)\n1: { field: {} }\n ^\n"],
['notanoperation Foo { field }', 'Syntax Error: Unexpected Name "notanoperation"', "Syntax Error: Unexpected Name \"notanoperation\"\n\nGraphQL request (1:1)\n1: notanoperation Foo { field }\n ^\n"],
['...', 'Syntax Error: Unexpected ...', "Syntax Error: Unexpected ...\n\nGraphQL request (1:1)\n1: ...\n ^\n"],
]; ];
} }
@ -57,8 +75,13 @@ fragment MissingOn Type
* @dataProvider parseProvidesUsefulErrors * @dataProvider parseProvidesUsefulErrors
* @see it('parse provides useful errors') * @see it('parse provides useful errors')
*/ */
public function testParseProvidesUsefulErrors($str, $expectedMessage, $stringRepresentation, $expectedPositions = null, $expectedLocations = null) : void public function testParseProvidesUsefulErrors(
{ $str,
$expectedMessage,
$stringRepresentation,
$expectedPositions = null,
$expectedLocations = null
) : void {
try { try {
Parser::parse($str); Parser::parse($str);
$this->fail('Expected exception not thrown'); $this->fail('Expected exception not thrown');
@ -110,10 +133,27 @@ fragment MissingOn Type
$this->expectSyntaxError( $this->expectSyntaxError(
'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }', 'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }',
'Unexpected $', 'Unexpected $',
$this->loc(1,37) $this->loc(1, 37)
); );
} }
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
Parser::parse($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
/** /**
* @see it('does not accept fragments spread of "on"') * @see it('does not accept fragments spread of "on"')
*/ */
@ -122,7 +162,7 @@ fragment MissingOn Type
$this->expectSyntaxError( $this->expectSyntaxError(
'fragment on on on { on }', 'fragment on on on { on }',
'Unexpected Name "on"', 'Unexpected Name "on"',
$this->loc(1,10) $this->loc(1, 10)
); );
} }
@ -134,7 +174,7 @@ fragment MissingOn Type
$this->expectSyntaxError( $this->expectSyntaxError(
'{ ...on }', '{ ...on }',
'Expected Name, found }', 'Expected Name, found }',
$this->loc(1,9) $this->loc(1, 9)
); );
} }
@ -160,14 +200,14 @@ HEREDOC;
'arguments' => new NodeList([ 'arguments' => new NodeList([
new ArgumentNode([ new ArgumentNode([
'name' => new NameNode(['value' => 'arg']), 'name' => new NameNode(['value' => 'arg']),
'value' => new StringValueNode([ 'value' => new StringValueNode(
'value' => "Has a $char multi-byte character." ['value' => sprintf('Has a %s multi-byte character.', $char)]
]) ),
]) ]),
]),
'directives' => new NodeList([]),
]),
]), ]),
'directives' => new NodeList([])
])
])
]); ]);
$this->assertEquals($expected, $result->definitions[0]->selectionSet); $this->assertEquals($expected, $result->definitions[0]->selectionSet);
@ -196,7 +236,7 @@ HEREDOC;
'mutation', 'mutation',
'subscription', 'subscription',
'true', 'true',
'false' 'false',
]; ];
foreach ($nonKeywords as $keyword) { foreach ($nonKeywords as $keyword) {
$fragmentName = $keyword; $fragmentName = $keyword;
@ -205,14 +245,19 @@ HEREDOC;
} }
// Expected not to throw: // Expected not to throw:
$result = Parser::parse("query $keyword { $result = Parser::parse(<<<GRAPHQL
... $fragmentName query $keyword {
... on $keyword { field } ... $fragmentName
... on $keyword { field }
}
fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
} }
fragment $fragmentName on Type { fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword) $keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
} }
"); GRAPHQL
);
$this->assertNotEmpty($result); $this->assertNotEmpty($result);
} }
} }
@ -286,10 +331,10 @@ fragment $fragmentName on Type {
'); ');
$result = Parser::parse($source); $result = Parser::parse($source);
$loc = function($start, $end) use ($source) { $loc = function ($start, $end) {
return [ return [
'start' => $start, 'start' => $start,
'end' => $end 'end' => $end,
]; ];
}; };
@ -315,7 +360,7 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(4, 8), 'loc' => $loc(4, 8),
'value' => 'node' 'value' => 'node',
], ],
'arguments' => [ 'arguments' => [
[ [
@ -323,15 +368,15 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(9, 11), 'loc' => $loc(9, 11),
'value' => 'id' 'value' => 'id',
], ],
'value' => [ 'value' => [
'kind' => NodeKind::INT, 'kind' => NodeKind::INT,
'loc' => $loc(13, 14), 'loc' => $loc(13, 14),
'value' => '4' 'value' => '4',
],
'loc' => $loc(9, 14, $source),
], ],
'loc' => $loc(9, 14, $source)
]
], ],
'directives' => [], 'directives' => [],
'selectionSet' => [ 'selectionSet' => [
@ -345,11 +390,11 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(22, 24), 'loc' => $loc(22, 24),
'value' => 'id' 'value' => 'id',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null 'selectionSet' => null,
], ],
[ [
'kind' => NodeKind::FIELD, 'kind' => NodeKind::FIELD,
@ -358,22 +403,30 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(30, 34), 'loc' => $loc(30, 34),
'value' => 'name' 'value' => 'name',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null 'selectionSet' => null,
] ],
] ],
] ],
] ],
] ],
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, $this->nodeToArray($result)); $this->assertEquals($expected, self::nodeToArray($result));
}
/**
* @return mixed[]
*/
public static function nodeToArray(Node $node) : array
{
return TestUtils::nodeToArray($node);
} }
/** /**
@ -389,10 +442,10 @@ fragment $fragmentName on Type {
'); ');
$result = Parser::parse($source); $result = Parser::parse($source);
$loc = function($start, $end) use ($source) { $loc = function ($start, $end) {
return [ return [
'start' => $start, 'start' => $start,
'end' => $end 'end' => $end,
]; ];
}; };
@ -418,7 +471,7 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(10, 14), 'loc' => $loc(10, 14),
'value' => 'node' 'value' => 'node',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
@ -433,19 +486,19 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => $loc(21, 23), 'loc' => $loc(21, 23),
'value' => 'id' 'value' => 'id',
], ],
'arguments' => [], 'arguments' => [],
'directives' => [], 'directives' => [],
'selectionSet' => null 'selectionSet' => null,
] ],
] ],
] ],
] ],
] ],
] ],
] ],
] ],
]; ];
$this->assertEquals($expected, $this->nodeToArray($result)); $this->assertEquals($expected, $this->nodeToArray($result));
@ -475,6 +528,8 @@ fragment $fragmentName on Type {
Parser::parse($source); Parser::parse($source);
} }
// Describe: parseValue
/** /**
* @see it('contains location information that only stringifys start/end') * @see it('contains location information that only stringifys start/end')
*/ */
@ -495,6 +550,8 @@ fragment $fragmentName on Type {
$this->assertEquals($source, $result->loc->source); $this->assertEquals($source, $result->loc->source);
} }
// Describe: parseType
/** /**
* @see it('contains references to start and end tokens') * @see it('contains references to start and end tokens')
*/ */
@ -506,17 +563,18 @@ fragment $fragmentName on Type {
$this->assertEquals('<EOF>', $result->loc->endToken->kind); $this->assertEquals('<EOF>', $result->loc->endToken->kind);
} }
// Describe: parseValue
/** /**
* @see it('parses null value') * @see it('parses null value')
*/ */
public function testParsesNullValues() : void public function testParsesNullValues() : void
{ {
$this->assertEquals([ $this->assertEquals(
[
'kind' => NodeKind::NULL, 'kind' => NodeKind::NULL,
'loc' => ['start' => 0, 'end' => 4] 'loc' => ['start' => 0, 'end' => 4],
], $this->nodeToArray(Parser::parseValue('null'))); ],
$this->nodeToArray(Parser::parseValue('null'))
);
} }
/** /**
@ -524,41 +582,45 @@ fragment $fragmentName on Type {
*/ */
public function testParsesListValues() : void public function testParsesListValues() : void
{ {
$this->assertEquals([ $this->assertEquals(
[
'kind' => NodeKind::LST, 'kind' => NodeKind::LST,
'loc' => ['start' => 0, 'end' => 11], 'loc' => ['start' => 0, 'end' => 11],
'values' => [ 'values' => [
[ [
'kind' => NodeKind::INT, 'kind' => NodeKind::INT,
'loc' => ['start' => 1, 'end' => 4], 'loc' => ['start' => 1, 'end' => 4],
'value' => '123' 'value' => '123',
], ],
[ [
'kind' => NodeKind::STRING, 'kind' => NodeKind::STRING,
'loc' => ['start' => 5, 'end' => 10], 'loc' => ['start' => 5, 'end' => 10],
'value' => 'abc', 'value' => 'abc',
'block' => false 'block' => false,
] ],
] ],
], $this->nodeToArray(Parser::parseValue('[123 "abc"]'))); ],
$this->nodeToArray(Parser::parseValue('[123 "abc"]'))
);
} }
// Describe: parseType
/** /**
* @see it('parses well known types') * @see it('parses well known types')
*/ */
public function testParsesWellKnownTypes() : void public function testParsesWellKnownTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
[
'kind' => NodeKind::NAMED_TYPE, 'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6], 'loc' => ['start' => 0, 'end' => 6],
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6], 'loc' => ['start' => 0, 'end' => 6],
'value' => 'String' 'value' => 'String',
] ],
], $this->nodeToArray(Parser::parseType('String'))); ],
$this->nodeToArray(Parser::parseType('String'))
);
} }
/** /**
@ -566,15 +628,18 @@ fragment $fragmentName on Type {
*/ */
public function testParsesCustomTypes() : void public function testParsesCustomTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
[
'kind' => NodeKind::NAMED_TYPE, 'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6], 'loc' => ['start' => 0, 'end' => 6],
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6], 'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType' 'value' => 'MyType',
] ],
], $this->nodeToArray(Parser::parseType('MyType'))); ],
$this->nodeToArray(Parser::parseType('MyType'))
);
} }
/** /**
@ -582,7 +647,8 @@ fragment $fragmentName on Type {
*/ */
public function testParsesListTypes() : void public function testParsesListTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
[
'kind' => NodeKind::LIST_TYPE, 'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 8], 'loc' => ['start' => 0, 'end' => 8],
'type' => [ 'type' => [
@ -591,10 +657,12 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7], 'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType' 'value' => 'MyType',
] ],
] ],
], $this->nodeToArray(Parser::parseType('[MyType]'))); ],
$this->nodeToArray(Parser::parseType('[MyType]'))
);
} }
/** /**
@ -602,7 +670,8 @@ fragment $fragmentName on Type {
*/ */
public function testParsesNonNullTypes() : void public function testParsesNonNullTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
[
'kind' => NodeKind::NON_NULL_TYPE, 'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 0, 'end' => 7], 'loc' => ['start' => 0, 'end' => 7],
'type' => [ 'type' => [
@ -611,10 +680,12 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6], 'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType' 'value' => 'MyType',
] ],
] ],
], $this->nodeToArray(Parser::parseType('MyType!'))); ],
$this->nodeToArray(Parser::parseType('MyType!'))
);
} }
/** /**
@ -622,7 +693,8 @@ fragment $fragmentName on Type {
*/ */
public function testParsesNestedTypes() : void public function testParsesNestedTypes() : void
{ {
$this->assertEquals([ $this->assertEquals(
[
'kind' => NodeKind::LIST_TYPE, 'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 9], 'loc' => ['start' => 0, 'end' => 9],
'type' => [ 'type' => [
@ -634,36 +706,12 @@ fragment $fragmentName on Type {
'name' => [ 'name' => [
'kind' => NodeKind::NAME, 'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7], 'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType' 'value' => 'MyType',
] ],
] ],
] ],
], $this->nodeToArray(Parser::parseType('[MyType!]'))); ],
} $this->nodeToArray(Parser::parseType('[MyType!]'))
);
/**
* @param Node $node
* @return array
*/
public static function nodeToArray(Node $node)
{
return TestUtils::nodeToArray($node);
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
Parser::parse($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
} }
} }

View File

@ -1,18 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function file_get_contents;
class PrinterTest extends TestCase class PrinterTest extends TestCase
{ {
@ -160,7 +157,8 @@ END;
*/ */
public function testExperimentalCorrectlyPrintsFragmentDefinedVariables() : void public function testExperimentalCorrectlyPrintsFragmentDefinedVariables() : void
{ {
$fragmentWithVariable = Parser::parse(' $fragmentWithVariable = Parser::parse(
'
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType { fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
id id
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Language; namespace GraphQL\Tests\Language;
use GraphQL\Error\SyntaxError; use GraphQL\Error\SyntaxError;
@ -10,7 +13,6 @@ use PHPUnit\Framework\TestCase;
class SchemaParserTest extends TestCase class SchemaParserTest extends TestCase
{ {
// Describe: Schema Parser // Describe: Schema Parser
/** /**
* @see it('Simple type') * @see it('Simple type')
*/ */
@ -21,7 +23,9 @@ type Hello {
world: String world: String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -36,17 +40,53 @@ type Hello {
$this->nameNode('world', $loc(16, 21)), $this->nameNode('world', $loc(16, 21)),
$this->typeNode('String', $loc(23, 29)), $this->typeNode('String', $loc(23, 29)),
$loc(16, 29) $loc(16, 29)
) ),
], ],
'loc' => $loc(1, 31), 'loc' => $loc(1, 31),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 31) ],
'loc' => $loc(0, 31),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
private function nameNode($name, $loc)
{
return [
'kind' => NodeKind::NAME,
'value' => $name,
'loc' => $loc,
];
}
private function fieldNode($name, $type, $loc)
{
return $this->fieldNodeWithArgs($name, $type, [], $loc);
}
private function fieldNodeWithArgs($name, $type, $args, $loc)
{
return [
'kind' => NodeKind::FIELD_DEFINITION,
'name' => $name,
'arguments' => $args,
'type' => $type,
'directives' => [],
'loc' => $loc,
'description' => null,
];
}
private function typeNode($name, $loc)
{
return [
'kind' => NodeKind::NAMED_TYPE,
'name' => ['kind' => NodeKind::NAME, 'value' => $name, 'loc' => $loc],
'loc' => $loc,
];
}
/** /**
* @see it('parses type with description string') * @see it('parses type with description string')
*/ */
@ -58,7 +98,9 @@ type Hello {
world: String world: String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -73,18 +115,18 @@ type Hello {
$this->nameNode('world', $loc(30, 35)), $this->nameNode('world', $loc(30, 35)),
$this->typeNode('String', $loc(37, 43)), $this->typeNode('String', $loc(37, 43)),
$loc(30, 43) $loc(30, 43)
) ),
], ],
'loc' => $loc(1, 45), 'loc' => $loc(1, 45),
'description' => [ 'description' => [
'kind' => NodeKind::STRING, 'kind' => NodeKind::STRING,
'value' => 'Description', 'value' => 'Description',
'loc' => $loc(1, 14), 'loc' => $loc(1, 14),
'block' => false 'block' => false,
]
]
], ],
'loc' => $loc(0, 45) ],
],
'loc' => $loc(0, 45),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -103,7 +145,9 @@ type Hello {
world: String world: String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -118,18 +162,18 @@ type Hello {
$this->nameNode('world', $loc(70, 75)), $this->nameNode('world', $loc(70, 75)),
$this->typeNode('String', $loc(77, 83)), $this->typeNode('String', $loc(77, 83)),
$loc(70, 83) $loc(70, 83)
) ),
], ],
'loc' => $loc(1, 85), 'loc' => $loc(1, 85),
'description' => [ 'description' => [
'kind' => NodeKind::STRING, 'kind' => NodeKind::STRING,
'value' => 'Description', 'value' => 'Description',
'loc' => $loc(1, 20), 'loc' => $loc(1, 20),
'block' => true 'block' => true,
]
]
], ],
'loc' => $loc(0, 85) ],
],
'loc' => $loc(0, 85),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -145,9 +189,10 @@ extend type Hello {
} }
'; ';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) { $loc = function ($start, $end) {
return TestUtils::locArray($start, $end); return TestUtils::locArray($start, $end);
}; };
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
'definitions' => [ 'definitions' => [
@ -161,12 +206,12 @@ extend type Hello {
$this->nameNode('world', $loc(23, 28)), $this->nameNode('world', $loc(23, 28)),
$this->typeNode('String', $loc(30, 36)), $this->typeNode('String', $loc(30, 36)),
$loc(23, 36) $loc(23, 36)
) ),
], ],
'loc' => $loc(1, 38) 'loc' => $loc(1, 38),
]
], ],
'loc' => $loc(0, 39) ],
'loc' => $loc(0, 39),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -178,9 +223,10 @@ extend type Hello {
{ {
$body = 'extend type Hello implements Greeting'; $body = 'extend type Hello implements Greeting';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) { $loc = function ($start, $end) {
return TestUtils::locArray($start, $end); return TestUtils::locArray($start, $end);
}; };
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
'definitions' => [ 'definitions' => [
@ -192,10 +238,10 @@ extend type Hello {
], ],
'directives' => [], 'directives' => [],
'fields' => [], 'fields' => [],
'loc' => $loc(0, 37) 'loc' => $loc(0, 37),
]
], ],
'loc' => $loc(0, 37) ],
'loc' => $loc(0, 37),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -248,6 +294,23 @@ extend type Hello {
); );
} }
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
Parser::parse($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
/** /**
* @see it('Extension do not include descriptions') * @see it('Extension do not include descriptions')
*/ */
@ -291,17 +354,18 @@ extend type Hello {
type Hello { type Hello {
world: String! world: String!
}'; }';
$loc = function($start, $end) { $doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end); return TestUtils::locArray($start, $end);
}; };
$doc = Parser::parse($body);
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
'definitions' => [ 'definitions' => [
[ [
'kind' => NodeKind::OBJECT_TYPE_DEFINITION, 'kind' => NodeKind::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(6,11)), 'name' => $this->nameNode('Hello', $loc(6, 11)),
'interfaces' => [], 'interfaces' => [],
'directives' => [], 'directives' => [],
'fields' => [ 'fields' => [
@ -310,16 +374,16 @@ type Hello {
[ [
'kind' => NodeKind::NON_NULL_TYPE, 'kind' => NodeKind::NON_NULL_TYPE,
'type' => $this->typeNode('String', $loc(23, 29)), 'type' => $this->typeNode('String', $loc(23, 29)),
'loc' => $loc(23, 30) 'loc' => $loc(23, 30),
], ],
$loc(16,30) $loc(16, 30)
) ),
], ],
'loc' => $loc(1,32), 'loc' => $loc(1, 32),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0,32) ],
'loc' => $loc(0, 32),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -331,8 +395,10 @@ type Hello {
public function testSimpleTypeInheritingInterface() : void public function testSimpleTypeInheritingInterface() : void
{ {
$body = 'type Hello implements World { field: String }'; $body = 'type Hello implements World { field: String }';
$loc = function($start, $end) { return TestUtils::locArray($start, $end); };
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -341,7 +407,7 @@ type Hello {
'kind' => NodeKind::OBJECT_TYPE_DEFINITION, 'kind' => NodeKind::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(5, 10)), 'name' => $this->nameNode('Hello', $loc(5, 10)),
'interfaces' => [ 'interfaces' => [
$this->typeNode('World', $loc(22, 27)) $this->typeNode('World', $loc(22, 27)),
], ],
'directives' => [], 'directives' => [],
'fields' => [ 'fields' => [
@ -349,13 +415,13 @@ type Hello {
$this->nameNode('field', $loc(30, 35)), $this->nameNode('field', $loc(30, 35)),
$this->typeNode('String', $loc(37, 43)), $this->typeNode('String', $loc(37, 43)),
$loc(30, 43) $loc(30, 43)
) ),
], ],
'loc' => $loc(0, 45), 'loc' => $loc(0, 45),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 45) ],
'loc' => $loc(0, 45),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -367,8 +433,10 @@ type Hello {
public function testSimpleTypeInheritingMultipleInterfaces() : void public function testSimpleTypeInheritingMultipleInterfaces() : void
{ {
$body = 'type Hello implements Wo & rld { field: String }'; $body = 'type Hello implements Wo & rld { field: String }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -378,7 +446,7 @@ type Hello {
'name' => $this->nameNode('Hello', $loc(5, 10)), 'name' => $this->nameNode('Hello', $loc(5, 10)),
'interfaces' => [ 'interfaces' => [
$this->typeNode('Wo', $loc(22, 24)), $this->typeNode('Wo', $loc(22, 24)),
$this->typeNode('rld', $loc(27, 30)) $this->typeNode('rld', $loc(27, 30)),
], ],
'directives' => [], 'directives' => [],
'fields' => [ 'fields' => [
@ -386,13 +454,13 @@ type Hello {
$this->nameNode('field', $loc(33, 38)), $this->nameNode('field', $loc(33, 38)),
$this->typeNode('String', $loc(40, 46)), $this->typeNode('String', $loc(40, 46)),
$loc(33, 46) $loc(33, 46)
) ),
], ],
'loc' => $loc(0, 48), 'loc' => $loc(0, 48),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 48) ],
'loc' => $loc(0, 48),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -404,8 +472,11 @@ type Hello {
public function testSimpleTypeInheritingMultipleInterfacesWithLeadingAmpersand() : void public function testSimpleTypeInheritingMultipleInterfacesWithLeadingAmpersand() : void
{ {
$body = 'type Hello implements & Wo & rld { field: String }'; $body = 'type Hello implements & Wo & rld { field: String }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => 'Document', 'kind' => 'Document',
'definitions' => [ 'definitions' => [
@ -439,8 +510,10 @@ type Hello {
public function testSingleValueEnum() : void public function testSingleValueEnum() : void
{ {
$body = 'enum Hello { WORLD }'; $body = 'enum Hello { WORLD }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -451,23 +524,36 @@ type Hello {
'directives' => [], 'directives' => [],
'values' => [$this->enumValueNode('WORLD', $loc(13, 18))], 'values' => [$this->enumValueNode('WORLD', $loc(13, 18))],
'loc' => $loc(0, 20), 'loc' => $loc(0, 20),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 20) ],
'loc' => $loc(0, 20),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
private function enumValueNode($name, $loc)
{
return [
'kind' => NodeKind::ENUM_VALUE_DEFINITION,
'name' => $this->nameNode($name, $loc),
'directives' => [],
'loc' => $loc,
'description' => null,
];
}
/** /**
* @see it('Double value enum') * @see it('Double value enum')
*/ */
public function testDoubleValueEnum() : void public function testDoubleValueEnum() : void
{ {
$body = 'enum Hello { WO, RLD }'; $body = 'enum Hello { WO, RLD }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -478,13 +564,13 @@ type Hello {
'directives' => [], 'directives' => [],
'values' => [ 'values' => [
$this->enumValueNode('WO', $loc(13, 15)), $this->enumValueNode('WO', $loc(13, 15)),
$this->enumValueNode('RLD', $loc(17, 20)) $this->enumValueNode('RLD', $loc(17, 20)),
], ],
'loc' => $loc(0, 22), 'loc' => $loc(0, 22),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 22) ],
'loc' => $loc(0, 22),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -500,7 +586,9 @@ interface Hello {
world: String world: String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -514,13 +602,13 @@ interface Hello {
$this->nameNode('world', $loc(21, 26)), $this->nameNode('world', $loc(21, 26)),
$this->typeNode('String', $loc(28, 34)), $this->typeNode('String', $loc(28, 34)),
$loc(21, 34) $loc(21, 34)
) ),
], ],
'loc' => $loc(1, 36), 'loc' => $loc(1, 36),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0,36) ],
'loc' => $loc(0, 36),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -535,7 +623,9 @@ type Hello {
world(flag: Boolean): String world(flag: Boolean): String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -555,21 +645,34 @@ type Hello {
$this->typeNode('Boolean', $loc(28, 35)), $this->typeNode('Boolean', $loc(28, 35)),
null, null,
$loc(22, 35) $loc(22, 35)
) ),
], ],
$loc(16, 44) $loc(16, 44)
) ),
], ],
'loc' => $loc(1, 46), 'loc' => $loc(1, 46),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 46) ],
'loc' => $loc(0, 46),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
private function inputValueNode($name, $type, $defaultValue, $loc)
{
return [
'kind' => NodeKind::INPUT_VALUE_DEFINITION,
'name' => $name,
'type' => $type,
'defaultValue' => $defaultValue,
'directives' => [],
'loc' => $loc,
'description' => null,
];
}
/** /**
* @see it('Simple field with arg with default value') * @see it('Simple field with arg with default value')
*/ */
@ -580,7 +683,9 @@ type Hello {
world(flag: Boolean = true): String world(flag: Boolean = true): String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -600,16 +705,16 @@ type Hello {
$this->typeNode('Boolean', $loc(28, 35)), $this->typeNode('Boolean', $loc(28, 35)),
['kind' => NodeKind::BOOLEAN, 'value' => true, 'loc' => $loc(38, 42)], ['kind' => NodeKind::BOOLEAN, 'value' => true, 'loc' => $loc(38, 42)],
$loc(22, 42) $loc(22, 42)
) ),
], ],
$loc(16, 51) $loc(16, 51)
) ),
], ],
'loc' => $loc(1, 53), 'loc' => $loc(1, 53),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 53) ],
'loc' => $loc(0, 53),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -624,7 +729,9 @@ type Hello {
world(things: [String]): String world(things: [String]): String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -640,20 +747,26 @@ type Hello {
$this->typeNode('String', $loc(41, 47)), $this->typeNode('String', $loc(41, 47)),
[ [
$this->inputValueNode( $this->inputValueNode(
$this->nameNode('things', $loc(22,28)), $this->nameNode('things', $loc(22, 28)),
['kind' => NodeKind::LIST_TYPE, 'type' => $this->typeNode('String', $loc(31, 37)), 'loc' => $loc(30, 38)], [
'kind' => NodeKind::LIST_TYPE,
'type' => $this->typeNode(
'String',
$loc(31, 37)
), 'loc' => $loc(30, 38),
],
null, null,
$loc(22, 38) $loc(22, 38)
) ),
], ],
$loc(16, 47) $loc(16, 47)
) ),
], ],
'loc' => $loc(1, 49), 'loc' => $loc(1, 49),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 49) ],
'loc' => $loc(0, 49),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -669,7 +782,9 @@ type Hello {
world(argOne: Boolean, argTwo: Int): String world(argOne: Boolean, argTwo: Int): String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -695,16 +810,16 @@ type Hello {
$this->typeNode('Int', $loc(47, 50)), $this->typeNode('Int', $loc(47, 50)),
null, null,
$loc(39, 50) $loc(39, 50)
) ),
], ],
$loc(16, 59) $loc(16, 59)
) ),
], ],
'loc' => $loc(1, 61), 'loc' => $loc(1, 61),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 61) ],
'loc' => $loc(0, 61),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -717,7 +832,10 @@ type Hello {
{ {
$body = 'union Hello = World'; $body = 'union Hello = World';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
'definitions' => [ 'definitions' => [
@ -727,10 +845,10 @@ type Hello {
'directives' => [], 'directives' => [],
'types' => [$this->typeNode('World', $loc(14, 19))], 'types' => [$this->typeNode('World', $loc(14, 19))],
'loc' => $loc(0, 19), 'loc' => $loc(0, 19),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 19) ],
'loc' => $loc(0, 19),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -743,7 +861,9 @@ type Hello {
{ {
$body = 'union Hello = Wo | Rld'; $body = 'union Hello = Wo | Rld';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -754,18 +874,17 @@ type Hello {
'directives' => [], 'directives' => [],
'types' => [ 'types' => [
$this->typeNode('Wo', $loc(14, 16)), $this->typeNode('Wo', $loc(14, 16)),
$this->typeNode('Rld', $loc(19, 22)) $this->typeNode('Rld', $loc(19, 22)),
], ],
'loc' => $loc(0, 22), 'loc' => $loc(0, 22),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 22) ],
'loc' => $loc(0, 22),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
/** /**
* @see it('Union with two types and leading pipe') * @see it('Union with two types and leading pipe')
*/ */
@ -785,8 +904,8 @@ type Hello {
$this->typeNode('Rld', ['start' => 21, 'end' => 24]), $this->typeNode('Rld', ['start' => 21, 'end' => 24]),
], ],
'loc' => ['start' => 0, 'end' => 24], 'loc' => ['start' => 0, 'end' => 24],
'description' => null 'description' => null,
] ],
], ],
'loc' => ['start' => 0, 'end' => 24], 'loc' => ['start' => 0, 'end' => 24],
]; ];
@ -848,7 +967,10 @@ type Hello {
{ {
$body = 'scalar Hello'; $body = 'scalar Hello';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
'definitions' => [ 'definitions' => [
@ -857,10 +979,10 @@ type Hello {
'name' => $this->nameNode('Hello', $loc(7, 12)), 'name' => $this->nameNode('Hello', $loc(7, 12)),
'directives' => [], 'directives' => [],
'loc' => $loc(0, 12), 'loc' => $loc(0, 12),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 12) ],
'loc' => $loc(0, 12),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -875,7 +997,9 @@ input Hello {
world: String world: String
}'; }';
$doc = Parser::parse($body); $doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);}; $loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [ $expected = [
'kind' => NodeKind::DOCUMENT, 'kind' => NodeKind::DOCUMENT,
@ -890,13 +1014,13 @@ input Hello {
$this->typeNode('String', $loc(24, 30)), $this->typeNode('String', $loc(24, 30)),
null, null,
$loc(17, 30) $loc(17, 30)
) ),
], ],
'loc' => $loc(1, 32), 'loc' => $loc(1, 32),
'description' => null 'description' => null,
]
], ],
'loc' => $loc(0, 32) ],
'loc' => $loc(0, 32),
]; ];
$this->assertEquals($expected, TestUtils::nodeToArray($doc)); $this->assertEquals($expected, TestUtils::nodeToArray($doc));
} }
@ -980,81 +1104,4 @@ input Hello {
]; ];
$this->assertArraySubset($expected, $doc->toArray(true)); $this->assertArraySubset($expected, $doc->toArray(true));
} }
private function typeNode($name, $loc)
{
return [
'kind' => NodeKind::NAMED_TYPE,
'name' => ['kind' => NodeKind::NAME, 'value' => $name, 'loc' => $loc],
'loc' => $loc
];
}
private function nameNode($name, $loc)
{
return [
'kind' => NodeKind::NAME,
'value' => $name,
'loc' => $loc
];
}
private function fieldNode($name, $type, $loc)
{
return $this->fieldNodeWithArgs($name, $type, [], $loc);
}
private function fieldNodeWithArgs($name, $type, $args, $loc)
{
return [
'kind' => NodeKind::FIELD_DEFINITION,
'name' => $name,
'arguments' => $args,
'type' => $type,
'directives' => [],
'loc' => $loc,
'description' => null
];
}
private function enumValueNode($name, $loc)
{
return [
'kind' => NodeKind::ENUM_VALUE_DEFINITION,
'name' => $this->nameNode($name, $loc),
'directives' => [],
'loc' => $loc,
'description' => null
];
}
private function inputValueNode($name, $type, $defaultValue, $loc)
{
return [
'kind' => NodeKind::INPUT_VALUE_DEFINITION,
'name' => $name,
'type' => $type,
'defaultValue' => $defaultValue,
'directives' => [],
'loc' => $loc,
'description' => null
];
}
private function loc($line, $column)
{
return new SourceLocation($line, $column);
}
private function expectSyntaxError($text, $message, $location)
{
$this->expectException(SyntaxError::class);
$this->expectExceptionMessage($message);
try {
Parser::parse($text);
} catch (SyntaxError $error) {
$this->assertEquals([$location], $error->getLocations());
throw $error;
}
}
} }

View File

@ -1,5 +1,8 @@
<?php <?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode;
@ -7,6 +10,7 @@ use GraphQL\Language\Parser;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Throwable; use Throwable;
use function file_get_contents;
class SchemaPrinterTest extends TestCase class SchemaPrinterTest extends TestCase
{ {
@ -16,7 +20,7 @@ class SchemaPrinterTest extends TestCase
public function testPrintsMinimalAst() : void public function testPrintsMinimalAst() : void
{ {
$ast = new ScalarTypeDefinitionNode([ $ast = new ScalarTypeDefinitionNode([
'name' => new NameNode(['value' => 'foo']) 'name' => new NameNode(['value' => 'foo']),
]); ]);
$this->assertEquals('scalar foo', Printer::doPrint($ast)); $this->assertEquals('scalar foo', Printer::doPrint($ast));
} }

View File

@ -1,5 +1,8 @@
<?php <?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
@ -7,6 +10,13 @@ use GraphQL\Language\AST\NodeList;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function array_keys;
use function count;
use function file_get_contents;
use function get_class;
use function get_object_vars;
use function implode;
use function json_decode;
class SerializationTest extends TestCase class SerializationTest extends TestCase
{ {
@ -27,6 +37,50 @@ class SerializationTest extends TestCase
$this->assertNodesAreEqual($parsedAst, $actualAst); $this->assertNodesAreEqual($parsedAst, $actualAst);
} }
/**
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
*
* @param string[] $path
*/
private function assertNodesAreEqual(Node $expected, Node $actual, array $path = []) : void
{
$err = 'Mismatch at AST path: ' . implode(', ', $path);
$this->assertInstanceOf(Node::class, $actual, $err);
$this->assertEquals(get_class($expected), get_class($actual), $err);
$expectedVars = get_object_vars($expected);
$actualVars = get_object_vars($actual);
$this->assertCount(count($expectedVars), $actualVars, $err);
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
foreach ($expectedVars as $name => $expectedValue) {
$actualValue = $actualVars[$name];
$tmpPath = $path;
$tmpPath[] = $name;
$err = 'Mismatch at AST path: ' . implode(', ', $tmpPath);
if ($expectedValue instanceof Node) {
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
} elseif ($expectedValue instanceof NodeList) {
$this->assertEquals(count($expectedValue), count($actualValue), $err);
$this->assertInstanceOf(NodeList::class, $actualValue, $err);
foreach ($expectedValue as $index => $listNode) {
$tmpPath2 = $tmpPath;
$tmpPath2[] = $index;
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
}
} elseif ($expectedValue instanceof Location) {
$this->assertInstanceOf(Location::class, $actualValue, $err);
$this->assertSame($expectedValue->start, $actualValue->start, $err);
$this->assertSame($expectedValue->end, $actualValue->end, $err);
} else {
$this->assertEquals($expectedValue, $actualValue, $err);
}
}
}
public function testSerializeSupportsNoLocationOption() : void public function testSerializeSupportsNoLocationOption() : void
{ {
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql'); $kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
@ -43,50 +97,4 @@ class SerializationTest extends TestCase
$parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]); $parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]);
$this->assertNodesAreEqual($parsedAst, $actualAst); $this->assertNodesAreEqual($parsedAst, $actualAst);
} }
/**
* Compares two nodes by actually iterating over all NodeLists, properly comparing locations (ignoring tokens), etc
*
* @param $expected
* @param $actual
* @param array $path
*/
private function assertNodesAreEqual($expected, $actual, $path = [])
{
$err = "Mismatch at AST path: " . implode(', ', $path);
$this->assertInstanceOf(Node::class, $actual, $err);
$this->assertEquals(get_class($expected), get_class($actual), $err);
$expectedVars = get_object_vars($expected);
$actualVars = get_object_vars($actual);
$this->assertSame(count($expectedVars), count($actualVars), $err);
$this->assertEquals(array_keys($expectedVars), array_keys($actualVars), $err);
foreach ($expectedVars as $name => $expectedValue) {
$actualValue = $actualVars[$name];
$tmpPath = $path;
$tmpPath[] = $name;
$err = "Mismatch at AST path: " . implode(', ', $tmpPath);
if ($expectedValue instanceof Node) {
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
} else if ($expectedValue instanceof NodeList) {
$this->assertEquals(count($expectedValue), count($actualValue), $err);
$this->assertInstanceOf(NodeList::class, $actualValue, $err);
foreach ($expectedValue as $index => $listNode) {
$tmpPath2 = $tmpPath;
$tmpPath2 [] = $index;
$this->assertNodesAreEqual($listNode, $actualValue[$index], $tmpPath2);
}
} else if ($expectedValue instanceof Location) {
$this->assertInstanceOf(Location::class, $actualValue, $err);
$this->assertSame($expectedValue->start, $actualValue->start, $err);
$this->assertSame($expectedValue->end, $actualValue->end, $err);
} else {
$this->assertEquals($expectedValue, $actualValue, $err);
}
}
}
} }

View File

@ -1,36 +1,41 @@
<?php <?php
namespace GraphQL\Tests\Language;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location; use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NodeList;
use function get_object_vars;
use function is_array;
use function is_scalar;
class TestUtils class TestUtils
{ {
/** /**
* @param Node $node * @return mixed[]
* @return array
*/ */
public static function nodeToArray(Node $node) public static function nodeToArray(Node $node) : array
{ {
$result = [ $result = [
'kind' => $node->kind, 'kind' => $node->kind,
'loc' => self::locationToArray($node->loc) 'loc' => self::locationToArray($node->loc),
]; ];
foreach (get_object_vars($node) as $prop => $propValue) { foreach (get_object_vars($node) as $prop => $propValue) {
if (isset($result[$prop])) if (isset($result[$prop])) {
continue; continue;
}
if (is_array($propValue) || $propValue instanceof NodeList) { if (is_array($propValue) || $propValue instanceof NodeList) {
$tmp = []; $tmp = [];
foreach ($propValue as $tmp1) { foreach ($propValue as $tmp1) {
$tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1; $tmp[] = $tmp1 instanceof Node ? self::nodeToArray($tmp1) : (array) $tmp1;
} }
} else if ($propValue instanceof Node) { } elseif ($propValue instanceof Node) {
$tmp = self::nodeToArray($propValue); $tmp = self::nodeToArray($propValue);
} else if (is_scalar($propValue) || null === $propValue) { } elseif (is_scalar($propValue) || $propValue === null) {
$tmp = $propValue; $tmp = $propValue;
} else { } else {
$tmp = null; $tmp = null;
@ -38,27 +43,25 @@ class TestUtils
$result[$prop] = $tmp; $result[$prop] = $tmp;
} }
return $result; return $result;
} }
/** /**
* @param Location $loc * @return int[]
* @return array
*/ */
public static function locationToArray(Location $loc) public static function locationToArray(Location $loc) : array
{ {
return [ return [
'start' => $loc->start, 'start' => $loc->start,
'end' => $loc->end 'end' => $loc->end,
]; ];
} }
/** /**
* @param $start * @return int[]
* @param $end
* @return array
*/ */
public static function locArray($start, $end) public static function locArray(int $start, int $end) : array
{ {
return ['start' => $start, 'end' => $end]; return ['start' => $start, 'end' => $end];
} }

View File

@ -1,5 +1,8 @@
<?php <?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\Token; use GraphQL\Language\Token;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -13,7 +16,7 @@ class TokenTest extends TestCase
'kind' => 'Kind', 'kind' => 'Kind',
'value' => null, 'value' => null,
'line' => 3, 'line' => 3,
'column' => 5 'column' => 5,
]; ];
$this->assertEquals($expected, $token->toArray()); $this->assertEquals($expected, $token->toArray());

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,17 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7; namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use function strtolower;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrRequestStub implements ServerRequestInterface class PsrRequestStub implements ServerRequestInterface
{ {
public $queryParams = []; public $queryParams = [];
@ -13,14 +20,10 @@ class PsrRequestStub implements ServerRequestInterface
public $method; public $method;
/** /** @var PsrStreamStub */
* @var PsrStreamStub
*/
public $body; public $body;
/** /** @var mixed[] */
* @var array
*/
public $parsedBody; public $parsedBody;
/** /**
@ -32,7 +35,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getProtocolVersion() public function getProtocolVersion()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -50,7 +53,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withProtocolVersion($version) public function withProtocolVersion($version)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -80,7 +83,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getHeaders() public function getHeaders()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -93,7 +96,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function hasHeader($name) public function hasHeader($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -113,7 +116,8 @@ class PsrRequestStub implements ServerRequestInterface
public function getHeader($name) public function getHeader($name)
{ {
$name = strtolower($name); $name = strtolower($name);
return isset($this->headers[$name]) ? $this->headers[$name] : [];
return $this->headers[$name] ?? [];
} }
/** /**
@ -137,7 +141,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getHeaderLine($name) public function getHeaderLine($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -157,7 +161,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withHeader($name, $value) public function withHeader($name, $value)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -178,7 +182,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withAddedHeader($name, $value) public function withAddedHeader($name, $value)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -195,7 +199,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withoutHeader($name) public function withoutHeader($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -223,7 +227,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withBody(StreamInterface $body) public function withBody(StreamInterface $body)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -244,7 +248,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getRequestTarget() public function getRequestTarget()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -266,7 +270,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withRequestTarget($requestTarget) public function withRequestTarget($requestTarget)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -296,7 +300,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withMethod($method) public function withMethod($method)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -345,7 +349,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withUri(UriInterface $uri, $preserveHost = false) public function withUri(UriInterface $uri, $preserveHost = false)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -359,7 +363,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getServerParams() public function getServerParams()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -374,7 +378,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getCookieParams() public function getCookieParams()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -396,7 +400,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withCookieParams(array $cookies) public function withCookieParams(array $cookies)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -440,7 +444,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withQueryParams(array $query) public function withQueryParams(array $query)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -457,7 +461,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getUploadedFiles() public function getUploadedFiles()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -473,7 +477,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withUploadedFiles(array $uploadedFiles) public function withUploadedFiles(array $uploadedFiles)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -526,7 +530,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withParsedBody($data) public function withParsedBody($data)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -542,7 +546,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getAttributes() public function getAttributes()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -562,7 +566,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function getAttribute($name, $default = null) public function getAttribute($name, $default = null)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -582,7 +586,7 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withAttribute($name, $value) public function withAttribute($name, $value)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -601,6 +605,6 @@ class PsrRequestStub implements ServerRequestInterface
*/ */
public function withoutAttribute($name) public function withoutAttribute($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
} }

View File

@ -1,10 +1,15 @@
<?php <?php
namespace GraphQL\Tests\Server\Psr7;
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrResponseStub implements ResponseInterface class PsrResponseStub implements ResponseInterface
{ {
public $headers = []; public $headers = [];
@ -22,7 +27,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getProtocolVersion() public function getProtocolVersion()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -40,7 +45,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function withProtocolVersion($version) public function withProtocolVersion($version)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -70,7 +75,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getHeaders() public function getHeaders()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -83,7 +88,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function hasHeader($name) public function hasHeader($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -102,7 +107,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getHeader($name) public function getHeader($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -126,7 +131,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getHeaderLine($name) public function getHeaderLine($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -148,6 +153,7 @@ class PsrResponseStub implements ResponseInterface
{ {
$tmp = clone $this; $tmp = clone $this;
$tmp->headers[$name][] = $value; $tmp->headers[$name][] = $value;
return $tmp; return $tmp;
} }
@ -169,7 +175,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function withAddedHeader($name, $value) public function withAddedHeader($name, $value)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -186,7 +192,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function withoutHeader($name) public function withoutHeader($name)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -196,7 +202,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getBody() public function getBody()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -216,6 +222,7 @@ class PsrResponseStub implements ResponseInterface
{ {
$tmp = clone $this; $tmp = clone $this;
$tmp->body = $body; $tmp->body = $body;
return $tmp; return $tmp;
} }
@ -229,7 +236,7 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getStatusCode() public function getStatusCode()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -256,6 +263,7 @@ class PsrResponseStub implements ResponseInterface
{ {
$tmp = clone $this; $tmp = clone $this;
$tmp->statusCode = $code; $tmp->statusCode = $code;
return $tmp; return $tmp;
} }
@ -274,6 +282,6 @@ class PsrResponseStub implements ResponseInterface
*/ */
public function getReasonPhrase() public function getReasonPhrase()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
} }

View File

@ -1,8 +1,16 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server\Psr7; namespace GraphQL\Tests\Server\Psr7;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use function strlen;
use const SEEK_SET;
/**
* phpcs:ignoreFile -- this is not a core file
*/
class PsrStreamStub implements StreamInterface class PsrStreamStub implements StreamInterface
{ {
public $content; public $content;
@ -33,7 +41,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function close() public function close()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -45,7 +53,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function detach() public function detach()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -55,7 +63,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function getSize() public function getSize()
{ {
return strlen($this->content?:''); return strlen($this->content ?: '');
} }
/** /**
@ -66,7 +74,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function tell() public function tell()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -76,7 +84,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function eof() public function eof()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -86,7 +94,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function isSeekable() public function isSeekable()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -103,7 +111,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function seek($offset, $whence = SEEK_SET) public function seek($offset, $whence = SEEK_SET)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -118,7 +126,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function rewind() public function rewind()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -141,6 +149,7 @@ class PsrStreamStub implements StreamInterface
public function write($string) public function write($string)
{ {
$this->content = $string; $this->content = $string;
return strlen($string); return strlen($string);
} }
@ -151,7 +160,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function isReadable() public function isReadable()
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -166,7 +175,7 @@ class PsrStreamStub implements StreamInterface
*/ */
public function read($length) public function read($length)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
/** /**
@ -195,6 +204,6 @@ class PsrStreamStub implements StreamInterface
*/ */
public function getMetadata($key = null) public function getMetadata($key = null)
{ {
throw new \Exception("Not implemented"); throw new \Exception('Not implemented');
} }
} }

View File

@ -1,11 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Server\Helper; use GraphQL\Server\Helper;
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use GraphQL\Tests\Server\Psr7\PsrResponseStub; use GraphQL\Tests\Server\Psr7\PsrResponseStub;
use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_encode;
class PsrResponseTest extends TestCase class PsrResponseTest extends TestCase
{ {

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Error\Debug; use GraphQL\Error\Debug;
@ -14,12 +17,12 @@ use GraphQL\Server\ServerConfig;
use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\CustomValidationRule; use GraphQL\Validator\Rules\CustomValidationRule;
use GraphQL\Validator\ValidationContext; use GraphQL\Validator\ValidationContext;
use function count;
use function sprintf;
class QueryExecutionTest extends ServerTestCase class QueryExecutionTest extends ServerTestCase
{ {
/** /** @var ServerConfig */
* @var ServerConfig
*/
private $config; private $config;
public function setUp() public function setUp()
@ -34,20 +37,36 @@ class QueryExecutionTest extends ServerTestCase
$query = '{f1}'; $query = '{f1}';
$expected = [ $expected = [
'data' => [ 'data' => ['f1' => 'f1'],
'f1' => 'f1'
]
]; ];
$this->assertQueryResultEquals($expected, $query); $this->assertQueryResultEquals($expected, $query);
} }
private function assertQueryResultEquals($expected, $query, $variables = null)
{
$result = $this->executeQuery($query, $variables);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
private function executeQuery($query, $variables = null, $readonly = false)
{
$op = OperationParams::create(['query' => $query, 'variables' => $variables], $readonly);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
public function testReturnsSyntaxErrors() : void public function testReturnsSyntaxErrors() : void
{ {
$query = '{f1'; $query = '{f1';
$result = $this->executeQuery($query); $result = $this->executeQuery($query);
$this->assertSame(null, $result->data); $this->assertNull($result->data);
$this->assertCount(1, $result->errors); $this->assertCount(1, $result->errors);
$this->assertContains( $this->assertContains(
'Syntax Error: Expected Name, found <EOF>', 'Syntax Error: Expected Name, found <EOF>',
@ -70,11 +89,12 @@ class QueryExecutionTest extends ServerTestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'fieldWithSafeException' => null, 'fieldWithSafeException' => null,
'f1' => 'f1' 'f1' => 'f1',
], ],
'errors' => [ 'errors' => [
[ [
'message' => 'This is the exception we want', 'message' => 'This is the exception we want',
'path' => ['fieldWithSafeException'], 'path' => ['fieldWithSafeException'],
'trace' => [] 'trace' => []
] ]
@ -112,7 +132,7 @@ class QueryExecutionTest extends ServerTestCase
} }
'; ';
$this->assertTrue(!isset($context->testedRootValue)); $this->assertTrue(! isset($context->testedRootValue));
$this->executeQuery($query); $this->executeQuery($query);
$this->assertSame($rootValue, $context->testedRootValue); $this->assertSame($rootValue, $context->testedRootValue);
} }
@ -129,8 +149,8 @@ class QueryExecutionTest extends ServerTestCase
$expected = [ $expected = [
'data' => [ 'data' => [
'a' => 'a', 'a' => 'a',
'b' => 'b' 'b' => 'b',
] ],
]; ];
$this->assertQueryResultEquals($expected, $query, $variables); $this->assertQueryResultEquals($expected, $query, $variables);
} }
@ -142,8 +162,8 @@ class QueryExecutionTest extends ServerTestCase
'; ';
$expected = [ $expected = [
'errors' => [ 'errors' => [
['message' => 'Cannot query field "nonExistentField" on type "Query".'] ['message' => 'Cannot query field "nonExistentField" on type "Query".'],
] ],
]; ];
$this->assertQueryResultEquals($expected, $query); $this->assertQueryResultEquals($expected, $query);
@ -151,15 +171,16 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$rules = [ $rules = [
new CustomValidationRule('SomeRule', function() use (&$called) { new CustomValidationRule('SomeRule', function () use (&$called) {
$called = true; $called = true;
return []; return [];
}) }),
]; ];
$this->config->setValidationRules($rules); $this->config->setValidationRules($rules);
$expected = [ $expected = [
'data' => [] 'data' => [],
]; ];
$this->assertQueryResultEquals($expected, $query); $this->assertQueryResultEquals($expected, $query);
$this->assertTrue($called); $this->assertTrue($called);
@ -170,11 +191,12 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$params = $doc = $operationType = null; $params = $doc = $operationType = null;
$this->config->setValidationRules(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { $this->config->setValidationRules(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true; $called = true;
$params = $p; $params = $p;
$doc = $d; $doc = $d;
$operationType = $o; $operationType = $o;
return []; return [];
}); });
@ -193,18 +215,20 @@ class QueryExecutionTest extends ServerTestCase
$called1 = false; $called1 = false;
$called2 = false; $called2 = false;
$this->config->setValidationRules(function(OperationParams $params) use ($q1, $q2, &$called1, &$called2) { $this->config->setValidationRules(function (OperationParams $params) use ($q1, &$called1, &$called2) {
if ($params->query === $q1) { if ($params->query === $q1) {
$called1 = true; $called1 = true;
return DocumentValidator::allRules(); return DocumentValidator::allRules();
} else {
$called2 = true;
return [
new CustomValidationRule('MyRule', function(ValidationContext $context) {
$context->reportError(new Error("This is the error we are looking for!"));
})
];
} }
$called2 = true;
return [
new CustomValidationRule('MyRule', function (ValidationContext $context) {
$context->reportError(new Error('This is the error we are looking for!'));
}),
];
}); });
$expected = ['data' => ['f1' => 'f1']]; $expected = ['data' => ['f1' => 'f1']];
@ -236,22 +260,28 @@ class QueryExecutionTest extends ServerTestCase
'errors' => [ 'errors' => [
[ [
'message' => 'Persisted queries are not supported by this server', 'message' => 'Persisted queries are not supported by this server',
'category' => 'request' 'category' => 'request',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
private function executePersistedQuery($queryId, $variables = null)
{
$op = OperationParams::create(['queryId' => $queryId, 'variables' => $variables]);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
public function testBatchedQueriesAreDisabledByDefault() : void public function testBatchedQueriesAreDisabledByDefault() : void
{ {
$batch = [ $batch = [
[ ['query' => '{invalid}'],
'query' => '{invalid}' ['query' => '{f1,fieldWithSafeException}'],
],
[
'query' => '{f1,fieldWithSafeException}'
]
]; ];
$result = $this->executeBatchedQuery($batch); $result = $this->executeBatchedQuery($batch);
@ -261,17 +291,17 @@ class QueryExecutionTest extends ServerTestCase
'errors' => [ 'errors' => [
[ [
'message' => 'Batched queries are not supported by this server', 'message' => 'Batched queries are not supported by this server',
'category' => 'request' 'category' => 'request',
] ],
] ],
], ],
[ [
'errors' => [ 'errors' => [
[ [
'message' => 'Batched queries are not supported by this server', 'message' => 'Batched queries are not supported by this server',
'category' => 'request' 'category' => 'request',
] ],
] ],
], ],
]; ];
@ -279,6 +309,31 @@ class QueryExecutionTest extends ServerTestCase
$this->assertEquals($expected[1], $result[1]->toArray()); $this->assertEquals($expected[1], $result[1]->toArray());
} }
/**
* @param mixed[][] $qs
*/
private function executeBatchedQuery(array $qs)
{
$batch = [];
foreach ($qs as $params) {
$batch[] = OperationParams::create($params);
}
$helper = new Helper();
$result = $helper->executeBatch($this->config, $batch);
$this->assertInternalType('array', $result);
$this->assertCount(count($qs), $result);
foreach ($result as $index => $entry) {
$this->assertInstanceOf(
ExecutionResult::class,
$entry,
sprintf('Result at %s is not an instance of %s', $index, ExecutionResult::class)
);
}
return $result;
}
public function testMutationsAreNotAllowedInReadonlyMode() : void public function testMutationsAreNotAllowedInReadonlyMode() : void
{ {
$mutation = 'mutation { a }'; $mutation = 'mutation { a }';
@ -287,9 +342,9 @@ class QueryExecutionTest extends ServerTestCase
'errors' => [ 'errors' => [
[ [
'message' => 'GET supports only query operation', 'message' => 'GET supports only query operation',
'category' => 'request' 'category' => 'request',
] ],
] ],
]; ];
$result = $this->executeQuery($mutation, null, true); $result = $this->executeQuery($mutation, null, true);
@ -299,9 +354,10 @@ class QueryExecutionTest extends ServerTestCase
public function testAllowsPersistentQueries() : void public function testAllowsPersistentQueries() : void
{ {
$called = false; $called = false;
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) { $this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
$called = true; $called = true;
$this->assertEquals('some-id', $queryId); $this->assertEquals('some-id', $queryId);
return '{f1}'; return '{f1}';
}); });
@ -309,17 +365,16 @@ class QueryExecutionTest extends ServerTestCase
$this->assertTrue($called); $this->assertTrue($called);
$expected = [ $expected = [
'data' => [ 'data' => ['f1' => 'f1'],
'f1' => 'f1'
]
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
// Make sure it allows returning document node: // Make sure it allows returning document node:
$called = false; $called = false;
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) { $this->config->setPersistentQueryLoader(function ($queryId, OperationParams $params) use (&$called) {
$called = true; $called = true;
$this->assertEquals('some-id', $queryId); $this->assertEquals('some-id', $queryId);
return Parser::parse('{f1}'); return Parser::parse('{f1}');
}); });
$result = $this->executePersistedQuery('some-id'); $result = $this->executePersistedQuery('some-id');
@ -334,7 +389,7 @@ class QueryExecutionTest extends ServerTestCase
'Persistent query loader must return query string or instance of GraphQL\Language\AST\DocumentNode ' . 'Persistent query loader must return query string or instance of GraphQL\Language\AST\DocumentNode ' .
'but got: {"err":"err"}' 'but got: {"err":"err"}'
); );
$this->config->setPersistentQueryLoader(function($queryId, OperationParams $params) use (&$called) { $this->config->setPersistentQueryLoader(function () {
return ['err' => 'err']; return ['err' => 'err'];
}); });
$this->executePersistedQuery('some-id'); $this->executePersistedQuery('some-id');
@ -342,7 +397,7 @@ class QueryExecutionTest extends ServerTestCase
public function testPersistedQueriesAreStillValidatedByDefault() : void public function testPersistedQueriesAreStillValidatedByDefault() : void
{ {
$this->config->setPersistentQueryLoader(function() { $this->config->setPersistentQueryLoader(function () {
return '{invalid}'; return '{invalid}';
}); });
$result = $this->executePersistedQuery('some-id'); $result = $this->executePersistedQuery('some-id');
@ -350,36 +405,35 @@ class QueryExecutionTest extends ServerTestCase
'errors' => [ 'errors' => [
[ [
'message' => 'Cannot query field "invalid" on type "Query".', 'message' => 'Cannot query field "invalid" on type "Query".',
'locations' => [ ['line' => 1, 'column' => 2] ], 'locations' => [['line' => 1, 'column' => 2]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
public function testAllowSkippingValidationForPersistedQueries() : void public function testAllowSkippingValidationForPersistedQueries() : void
{ {
$this->config $this->config
->setPersistentQueryLoader(function($queryId) { ->setPersistentQueryLoader(function ($queryId) {
if ($queryId === 'some-id') { if ($queryId === 'some-id') {
return '{invalid}'; return '{invalid}';
} else {
return '{invalid2}';
} }
return '{invalid2}';
}) })
->setValidationRules(function(OperationParams $params) { ->setValidationRules(function (OperationParams $params) {
if ($params->queryId === 'some-id') { if ($params->queryId === 'some-id') {
return []; return [];
} else {
return DocumentValidator::allRules();
} }
return DocumentValidator::allRules();
}); });
$result = $this->executePersistedQuery('some-id'); $result = $this->executePersistedQuery('some-id');
$expected = [ $expected = [
'data' => [] 'data' => [],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
@ -388,10 +442,10 @@ class QueryExecutionTest extends ServerTestCase
'errors' => [ 'errors' => [
[ [
'message' => 'Cannot query field "invalid2" on type "Query".', 'message' => 'Cannot query field "invalid2" on type "Query".',
'locations' => [ ['line' => 1, 'column' => 2] ], 'locations' => [['line' => 1, 'column' => 2]],
'category' => 'graphql' 'category' => 'graphql',
] ],
] ],
]; ];
$this->assertEquals($expected, $result->toArray()); $this->assertEquals($expected, $result->toArray());
} }
@ -400,7 +454,7 @@ class QueryExecutionTest extends ServerTestCase
{ {
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Expecting validation rules to be array or callable returning array, but got: instance of stdClass'); $this->expectExceptionMessage('Expecting validation rules to be array or callable returning array, but got: instance of stdClass');
$this->config->setValidationRules(function(OperationParams $params) { $this->config->setValidationRules(function (OperationParams $params) {
return new \stdClass(); return new \stdClass();
}); });
$this->executeQuery('{f1}'); $this->executeQuery('{f1}');
@ -411,12 +465,8 @@ class QueryExecutionTest extends ServerTestCase
$this->config->setQueryBatching(true); $this->config->setQueryBatching(true);
$batch = [ $batch = [
[ ['query' => '{invalid}'],
'query' => '{invalid}' ['query' => '{f1,fieldWithSafeException}'],
],
[
'query' => '{f1,fieldWithSafeException}'
],
[ [
'query' => ' 'query' => '
query ($a: String!, $b: String!) { query ($a: String!, $b: String!) {
@ -425,14 +475,14 @@ class QueryExecutionTest extends ServerTestCase
} }
', ',
'variables' => ['a' => 'a', 'b' => 'b'], 'variables' => ['a' => 'a', 'b' => 'b'],
] ],
]; ];
$result = $this->executeBatchedQuery($batch); $result = $this->executeBatchedQuery($batch);
$expected = [ $expected = [
[ [
'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']] 'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']],
], ],
[ [
'data' => [ 'data' => [
@ -440,15 +490,15 @@ class QueryExecutionTest extends ServerTestCase
'fieldWithSafeException' => null 'fieldWithSafeException' => null
], ],
'errors' => [ 'errors' => [
['message' => 'This is the exception we want'] ['message' => 'This is the exception we want'],
] ],
], ],
[ [
'data' => [ 'data' => [
'a' => 'a', 'a' => 'a',
'b' => 'b' 'b' => 'b',
] ],
] ],
]; ];
$this->assertArraySubset($expected[0], $result[0]->toArray()); $this->assertArraySubset($expected[0], $result[0]->toArray());
@ -459,15 +509,9 @@ class QueryExecutionTest extends ServerTestCase
public function testDeferredsAreSharedAmongAllBatchedQueries() : void public function testDeferredsAreSharedAmongAllBatchedQueries() : void
{ {
$batch = [ $batch = [
[ ['query' => '{dfd(num: 1)}'],
'query' => '{dfd(num: 1)}' ['query' => '{dfd(num: 2)}'],
], ['query' => '{dfd(num: 3)}'],
[
'query' => '{dfd(num: 2)}'
],
[
'query' => '{dfd(num: 3)}',
]
]; ];
$calls = []; $calls = [];
@ -476,13 +520,14 @@ class QueryExecutionTest extends ServerTestCase
->setQueryBatching(true) ->setQueryBatching(true)
->setRootValue('1') ->setRootValue('1')
->setContext([ ->setContext([
'buffer' => function($num) use (&$calls) { 'buffer' => function ($num) use (&$calls) {
$calls[] = "buffer: $num"; $calls[] = sprintf('buffer: %d', $num);
},
'load' => function ($num) use (&$calls) {
$calls[] = sprintf('load: %d', $num);
return sprintf('loaded: %d', $num);
}, },
'load' => function($num) use (&$calls) {
$calls[] = "load: $num";
return "loaded: $num";
}
]); ]);
$result = $this->executeBatchedQuery($batch); $result = $this->executeBatchedQuery($batch);
@ -499,19 +544,13 @@ class QueryExecutionTest extends ServerTestCase
$expected = [ $expected = [
[ [
'data' => [ 'data' => ['dfd' => 'loaded: 1'],
'dfd' => 'loaded: 1'
]
], ],
[ [
'data' => [ 'data' => ['dfd' => 'loaded: 2'],
'dfd' => 'loaded: 2'
]
], ],
[ [
'data' => [ 'data' => ['dfd' => 'loaded: 3'],
'dfd' => 'loaded: 3'
]
], ],
]; ];
@ -546,7 +585,7 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$params = $doc = $operationType = null; $params = $doc = $operationType = null;
$this->config->setContext(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { $this->config->setContext(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true; $called = true;
$params = $p; $params = $p;
$doc = $d; $doc = $d;
@ -566,7 +605,7 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$params = $doc = $operationType = null; $params = $doc = $operationType = null;
$this->config->setRootValue(function($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { $this->config->setRootValue(function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) {
$called = true; $called = true;
$params = $p; $params = $p;
$doc = $d; $doc = $d;
@ -585,9 +624,10 @@ class QueryExecutionTest extends ServerTestCase
{ {
$called = false; $called = false;
$error = null; $error = null;
$this->config->setErrorFormatter(function($e) use (&$called, &$error) { $this->config->setErrorFormatter(function ($e) use (&$called, &$error) {
$called = true; $called = true;
$error = $e; $error = $e;
return ['test' => 'formatted']; return ['test' => 'formatted'];
}); });
@ -596,8 +636,8 @@ class QueryExecutionTest extends ServerTestCase
$formatted = $result->toArray(); $formatted = $result->toArray();
$expected = [ $expected = [
'errors' => [ 'errors' => [
['test' => 'formatted'] ['test' => 'formatted'],
] ],
]; ];
$this->assertTrue($called); $this->assertTrue($called);
$this->assertArraySubset($expected, $formatted); $this->assertArraySubset($expected, $formatted);
@ -609,9 +649,9 @@ class QueryExecutionTest extends ServerTestCase
'errors' => [ 'errors' => [
[ [
'test' => 'formatted', 'test' => 'formatted',
'trace' => [] 'trace' => [],
] ],
] ],
]; ];
$this->assertArraySubset($expected, $formatted); $this->assertArraySubset($expected, $formatted);
} }
@ -621,12 +661,13 @@ class QueryExecutionTest extends ServerTestCase
$called = false; $called = false;
$errors = null; $errors = null;
$formatter = null; $formatter = null;
$this->config->setErrorsHandler(function($e, $f) use (&$called, &$errors, &$formatter) { $this->config->setErrorsHandler(function ($e, $f) use (&$called, &$errors, &$formatter) {
$called = true; $called = true;
$errors = $e; $errors = $e;
$formatter = $f; $formatter = $f;
return [ return [
['test' => 'handled'] ['test' => 'handled'],
]; ];
}); });
@ -636,8 +677,8 @@ class QueryExecutionTest extends ServerTestCase
$formatted = $result->toArray(); $formatted = $result->toArray();
$expected = [ $expected = [
'errors' => [ 'errors' => [
['test' => 'handled'] ['test' => 'handled'],
] ],
]; ];
$this->assertTrue($called); $this->assertTrue($called);
$this->assertArraySubset($expected, $formatted); $this->assertArraySubset($expected, $formatted);
@ -646,46 +687,4 @@ class QueryExecutionTest extends ServerTestCase
$this->assertInternalType('callable', $formatter); $this->assertInternalType('callable', $formatter);
$this->assertArraySubset($expected, $formatted); $this->assertArraySubset($expected, $formatted);
} }
private function executePersistedQuery($queryId, $variables = null)
{
$op = OperationParams::create(['queryId' => $queryId, 'variables' => $variables]);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
private function executeQuery($query, $variables = null, $readonly = false)
{
$op = OperationParams::create(['query' => $query, 'variables' => $variables], $readonly);
$helper = new Helper();
$result = $helper->executeOperation($this->config, $op);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
private function executeBatchedQuery(array $qs)
{
$batch = [];
foreach ($qs as $params) {
$batch[] = OperationParams::create($params);
}
$helper = new Helper();
$result = $helper->executeBatch($this->config, $batch);
$this->assertInternalType('array', $result);
$this->assertCount(count($qs), $result);
foreach ($result as $index => $entry) {
$this->assertInstanceOf(ExecutionResult::class, $entry, "Result at $index is not an instance of " . ExecutionResult::class);
}
return $result;
}
private function assertQueryResultEquals($expected, $query, $variables = null)
{
$result = $this->executeQuery($query, $variables);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
} }

View File

@ -1,7 +1,9 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Server\Helper; use GraphQL\Server\Helper;
use GraphQL\Server\OperationParams; use GraphQL\Server\OperationParams;
@ -9,6 +11,8 @@ use GraphQL\Server\RequestError;
use GraphQL\Tests\Server\Psr7\PsrRequestStub; use GraphQL\Tests\Server\Psr7\PsrRequestStub;
use GraphQL\Tests\Server\Psr7\PsrStreamStub; use GraphQL\Tests\Server\Psr7\PsrStreamStub;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function json_decode;
use function json_encode;
class RequestParsingTest extends TestCase class RequestParsingTest extends TestCase
{ {
@ -17,7 +21,7 @@ class RequestParsingTest extends TestCase
$query = '{my query}'; $query = '{my query}';
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/graphql', $query), 'raw' => $this->parseRawRequest('application/graphql', $query),
'psr' => $this->parsePsrRequest('application/graphql', $query) 'psr' => $this->parsePsrRequest('application/graphql', $query),
]; ];
foreach ($parsed as $source => $parsedBody) { foreach ($parsed as $source => $parsedBody) {
@ -26,6 +30,77 @@ class RequestParsingTest extends TestCase
} }
} }
/**
* @param string $contentType
* @param string $content
*
* @return OperationParams|OperationParams[]
*/
private function parseRawRequest($contentType, $content, string $method = 'POST')
{
$_SERVER['CONTENT_TYPE'] = $contentType;
$_SERVER['REQUEST_METHOD'] = $method;
$helper = new Helper();
return $helper->parseHttpRequest(function () use ($content) {
return $content;
});
}
/**
* @param string $contentType
* @param string $content
*
* @return OperationParams|OperationParams[]
*/
private function parsePsrRequest($contentType, $content, string $method = 'POST')
{
$psrRequestBody = new PsrStreamStub();
$psrRequestBody->content = $content;
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = $method;
$psrRequest->body = $psrRequestBody;
if ($contentType === 'application/json') {
$parsedBody = json_decode($content, true);
$parsedBody = $parsedBody === false ? null : $parsedBody;
} else {
$parsedBody = null;
}
$psrRequest->parsedBody = $parsedBody;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param OperationParams $params
* @param string $query
* @param string $queryId
* @param mixed|null $variables
* @param string $operation
*/
private function assertValidOperationParams(
$params,
$query,
$queryId = null,
$variables = null,
$operation = null,
$message = ''
) {
$this->assertInstanceOf(OperationParams::class, $params, $message);
$this->assertSame($query, $params->query, $message);
$this->assertSame($queryId, $params->queryId, $message);
$this->assertSame($variables, $params->variables, $message);
$this->assertSame($operation, $params->operation, $message);
}
public function testParsesUrlencodedRequest() : void public function testParsesUrlencodedRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
@ -35,11 +110,11 @@ class RequestParsingTest extends TestCase
$post = [ $post = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawFormUrlencodedRequest($post), 'raw' => $this->parseRawFormUrlencodedRequest($post),
'psr' => $this->parsePsrFormUrlEncodedRequest($post) 'psr' => $this->parsePsrFormUrlEncodedRequest($post),
]; ];
foreach ($parsed as $method => $parsedBody) { foreach ($parsed as $method => $parsedBody) {
@ -48,6 +123,39 @@ class RequestParsingTest extends TestCase
} }
} }
/**
* @param mixed[] $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawFormUrlencodedRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function () {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param mixed[] $postValue
* @return OperationParams[]|OperationParams
*/
private function parsePsrFormUrlEncodedRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
public function testParsesGetRequest() : void public function testParsesGetRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
@ -57,11 +165,11 @@ class RequestParsingTest extends TestCase
$get = [ $get = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawGetRequest($get), 'raw' => $this->parseRawGetRequest($get),
'psr' => $this->parsePsrGetRequest($get) 'psr' => $this->parsePsrGetRequest($get),
]; ];
foreach ($parsed as $method => $parsedBody) { foreach ($parsed as $method => $parsedBody) {
@ -70,6 +178,37 @@ class RequestParsingTest extends TestCase
} }
} }
/**
* @param mixed[] $getValue
* @return OperationParams
*/
private function parseRawGetRequest($getValue)
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$_GET = $getValue;
$helper = new Helper();
return $helper->parseHttpRequest(function () {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param mixed[] $getValue
* @return OperationParams[]|OperationParams
*/
private function parsePsrGetRequest($getValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->method = 'GET';
$psrRequest->queryParams = $getValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
public function testParsesMultipartFormdataRequest() : void public function testParsesMultipartFormdataRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
@ -79,11 +218,11 @@ class RequestParsingTest extends TestCase
$post = [ $post = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawMultipartFormdataRequest($post), 'raw' => $this->parseRawMultipartFormdataRequest($post),
'psr' => $this->parsePsrMultipartFormdataRequest($post) 'psr' => $this->parsePsrMultipartFormdataRequest($post),
]; ];
foreach ($parsed as $method => $parsedBody) { foreach ($parsed as $method => $parsedBody) {
@ -92,6 +231,39 @@ class RequestParsingTest extends TestCase
} }
} }
/**
* @param mixed[] $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawMultipartFormDataRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function () {
throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
});
}
/**
* @param mixed[] $postValue
* @return OperationParams|OperationParams[]
*/
private function parsePsrMultipartFormDataRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
public function testParsesJSONRequest() : void public function testParsesJSONRequest() : void
{ {
$query = '{my query}'; $query = '{my query}';
@ -101,11 +273,11 @@ class RequestParsingTest extends TestCase
$body = [ $body = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'raw' => $this->parseRawRequest('application/json', json_encode($body)),
'psr' => $this->parsePsrRequest('application/json', json_encode($body)) 'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
]; ];
foreach ($parsed as $method => $parsedBody) { foreach ($parsed as $method => $parsedBody) {
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
@ -122,11 +294,11 @@ class RequestParsingTest extends TestCase
$body = [ $body = [
'query' => $query, 'query' => $query,
'variables' => json_encode($variables), 'variables' => json_encode($variables),
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'raw' => $this->parseRawRequest('application/json', json_encode($body)),
'psr' => $this->parsePsrRequest('application/json', json_encode($body)) 'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
]; ];
foreach ($parsed as $method => $parsedBody) { foreach ($parsed as $method => $parsedBody) {
$this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); $this->assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method);
@ -143,7 +315,7 @@ class RequestParsingTest extends TestCase
$body = [ $body = [
'query' => $query, 'query' => $query,
'variables' => $variables, 'variables' => $variables,
'operationName' => $operation 'operationName' => $operation,
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'raw' => $this->parseRawRequest('application/json', json_encode($body)),
@ -161,23 +333,37 @@ class RequestParsingTest extends TestCase
[ [
'query' => '{my query}', 'query' => '{my query}',
'variables' => ['test' => 1, 'test2' => 2], 'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op' 'operationName' => 'op',
], ],
[ [
'queryId' => 'my-query-id', 'queryId' => 'my-query-id',
'variables' => ['test' => 1, 'test2' => 2], 'variables' => ['test' => 1, 'test2' => 2],
'operationName' => 'op2' 'operationName' => 'op2',
], ],
]; ];
$parsed = [ $parsed = [
'raw' => $this->parseRawRequest('application/json', json_encode($body)), 'raw' => $this->parseRawRequest('application/json', json_encode($body)),
'psr' => $this->parsePsrRequest('application/json', json_encode($body)) 'psr' => $this->parsePsrRequest('application/json', json_encode($body)),
]; ];
foreach ($parsed as $method => $parsedBody) { foreach ($parsed as $method => $parsedBody) {
$this->assertInternalType('array', $parsedBody, $method); $this->assertInternalType('array', $parsedBody, $method);
$this->assertCount(2, $parsedBody, $method); $this->assertCount(2, $parsedBody, $method);
$this->assertValidOperationParams($parsedBody[0], $body[0]['query'], null, $body[0]['variables'], $body[0]['operationName'], $method); $this->assertValidOperationParams(
$this->assertValidOperationParams($parsedBody[1], null, $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], $method); $parsedBody[0],
$body[0]['query'],
null,
$body[0]['variables'],
$body[0]['operationName'],
$method
);
$this->assertValidOperationParams(
$parsedBody[1],
null,
$body[1]['queryId'],
$body[1]['variables'],
$body[1]['operationName'],
$method
);
} }
} }
@ -272,169 +458,13 @@ class RequestParsingTest extends TestCase
{ {
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('HTTP Method "PUT" is not supported'); $this->expectExceptionMessage('HTTP Method "PUT" is not supported');
$this->parseRawRequest('application/json', json_encode([]), "PUT"); $this->parseRawRequest('application/json', json_encode([]), 'PUT');
} }
public function testFailsOnMethodsOtherThanPostOrGetPsr() : void public function testFailsOnMethodsOtherThanPostOrGetPsr() : void
{ {
$this->expectException(RequestError::class); $this->expectException(RequestError::class);
$this->expectExceptionMessage('HTTP Method "PUT" is not supported'); $this->expectExceptionMessage('HTTP Method "PUT" is not supported');
$this->parsePsrRequest('application/json', json_encode([]), "PUT"); $this->parsePsrRequest('application/json', json_encode([]), 'PUT');
}
/**
* @param string $contentType
* @param string $content
* @param $method
*
* @return OperationParams|OperationParams[]
*/
private function parseRawRequest($contentType, $content, $method = 'POST')
{
$_SERVER['CONTENT_TYPE'] = $contentType;
$_SERVER['REQUEST_METHOD'] = $method;
$helper = new Helper();
return $helper->parseHttpRequest(function() use ($content) {
return $content;
});
}
/**
* @param string $contentType
* @param string $content
* @param $method
*
* @return OperationParams|OperationParams[]
*/
private function parsePsrRequest($contentType, $content, $method = 'POST')
{
$psrRequestBody = new PsrStreamStub();
$psrRequestBody->content = $content;
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = $method;
$psrRequest->body = $psrRequestBody;
if ($contentType === 'application/json') {
$parsedBody = json_decode($content, true);
$parsedBody = $parsedBody === false ? null : $parsedBody;
} else {
$parsedBody = null;
}
$psrRequest->parsedBody = $parsedBody;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param array $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawFormUrlencodedRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function() {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param $postValue
* @return array|Helper
*/
private function parsePsrFormUrlEncodedRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param array $postValue
* @return OperationParams|OperationParams[]
*/
private function parseRawMultipartFormDataRequest($postValue)
{
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data; boundary=----FormBoundary';
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = $postValue;
$helper = new Helper();
return $helper->parseHttpRequest(function() {
throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request");
});
}
/**
* @param $postValue
* @return array|Helper
*/
private function parsePsrMultipartFormDataRequest($postValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary'];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $postValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param $getValue
* @return OperationParams
*/
private function parseRawGetRequest($getValue)
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$_GET = $getValue;
$helper = new Helper();
return $helper->parseHttpRequest(function() {
throw new InvariantViolation("Shouldn't read from php://input for urlencoded request");
});
}
/**
* @param $getValue
* @return array|Helper
*/
private function parsePsrGetRequest($getValue)
{
$psrRequest = new PsrRequestStub();
$psrRequest->method = 'GET';
$psrRequest->queryParams = $getValue;
$helper = new Helper();
return $helper->parsePsrRequest($psrRequest);
}
/**
* @param OperationParams $params
* @param string $query
* @param string $queryId
* @param array $variables
* @param string $operation
*/
private function assertValidOperationParams($params, $query, $queryId = null, $variables = null, $operation = null, $message = '')
{
$this->assertInstanceOf(OperationParams::class, $params, $message);
$this->assertSame($query, $params->query, $message);
$this->assertSame($queryId, $params->queryId, $message);
$this->assertSame($variables, $params->variables, $message);
$this->assertSame($operation, $params->operation, $message);
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Server\Helper; use GraphQL\Server\Helper;
@ -22,6 +25,13 @@ class RequestValidationTest extends TestCase
$this->assertValid($parsedBody); $this->assertValid($parsedBody);
} }
private function assertValid($parsedRequest)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
$this->assertEmpty($errors, isset($errors[0]) ? $errors[0]->getMessage() : '');
}
public function testRequestWithQueryIdShouldValidate() : void public function testRequestWithQueryIdShouldValidate() : void
{ {
$queryId = 'some-query-id'; $queryId = 'some-query-id';
@ -50,6 +60,17 @@ class RequestValidationTest extends TestCase
); );
} }
private function assertInputError($parsedRequest, $expectedMessage)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
if (! empty($errors[0])) {
$this->assertEquals($expectedMessage, $errors[0]->getMessage());
} else {
$this->fail('Expected error not returned');
}
}
public function testFailsWhenBothQueryAndQueryIdArePresent() : void public function testFailsWhenBothQueryAndQueryIdArePresent() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
@ -66,7 +87,7 @@ class RequestValidationTest extends TestCase
public function testFailsWhenQueryParameterIsNotString() : void public function testFailsWhenQueryParameterIsNotString() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => ['t' => '{my query}'] 'query' => ['t' => '{my query}'],
]); ]);
$this->assertInputError( $this->assertInputError(
@ -78,7 +99,7 @@ class RequestValidationTest extends TestCase
public function testFailsWhenQueryIdParameterIsNotString() : void public function testFailsWhenQueryIdParameterIsNotString() : void
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'queryId' => ['t' => '{my query}'] 'queryId' => ['t' => '{my query}'],
]); ]);
$this->assertInputError( $this->assertInputError(
@ -91,7 +112,7 @@ class RequestValidationTest extends TestCase
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => '{my query}', 'query' => '{my query}',
'operationName' => [] 'operationName' => [],
]); ]);
$this->assertInputError( $this->assertInputError(
@ -108,14 +129,14 @@ class RequestValidationTest extends TestCase
$query = '{my q}'; $query = '{my q}';
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => $query, 'query' => $query,
'variables' => null 'variables' => null,
]); ]);
$this->assertValid($parsedBody); $this->assertValid($parsedBody);
$variables = ""; $variables = '';
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => $query, 'query' => $query,
'variables' => $variables 'variables' => $variables,
]); ]);
$this->assertValid($parsedBody); $this->assertValid($parsedBody);
} }
@ -124,7 +145,7 @@ class RequestValidationTest extends TestCase
{ {
$parsedBody = OperationParams::create([ $parsedBody = OperationParams::create([
'query' => '{my query}', 'query' => '{my query}',
'variables' => 0 'variables' => 0,
]); ]);
$this->assertInputError( $this->assertInputError(
@ -132,22 +153,4 @@ class RequestValidationTest extends TestCase
'GraphQL Request parameter "variables" must be object or JSON string parsed to object, but got 0' 'GraphQL Request parameter "variables" must be object or JSON string parsed to object, but got 0'
); );
} }
private function assertValid($parsedRequest)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
$this->assertEmpty($errors, isset($errors[0]) ? $errors[0]->getMessage() : '');
}
private function assertInputError($parsedRequest, $expectedMessage)
{
$helper = new Helper();
$errors = $helper->validateOperationParams($parsedRequest);
if (!empty($errors[0])) {
$this->assertEquals($expectedMessage, $errors[0]->getMessage());
} else {
$this->fail('Expected error not returned');
}
}
} }

View File

@ -1,12 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Type\Schema;
use GraphQL\Server\ServerConfig; use GraphQL\Server\ServerConfig;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ServerConfigTest extends TestCase class ServerConfigTest extends TestCase
@ -14,17 +17,17 @@ class ServerConfigTest extends TestCase
public function testDefaults() : void public function testDefaults() : void
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$this->assertEquals(null, $config->getSchema()); $this->assertNull($config->getSchema());
$this->assertEquals(null, $config->getContext()); $this->assertNull($config->getContext());
$this->assertEquals(null, $config->getRootValue()); $this->assertNull($config->getRootValue());
$this->assertEquals(null, $config->getErrorFormatter()); $this->assertNull($config->getErrorFormatter());
$this->assertEquals(null, $config->getErrorsHandler()); $this->assertNull($config->getErrorsHandler());
$this->assertEquals(null, $config->getPromiseAdapter()); $this->assertNull($config->getPromiseAdapter());
$this->assertEquals(null, $config->getValidationRules()); $this->assertNull($config->getValidationRules());
$this->assertEquals(null, $config->getFieldResolver()); $this->assertNull($config->getFieldResolver());
$this->assertEquals(null, $config->getPersistentQueryLoader()); $this->assertNull($config->getPersistentQueryLoader());
$this->assertEquals(false, $config->getDebug()); $this->assertFalse($config->getDebug());
$this->assertEquals(false, $config->getQueryBatching()); $this->assertFalse($config->getQueryBatching());
} }
public function testAllowsSettingSchema() : void public function testAllowsSettingSchema() : void
@ -70,7 +73,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$formatter = function() {}; $formatter = function () {
};
$config->setErrorFormatter($formatter); $config->setErrorFormatter($formatter);
$this->assertSame($formatter, $config->getErrorFormatter()); $this->assertSame($formatter, $config->getErrorFormatter());
@ -83,7 +87,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$handler = function() {}; $handler = function () {
};
$config->setErrorsHandler($handler); $config->setErrorsHandler($handler);
$this->assertSame($handler, $config->getErrorsHandler()); $this->assertSame($handler, $config->getErrorsHandler());
@ -113,11 +118,17 @@ class ServerConfigTest extends TestCase
$config->setValidationRules($rules); $config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules()); $this->assertSame($rules, $config->getValidationRules());
$rules = [function() {}]; $rules = [function () {
},
];
$config->setValidationRules($rules); $config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules()); $this->assertSame($rules, $config->getValidationRules());
$rules = function() {return [function() {}];}; $rules = function () {
return [function () {
},
];
};
$config->setValidationRules($rules); $config->setValidationRules($rules);
$this->assertSame($rules, $config->getValidationRules()); $this->assertSame($rules, $config->getValidationRules());
} }
@ -126,7 +137,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$resolver = function() {}; $resolver = function () {
};
$config->setFieldResolver($resolver); $config->setFieldResolver($resolver);
$this->assertSame($resolver, $config->getFieldResolver()); $this->assertSame($resolver, $config->getFieldResolver());
@ -139,7 +151,8 @@ class ServerConfigTest extends TestCase
{ {
$config = ServerConfig::create(); $config = ServerConfig::create();
$loader = function() {}; $loader = function () {
};
$config->setPersistentQueryLoader($loader); $config->setPersistentQueryLoader($loader);
$this->assertSame($loader, $config->getPersistentQueryLoader()); $this->assertSame($loader, $config->getPersistentQueryLoader());
@ -153,25 +166,30 @@ class ServerConfigTest extends TestCase
$config = ServerConfig::create(); $config = ServerConfig::create();
$config->setDebug(true); $config->setDebug(true);
$this->assertSame(true, $config->getDebug()); $this->assertTrue($config->getDebug());
$config->setDebug(false); $config->setDebug(false);
$this->assertSame(false, $config->getDebug()); $this->assertFalse($config->getDebug());
} }
public function testAcceptsArray() : void public function testAcceptsArray() : void
{ {
$arr = [ $arr = [
'schema' => new \GraphQL\Type\Schema([ 'schema' => new Schema([
'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]]) 'query' => new ObjectType(['name' => 't', 'fields' => ['a' => Type::string()]]),
]), ]),
'context' => new \stdClass(), 'context' => new \stdClass(),
'rootValue' => new \stdClass(), 'rootValue' => new \stdClass(),
'errorFormatter' => function() {}, 'errorFormatter' => function () {
},
'promiseAdapter' => new SyncPromiseAdapter(), 'promiseAdapter' => new SyncPromiseAdapter(),
'validationRules' => [function() {}], 'validationRules' => [function () {
'fieldResolver' => function() {}, },
'persistentQueryLoader' => function() {}, ],
'fieldResolver' => function () {
},
'persistentQueryLoader' => function () {
},
'debug' => true, 'debug' => true,
'queryBatching' => true, 'queryBatching' => true,
]; ];
@ -186,15 +204,13 @@ class ServerConfigTest extends TestCase
$this->assertSame($arr['validationRules'], $config->getValidationRules()); $this->assertSame($arr['validationRules'], $config->getValidationRules());
$this->assertSame($arr['fieldResolver'], $config->getFieldResolver()); $this->assertSame($arr['fieldResolver'], $config->getFieldResolver());
$this->assertSame($arr['persistentQueryLoader'], $config->getPersistentQueryLoader()); $this->assertSame($arr['persistentQueryLoader'], $config->getPersistentQueryLoader());
$this->assertSame(true, $config->getDebug()); $this->assertTrue($config->getDebug());
$this->assertSame(true, $config->getQueryBatching()); $this->assertTrue($config->getQueryBatching());
} }
public function testThrowsOnInvalidArrayKey() : void public function testThrowsOnInvalidArrayKey() : void
{ {
$arr = [ $arr = ['missingKey' => 'value'];
'missingKey' => 'value'
];
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Unknown server config option "missingKey"'); $this->expectExceptionMessage('Unknown server config option "missingKey"');

View File

@ -1,6 +1,8 @@
<?php <?php
namespace GraphQL\Tests\Server;
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\ClientAware; use GraphQL\Error\ClientAware;
@ -9,6 +11,10 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function trigger_error;
use const E_USER_DEPRECATED;
use const E_USER_NOTICE;
use const E_USER_WARNING;
abstract class ServerTestCase extends TestCase abstract class ServerTestCase extends TestCase
{ {
@ -20,20 +26,21 @@ abstract class ServerTestCase extends TestCase
'fields' => [ 'fields' => [
'f1' => [ 'f1' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($root, $args, $context, $info) { 'resolve' => function ($root, $args, $context, $info) {
return $info->fieldName; return $info->fieldName;
} },
], ],
'fieldWithPhpError' => [ 'fieldWithPhpError' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($root, $args, $context, $info) { 'resolve' => function ($root, $args, $context, $info) {
trigger_error('deprecated', E_USER_DEPRECATED); trigger_error('deprecated', E_USER_DEPRECATED);
trigger_error('notice', E_USER_NOTICE); trigger_error('notice', E_USER_NOTICE);
trigger_error('warning', E_USER_WARNING); trigger_error('warning', E_USER_WARNING);
$a = []; $a = [];
$a['test']; // should produce PHP notice $a['test']; // should produce PHP notice
return $info->fieldName; return $info->fieldName;
} },
], ],
'fieldWithSafeException' => [ 'fieldWithSafeException' => [
'type' => Type::string(), 'type' => Type::string(),
@ -49,38 +56,39 @@ abstract class ServerTestCase extends TestCase
], ],
'testContextAndRootValue' => [ 'testContextAndRootValue' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function($root, $args, $context, $info) { 'resolve' => function ($root, $args, $context, $info) {
$context->testedRootValue = $root; $context->testedRootValue = $root;
return $info->fieldName; return $info->fieldName;
} },
], ],
'fieldWithArg' => [ 'fieldWithArg' => [
'type' => Type::string(), 'type' => Type::string(),
'args' => [ 'args' => [
'arg' => [ 'arg' => [
'type' => Type::nonNull(Type::string()) 'type' => Type::nonNull(Type::string()),
], ],
], ],
'resolve' => function($root, $args) { 'resolve' => function ($root, $args) {
return $args['arg']; return $args['arg'];
} },
], ],
'dfd' => [ 'dfd' => [
'type' => Type::string(), 'type' => Type::string(),
'args' => [ 'args' => [
'num' => [ 'num' => [
'type' => Type::nonNull(Type::int()) 'type' => Type::nonNull(Type::int()),
], ],
], ],
'resolve' => function($root, $args, $context) { 'resolve' => function ($root, $args, $context) {
$context['buffer']($args['num']); $context['buffer']($args['num']);
return new Deferred(function() use ($args, $context) { return new Deferred(function () use ($args, $context) {
return $context['load']($args['num']); return $context['load']($args['num']);
}); });
} },
] ],
] ],
]), ]),
'mutation' => new ObjectType([ 'mutation' => new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
@ -89,13 +97,14 @@ abstract class ServerTestCase extends TestCase
'type' => new ObjectType([ 'type' => new ObjectType([
'name' => 'TestMutation', 'name' => 'TestMutation',
'fields' => [ 'fields' => [
'result' => Type::string() 'result' => Type::string(),
] ],
]) ]),
] ],
] ],
]) ]),
]); ]);
return $schema; return $schema;
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
@ -6,13 +9,11 @@ use GraphQL\Server\Helper;
use GraphQL\Server\ServerConfig; use GraphQL\Server\ServerConfig;
use GraphQL\Server\StandardServer; use GraphQL\Server\StandardServer;
use GraphQL\Tests\Server\Psr7\PsrRequestStub; use GraphQL\Tests\Server\Psr7\PsrRequestStub;
use GraphQL\Tests\Server\Psr7\PsrStreamStub; use function json_encode;
class StandardServerTest extends ServerTestCase class StandardServerTest extends ServerTestCase
{ {
/** /** @var ServerConfig */
* @var ServerConfig
*/
private $config; private $config;
public function setUp() public function setUp()
@ -24,88 +25,82 @@ class StandardServerTest extends ServerTestCase
public function testSimpleRequestExecutionWithOutsideParsing() : void public function testSimpleRequestExecutionWithOutsideParsing() : void
{ {
$body = json_encode([ $body = json_encode(['query' => '{f1}']);
'query' => '{f1}'
]);
$parsedBody = $this->parseRawRequest('application/json', $body); $parsedBody = $this->parseRawRequest('application/json', $body);
$server = new StandardServer($this->config); $server = new StandardServer($this->config);
$result = $server->executeRequest($parsedBody); $result = $server->executeRequest($parsedBody);
$expected = [ $expected = [
'data' => [ 'data' => ['f1' => 'f1'],
'f1' => 'f1',
]
]; ];
$this->assertEquals($expected, $result->toArray(true)); $this->assertEquals($expected, $result->toArray(true));
} }
public function testSimplePsrRequestExecution() : void
{
$body = [
'query' => '{f1}'
];
$expected = [
'data' => [
'f1' => 'f1'
]
];
$request = $this->preparePsrRequest('application/json', $body);
$this->assertPsrRequestEquals($expected, $request);
}
public function testMultipleOperationPsrRequestExecution() : void
{
$body = [
'query' => 'query firstOp {fieldWithPhpError} query secondOp {f1}',
'operationName' => 'secondOp'
];
$expected = [
'data' => [
'f1' => 'f1'
]
];
$request = $this->preparePsrRequest('application/json', $body);
$this->assertPsrRequestEquals($expected, $request);
}
private function executePsrRequest($psrRequest)
{
$server = new StandardServer($this->config);
$result = $server->executePsrRequest($psrRequest);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
private function assertPsrRequestEquals($expected, $request)
{
$result = $this->executePsrRequest($request);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
private function preparePsrRequest($contentType, $parsedBody)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $parsedBody;
return $psrRequest;
}
private function parseRawRequest($contentType, $content, $method = 'POST') private function parseRawRequest($contentType, $content, $method = 'POST')
{ {
$_SERVER['CONTENT_TYPE'] = $contentType; $_SERVER['CONTENT_TYPE'] = $contentType;
$_SERVER['REQUEST_METHOD'] = $method; $_SERVER['REQUEST_METHOD'] = $method;
$helper = new Helper(); $helper = new Helper();
return $helper->parseHttpRequest(function() use ($content) {
return $helper->parseHttpRequest(function () use ($content) {
return $content; return $content;
}); });
} }
public function testSimplePsrRequestExecution() : void
{
$body = ['query' => '{f1}'];
$expected = [
'data' => ['f1' => 'f1'],
];
$request = $this->preparePsrRequest('application/json', $body);
$this->assertPsrRequestEquals($expected, $request);
}
private function preparePsrRequest($contentType, $parsedBody)
{
$psrRequest = new PsrRequestStub();
$psrRequest->headers['content-type'] = [$contentType];
$psrRequest->method = 'POST';
$psrRequest->parsedBody = $parsedBody;
return $psrRequest;
}
private function assertPsrRequestEquals($expected, $request)
{
$result = $this->executePsrRequest($request);
$this->assertArraySubset($expected, $result->toArray(true));
return $result;
}
private function executePsrRequest($psrRequest)
{
$server = new StandardServer($this->config);
$result = $server->executePsrRequest($psrRequest);
$this->assertInstanceOf(ExecutionResult::class, $result);
return $result;
}
public function testMultipleOperationPsrRequestExecution() : void
{
$body = [
'query' => 'query firstOp {fieldWithPhpError} query secondOp {f1}',
'operationName' => 'secondOp',
];
$expected = [
'data' => ['f1' => 'f1'],
];
$request = $this->preparePsrRequest('application/json', $body);
$this->assertPsrRequestEquals($expected, $request);
}
} }

View File

@ -1,8 +1,41 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
use function array_map;
class StarWarsData class StarWarsData
{ {
/**
* Helper function to get a character by ID.
*/
public static function getCharacter($id)
{
$humans = self::humans();
$droids = self::droids();
if (isset($humans[$id])) {
return $humans[$id];
}
if (isset($droids[$id])) {
return $droids[$id];
}
return null;
}
public static function humans()
{
return [
'1000' => self::luke(),
'1001' => self::vader(),
'1002' => self::han(),
'1003' => self::leia(),
'1004' => self::tarkin(),
];
}
private static function luke() private static function luke()
{ {
return [ return [
@ -56,14 +89,11 @@ class StarWarsData
]; ];
} }
static function humans() public static function droids()
{ {
return [ return [
'1000' => self::luke(), '2000' => self::threepio(),
'1001' => self::vader(), '2001' => self::artoo(),
'1002' => self::han(),
'1003' => self::leia(),
'1004' => self::tarkin(),
]; ];
} }
@ -82,7 +112,7 @@ class StarWarsData
* We export artoo directly because the schema returns him * We export artoo directly because the schema returns him
* from a root field, and hence needs to reference him. * from a root field, and hence needs to reference him.
*/ */
static function artoo() private static function artoo()
{ {
return [ return [
@ -94,69 +124,48 @@ class StarWarsData
]; ];
} }
static function droids()
{
return [
'2000' => self::threepio(),
'2001' => self::artoo(),
];
}
/**
* Helper function to get a character by ID.
*/
static function getCharacter($id)
{
$humans = self::humans();
$droids = self::droids();
if (isset($humans[$id])) {
return $humans[$id];
}
if (isset($droids[$id])) {
return $droids[$id];
}
return null;
}
/** /**
* Allows us to query for a character's friends. * Allows us to query for a character's friends.
*/ */
static function getFriends($character) public static function getFriends($character)
{ {
return array_map([__CLASS__, 'getCharacter'], $character['friends']); return array_map([__CLASS__, 'getCharacter'], $character['friends']);
} }
/** /**
* @param $episode * @param int $episode
* @return array * @return mixed[]
*/ */
static function getHero($episode) public static function getHero($episode)
{ {
if ($episode === 5) { if ($episode === 5) {
// Luke is the hero of Episode V. // Luke is the hero of Episode V.
return self::luke(); return self::luke();
} }
// Artoo is the hero otherwise. // Artoo is the hero otherwise.
return self::artoo(); return self::artoo();
} }
/** /**
* @param $id * @param string $id
* @return mixed|null * @return mixed|null
*/ */
static function getHuman($id) public static function getHuman($id)
{ {
$humans = self::humans(); $humans = self::humans();
return isset($humans[$id]) ? $humans[$id] : null;
return $humans[$id] ?? null;
} }
/** /**
* @param $id * @param string $id
* @return mixed|null * @return mixed|null
*/ */
static function getDroid($id) public static function getDroid($id)
{ {
$droids = self::droids(); $droids = self::droids();
return isset($droids[$id]) ? $droids[$id] : null;
return $droids[$id] ?? null;
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
use GraphQL\GraphQL; use GraphQL\GraphQL;
@ -44,12 +47,20 @@ class StarWarsIntrospectionTest extends TestCase
['name' => '__EnumValue'], ['name' => '__EnumValue'],
['name' => '__Directive'], ['name' => '__Directive'],
['name' => '__DirectiveLocation'], ['name' => '__DirectiveLocation'],
] ],
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected) : void
{
$this->assertEquals(['data' => $expected], GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray());
}
/** /**
* @see it('Allows querying the schema for query type') * @see it('Allows querying the schema for query type')
*/ */
@ -66,10 +77,8 @@ class StarWarsIntrospectionTest extends TestCase
'; ';
$expected = [ $expected = [
'__schema' => [ '__schema' => [
'queryType' => [ 'queryType' => ['name' => 'Query'],
'name' => 'Query'
], ],
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -87,9 +96,7 @@ class StarWarsIntrospectionTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'__type' => [ '__type' => ['name' => 'Droid'],
'name' => 'Droid'
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -110,8 +117,8 @@ class StarWarsIntrospectionTest extends TestCase
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Droid', 'name' => 'Droid',
'kind' => 'OBJECT' 'kind' => 'OBJECT',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -132,8 +139,8 @@ class StarWarsIntrospectionTest extends TestCase
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Character', 'name' => 'Character',
'kind' => 'INTERFACE' 'kind' => 'INTERFACE',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -165,46 +172,46 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'id', 'name' => 'id',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'NON_NULL' 'kind' => 'NON_NULL',
] ],
], ],
[ [
'name' => 'name', 'name' => 'name',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
], ],
[ [
'name' => 'friends', 'name' => 'friends',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'LIST' 'kind' => 'LIST',
] ],
], ],
[ [
'name' => 'appearsIn', 'name' => 'appearsIn',
'type' => [ 'type' => [
'name' => null, 'name' => null,
'kind' => 'LIST' 'kind' => 'LIST',
] ],
], ],
[ [
'name' => 'secretBackstory', 'name' => 'secretBackstory',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
], ],
[ [
'name' => 'primaryFunction', 'name' => 'primaryFunction',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
] ],
] ],
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -243,17 +250,17 @@ class StarWarsIntrospectionTest extends TestCase
'kind' => 'NON_NULL', 'kind' => 'NON_NULL',
'ofType' => [ 'ofType' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR' 'kind' => 'SCALAR',
] ],
] ],
], ],
[ [
'name' => 'name', 'name' => 'name',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'ofType' => null 'ofType' => null,
] ],
], ],
[ [
'name' => 'friends', 'name' => 'friends',
@ -262,9 +269,9 @@ class StarWarsIntrospectionTest extends TestCase
'kind' => 'LIST', 'kind' => 'LIST',
'ofType' => [ 'ofType' => [
'name' => 'Character', 'name' => 'Character',
'kind' => 'INTERFACE' 'kind' => 'INTERFACE',
] ],
] ],
], ],
[ [
'name' => 'appearsIn', 'name' => 'appearsIn',
@ -273,28 +280,28 @@ class StarWarsIntrospectionTest extends TestCase
'kind' => 'LIST', 'kind' => 'LIST',
'ofType' => [ 'ofType' => [
'name' => 'Episode', 'name' => 'Episode',
'kind' => 'ENUM' 'kind' => 'ENUM',
] ],
] ],
], ],
[ [
'name' => 'secretBackstory', 'name' => 'secretBackstory',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'ofType' => null 'ofType' => null,
] ],
], ],
[ [
'name' => 'primaryFunction', 'name' => 'primaryFunction',
'type' => [ 'type' => [
'name' => 'String', 'name' => 'String',
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'ofType' => null 'ofType' => null,
] ],
] ],
] ],
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -329,7 +336,7 @@ class StarWarsIntrospectionTest extends TestCase
} }
'; ';
$expected = array( $expected = [
'__schema' => [ '__schema' => [
'queryType' => [ 'queryType' => [
'fields' => [ 'fields' => [
@ -337,13 +344,13 @@ class StarWarsIntrospectionTest extends TestCase
'name' => 'hero', 'name' => 'hero',
'args' => [ 'args' => [
[ [
'defaultValue' => NULL, 'defaultValue' => null,
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.', 'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
'name' => 'episode', 'name' => 'episode',
'type' => [ 'type' => [
'kind' => 'ENUM', 'kind' => 'ENUM',
'name' => 'Episode', 'name' => 'Episode',
'ofType' => NULL, 'ofType' => null,
], ],
], ],
], ],
@ -356,13 +363,13 @@ class StarWarsIntrospectionTest extends TestCase
'description' => 'id of the human', 'description' => 'id of the human',
'type' => [ 'type' => [
'kind' => 'NON_NULL', 'kind' => 'NON_NULL',
'name' => NULL, 'name' => null,
'ofType' => [ 'ofType' => [
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'name' => 'String', 'name' => 'String',
], ],
], ],
'defaultValue' => NULL, 'defaultValue' => null,
], ],
], ],
], ],
@ -374,21 +381,21 @@ class StarWarsIntrospectionTest extends TestCase
'description' => 'id of the droid', 'description' => 'id of the droid',
'type' => [ 'type' => [
'kind' => 'NON_NULL', 'kind' => 'NON_NULL',
'name' => NULL, 'name' => null,
'ofType' => 'ofType' =>
[ [
'kind' => 'SCALAR', 'kind' => 'SCALAR',
'name' => 'String', 'name' => 'String',
], ],
], ],
'defaultValue' => NULL, 'defaultValue' => null,
], ],
], ],
], ],
], ],
], ],
], ],
); ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -409,17 +416,9 @@ class StarWarsIntrospectionTest extends TestCase
$expected = [ $expected = [
'__type' => [ '__type' => [
'name' => 'Droid', 'name' => 'Droid',
'description' => 'A mechanical creature in the Star Wars universe.' 'description' => 'A mechanical creature in the Star Wars universe.',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected)
{
$this->assertEquals(['data' => $expected], GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray());
}
} }

View File

@ -1,15 +1,14 @@
<?php <?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class StarWarsQueryTest extends TestCase class StarWarsQueryTest extends TestCase
{ {
// Star Wars Query Tests
// Basic Queries
/** /**
* @see it('Correctly identifies R2-D2 as the hero of the Star Wars Saga') * @see it('Correctly identifies R2-D2 as the hero of the Star Wars Saga')
*/ */
@ -23,13 +22,24 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'hero' => [ 'hero' => ['name' => 'R2-D2'],
'name' => 'R2-D2'
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected) : void
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray()
);
}
// Describe: Nested Queries
/** /**
* @see it('Allows us to query for the ID and friends of R2-D2') * @see it('Allows us to query for the ID and friends of R2-D2')
*/ */
@ -51,22 +61,16 @@ class StarWarsQueryTest extends TestCase
'id' => '2001', 'id' => '2001',
'name' => 'R2-D2', 'name' => 'R2-D2',
'friends' => [ 'friends' => [
[ ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker', ['name' => 'Han Solo'],
['name' => 'Leia Organa'],
], ],
[
'name' => 'Han Solo',
], ],
[
'name' => 'Leia Organa',
],
]
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
// Describe: Nested Queries // Describe: Using IDs and query parameters to refetch objects
/** /**
* @see it('Allows us to query for the friends of friends of R2-D2') * @see it('Allows us to query for the friends of friends of R2-D2')
@ -93,42 +97,40 @@ class StarWarsQueryTest extends TestCase
'friends' => [ 'friends' => [
[ [
'name' => 'Luke Skywalker', 'name' => 'Luke Skywalker',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI',], 'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => [ 'friends' => [
['name' => 'Han Solo',], ['name' => 'Han Solo'],
['name' => 'Leia Organa',], ['name' => 'Leia Organa'],
['name' => 'C-3PO',], ['name' => 'C-3PO'],
['name' => 'R2-D2',], ['name' => 'R2-D2'],
], ],
], ],
[ [
'name' => 'Han Solo', 'name' => 'Han Solo',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'], 'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => [ 'friends' => [
['name' => 'Luke Skywalker',], ['name' => 'Luke Skywalker'],
['name' => 'Leia Organa'], ['name' => 'Leia Organa'],
['name' => 'R2-D2',], ['name' => 'R2-D2'],
] ],
], ],
[ [
'name' => 'Leia Organa', 'name' => 'Leia Organa',
'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'], 'appearsIn' => ['NEWHOPE', 'EMPIRE', 'JEDI'],
'friends' => 'friends' =>
[ [
['name' => 'Luke Skywalker',], ['name' => 'Luke Skywalker'],
['name' => 'Han Solo',], ['name' => 'Han Solo'],
['name' => 'C-3PO',], ['name' => 'C-3PO'],
['name' => 'R2-D2',], ['name' => 'R2-D2'],
],
], ],
], ],
], ],
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
// Describe: Using IDs and query parameters to refetch objects
/** /**
* @see it('Using IDs and query parameters to refetch objects') * @see it('Using IDs and query parameters to refetch objects')
*/ */
@ -142,9 +144,7 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'human' => [ 'human' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker'
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
@ -162,18 +162,27 @@ class StarWarsQueryTest extends TestCase
} }
} }
'; ';
$params = [ $params = ['someId' => '1000'];
'someId' => '1000'
];
$expected = [ $expected = [
'human' => [ 'human' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker'
]
]; ];
$this->assertValidQueryWithParams($query, $params, $expected); $this->assertValidQueryWithParams($query, $params, $expected);
} }
/**
* Helper function to test a query with params and the expected response.
*/
private function assertValidQueryWithParams($query, $params, $expected)
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query, null, null, $params)->toArray()
);
}
// Using aliases to change the key in the response
/** /**
* @see it('Allows us to create a generic query, then use it to fetch Han Solo using his ID') * @see it('Allows us to create a generic query, then use it to fetch Han Solo using his ID')
*/ */
@ -186,13 +195,9 @@ class StarWarsQueryTest extends TestCase
} }
} }
'; ';
$params = [ $params = ['someId' => '1002'];
'someId' => '1002'
];
$expected = [ $expected = [
'human' => [ 'human' => ['name' => 'Han Solo'],
'name' => 'Han Solo'
]
]; ];
$this->assertValidQueryWithParams($query, $params, $expected); $this->assertValidQueryWithParams($query, $params, $expected);
} }
@ -209,21 +214,17 @@ class StarWarsQueryTest extends TestCase
} }
} }
'; ';
$params = [ $params = ['id' => 'not a valid id'];
'id' => 'not a valid id' $expected = ['human' => null];
];
$expected = [
'human' => null
];
$this->assertValidQueryWithParams($query, $params, $expected); $this->assertValidQueryWithParams($query, $params, $expected);
} }
// Using aliases to change the key in the response // Uses fragments to express more complex queries
/** /**
* @see it('Allows us to query for Luke, changing his key with an alias') * @see it('Allows us to query for Luke, changing his key with an alias')
*/ */
function testLukeKeyAlias() public function testLukeKeyAlias() : void
{ {
$query = ' $query = '
query FetchLukeAliased { query FetchLukeAliased {
@ -233,9 +234,7 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'luke' => [ 'luke' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker'
],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -243,7 +242,7 @@ class StarWarsQueryTest extends TestCase
/** /**
* @see it('Allows us to query for both Luke and Leia, using two root fields and an alias') * @see it('Allows us to query for both Luke and Leia, using two root fields and an alias')
*/ */
function testTwoRootKeysAsAnAlias() public function testTwoRootKeysAsAnAlias() : void
{ {
$query = ' $query = '
query FetchLukeAndLeiaAliased { query FetchLukeAndLeiaAliased {
@ -256,22 +255,16 @@ class StarWarsQueryTest extends TestCase
} }
'; ';
$expected = [ $expected = [
'luke' => [ 'luke' => ['name' => 'Luke Skywalker'],
'name' => 'Luke Skywalker' 'leia' => ['name' => 'Leia Organa'],
],
'leia' => [
'name' => 'Leia Organa'
]
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
// Uses fragments to express more complex queries
/** /**
* @see it('Allows us to query using duplicated content') * @see it('Allows us to query using duplicated content')
*/ */
function testQueryUsingDuplicatedContent() public function testQueryUsingDuplicatedContent() : void
{ {
$query = ' $query = '
query DuplicateFields { query DuplicateFields {
@ -288,12 +281,12 @@ class StarWarsQueryTest extends TestCase
$expected = [ $expected = [
'luke' => [ 'luke' => [
'name' => 'Luke Skywalker', 'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine' 'homePlanet' => 'Tatooine',
], ],
'leia' => [ 'leia' => [
'name' => 'Leia Organa', 'name' => 'Leia Organa',
'homePlanet' => 'Alderaan' 'homePlanet' => 'Alderaan',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -301,7 +294,7 @@ class StarWarsQueryTest extends TestCase
/** /**
* @see it('Allows us to use a fragment to avoid duplicating content') * @see it('Allows us to use a fragment to avoid duplicating content')
*/ */
function testUsingFragment() public function testUsingFragment() : void
{ {
$query = ' $query = '
query UseFragment { query UseFragment {
@ -322,12 +315,12 @@ class StarWarsQueryTest extends TestCase
$expected = [ $expected = [
'luke' => [ 'luke' => [
'name' => 'Luke Skywalker', 'name' => 'Luke Skywalker',
'homePlanet' => 'Tatooine' 'homePlanet' => 'Tatooine',
], ],
'leia' => [ 'leia' => [
'name' => 'Leia Organa', 'name' => 'Leia Organa',
'homePlanet' => 'Alderaan' 'homePlanet' => 'Alderaan',
] ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
@ -348,7 +341,7 @@ class StarWarsQueryTest extends TestCase
$expected = [ $expected = [
'hero' => [ 'hero' => [
'__typename' => 'Droid', '__typename' => 'Droid',
'name' => 'R2-D2' 'name' => 'R2-D2',
], ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
@ -371,32 +364,10 @@ class StarWarsQueryTest extends TestCase
$expected = [ $expected = [
'hero' => [ 'hero' => [
'__typename' => 'Human', '__typename' => 'Human',
'name' => 'Luke Skywalker' 'name' => 'Luke Skywalker',
], ],
]; ];
$this->assertValidQuery($query, $expected); $this->assertValidQuery($query, $expected);
} }
/**
* Helper function to test a query and the expected response.
*/
private function assertValidQuery($query, $expected)
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query)->toArray()
);
}
/**
* Helper function to test a query with params and the expected response.
*/
private function assertValidQueryWithParams($query, $params, $expected)
{
$this->assertEquals(
['data' => $expected],
GraphQL::executeQuery(StarWarsSchema::build(), $query, null, null, $params)->toArray()
);
}
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
/** /**
@ -11,13 +14,16 @@ namespace GraphQL\Tests;
* NOTE: This may contain spoilers for the original Star * NOTE: This may contain spoilers for the original Star
* Wars trilogy. * Wars trilogy.
*/ */
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use function array_intersect_key;
use function array_map;
/** /**
* Using our shorthand to describe type systems, the type system for our * Using our shorthand to describe type systems, the type system for our
@ -56,10 +62,9 @@ use GraphQL\Type\Definition\Type;
* *
* We begin by setting up our schema. * We begin by setting up our schema.
*/ */
class StarWarsSchema class StarWarsSchema
{ {
public static function build() public static function build() : Schema
{ {
/** /**
* The original trilogy consists of three movies. * The original trilogy consists of three movies.
@ -73,17 +78,17 @@ class StarWarsSchema
'values' => [ 'values' => [
'NEWHOPE' => [ 'NEWHOPE' => [
'value' => 4, 'value' => 4,
'description' => 'Released in 1977.' 'description' => 'Released in 1977.',
], ],
'EMPIRE' => [ 'EMPIRE' => [
'value' => 5, 'value' => 5,
'description' => 'Released in 1980.' 'description' => 'Released in 1980.',
], ],
'JEDI' => [ 'JEDI' => [
'value' => 6, 'value' => 6,
'description' => 'Released in 1983.' 'description' => 'Released in 1983.',
],
], ],
]
]); ]);
$humanType = null; $humanType = null;
@ -103,7 +108,7 @@ class StarWarsSchema
$characterInterface = new InterfaceType([ $characterInterface = new InterfaceType([
'name' => 'Character', 'name' => 'Character',
'description' => 'A character in the Star Wars Trilogy', 'description' => 'A character in the Star Wars Trilogy',
'fields' => function() use (&$characterInterface, $episodeEnum) { 'fields' => function () use (&$characterInterface, $episodeEnum) {
return [ return [
'id' => [ 'id' => [
'type' => Type::nonNull(Type::string()), 'type' => Type::nonNull(Type::string()),
@ -111,7 +116,7 @@ class StarWarsSchema
], ],
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The name of the character.' 'description' => 'The name of the character.',
], ],
'friends' => [ 'friends' => [
'type' => Type::listOf($characterInterface), 'type' => Type::listOf($characterInterface),
@ -119,7 +124,7 @@ class StarWarsSchema
], ],
'appearsIn' => [ 'appearsIn' => [
'type' => Type::listOf($episodeEnum), 'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.' 'description' => 'Which movies they appear in.',
], ],
'secretBackstory' => [ 'secretBackstory' => [
'type' => Type::string(), 'type' => Type::string(),
@ -164,7 +169,7 @@ class StarWarsSchema
$fieldSelection['id'] = true; $fieldSelection['id'] = true;
$friends = array_map( $friends = array_map(
function($friend) use ($fieldSelection) { function ($friend) use ($fieldSelection) {
return array_intersect_key($friend, $fieldSelection); return array_intersect_key($friend, $fieldSelection);
}, },
StarWarsData::getFriends($human) StarWarsData::getFriends($human)
@ -175,22 +180,22 @@ class StarWarsSchema
], ],
'appearsIn' => [ 'appearsIn' => [
'type' => Type::listOf($episodeEnum), 'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.' 'description' => 'Which movies they appear in.',
], ],
'homePlanet' => [ 'homePlanet' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The home planet of the human, or null if unknown.' 'description' => 'The home planet of the human, or null if unknown.',
], ],
'secretBackstory' => [ 'secretBackstory' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'Where are they from and how they came to be who they are.', 'description' => 'Where are they from and how they came to be who they are.',
'resolve' => function() { 'resolve' => function () {
// This is to demonstrate error reporting // This is to demonstrate error reporting
throw new \Exception('secretBackstory is secret.'); throw new \Exception('secretBackstory is secret.');
}, },
], ],
], ],
'interfaces' => [$characterInterface] 'interfaces' => [$characterInterface],
]); ]);
/** /**
@ -216,7 +221,7 @@ class StarWarsSchema
], ],
'name' => [ 'name' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The name of the droid.' 'description' => 'The name of the droid.',
], ],
'friends' => [ 'friends' => [
'type' => Type::listOf($characterInterface), 'type' => Type::listOf($characterInterface),
@ -227,22 +232,22 @@ class StarWarsSchema
], ],
'appearsIn' => [ 'appearsIn' => [
'type' => Type::listOf($episodeEnum), 'type' => Type::listOf($episodeEnum),
'description' => 'Which movies they appear in.' 'description' => 'Which movies they appear in.',
], ],
'secretBackstory' => [ 'secretBackstory' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'Construction date and the name of the designer.', 'description' => 'Construction date and the name of the designer.',
'resolve' => function() { 'resolve' => function () {
// This is to demonstrate error reporting // This is to demonstrate error reporting
throw new \Exception('secretBackstory is secret.'); throw new \Exception('secretBackstory is secret.');
}, },
], ],
'primaryFunction' => [ 'primaryFunction' => [
'type' => Type::string(), 'type' => Type::string(),
'description' => 'The primary function of the droid.' 'description' => 'The primary function of the droid.',
]
], ],
'interfaces' => [$characterInterface] ],
'interfaces' => [$characterInterface],
]); ]);
/** /**
@ -267,11 +272,11 @@ class StarWarsSchema
'args' => [ 'args' => [
'episode' => [ 'episode' => [
'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.', 'description' => 'If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.',
'type' => $episodeEnum 'type' => $episodeEnum,
] ],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($root, $args) {
return StarWarsData::getHero(isset($args['episode']) ? $args['episode'] : null); return StarWarsData::getHero($args['episode'] ?? null);
}, },
], ],
'human' => [ 'human' => [
@ -280,13 +285,14 @@ class StarWarsSchema
'id' => [ 'id' => [
'name' => 'id', 'name' => 'id',
'description' => 'id of the human', 'description' => 'id of the human',
'type' => Type::nonNull(Type::string()) 'type' => Type::nonNull(Type::string()),
] ],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($root, $args) {
$humans = StarWarsData::humans(); $humans = StarWarsData::humans();
return isset($humans[$args['id']]) ? $humans[$args['id']] : null;
} return $humans[$args['id']] ?? null;
},
], ],
'droid' => [ 'droid' => [
'type' => $droidType, 'type' => $droidType,
@ -294,15 +300,16 @@ class StarWarsSchema
'id' => [ 'id' => [
'name' => 'id', 'name' => 'id',
'description' => 'id of the droid', 'description' => 'id of the droid',
'type' => Type::nonNull(Type::string()) 'type' => Type::nonNull(Type::string()),
] ],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($root, $args) {
$droids = StarWarsData::droids(); $droids = StarWarsData::droids();
return isset($droids[$args['id']]) ? $droids[$args['id']] : null;
} return $droids[$args['id']] ?? null;
] },
] ],
],
]); ]);
return new Schema(['query' => $queryType]); return new Schema(['query' => $queryType]);

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests; namespace GraphQL\Tests;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
@ -37,6 +40,16 @@ class StarWarsValidationTest extends TestCase
$this->assertEquals(true, empty($errors)); $this->assertEquals(true, empty($errors));
} }
/**
* Helper function to test a query and the expected response.
*/
private function validationErrors($query)
{
$ast = Parser::parse($query);
return DocumentValidator::validate(StarWarsSchema::build(), $ast);
}
/** /**
* @see it('Notes that non-existent fields are invalid') * @see it('Notes that non-existent fields are invalid')
*/ */
@ -142,13 +155,4 @@ class StarWarsValidationTest extends TestCase
$errors = $this->validationErrors($query); $errors = $this->validationErrors($query);
$this->assertEquals(true, empty($errors)); $this->assertEquals(true, empty($errors));
} }
/**
* Helper function to test a query and the expected response.
*/
private function validationErrors($query)
{
$ast = Parser::parse($query);
return DocumentValidator::validate(StarWarsSchema::build(), $ast);
}
} }

View File

@ -1,11 +1,13 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
require_once __DIR__ . '/TestClasses.php';
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Tests\Type\TestClasses\MyCustomType;
use GraphQL\Tests\Type\TestClasses\OtherCustom;
use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
@ -14,74 +16,53 @@ use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function get_class;
use function json_encode;
use function sprintf;
class DefinitionTest extends TestCase class DefinitionTest extends TestCase
{ {
/** /** @var ObjectType */
* @var ObjectType
*/
public $blogImage; public $blogImage;
/** /** @var ObjectType */
* @var ObjectType
*/
public $blogArticle; public $blogArticle;
/** /** @var ObjectType */
* @var ObjectType
*/
public $blogAuthor; public $blogAuthor;
/** /** @var ObjectType */
* @var ObjectType
*/
public $blogMutation; public $blogMutation;
/** /** @var ObjectType */
* @var ObjectType
*/
public $blogQuery; public $blogQuery;
/** /** @var ObjectType */
* @var ObjectType
*/
public $blogSubscription; public $blogSubscription;
/** /** @var ObjectType */
* @var ObjectType
*/
public $objectType; public $objectType;
/** /** @var ObjectType */
* @var ObjectType
*/
public $objectWithIsTypeOf; public $objectWithIsTypeOf;
/** /** @var InterfaceType */
* @var InterfaceType
*/
public $interfaceType; public $interfaceType;
/** /** @var UnionType */
* @var UnionType
*/
public $unionType; public $unionType;
/** /** @var EnumType */
* @var EnumType
*/
public $enumType; public $enumType;
/** /** @var InputObjectType */
* @var InputObjectType
*/
public $inputObjectType; public $inputObjectType;
/** /** @var CustomScalarType */
* @var CustomScalarType
*/
public $scalarType; public $scalarType;
public function setUp() public function setUp()
@ -99,9 +80,12 @@ class DefinitionTest extends TestCase
$this->scalarType = new CustomScalarType([ $this->scalarType = new CustomScalarType([
'name' => 'Scalar', 'name' => 'Scalar',
'serialize' => function () {}, 'serialize' => function () {
'parseValue' => function () {}, },
'parseLiteral' => function () {}, 'parseValue' => function () {
},
'parseLiteral' => function () {
},
]); ]);
$this->blogImage = new ObjectType([ $this->blogImage = new ObjectType([
@ -109,20 +93,23 @@ class DefinitionTest extends TestCase
'fields' => [ 'fields' => [
'url' => ['type' => Type::string()], 'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()] 'height' => ['type' => Type::int()],
] ],
]); ]);
$this->blogAuthor = new ObjectType([ $this->blogAuthor = new ObjectType([
'name' => 'Author', 'name' => 'Author',
'fields' => function() { 'fields' => function () {
return [ return [
'id' => ['type' => Type::string()], 'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pic' => [ 'type' => $this->blogImage, 'args' => [ 'pic' => [
'type' => $this->blogImage,
'args' => [
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()] 'height' => ['type' => Type::int()],
]], ],
],
'recentArticle' => $this->blogArticle, 'recentArticle' => $this->blogArticle,
]; ];
}, },
@ -135,35 +122,38 @@ class DefinitionTest extends TestCase
'isPublished' => ['type' => Type::boolean()], 'isPublished' => ['type' => Type::boolean()],
'author' => ['type' => $this->blogAuthor], 'author' => ['type' => $this->blogAuthor],
'title' => ['type' => Type::string()], 'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()] 'body' => ['type' => Type::string()],
] ],
]); ]);
$this->blogQuery = new ObjectType([ $this->blogQuery = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'article' => ['type' => $this->blogArticle, 'args' => [ 'article' => [
'id' => ['type' => Type::string()] 'type' => $this->blogArticle,
]], 'args' => [
'feed' => ['type' => new ListOfType($this->blogArticle)] 'id' => ['type' => Type::string()],
] ],
],
'feed' => ['type' => new ListOfType($this->blogArticle)],
],
]); ]);
$this->blogMutation = new ObjectType([ $this->blogMutation = new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
'fields' => [ 'fields' => [
'writeArticle' => ['type' => $this->blogArticle] 'writeArticle' => ['type' => $this->blogArticle],
] ],
]); ]);
$this->blogSubscription = new ObjectType([ $this->blogSubscription = new ObjectType([
'name' => 'Subscription', 'name' => 'Subscription',
'fields' => [ 'fields' => [
'articleSubscribe' => [ 'articleSubscribe' => [
'args' => [ 'id' => [ 'type' => Type::string() ]], 'args' => ['id' => ['type' => Type::string()]],
'type' => $this->blogArticle 'type' => $this->blogArticle,
] ],
] ],
]); ]);
} }
@ -175,7 +165,7 @@ class DefinitionTest extends TestCase
public function testDefinesAQueryOnlySchema() : void public function testDefinesAQueryOnlySchema() : void
{ {
$blogSchema = new Schema([ $blogSchema = new Schema([
'query' => $this->blogQuery 'query' => $this->blogQuery,
]); ]);
$this->assertSame($blogSchema->getQueryType(), $this->blogQuery); $this->assertSame($blogSchema->getQueryType(), $this->blogQuery);
@ -220,7 +210,7 @@ class DefinitionTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->blogQuery, 'query' => $this->blogQuery,
'mutation' => $this->blogMutation 'mutation' => $this->blogMutation,
]); ]);
$this->assertSame($this->blogMutation, $schema->getMutationType()); $this->assertSame($this->blogMutation, $schema->getMutationType());
@ -239,7 +229,7 @@ class DefinitionTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->blogQuery, 'query' => $this->blogQuery,
'subscription' => $this->blogSubscription 'subscription' => $this->blogSubscription,
]); ]);
$this->assertEquals($this->blogSubscription, $schema->getSubscriptionType()); $this->assertEquals($this->blogSubscription, $schema->getSubscriptionType());
@ -258,19 +248,22 @@ class DefinitionTest extends TestCase
$enumTypeWithDeprecatedValue = new EnumType([ $enumTypeWithDeprecatedValue = new EnumType([
'name' => 'EnumWithDeprecatedValue', 'name' => 'EnumWithDeprecatedValue',
'values' => [ 'values' => [
'foo' => ['deprecationReason' => 'Just because'] 'foo' => ['deprecationReason' => 'Just because'],
] ],
]); ]);
$value = $enumTypeWithDeprecatedValue->getValues()[0]; $value = $enumTypeWithDeprecatedValue->getValues()[0];
$this->assertArraySubset([ $this->assertArraySubset(
[
'name' => 'foo', 'name' => 'foo',
'description' => null, 'description' => null,
'deprecationReason' => 'Just because', 'deprecationReason' => 'Just because',
'value' => 'foo', 'value' => 'foo',
'astNode' => null 'astNode' => null,
], (array) $value); ],
(array) $value
);
$this->assertEquals(true, $value->isDeprecated()); $this->assertEquals(true, $value->isDeprecated());
} }
@ -285,7 +278,7 @@ class DefinitionTest extends TestCase
'values' => [ 'values' => [
'NULL' => ['value' => null], 'NULL' => ['value' => null],
'UNDEFINED' => ['value' => null], 'UNDEFINED' => ['value' => null],
] ],
]); ]);
$expected = [ $expected = [
@ -308,8 +301,8 @@ class DefinitionTest extends TestCase
$actual = $EnumTypeWithNullishValue->getValues(); $actual = $EnumTypeWithNullishValue->getValues();
$this->assertEquals(count($expected), count($actual)); $this->assertEquals(count($expected), count($actual));
$this->assertArraySubset($expected[0], (array)$actual[0]); $this->assertArraySubset($expected[0], (array) $actual[0]);
$this->assertArraySubset($expected[1], (array)$actual[1]); $this->assertArraySubset($expected[1], (array) $actual[1]);
} }
/** /**
@ -322,9 +315,9 @@ class DefinitionTest extends TestCase
'fields' => [ 'fields' => [
'bar' => [ 'bar' => [
'type' => Type::string(), 'type' => Type::string(),
'deprecationReason' => 'A terrible reason' 'deprecationReason' => 'A terrible reason',
] ],
] ],
]); ]);
$field = $TypeWithDeprecatedField->getField('bar'); $field = $TypeWithDeprecatedField->getField('bar');
@ -343,25 +336,25 @@ class DefinitionTest extends TestCase
{ {
$nestedInputObject = new InputObjectType([ $nestedInputObject = new InputObjectType([
'name' => 'NestedInputObject', 'name' => 'NestedInputObject',
'fields' => ['value' => ['type' => Type::string()]] 'fields' => ['value' => ['type' => Type::string()]],
]); ]);
$someInputObject = new InputObjectType([ $someInputObject = new InputObjectType([
'name' => 'SomeInputObject', 'name' => 'SomeInputObject',
'fields' => ['nested' => ['type' => $nestedInputObject]] 'fields' => ['nested' => ['type' => $nestedInputObject]],
]); ]);
$someMutation = new ObjectType([ $someMutation = new ObjectType([
'name' => 'SomeMutation', 'name' => 'SomeMutation',
'fields' => [ 'fields' => [
'mutateSomething' => [ 'mutateSomething' => [
'type' => $this->blogArticle, 'type' => $this->blogArticle,
'args' => ['input' => ['type' => $someInputObject]] 'args' => ['input' => ['type' => $someInputObject]],
] ],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => $this->blogQuery, 'query' => $this->blogQuery,
'mutation' => $someMutation 'mutation' => $someMutation,
]); ]);
$this->assertSame($nestedInputObject, $schema->getType('NestedInputObject')); $this->assertSame($nestedInputObject, $schema->getType('NestedInputObject'));
} }
@ -374,14 +367,14 @@ class DefinitionTest extends TestCase
$someInterface = new InterfaceType([ $someInterface = new InterfaceType([
'name' => 'SomeInterface', 'name' => 'SomeInterface',
'fields' => [ 'fields' => [
'f' => ['type' => Type::int()] 'f' => ['type' => Type::int()],
] ],
]); ]);
$someSubtype = new ObjectType([ $someSubtype = new ObjectType([
'name' => 'SomeSubtype', 'name' => 'SomeSubtype',
'fields' => [ 'fields' => [
'f' => ['type' => Type::int()] 'f' => ['type' => Type::int()],
], ],
'interfaces' => [$someInterface], 'interfaces' => [$someInterface],
]); ]);
@ -390,10 +383,10 @@ class DefinitionTest extends TestCase
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'iface' => ['type' => $someInterface] 'iface' => ['type' => $someInterface],
] ],
]), ]),
'types' => [$someSubtype] 'types' => [$someSubtype],
]); ]);
$this->assertSame($someSubtype, $schema->getType('SomeSubtype')); $this->assertSame($someSubtype, $schema->getType('SomeSubtype'));
} }
@ -408,26 +401,28 @@ class DefinitionTest extends TestCase
$someSubtype = new ObjectType([ $someSubtype = new ObjectType([
'name' => 'SomeSubtype', 'name' => 'SomeSubtype',
'fields' => [ 'fields' => [
'f' => ['type' => Type::int()] 'f' => ['type' => Type::int()],
], ],
'interfaces' => function() use (&$someInterface) { return [$someInterface]; }, 'interfaces' => function () use (&$someInterface) {
return [$someInterface];
},
]); ]);
$someInterface = new InterfaceType([ $someInterface = new InterfaceType([
'name' => 'SomeInterface', 'name' => 'SomeInterface',
'fields' => [ 'fields' => [
'f' => ['type' => Type::int()] 'f' => ['type' => Type::int()],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'iface' => ['type' => $someInterface] 'iface' => ['type' => $someInterface],
] ],
]), ]),
'types' => [$someSubtype] 'types' => [$someSubtype],
]); ]);
$this->assertSame($someSubtype, $schema->getType('SomeSubtype')); $this->assertSame($someSubtype, $schema->getType('SomeSubtype'));
@ -483,11 +478,15 @@ class DefinitionTest extends TestCase
[$this->interfaceType, false], [$this->interfaceType, false],
[$this->unionType, false], [$this->unionType, false],
[$this->enumType, true], [$this->enumType, true],
[$this->inputObjectType, true] [$this->inputObjectType, true],
]; ];
foreach ($expected as $index => $entry) { foreach ($expected as $index => $entry) {
$this->assertSame($entry[1], Type::isInputType($entry[0]), "Type {$entry[0]} was detected incorrectly"); $this->assertSame(
$entry[1],
Type::isInputType($entry[0]),
sprintf('Type %s was detected incorrectly', $entry[0])
);
} }
} }
@ -502,11 +501,15 @@ class DefinitionTest extends TestCase
[$this->interfaceType, true], [$this->interfaceType, true],
[$this->unionType, true], [$this->unionType, true],
[$this->enumType, true], [$this->enumType, true],
[$this->inputObjectType, false] [$this->inputObjectType, false],
]; ];
foreach ($expected as $index => $entry) { foreach ($expected as $index => $entry) {
$this->assertSame($entry[1], Type::isOutputType($entry[0]), "Type {$entry[0]} was detected incorrectly"); $this->assertSame(
$entry[1],
Type::isOutputType($entry[0]),
sprintf('Type %s was detected incorrectly', $entry[0])
);
} }
} }
@ -529,7 +532,9 @@ class DefinitionTest extends TestCase
{ {
$union = new UnionType([ $union = new UnionType([
'name' => 'ThunkUnion', 'name' => 'ThunkUnion',
'types' => function() {return [$this->objectType]; } 'types' => function () {
return [$this->objectType];
},
]); ]);
$types = $union->getTypes(); $types = $union->getTypes();
@ -543,8 +548,8 @@ class DefinitionTest extends TestCase
$node = new InterfaceType([ $node = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => [ 'fields' => [
'id' => ['type' => Type::nonNull(Type::id())] 'id' => ['type' => Type::nonNull(Type::id())],
] ],
]); ]);
$blog = null; $blog = null;
@ -552,41 +557,41 @@ class DefinitionTest extends TestCase
$user = new ObjectType([ $user = new ObjectType([
'name' => 'User', 'name' => 'User',
'fields' => function() use (&$blog, &$called) { 'fields' => function () use (&$blog, &$called) {
$this->assertNotNull($blog, 'Blog type is expected to be defined at this point, but it is null'); $this->assertNotNull($blog, 'Blog type is expected to be defined at this point, but it is null');
$called = true; $called = true;
return [ return [
'id' => ['type' => Type::nonNull(Type::id())], 'id' => ['type' => Type::nonNull(Type::id())],
'blogs' => ['type' => Type::nonNull(Type::listOf(Type::nonNull($blog)))] 'blogs' => ['type' => Type::nonNull(Type::listOf(Type::nonNull($blog)))],
]; ];
}, },
'interfaces' => function() use ($node) { 'interfaces' => function () use ($node) {
return [$node]; return [$node];
} },
]); ]);
$blog = new ObjectType([ $blog = new ObjectType([
'name' => 'Blog', 'name' => 'Blog',
'fields' => function() use ($user) { 'fields' => function () use ($user) {
return [ return [
'id' => ['type' => Type::nonNull(Type::id())], 'id' => ['type' => Type::nonNull(Type::id())],
'owner' => ['type' => Type::nonNull($user)] 'owner' => ['type' => Type::nonNull($user)],
]; ];
}, },
'interfaces' => function() use ($node) { 'interfaces' => function () use ($node) {
return [$node]; return [$node];
} },
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'node' => ['type' => $node] 'node' => ['type' => $node],
] ],
]), ]),
'types' => [$user, $blog] 'types' => [$user, $blog],
]); ]);
$this->assertTrue($called); $this->assertTrue($called);
@ -607,27 +612,29 @@ class DefinitionTest extends TestCase
$called = false; $called = false;
$inputObject = new InputObjectType([ $inputObject = new InputObjectType([
'name' => 'InputObject', 'name' => 'InputObject',
'fields' => function() use (&$inputObject, &$called) { 'fields' => function () use (&$inputObject, &$called) {
$called = true; $called = true;
return [ return [
'value' => ['type' => Type::string()], 'value' => ['type' => Type::string()],
'nested' => ['type' => $inputObject ] 'nested' => ['type' => $inputObject],
]; ];
} },
]); ]);
$someMutation = new ObjectType([ $someMutation = new ObjectType([
'name' => 'SomeMutation', 'name' => 'SomeMutation',
'fields' => [ 'fields' => [
'mutateSomething' => [ 'mutateSomething' => [
'type' => $this->blogArticle, 'type' => $this->blogArticle,
'args' => ['input' => ['type' => $inputObject]] 'args' => ['input' => ['type' => $inputObject]],
] ],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => $this->blogQuery, 'query' => $this->blogQuery,
'mutation' => $someMutation 'mutation' => $someMutation,
]); ]);
$this->assertSame($inputObject, $schema->getType('InputObject')); $this->assertSame($inputObject, $schema->getType('InputObject'));
@ -642,25 +649,24 @@ class DefinitionTest extends TestCase
$called = false; $called = false;
$interface = new InterfaceType([ $interface = new InterfaceType([
'name' => 'SomeInterface', 'name' => 'SomeInterface',
'fields' => function() use (&$interface, &$called) { 'fields' => function () use (&$interface, &$called) {
$called = true; $called = true;
return [ return [
'value' => ['type' => Type::string()], 'value' => ['type' => Type::string()],
'nested' => ['type' => $interface ] 'nested' => ['type' => $interface],
]; ];
} },
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'test' => ['type' => $interface] 'test' => ['type' => $interface],
] ],
]); ]);
$schema = new Schema([ $schema = new Schema(['query' => $query]);
'query' => $query
]);
$this->assertSame($interface, $schema->getType('SomeInterface')); $this->assertSame($interface, $schema->getType('SomeInterface'));
$this->assertTrue($called); $this->assertTrue($called);
@ -673,30 +679,26 @@ class DefinitionTest extends TestCase
{ {
$interface = new InterfaceType([ $interface = new InterfaceType([
'name' => 'SomeInterface', 'name' => 'SomeInterface',
'fields' => function() use (&$interface) { 'fields' => function () use (&$interface) {
return [ return [
'value' => Type::string(), 'value' => Type::string(),
'nested' => $interface, 'nested' => $interface,
'withArg' => [ 'withArg' => [
'type' => Type::string(), 'type' => Type::string(),
'args' => [ 'args' => [
'arg1' => Type::int() 'arg1' => Type::int(),
] ],
] ],
]; ];
} },
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => ['test' => $interface],
'test' => $interface
]
]); ]);
$schema = new Schema([ $schema = new Schema(['query' => $query]);
'query' => $query
]);
$valueField = $schema->getType('SomeInterface')->getField('value'); $valueField = $schema->getType('SomeInterface')->getField('value');
$nestedField = $schema->getType('SomeInterface')->getField('nested'); $nestedField = $schema->getType('SomeInterface')->getField('nested');
@ -728,14 +730,17 @@ class DefinitionTest extends TestCase
{ {
$idType = new CustomScalarType([ $idType = new CustomScalarType([
'name' => 'ID', 'name' => 'ID',
'serialize' => function() {}, 'serialize' => function () {
'parseValue' => function() {}, },
'parseLiteral' => function() {} 'parseValue' => function () {
},
'parseLiteral' => function () {
},
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => new ObjectType(['name' => 'Query', 'fields' => []]), 'query' => new ObjectType(['name' => 'Query', 'fields' => []]),
'types' => [$idType] 'types' => [$idType],
]); ]);
$this->assertSame($idType, $schema->getType('ID')); $this->assertSame($idType, $schema->getType('ID'));
@ -767,9 +772,7 @@ class DefinitionTest extends TestCase
{ {
$objType = new ObjectType([ $objType = new ObjectType([
'name' => 'SomeObject', 'name' => 'SomeObject',
'fields' => [ 'fields' => ['f' => null],
'f' => null,
],
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage( $this->expectExceptionMessage(
@ -929,6 +932,17 @@ class DefinitionTest extends TestCase
// Type System: Object fields must have valid resolve values // Type System: Object fields must have valid resolve values
/**
* @see it('accepts a lambda as an Object field resolver')
*/
public function testAcceptsALambdaAsAnObjectFieldResolver() : void
{
$this->expectNotToPerformAssertions();
// should not throw:
$this->schemaWithObjectWithFieldResolver(function () {
});
}
private function schemaWithObjectWithFieldResolver($resolveValue) private function schemaWithObjectWithFieldResolver($resolveValue)
{ {
$BadResolverType = new ObjectType([ $BadResolverType = new ObjectType([
@ -950,17 +964,8 @@ class DefinitionTest extends TestCase
]), ]),
]); ]);
$schema->assertValid(); $schema->assertValid();
return $schema;
}
/** return $schema;
* @see it('accepts a lambda as an Object field resolver')
*/
public function testAcceptsALambdaAsAnObjectFieldResolver() : void
{
$this->expectNotToPerformAssertions();
// should not throw:
$this->schemaWithObjectWithFieldResolver(function () {});
} }
/** /**
@ -987,21 +992,8 @@ class DefinitionTest extends TestCase
$this->schemaWithObjectWithFieldResolver(0); $this->schemaWithObjectWithFieldResolver(0);
} }
// Type System: Interface types must be resolvable // Type System: Interface types must be resolvable
private function schemaWithFieldType($type)
{
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['field' => ['type' => $type]],
]),
'types' => [$type],
]);
$schema->assertValid();
return $schema;
}
/** /**
* @see it('accepts an Interface type defining resolveType') * @see it('accepts an Interface type defining resolveType')
*/ */
@ -1023,6 +1015,20 @@ class DefinitionTest extends TestCase
); );
} }
private function schemaWithFieldType($type)
{
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => ['field' => ['type' => $type]],
]),
'types' => [$type],
]);
$schema->assertValid();
return $schema;
}
/** /**
* @see it('accepts an Interface with implementing type defining isTypeOf') * @see it('accepts an Interface with implementing type defining isTypeOf')
*/ */
@ -1085,14 +1091,6 @@ class DefinitionTest extends TestCase
// Type System: Union types must be resolvable // Type System: Union types must be resolvable
private function ObjectWithIsTypeOf()
{
return new ObjectType([
'name' => 'ObjectWithIsTypeOf',
'fields' => ['f' => ['type' => Type::string()]],
]);
}
/** /**
* @see it('accepts a Union type defining resolveType') * @see it('accepts a Union type defining resolveType')
*/ */
@ -1156,8 +1154,6 @@ class DefinitionTest extends TestCase
); );
} }
// Type System: Scalar types must be serializable
/** /**
* @see it('accepts a Scalar type defining serialize') * @see it('accepts a Scalar type defining serialize')
*/ */
@ -1175,6 +1171,8 @@ class DefinitionTest extends TestCase
); );
} }
// Type System: Scalar types must be serializable
/** /**
* @see it('rejects a Scalar type not defining serialize') * @see it('rejects a Scalar type not defining serialize')
*/ */
@ -1187,9 +1185,7 @@ class DefinitionTest extends TestCase
'functions are also provided.' 'functions are also provided.'
); );
$this->schemaWithFieldType( $this->schemaWithFieldType(
new CustomScalarType([ new CustomScalarType(['name' => 'SomeScalar'])
'name' => 'SomeScalar',
])
); );
} }
@ -1292,8 +1288,6 @@ class DefinitionTest extends TestCase
); );
} }
// Type System: Object types must be assertable
/** /**
* @see it('accepts an Object type with an isTypeOf function') * @see it('accepts an Object type with an isTypeOf function')
*/ */
@ -1309,6 +1303,8 @@ class DefinitionTest extends TestCase
); );
} }
// Type System: Object types must be assertable
/** /**
* @see it('rejects an Object type with an incorrect type for isTypeOf') * @see it('rejects an Object type with an incorrect type for isTypeOf')
*/ */
@ -1327,8 +1323,6 @@ class DefinitionTest extends TestCase
); );
} }
// Type System: Union types must be array
/** /**
* @see it('accepts a Union type with array types') * @see it('accepts a Union type with array types')
*/ */
@ -1344,6 +1338,8 @@ class DefinitionTest extends TestCase
); );
} }
// Type System: Union types must be array
/** /**
* @see it('accepts a Union type with function returning an array of types') * @see it('accepts a Union type with function returning an array of types')
*/ */
@ -1370,9 +1366,7 @@ class DefinitionTest extends TestCase
'Must provide Array of types or a callable which returns such an array for Union SomeUnion' 'Must provide Array of types or a callable which returns such an array for Union SomeUnion'
); );
$this->schemaWithFieldType( $this->schemaWithFieldType(
new UnionType([ new UnionType(['name' => 'SomeUnion'])
'name' => 'SomeUnion',
])
); );
} }
@ -1388,13 +1382,11 @@ class DefinitionTest extends TestCase
$this->schemaWithFieldType( $this->schemaWithFieldType(
new UnionType([ new UnionType([
'name' => 'SomeUnion', 'name' => 'SomeUnion',
'types' => (object)[ 'test' => $this->objectType, ], 'types' => (object) ['test' => $this->objectType],
]) ])
); );
} }
// Type System: Input Objects must have fields
/** /**
* @see it('accepts an Input Object type with fields') * @see it('accepts an Input Object type with fields')
*/ */
@ -1410,6 +1402,8 @@ class DefinitionTest extends TestCase
$this->assertSame(Type::string(), $inputObjType->getField('f')->getType()); $this->assertSame(Type::string(), $inputObjType->getField('f')->getType());
} }
// Type System: Input Objects must have fields
/** /**
* @see it('accepts an Input Object type with a field function') * @see it('accepts an Input Object type with a field function')
*/ */
@ -1438,7 +1432,7 @@ class DefinitionTest extends TestCase
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage( $this->expectExceptionMessage(
'SomeInputObject fields must be an associative array with field names as keys or a callable '. 'SomeInputObject fields must be an associative array with field names as keys or a callable ' .
'which returns such an array.' 'which returns such an array.'
); );
$inputObjType->assertValid(); $inputObjType->assertValid();
@ -1463,8 +1457,6 @@ class DefinitionTest extends TestCase
$inputObjType->assertValid(); $inputObjType->assertValid();
} }
// Type System: Input Object fields must not have resolvers
/** /**
* @see it('rejects an Input Object type with resolvers') * @see it('rejects an Input Object type with resolvers')
*/ */
@ -1489,6 +1481,8 @@ class DefinitionTest extends TestCase
$inputObjType->assertValid(); $inputObjType->assertValid();
} }
// Type System: Input Object fields must not have resolvers
/** /**
* @see it('rejects an Input Object type with resolver constant') * @see it('rejects an Input Object type with resolver constant')
*/ */
@ -1511,8 +1505,6 @@ class DefinitionTest extends TestCase
$inputObjType->assertValid(); $inputObjType->assertValid();
} }
// Type System: Enum types must be well defined
/** /**
* @see it('accepts a well defined Enum type with empty value definition') * @see it('accepts a well defined Enum type with empty value definition')
*/ */
@ -1529,6 +1521,8 @@ class DefinitionTest extends TestCase
$this->assertEquals('BAR', $enumType->getValue('BAR')->value); $this->assertEquals('BAR', $enumType->getValue('BAR')->value);
} }
// Type System: Enum types must be well defined
/** /**
* @see it('accepts a well defined Enum type with internal value definition') * @see it('accepts a well defined Enum type with internal value definition')
*/ */
@ -1569,9 +1563,7 @@ class DefinitionTest extends TestCase
$enumType = new EnumType([ $enumType = new EnumType([
'name' => 'SomeEnum', 'name' => 'SomeEnum',
'values' => [ 'values' => [
'FOO' => [ 'FOO' => ['isDeprecated' => true],
'isDeprecated' => true,
],
], ],
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
@ -1582,7 +1574,6 @@ class DefinitionTest extends TestCase
$enumType->assertValid(); $enumType->assertValid();
} }
/** /**
* Type System: List must accept only types * Type System: List must accept only types
*/ */
@ -1606,21 +1597,20 @@ class DefinitionTest extends TestCase
try { try {
Type::listOf($type); Type::listOf($type);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->fail("List is expected to accept type: " . get_class($type) . ", but got error: ". $e->getMessage()); $this->fail('List is expected to accept type: ' . get_class($type) . ', but got error: ' . $e->getMessage());
} }
} }
foreach ($badTypes as $badType) { foreach ($badTypes as $badType) {
$typeStr = Utils::printSafe($badType); $typeStr = Utils::printSafe($badType);
try { try {
Type::listOf($badType); Type::listOf($badType);
$this->fail("List should not accept $typeStr"); $this->fail(sprintf('List should not accept %s', $typeStr));
} catch (InvariantViolation $e) { } catch (InvariantViolation $e) {
$this->assertEquals("Expected $typeStr to be a GraphQL type.", $e->getMessage()); $this->assertEquals(sprintf('Expected %s to be a GraphQL type.', $typeStr), $e->getMessage());
} }
} }
} }
/** /**
* Type System: NonNull must only accept non-nullable types * Type System: NonNull must only accept non-nullable types
*/ */
@ -1648,22 +1638,20 @@ class DefinitionTest extends TestCase
try { try {
Type::nonNull($type); Type::nonNull($type);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->fail("NonNull is expected to accept type: " . get_class($type) . ", but got error: ". $e->getMessage()); $this->fail('NonNull is expected to accept type: ' . get_class($type) . ', but got error: ' . $e->getMessage());
} }
} }
foreach ($notNullableTypes as $badType) { foreach ($notNullableTypes as $badType) {
$typeStr = Utils::printSafe($badType); $typeStr = Utils::printSafe($badType);
try { try {
Type::nonNull($badType); Type::nonNull($badType);
$this->fail("Nulls should not accept $typeStr"); $this->fail(sprintf('Nulls should not accept %s', $typeStr));
} catch (InvariantViolation $e) { } catch (InvariantViolation $e) {
$this->assertEquals("Expected $typeStr to be a GraphQL nullable type.", $e->getMessage()); $this->assertEquals(sprintf('Expected %s to be a GraphQL nullable type.', $typeStr), $e->getMessage());
} }
} }
} }
// Type System: A Schema must contain uniquely named types
/** /**
* @see it('rejects a Schema which redefines a built-in type') * @see it('rejects a Schema which redefines a built-in type')
*/ */
@ -1685,13 +1673,15 @@ class DefinitionTest extends TestCase
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
$this->expectExceptionMessage( $this->expectExceptionMessage(
'Schema must contain unique named types but contains multiple types named "String" '. 'Schema must contain unique named types but contains multiple types named "String" ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).' '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
); );
$schema = new Schema(['query' => $QueryType]); $schema = new Schema(['query' => $QueryType]);
$schema->assertValid(); $schema->assertValid();
} }
// Type System: A Schema must contain uniquely named types
/** /**
* @see it('rejects a Schema which defines an object type twice') * @see it('rejects a Schema which defines an object type twice')
*/ */
@ -1719,7 +1709,7 @@ class DefinitionTest extends TestCase
'Schema must contain unique named types but contains multiple types named "SameName" ' . 'Schema must contain unique named types but contains multiple types named "SameName" ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).' '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
); );
$schema = new Schema([ 'query' => $QueryType ]); $schema = new Schema(['query' => $QueryType]);
$schema->assertValid(); $schema->assertValid();
} }
@ -1763,4 +1753,12 @@ class DefinitionTest extends TestCase
]); ]);
$schema->assertValid(); $schema->assertValid();
} }
public function objectWithIsTypeOf() : ObjectType
{
return new ObjectType([
'name' => 'ObjectWithIsTypeOf',
'fields' => ['f' => ['type' => Type::string()]],
]);
}
} }

View File

@ -1,29 +1,33 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
use ArrayObject;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function count;
use function is_array;
class EnumTypeTest extends TestCase class EnumTypeTest extends TestCase
{ {
/** /** @var Schema */
* @var Schema
*/
private $schema; private $schema;
/** /** @var EnumType */
* @var EnumType
*/
private $ComplexEnum; private $ComplexEnum;
/** @var mixed[] */
private $Complex1; private $Complex1;
/** @var ArrayObject */
private $Complex2; private $Complex2;
public function setUp() public function setUp()
@ -34,25 +38,30 @@ class EnumTypeTest extends TestCase
'RED' => ['value' => 0], 'RED' => ['value' => 0],
'GREEN' => ['value' => 1], 'GREEN' => ['value' => 1],
'BLUE' => ['value' => 2], 'BLUE' => ['value' => 2],
] ],
]); ]);
$simpleEnum = new EnumType([ $simpleEnum = new EnumType([
'name' => 'SimpleEnum', 'name' => 'SimpleEnum',
'values' => [ 'values' => [
'ONE', 'TWO', 'THREE' 'ONE',
] 'TWO',
'THREE',
],
]); ]);
$Complex1 = ['someRandomFunction' => function() {}]; $Complex1 = [
'someRandomFunction' => function () {
},
];
$Complex2 = new \ArrayObject(['someRandomValue' => 123]); $Complex2 = new \ArrayObject(['someRandomValue' => 123]);
$ComplexEnum = new EnumType([ $ComplexEnum = new EnumType([
'name' => 'Complex', 'name' => 'Complex',
'values' => [ 'values' => [
'ONE' => ['value' => $Complex1], 'ONE' => ['value' => $Complex1],
'TWO' => ['value' => $Complex2] 'TWO' => ['value' => $Complex2],
] ],
]); ]);
$QueryType = new ObjectType([ $QueryType = new ObjectType([
@ -75,22 +84,22 @@ class EnumTypeTest extends TestCase
if (isset($args['fromEnum'])) { if (isset($args['fromEnum'])) {
return $args['fromEnum']; return $args['fromEnum'];
} }
} },
], ],
'simpleEnum' => [ 'simpleEnum' => [
'type' => $simpleEnum, 'type' => $simpleEnum,
'args' => [ 'args' => [
'fromName' => ['type' => Type::string()], 'fromName' => ['type' => Type::string()],
'fromValue' => ['type' => Type::string()] 'fromValue' => ['type' => Type::string()],
], ],
'resolve' => function($value, $args) { 'resolve' => function ($value, $args) {
if (isset($args['fromName'])) { if (isset($args['fromName'])) {
return $args['fromName']; return $args['fromName'];
} }
if (isset($args['fromValue'])) { if (isset($args['fromValue'])) {
return $args['fromValue']; return $args['fromValue'];
} }
} },
], ],
'colorInt' => [ 'colorInt' => [
'type' => Type::int(), 'type' => Type::int(),
@ -105,7 +114,7 @@ class EnumTypeTest extends TestCase
if (isset($args['fromEnum'])) { if (isset($args['fromEnum'])) {
return $args['fromEnum']; return $args['fromEnum'];
} }
} },
], ],
'complexEnum' => [ 'complexEnum' => [
'type' => $ComplexEnum, 'type' => $ComplexEnum,
@ -114,30 +123,31 @@ class EnumTypeTest extends TestCase
'type' => $ComplexEnum, 'type' => $ComplexEnum,
// Note: defaultValue is provided an *internal* representation for // Note: defaultValue is provided an *internal* representation for
// Enums, rather than the string name. // Enums, rather than the string name.
'defaultValue' => $Complex1 'defaultValue' => $Complex1,
], ],
'provideGoodValue' => [ 'provideGoodValue' => [
'type' => Type::boolean(), 'type' => Type::boolean(),
], ],
'provideBadValue' => [ 'provideBadValue' => [
'type' => Type::boolean() 'type' => Type::boolean(),
]
], ],
'resolve' => function($value, $args) use ($Complex1, $Complex2) { ],
if (!empty($args['provideGoodValue'])) { 'resolve' => function ($value, $args) use ($Complex2) {
if (! empty($args['provideGoodValue'])) {
// Note: this is one of the references of the internal values which // Note: this is one of the references of the internal values which
// ComplexEnum allows. // ComplexEnum allows.
return $Complex2; return $Complex2;
} }
if (!empty($args['provideBadValue'])) { if (! empty($args['provideBadValue'])) {
// Note: similar shape, but not the same *reference* // Note: similar shape, but not the same *reference*
// as Complex2 above. Enum internal values require === equality. // as Complex2 above. Enum internal values require === equality.
return new \ArrayObject(['someRandomValue' => 123]); return new \ArrayObject(['someRandomValue' => 123]);
} }
return $args['fromEnum']; return $args['fromEnum'];
} },
] ],
] ],
]); ]);
$MutationType = new ObjectType([ $MutationType = new ObjectType([
@ -147,10 +157,10 @@ class EnumTypeTest extends TestCase
'type' => $ColorType, 'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]], 'args' => ['color' => ['type' => $ColorType]],
'resolve' => function ($value, $args) { 'resolve' => function ($value, $args) {
return isset($args['color']) ? $args['color'] : null; return $args['color'] ?? null;
} },
] ],
] ],
]); ]);
$SubscriptionType = new ObjectType([ $SubscriptionType = new ObjectType([
@ -160,10 +170,10 @@ class EnumTypeTest extends TestCase
'type' => $ColorType, 'type' => $ColorType,
'args' => ['color' => ['type' => $ColorType]], 'args' => ['color' => ['type' => $ColorType]],
'resolve' => function ($value, $args) { 'resolve' => function ($value, $args) {
return isset($args['color']) ? $args['color'] : null; return $args['color'] ?? null;
} },
] ],
] ],
]); ]);
$this->Complex1 = $Complex1; $this->Complex1 = $Complex1;
@ -173,7 +183,7 @@ class EnumTypeTest extends TestCase
$this->schema = new Schema([ $this->schema = new Schema([
'query' => $QueryType, 'query' => $QueryType,
'mutation' => $MutationType, 'mutation' => $MutationType,
'subscription' => $SubscriptionType 'subscription' => $SubscriptionType,
]); ]);
} }
@ -221,12 +231,34 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: "GREEN") }', '{ colorEnum(fromEnum: "GREEN") }',
null, null,
[ [
'message' => "Expected type Color, found \"GREEN\"; Did you mean the enum value GREEN?", 'message' => 'Expected type Color, found "GREEN"; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)] 'locations' => [new SourceLocation(1, 23)],
] ]
); );
} }
private function expectFailure($query, $vars, $err)
{
$result = GraphQL::executeQuery($this->schema, $query, null, null, $vars);
$this->assertEquals(1, count($result->errors));
if (is_array($err)) {
$this->assertEquals(
$err['message'],
$result->errors[0]->getMessage()
);
$this->assertEquals(
$err['locations'],
$result->errors[0]->getLocations()
);
} else {
$this->assertEquals(
$err,
$result->errors[0]->getMessage()
);
}
}
/** /**
* @see it('does not accept valuesNotInTheEnum') * @see it('does not accept valuesNotInTheEnum')
*/ */
@ -236,8 +268,8 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: GREENISH) }', '{ colorEnum(fromEnum: GREENISH) }',
null, null,
[ [
'message' => "Expected type Color, found GREENISH; Did you mean the enum value GREEN?", 'message' => 'Expected type Color, found GREENISH; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)] 'locations' => [new SourceLocation(1, 23)],
] ]
); );
} }
@ -251,8 +283,8 @@ class EnumTypeTest extends TestCase
'{ colorEnum(fromEnum: green) }', '{ colorEnum(fromEnum: green) }',
null, null,
[ [
'message' => "Expected type Color, found green; Did you mean the enum value GREEN?", 'message' => 'Expected type Color, found green; Did you mean the enum value GREEN?',
'locations' => [new SourceLocation(1, 23)] 'locations' => [new SourceLocation(1, 23)],
] ]
); );
} }
@ -281,7 +313,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure( $this->expectFailure(
'{ colorEnum(fromEnum: 1) }', '{ colorEnum(fromEnum: 1) }',
null, null,
"Expected type Color, found 1." 'Expected type Color, found 1.'
); );
} }
@ -293,7 +325,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure( $this->expectFailure(
'{ colorEnum(fromInt: GREEN) }', '{ colorEnum(fromInt: GREEN) }',
null, null,
"Expected type Int, found GREEN." 'Expected type Int, found GREEN.'
); );
} }
@ -381,7 +413,7 @@ class EnumTypeTest extends TestCase
$this->expectFailure( $this->expectFailure(
'query test($color: Int!) { colorEnum(fromEnum: $color) }', 'query test($color: Int!) { colorEnum(fromEnum: $color) }',
['color' => 2], ['color' => 2],
'Variable "$color" of type "Int!" used in position ' . 'expecting type "Color".' 'Variable "$color" of type "Int!" used in position expecting type "Color".'
); );
} }
@ -392,10 +424,13 @@ class EnumTypeTest extends TestCase
{ {
$this->assertEquals( $this->assertEquals(
['data' => ['colorEnum' => 'RED', 'colorInt' => 0]], ['data' => ['colorEnum' => 'RED', 'colorInt' => 0]],
GraphQL::executeQuery($this->schema, "{ GraphQL::executeQuery(
$this->schema,
'{
colorEnum(fromEnum: RED) colorEnum(fromEnum: RED)
colorInt(fromEnum: RED) colorInt(fromEnum: RED)
}")->toArray() }'
)->toArray()
); );
} }
@ -406,10 +441,13 @@ class EnumTypeTest extends TestCase
{ {
$this->assertEquals( $this->assertEquals(
['data' => ['colorEnum' => null, 'colorInt' => null]], ['data' => ['colorEnum' => null, 'colorInt' => null]],
GraphQL::executeQuery($this->schema, "{ GraphQL::executeQuery(
$this->schema,
'{
colorEnum colorEnum
colorInt colorInt
}")->toArray() }'
)->toArray()
); );
} }
@ -446,25 +484,29 @@ class EnumTypeTest extends TestCase
*/ */
public function testMayBeInternallyRepresentedWithComplexValues() : void public function testMayBeInternallyRepresentedWithComplexValues() : void
{ {
$result = GraphQL::executeQuery($this->schema, '{ $result = GraphQL::executeQuery(
$this->schema,
'{
first: complexEnum first: complexEnum
second: complexEnum(fromEnum: TWO) second: complexEnum(fromEnum: TWO)
good: complexEnum(provideGoodValue: true) good: complexEnum(provideGoodValue: true)
bad: complexEnum(provideBadValue: true) bad: complexEnum(provideBadValue: true)
}')->toArray(true); }'
)->toArray(true);
$expected = [ $expected = [
'data' => [ 'data' => [
'first' => 'ONE', 'first' => 'ONE',
'second' => 'TWO', 'second' => 'TWO',
'good' => 'TWO', 'good' => 'TWO',
'bad' => null 'bad' => null,
], ],
'errors' => [[ 'errors' => [[
'debugMessage' => 'debugMessage' =>
'Expected a value of type "Complex" but received: instance of ArrayObject', 'Expected a value of type "Complex" but received: instance of ArrayObject',
'locations' => [['line' => 5, 'column' => 9]] 'locations' => [['line' => 5, 'column' => 9]],
]] ],
],
]; ];
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
@ -492,32 +534,11 @@ class EnumTypeTest extends TestCase
'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null], 'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null],
'errors' => [[ 'errors' => [[
'debugMessage' => 'Expected a value of type "SimpleEnum" but received: WRONG', 'debugMessage' => 'Expected a value of type "SimpleEnum" but received: WRONG',
'locations' => [['line' => 4, 'column' => 13]] 'locations' => [['line' => 4, 'column' => 13]],
]] ],
],
], ],
GraphQL::executeQuery($this->schema, $q)->toArray(true) GraphQL::executeQuery($this->schema, $q)->toArray(true)
); );
} }
private function expectFailure($query, $vars, $err)
{
$result = GraphQL::executeQuery($this->schema, $query, null, null, $vars);
$this->assertEquals(1, count($result->errors));
if (is_array($err)) {
$this->assertEquals(
$err['message'],
$result->errors[0]->getMessage()
);
$this->assertEquals(
$err['locations'],
$result->errors[0]->getLocations()
);
} else {
$this->assertEquals(
$err,
$result->errors[0]->getMessage()
);
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,12 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
class ObjectIdStub class ObjectIdStub
{ {
/** /** @var int */
* @var int
*/
private $id; private $id;
/** /**

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
@ -10,75 +13,53 @@ use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\EagerResolution; use GraphQL\Type\EagerResolution;
use GraphQL\Type\LazyResolution; use GraphQL\Type\LazyResolution;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function lcfirst;
class ResolutionTest extends TestCase class ResolutionTest extends TestCase
{ {
/** /** @var ObjectType */
* @var ObjectType
*/
private $query; private $query;
/** /** @var ObjectType */
* @var ObjectType
*/
private $mutation; private $mutation;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $node; private $node;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $content; private $content;
/** /** @var ObjectType */
* @var ObjectType
*/
private $blogStory; private $blogStory;
/** /** @var ObjectType */
* @var ObjectType
*/
private $link;
/**
* @var ObjectType
*/
private $video; private $video;
/** /** @var ObjectType */
* @var ObjectType
*/
private $videoMetadata; private $videoMetadata;
/** /** @var ObjectType */
* @var ObjectType
*/
private $comment; private $comment;
/** /** @var ObjectType */
* @var ObjectType
*/
private $user; private $user;
/** /** @var ObjectType */
* @var ObjectType
*/
private $category; private $category;
/** /** @var UnionType */
* @var UnionType
*/
private $mention; private $mention;
/** @var ObjectType */
private $postStoryMutation; private $postStoryMutation;
/** @var InputObjectType */
private $postStoryMutationInput; private $postStoryMutationInput;
/** @var ObjectType */
private $postCommentMutation; private $postCommentMutation;
/** @var InputObjectType */
private $postCommentMutationInput; private $postCommentMutationInput;
public function setUp() public function setUp()
@ -86,48 +67,30 @@ class ResolutionTest extends TestCase
$this->node = new InterfaceType([ $this->node = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => [ 'fields' => [
'id' => Type::string() 'id' => Type::string(),
] ],
]); ]);
$this->content = new InterfaceType([ $this->content = new InterfaceType([
'name' => 'Content', 'name' => 'Content',
'fields' => function() { 'fields' => function () {
return [ return [
'title' => Type::string(), 'title' => Type::string(),
'body' => Type::string(), 'body' => Type::string(),
'author' => $this->user, 'author' => $this->user,
'comments' => Type::listOf($this->comment), 'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category) 'categories' => Type::listOf($this->category),
]; ];
} },
]); ]);
$this->blogStory = new ObjectType([ $this->blogStory = new ObjectType([
'name' => 'BlogStory', 'name' => 'BlogStory',
'interfaces' => [ 'interfaces' => [
$this->node, $this->node,
$this->content $this->content,
], ],
'fields' => function() { 'fields' => function () {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories')
];
},
]);
$this->link = new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
return [ return [
$this->node->getField('id'), $this->node->getField('id'),
$this->content->getField('title'), $this->content->getField('title'),
@ -135,87 +98,108 @@ class ResolutionTest extends TestCase
$this->content->getField('author'), $this->content->getField('author'),
$this->content->getField('comments'), $this->content->getField('comments'),
$this->content->getField('categories'), $this->content->getField('categories'),
'url' => Type::string()
]; ];
}, },
]); ]);
new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content,
],
'fields' => function () {
return [
'id' => $this->node->getField('id'),
'title' => $this->content->getField('title'),
'body' => $this->content->getField('body'),
'author' => $this->content->getField('author'),
'comments' => $this->content->getField('comments'),
'categories' => $this->content->getField('categories'),
'url' => Type::string(),
];
},
]);
$this->videoMetadata = new ObjectType([
'name' => 'VideoMetadata',
'fields' => [
'lat' => Type::float(),
'lng' => Type::float(),
],
]);
$this->video = new ObjectType([ $this->video = new ObjectType([
'name' => 'Video', 'name' => 'Video',
'interfaces' => [ 'interfaces' => [
$this->node, $this->node,
$this->content $this->content,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
$this->content->getField('title'), 'title' => $this->content->getField('title'),
$this->content->getField('body'), 'body' => $this->content->getField('body'),
$this->content->getField('author'), 'author' => $this->content->getField('author'),
$this->content->getField('comments'), 'comments' => $this->content->getField('comments'),
$this->content->getField('categories'), 'categories' => $this->content->getField('categories'),
'streamUrl' => Type::string(), 'streamUrl' => Type::string(),
'downloadUrl' => Type::string(), 'downloadUrl' => Type::string(),
'metadata' => $this->videoMetadata = new ObjectType([ 'metadata' => $this->videoMetadata,
'name' => 'VideoMetadata',
'fields' => [
'lat' => Type::float(),
'lng' => Type::float()
]
])
]; ];
} },
]); ]);
$this->comment = new ObjectType([ $this->comment = new ObjectType([
'name' => 'Comment', 'name' => 'Comment',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'author' => $this->user, 'author' => $this->user,
'text' => Type::string(), 'text' => Type::string(),
'replies' => Type::listOf($this->comment), 'replies' => Type::listOf($this->comment),
'parent' => $this->comment, 'parent' => $this->comment,
'content' => $this->content 'content' => $this->content,
]; ];
} },
]); ]);
$this->user = new ObjectType([ $this->user = new ObjectType([
'name' => 'User', 'name' => 'User',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'name' => Type::string(), 'name' => Type::string(),
]; ];
} },
]); ]);
$this->category = new ObjectType([ $this->category = new ObjectType([
'name' => 'Category', 'name' => 'Category',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'name' => Type::string() 'name' => Type::string(),
]; ];
} },
]); ]);
$this->mention = new UnionType([ $this->mention = new UnionType([
'name' => 'Mention', 'name' => 'Mention',
'types' => [ 'types' => [
$this->user, $this->user,
$this->category $this->category,
] ],
]); ]);
$this->query = new ObjectType([ $this->query = new ObjectType([
@ -224,8 +208,18 @@ class ResolutionTest extends TestCase
'viewer' => $this->user, 'viewer' => $this->user,
'latestContent' => $this->content, 'latestContent' => $this->content,
'node' => $this->node, 'node' => $this->node,
'mentions' => Type::listOf($this->mention) 'mentions' => Type::listOf($this->mention),
] ],
]);
$this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id(),
],
]); ]);
$this->mutation = new ObjectType([ $this->mutation = new ObjectType([
@ -235,28 +229,20 @@ class ResolutionTest extends TestCase
'type' => $this->postStoryMutation = new ObjectType([ 'type' => $this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation', 'name' => 'PostStoryMutation',
'fields' => [ 'fields' => [
'story' => $this->blogStory 'story' => $this->blogStory,
] ],
]), ]),
'args' => [ 'args' => [
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([ 'input' => Type::nonNull($this->postStoryMutationInput),
'name' => 'PostStoryMutationInput', 'clientRequestId' => Type::string(),
'fields' => [ ],
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
])),
'clientRequestId' => Type::string()
]
], ],
'postComment' => [ 'postComment' => [
'type' => $this->postCommentMutation = new ObjectType([ 'type' => $this->postCommentMutation = new ObjectType([
'name' => 'PostCommentMutation', 'name' => 'PostCommentMutation',
'fields' => [ 'fields' => [
'comment' => $this->comment 'comment' => $this->comment,
] ],
]), ]),
'args' => [ 'args' => [
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([ 'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
@ -265,13 +251,13 @@ class ResolutionTest extends TestCase
'text' => Type::nonNull(Type::string()), 'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()), 'author' => Type::nonNull(Type::id()),
'content' => Type::id(), 'content' => Type::id(),
'parent' => Type::id() 'parent' => Type::id(),
] ],
])), ])),
'clientRequestId' => Type::string() 'clientRequestId' => Type::string(),
] ],
] ],
] ],
]); ]);
} }
@ -284,7 +270,7 @@ class ResolutionTest extends TestCase
'String' => Type::string(), 'String' => Type::string(),
'Float' => Type::float(), 'Float' => Type::float(),
'Int' => Type::int(), 'Int' => Type::int(),
'Boolean' => Type::boolean() 'Boolean' => Type::boolean(),
]; ];
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap()); $this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
@ -297,11 +283,11 @@ class ResolutionTest extends TestCase
'Int' => 1, 'Int' => 1,
'Boolean' => 1, 'Boolean' => 1,
], ],
'possibleTypeMap' => [] 'possibleTypeMap' => [],
]; ];
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor()); $this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
$this->assertSame(null, $eagerTypeResolution->resolveType('User')); $this->assertNull($eagerTypeResolution->resolveType('User'));
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->node)); $this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->node));
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->content)); $this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->mention)); $this->assertSame([], $eagerTypeResolution->resolvePossibleTypes($this->mention));
@ -321,10 +307,16 @@ class ResolutionTest extends TestCase
$this->assertSame($this->postStoryMutation, $eagerTypeResolution->resolveType('PostStoryMutation')); $this->assertSame($this->postStoryMutation, $eagerTypeResolution->resolveType('PostStoryMutation'));
$this->assertSame($this->postStoryMutationInput, $eagerTypeResolution->resolveType('PostStoryMutationInput')); $this->assertSame($this->postStoryMutationInput, $eagerTypeResolution->resolveType('PostStoryMutationInput'));
$this->assertSame($this->postCommentMutation, $eagerTypeResolution->resolveType('PostCommentMutation')); $this->assertSame($this->postCommentMutation, $eagerTypeResolution->resolveType('PostCommentMutation'));
$this->assertSame($this->postCommentMutationInput, $eagerTypeResolution->resolveType('PostCommentMutationInput')); $this->assertSame(
$this->postCommentMutationInput,
$eagerTypeResolution->resolveType('PostCommentMutationInput')
);
$this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content)); $this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertEquals([$this->user, $this->comment, $this->category, $this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->node)); $this->assertEquals(
[$this->user, $this->comment, $this->category, $this->blogStory],
$eagerTypeResolution->resolvePossibleTypes($this->node)
);
$this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention)); $this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention));
$expectedTypeMap = [ $expectedTypeMap = [
@ -345,7 +337,7 @@ class ResolutionTest extends TestCase
'PostCommentMutation' => $this->postCommentMutation, 'PostCommentMutation' => $this->postCommentMutation,
'Float' => Type::float(), 'Float' => Type::float(),
'Int' => Type::int(), 'Int' => Type::int(),
'Boolean' => Type::boolean() 'Boolean' => Type::boolean(),
]; ];
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap()); $this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
@ -370,23 +362,21 @@ class ResolutionTest extends TestCase
'PostCommentMutation' => 1, 'PostCommentMutation' => 1,
'Float' => 1, 'Float' => 1,
'Int' => 1, 'Int' => 1,
'Boolean' => 1 'Boolean' => 1,
], ],
'possibleTypeMap' => [ 'possibleTypeMap' => [
'Node' => [ 'Node' => [
'User' => 1, 'User' => 1,
'Comment' => 1, 'Comment' => 1,
'Category' => 1, 'Category' => 1,
'BlogStory' => 1 'BlogStory' => 1,
],
'Content' => [
'BlogStory' => 1
], ],
'Content' => ['BlogStory' => 1],
'Mention' => [ 'Mention' => [
'User' => 1, 'User' => 1,
'Category' => 1 'Category' => 1,
] ],
] ],
]; ];
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor()); $this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
@ -402,7 +392,10 @@ class ResolutionTest extends TestCase
$this->assertEquals(null, $eagerTypeResolution->resolveType('VideoMetadata')); $this->assertEquals(null, $eagerTypeResolution->resolveType('VideoMetadata'));
$this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content)); $this->assertEquals([$this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertEquals([$this->user, $this->comment, $this->category, $this->blogStory], $eagerTypeResolution->resolvePossibleTypes($this->node)); $this->assertEquals(
[$this->user, $this->comment, $this->category, $this->blogStory],
$eagerTypeResolution->resolvePossibleTypes($this->node)
);
$this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention)); $this->assertEquals([$this->user, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->mention));
$eagerTypeResolution = new EagerResolution([null, $this->video, null]); $eagerTypeResolution = new EagerResolution([null, $this->video, null]);
@ -410,7 +403,10 @@ class ResolutionTest extends TestCase
$this->assertEquals($this->video, $eagerTypeResolution->resolveType('Video')); $this->assertEquals($this->video, $eagerTypeResolution->resolveType('Video'));
$this->assertEquals([$this->video], $eagerTypeResolution->resolvePossibleTypes($this->content)); $this->assertEquals([$this->video], $eagerTypeResolution->resolvePossibleTypes($this->content));
$this->assertEquals([$this->video, $this->user, $this->comment, $this->category], $eagerTypeResolution->resolvePossibleTypes($this->node)); $this->assertEquals(
[$this->video, $this->user, $this->comment, $this->category],
$eagerTypeResolution->resolvePossibleTypes($this->node)
);
$this->assertEquals([], $eagerTypeResolution->resolvePossibleTypes($this->mention)); $this->assertEquals([], $eagerTypeResolution->resolvePossibleTypes($this->mention));
$expectedTypeMap = [ $expectedTypeMap = [
@ -425,7 +421,7 @@ class ResolutionTest extends TestCase
'Float' => Type::float(), 'Float' => Type::float(),
'ID' => Type::id(), 'ID' => Type::id(),
'Int' => Type::int(), 'Int' => Type::int(),
'Boolean' => Type::boolean() 'Boolean' => Type::boolean(),
]; ];
$this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap()); $this->assertEquals($expectedTypeMap, $eagerTypeResolution->getTypeMap());
@ -443,19 +439,17 @@ class ResolutionTest extends TestCase
'Float' => 1, 'Float' => 1,
'ID' => 1, 'ID' => 1,
'Int' => 1, 'Int' => 1,
'Boolean' => 1 'Boolean' => 1,
], ],
'possibleTypeMap' => [ 'possibleTypeMap' => [
'Node' => [ 'Node' => [
'Video' => 1, 'Video' => 1,
'User' => 1, 'User' => 1,
'Comment' => 1, 'Comment' => 1,
'Category' => 1 'Category' => 1,
],
'Content' => ['Video' => 1],
], ],
'Content' => [
'Video' => 1
]
]
]; ];
$this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor()); $this->assertEquals($expectedDescriptor, $eagerTypeResolution->getDescriptor());
} }
@ -466,8 +460,8 @@ class ResolutionTest extends TestCase
$eager = new EagerResolution([]); $eager = new EagerResolution([]);
$emptyDescriptor = $eager->getDescriptor(); $emptyDescriptor = $eager->getDescriptor();
$typeLoader = function($name) { $typeLoader = function ($name) {
throw new \Exception("This should be never called for empty descriptor"); throw new \Exception('This should be never called for empty descriptor');
}; };
$lazy = new LazyResolution($emptyDescriptor, $typeLoader); $lazy = new LazyResolution($emptyDescriptor, $typeLoader);
@ -480,9 +474,10 @@ class ResolutionTest extends TestCase
$called = 0; $called = 0;
$descriptor = $eager->getDescriptor(); $descriptor = $eager->getDescriptor();
$typeLoader = function($name) use (&$called) { $typeLoader = function ($name) use (&$called) {
$called++; $called++;
$prop = lcfirst($name); $prop = lcfirst($name);
return $this->{$prop}; return $this->{$prop};
}; };
@ -507,7 +502,10 @@ class ResolutionTest extends TestCase
$this->assertSame($eager->resolveType('PostStoryMutation'), $lazy->resolveType('PostStoryMutation')); $this->assertSame($eager->resolveType('PostStoryMutation'), $lazy->resolveType('PostStoryMutation'));
$this->assertSame($eager->resolveType('PostStoryMutationInput'), $lazy->resolveType('PostStoryMutationInput')); $this->assertSame($eager->resolveType('PostStoryMutationInput'), $lazy->resolveType('PostStoryMutationInput'));
$this->assertSame($eager->resolveType('PostCommentMutation'), $lazy->resolveType('PostCommentMutation')); $this->assertSame($eager->resolveType('PostCommentMutation'), $lazy->resolveType('PostCommentMutation'));
$this->assertSame($eager->resolveType('PostCommentMutationInput'), $lazy->resolveType('PostCommentMutationInput')); $this->assertSame(
$eager->resolveType('PostCommentMutationInput'),
$lazy->resolveType('PostCommentMutationInput')
);
$this->assertSame(13, $called); $this->assertSame(13, $called);
$this->assertEquals($eager->resolvePossibleTypes($this->content), $lazy->resolvePossibleTypes($this->content)); $this->assertEquals($eager->resolvePossibleTypes($this->content), $lazy->resolvePossibleTypes($this->content));
@ -527,25 +525,29 @@ class ResolutionTest extends TestCase
$this->assertEquals($eager->resolvePossibleTypes($this->mention), $lazy->resolvePossibleTypes($this->mention)); $this->assertEquals($eager->resolvePossibleTypes($this->mention), $lazy->resolvePossibleTypes($this->mention));
} }
private function createLazy(){ public function testLazyThrowsOnInvalidLoadedType() : void
{
$lazy = $this->createLazy();
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Lazy Type Resolution Error: Expecting GraphQL Type instance, but got integer');
$lazy->resolveType('int');
}
private function createLazy()
{
$descriptor = [ $descriptor = [
'version' => '1.0', 'version' => '1.0',
'typeMap' => [ 'typeMap' => [
'null' => 1, 'null' => 1,
'int' => 1 'int' => 1,
], ],
'possibleTypeMap' => [ 'possibleTypeMap' => [
'a' => [ 'a' => ['null' => 1],
'null' => 1, 'b' => ['int' => 1],
], ],
'b' => [
'int' => 1
]
]
]; ];
$invalidTypeLoader = function($name) { $invalidTypeLoader = function ($name) {
switch ($name) { switch ($name) {
case 'null': case 'null':
return null; return null;
@ -561,14 +563,6 @@ class ResolutionTest extends TestCase
return $lazy; return $lazy;
} }
public function testLazyThrowsOnInvalidLoadedType() : void
{
$lazy = $this->createLazy();
$this->expectException(InvariantViolation::class);
$this->expectExceptionMessage('Lazy Type Resolution Error: Expecting GraphQL Type instance, but got integer');
$lazy->resolveType('int');
}
public function testLazyThrowsOnInvalidLoadedPossibleType() : void public function testLazyThrowsOnInvalidLoadedPossibleType() : void
{ {
$tmp = new InterfaceType(['name' => 'a', 'fields' => []]); $tmp = new InterfaceType(['name' => 'a', 'fields' => []]);

View File

@ -1,11 +1,14 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
use GraphQL\GraphQL; use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ResolveInfoTest extends TestCase class ResolveInfoTest extends TestCase
@ -17,22 +20,25 @@ class ResolveInfoTest extends TestCase
'fields' => [ 'fields' => [
'url' => ['type' => Type::string()], 'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()] 'height' => ['type' => Type::int()],
] ],
]); ]);
$article = null; $article = null;
$author = new ObjectType([ $author = new ObjectType([
'name' => 'Author', 'name' => 'Author',
'fields' => function() use ($image, &$article) { 'fields' => function () use ($image, &$article) {
return [ return [
'id' => ['type' => Type::string()], 'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pic' => [ 'type' => $image, 'args' => [ 'pic' => [
'type' => $image,
'args' => [
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()] 'height' => ['type' => Type::int()],
]], ],
],
'recentArticle' => ['type' => $article], 'recentArticle' => ['type' => $article],
]; ];
}, },
@ -42,8 +48,8 @@ class ResolveInfoTest extends TestCase
'name' => 'Reply', 'name' => 'Reply',
'fields' => [ 'fields' => [
'author' => ['type' => $author], 'author' => ['type' => $author],
'body' => ['type' => Type::string()] 'body' => ['type' => Type::string()],
] ],
]); ]);
$article = new ObjectType([ $article = new ObjectType([
@ -55,8 +61,8 @@ class ResolveInfoTest extends TestCase
'title' => ['type' => Type::string()], 'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()], 'body' => ['type' => Type::string()],
'image' => ['type' => $image], 'image' => ['type' => $image],
'replies' => ['type' => Type::listOf($reply)] 'replies' => ['type' => Type::listOf($reply)],
] ],
]); ]);
$doc = ' $doc = '
@ -102,20 +108,20 @@ class ResolveInfoTest extends TestCase
$expectedDefaultSelection = [ $expectedDefaultSelection = [
'author' => true, 'author' => true,
'image' => true, 'image' => true,
'replies' => true 'replies' => true,
]; ];
$expectedDeepSelection = [ $expectedDeepSelection = [
'author' => [ 'author' => [
'name' => true, 'name' => true,
'pic' => [ 'pic' => [
'url' => true, 'url' => true,
'width' => true 'width' => true,
] ],
], ],
'image' => [ 'image' => [
'width' => true, 'width' => true,
'height' => true, 'height' => true,
'url' => true 'url' => true,
], ],
'replies' => [ 'replies' => [
'body' => true, 'body' => true,
@ -125,15 +131,15 @@ class ResolveInfoTest extends TestCase
'pic' => [ 'pic' => [
'url' => true, 'url' => true,
'width' => true, 'width' => true,
'height' => true 'height' => true,
], ],
'recentArticle' => [ 'recentArticle' => [
'id' => true, 'id' => true,
'title' => true, 'title' => true,
'body' => true 'body' => true,
] ],
] ],
] ],
]; ];
$hasCalled = false; $hasCalled = false;
@ -145,14 +151,25 @@ class ResolveInfoTest extends TestCase
'fields' => [ 'fields' => [
'article' => [ 'article' => [
'type' => $article, 'type' => $article,
'resolve' => function($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$actualDefaultSelection, &$actualDeepSelection) { 'resolve' => function (
$value,
$args,
$context,
ResolveInfo $info
) use (
&$hasCalled,
&
$actualDefaultSelection,
&$actualDeepSelection
) {
$hasCalled = true; $hasCalled = true;
$actualDefaultSelection = $info->getFieldSelection(); $actualDefaultSelection = $info->getFieldSelection();
$actualDeepSelection = $info->getFieldSelection(5); $actualDeepSelection = $info->getFieldSelection(5);
return null; return null;
} },
] ],
] ],
]); ]);
$schema = new Schema(['query' => $blogQuery]); $schema = new Schema(['query' => $blogQuery]);
@ -171,22 +188,25 @@ class ResolveInfoTest extends TestCase
'fields' => [ 'fields' => [
'url' => ['type' => Type::string()], 'url' => ['type' => Type::string()],
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()] 'height' => ['type' => Type::int()],
] ],
]); ]);
$article = null; $article = null;
$author = new ObjectType([ $author = new ObjectType([
'name' => 'Author', 'name' => 'Author',
'fields' => function() use ($image, &$article) { 'fields' => function () use ($image, &$article) {
return [ return [
'id' => ['type' => Type::string()], 'id' => ['type' => Type::string()],
'name' => ['type' => Type::string()], 'name' => ['type' => Type::string()],
'pic' => [ 'type' => $image, 'args' => [ 'pic' => [
'type' => $image,
'args' => [
'width' => ['type' => Type::int()], 'width' => ['type' => Type::int()],
'height' => ['type' => Type::int()] 'height' => ['type' => Type::int()],
]], ],
],
'recentArticle' => ['type' => $article], 'recentArticle' => ['type' => $article],
]; ];
}, },
@ -196,8 +216,8 @@ class ResolveInfoTest extends TestCase
'name' => 'Reply', 'name' => 'Reply',
'fields' => [ 'fields' => [
'author' => ['type' => $author], 'author' => ['type' => $author],
'body' => ['type' => Type::string()] 'body' => ['type' => Type::string()],
] ],
]); ]);
$article = new ObjectType([ $article = new ObjectType([
@ -209,8 +229,8 @@ class ResolveInfoTest extends TestCase
'title' => ['type' => Type::string()], 'title' => ['type' => Type::string()],
'body' => ['type' => Type::string()], 'body' => ['type' => Type::string()],
'image' => ['type' => $image], 'image' => ['type' => $image],
'replies' => ['type' => Type::listOf($reply)] 'replies' => ['type' => Type::listOf($reply)],
] ],
]); ]);
$doc = ' $doc = '
@ -268,13 +288,13 @@ class ResolveInfoTest extends TestCase
'name' => true, 'name' => true,
'pic' => [ 'pic' => [
'url' => true, 'url' => true,
'width' => true 'width' => true,
] ],
], ],
'image' => [ 'image' => [
'width' => true, 'width' => true,
'height' => true, 'height' => true,
'url' => true 'url' => true,
], ],
'replies' => [ 'replies' => [
'body' => true, //this would be missing if not for the fix https://github.com/webonyx/graphql-php/pull/98 'body' => true, //this would be missing if not for the fix https://github.com/webonyx/graphql-php/pull/98
@ -284,15 +304,15 @@ class ResolveInfoTest extends TestCase
'pic' => [ 'pic' => [
'url' => true, 'url' => true,
'width' => true, 'width' => true,
'height' => true 'height' => true,
], ],
'recentArticle' => [ 'recentArticle' => [
'id' => true, 'id' => true,
'title' => true, 'title' => true,
'body' => true 'body' => true,
] ],
] ],
] ],
]; ];
$hasCalled = false; $hasCalled = false;
@ -304,13 +324,23 @@ class ResolveInfoTest extends TestCase
'fields' => [ 'fields' => [
'article' => [ 'article' => [
'type' => $article, 'type' => $article,
'resolve' => function($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$actualDeepSelection) { 'resolve' => function (
$value,
$args,
$context,
ResolveInfo $info
) use (
&$hasCalled,
&
$actualDeepSelection
) {
$hasCalled = true; $hasCalled = true;
$actualDeepSelection = $info->getFieldSelection(5); $actualDeepSelection = $info->getFieldSelection(5);
return null; return null;
} },
] ],
] ],
]); ]);
$schema = new Schema(['query' => $blogQuery]); $schema = new Schema(['query' => $blogQuery]);
@ -320,6 +350,4 @@ class ResolveInfoTest extends TestCase
$this->assertEquals(['data' => ['article' => null]], $result); $this->assertEquals(['data' => ['article' => null]], $result);
$this->assertEquals($expectedDeepSelection, $actualDeepSelection); $this->assertEquals($expectedDeepSelection, $actualDeepSelection);
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -8,7 +11,6 @@ use PHPUnit\Framework\TestCase;
class ScalarSerializationTest extends TestCase class ScalarSerializationTest extends TestCase
{ {
// Type System: Scalar coercion // Type System: Scalar coercion
/** /**
* @see it('serializes output int') * @see it('serializes output int')
*/ */
@ -42,7 +44,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class); $this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non-integer value: 1.1'); $this->expectExceptionMessage('Int cannot represent non-integer value: 1.1');
$intType->serialize(1.1); $intType->serialize(1.1);
} }
public function testSerializesOutputIntCannotRepresentNegativeFloat() : void public function testSerializesOutputIntCannotRepresentNegativeFloat() : void
@ -51,7 +52,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class); $this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non-integer value: -1.1'); $this->expectExceptionMessage('Int cannot represent non-integer value: -1.1');
$intType->serialize(-1.1); $intType->serialize(-1.1);
} }
public function testSerializesOutputIntCannotRepresentNumericString() : void public function testSerializesOutputIntCannotRepresentNumericString() : void
@ -60,7 +60,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class); $this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: Int cannot represent non-integer value: "-1.1"'); $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: Int cannot represent non-integer value: "-1.1"');
$intType->serialize('Int cannot represent non-integer value: "-1.1"'); $intType->serialize('Int cannot represent non-integer value: "-1.1"');
} }
public function testSerializesOutputIntCannotRepresentBiggerThan32Bits() : void public function testSerializesOutputIntCannotRepresentBiggerThan32Bits() : void
@ -71,7 +70,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class); $this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 9876504321'); $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 9876504321');
$intType->serialize(9876504321); $intType->serialize(9876504321);
} }
public function testSerializesOutputIntCannotRepresentLowerThan32Bits() : void public function testSerializesOutputIntCannotRepresentLowerThan32Bits() : void
@ -104,7 +102,6 @@ class ScalarSerializationTest extends TestCase
$this->expectException(Error::class); $this->expectException(Error::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: one'); $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: one');
$intType->serialize('one'); $intType->serialize('one');
} }
public function testSerializesOutputIntCannotRepresentEmptyString() : void public function testSerializesOutputIntCannotRepresentEmptyString() : void
@ -189,14 +186,13 @@ class ScalarSerializationTest extends TestCase
{ {
$boolType = Type::boolean(); $boolType = Type::boolean();
$this->assertSame(true, $boolType->serialize('string')); $this->assertTrue($boolType->serialize('string'));
$this->assertSame(false, $boolType->serialize('')); $this->assertFalse($boolType->serialize(''));
$this->assertSame(true, $boolType->serialize('1')); $this->assertTrue($boolType->serialize('1'));
$this->assertSame(true, $boolType->serialize(1)); $this->assertTrue($boolType->serialize(1));
$this->assertSame(false, $boolType->serialize(0)); $this->assertFalse($boolType->serialize(0));
$this->assertSame(true, $boolType->serialize(true)); $this->assertTrue($boolType->serialize(true));
$this->assertSame(false, $boolType->serialize(false)); $this->assertFalse($boolType->serialize(false));
// TODO: how should it behave on '0'? // TODO: how should it behave on '0'?
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Type; namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
@ -12,14 +15,19 @@ use PHPUnit\Framework\TestCase;
class SchemaTest extends TestCase class SchemaTest extends TestCase
{ {
/** @var InterfaceType */
private $interfaceType; private $interfaceType;
/** @var ObjectType */
private $implementingType; private $implementingType;
/** @var InputObjectType */
private $directiveInputType; private $directiveInputType;
/** @var InputObjectType */
private $wrappedDirectiveInputType; private $wrappedDirectiveInputType;
/** @var Directive */
private $directive; private $directive;
/** @var Schema */ /** @var Schema */
@ -35,9 +43,14 @@ class SchemaTest extends TestCase
$this->implementingType = new ObjectType([ $this->implementingType = new ObjectType([
'name' => 'Object', 'name' => 'Object',
'interfaces' => [$this->interfaceType], 'interfaces' => [$this->interfaceType],
'fields' => ['fieldName' => ['type' => Type::string(), 'resolve' => function () { 'fields' => [
'fieldName' => [
'type' => Type::string(),
'resolve' => function () {
return ''; return '';
}]], },
],
],
]); ]);
$this->directiveInputType = new InputObjectType([ $this->directiveInputType = new InputObjectType([

View File

@ -1,32 +0,0 @@
<?php
namespace GraphQL\Tests\Type;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
class MyCustomType extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'a' => Type::string()
]
];
parent::__construct($config);
}
}
// Note: named OtherCustom vs OtherCustomType intentionally
class OtherCustom extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'b' => Type::string()
]
];
parent::__construct($config);
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type\TestClasses;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
class MyCustomType extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'a' => Type::string(),
],
];
parent::__construct($config);
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Type\TestClasses;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
/**
* Note: named OtherCustom vs OtherCustomType intentionally
*/
class OtherCustom extends ObjectType
{
public function __construct()
{
$config = [
'fields' => [
'b' => Type::string(),
],
];
parent::__construct($config);
}
}

View File

@ -1,6 +1,8 @@
<?php <?php
namespace GraphQL\Tests\Type;
declare(strict_types=1);
namespace GraphQL\Tests\Type;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
@ -9,52 +11,35 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function lcfirst;
class TypeLoaderTest extends TestCase class TypeLoaderTest extends TestCase
{ {
/** /** @var ObjectType */
* @var ObjectType
*/
private $query; private $query;
/** /** @var ObjectType */
* @var ObjectType
*/
private $mutation; private $mutation;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $node; private $node;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $content; private $content;
/** /** @var ObjectType */
* @var ObjectType
*/
private $blogStory; private $blogStory;
/** /** @var ObjectType */
* @var ObjectType
*/
private $postStoryMutation; private $postStoryMutation;
/** /** @var InputObjectType */
* @var InputObjectType
*/
private $postStoryMutationInput; private $postStoryMutationInput;
/** /** @var callable */
* @var callable
*/
private $typeLoader; private $typeLoader;
/** /** @var string[] */
* @var array
*/
private $calls; private $calls;
public function setUp() public function setUp()
@ -63,35 +48,40 @@ class TypeLoaderTest extends TestCase
$this->node = new InterfaceType([ $this->node = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => function() { 'fields' => function () {
$this->calls[] = 'Node.fields'; $this->calls[] = 'Node.fields';
return [ return [
'id' => Type::string() 'id' => Type::string(),
]; ];
}, },
'resolveType' => function() {} 'resolveType' => function () {
},
]); ]);
$this->content = new InterfaceType([ $this->content = new InterfaceType([
'name' => 'Content', 'name' => 'Content',
'fields' => function() { 'fields' => function () {
$this->calls[] = 'Content.fields'; $this->calls[] = 'Content.fields';
return [ return [
'title' => Type::string(), 'title' => Type::string(),
'body' => Type::string(), 'body' => Type::string(),
]; ];
}, },
'resolveType' => function() {} 'resolveType' => function () {
},
]); ]);
$this->blogStory = new ObjectType([ $this->blogStory = new ObjectType([
'name' => 'BlogStory', 'name' => 'BlogStory',
'interfaces' => [ 'interfaces' => [
$this->node, $this->node,
$this->content $this->content,
], ],
'fields' => function() { 'fields' => function () {
$this->calls[] = 'BlogStory.fields'; $this->calls[] = 'BlogStory.fields';
return [ return [
$this->node->getField('id'), $this->node->getField('id'),
$this->content->getField('title'), $this->content->getField('title'),
@ -102,36 +92,38 @@ class TypeLoaderTest extends TestCase
$this->query = new ObjectType([ $this->query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => function() { 'fields' => function () {
$this->calls[] = 'Query.fields'; $this->calls[] = 'Query.fields';
return [ return [
'latestContent' => $this->content, 'latestContent' => $this->content,
'node' => $this->node, 'node' => $this->node,
]; ];
} },
]); ]);
$this->mutation = new ObjectType([ $this->mutation = new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
'fields' => function() { 'fields' => function () {
$this->calls[] = 'Mutation.fields'; $this->calls[] = 'Mutation.fields';
return [ return [
'postStory' => [ 'postStory' => [
'type' => $this->postStoryMutation, 'type' => $this->postStoryMutation,
'args' => [ 'args' => [
'input' => Type::nonNull($this->postStoryMutationInput), 'input' => Type::nonNull($this->postStoryMutationInput),
'clientRequestId' => Type::string() 'clientRequestId' => Type::string(),
] ],
] ],
]; ];
} },
]); ]);
$this->postStoryMutation = new ObjectType([ $this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation', 'name' => 'PostStoryMutation',
'fields' => [ 'fields' => [
'story' => $this->blogStory 'story' => $this->blogStory,
] ],
]); ]);
$this->postStoryMutationInput = new InputObjectType([ $this->postStoryMutationInput = new InputObjectType([
@ -140,14 +132,15 @@ class TypeLoaderTest extends TestCase
'title' => Type::string(), 'title' => Type::string(),
'body' => Type::string(), 'body' => Type::string(),
'author' => Type::id(), 'author' => Type::id(),
'category' => Type::id() 'category' => Type::id(),
] ],
]); ]);
$this->typeLoader = function($name) { $this->typeLoader = function ($name) {
$this->calls[] = $name; $this->calls[] = $name;
$prop = lcfirst($name); $prop = lcfirst($name);
return isset($this->{$prop}) ? $this->{$prop} : null;
return $this->{$prop} ?? null;
}; };
} }
@ -157,9 +150,10 @@ class TypeLoaderTest extends TestCase
new Schema([ new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => ['a' => Type::string()] 'fields' => ['a' => Type::string()],
]), ]),
'typeLoader' => function() {} 'typeLoader' => function () {
},
]); ]);
} }
@ -171,9 +165,9 @@ class TypeLoaderTest extends TestCase
new Schema([ new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => ['a' => Type::string()] 'fields' => ['a' => Type::string()],
]), ]),
'typeLoader' => [] 'typeLoader' => [],
]); ]);
} }
@ -182,7 +176,7 @@ class TypeLoaderTest extends TestCase
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'mutation' => $this->mutation, 'mutation' => $this->mutation,
'types' => [$this->blogStory] 'types' => [$this->blogStory],
]); ]);
$expected = [ $expected = [
@ -220,7 +214,7 @@ class TypeLoaderTest extends TestCase
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'mutation' => $this->mutation, 'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader 'typeLoader' => $this->typeLoader,
]); ]);
$this->assertEquals([], $this->calls); $this->assertEquals([], $this->calls);
@ -245,7 +239,7 @@ class TypeLoaderTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'typeLoader' => $this->typeLoader 'typeLoader' => $this->typeLoader,
]); ]);
$schema->getType('Node'); $schema->getType('Node');
@ -259,7 +253,8 @@ class TypeLoaderTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'typeLoader' => function() {} 'typeLoader' => function () {
},
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
@ -272,9 +267,9 @@ class TypeLoaderTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'typeLoader' => function() { 'typeLoader' => function () {
return new \stdClass(); return new \stdClass();
} },
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
@ -287,9 +282,9 @@ class TypeLoaderTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'typeLoader' => function() { 'typeLoader' => function () {
return $this->content; return $this->content;
} },
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);
@ -302,9 +297,9 @@ class TypeLoaderTest extends TestCase
{ {
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'typeLoader' => function() { 'typeLoader' => function () {
throw new \Exception("This is the exception we are looking for"); throw new \Exception('This is the exception we are looking for');
} },
]); ]);
$this->expectException(\Throwable::class); $this->expectException(\Throwable::class);
@ -317,13 +312,13 @@ class TypeLoaderTest extends TestCase
{ {
$withoutLoader = new Schema([ $withoutLoader = new Schema([
'query' => $this->query, 'query' => $this->query,
'mutation' => $this->mutation 'mutation' => $this->mutation,
]); ]);
$withLoader = new Schema([ $withLoader = new Schema([
'query' => $this->query, 'query' => $this->query,
'mutation' => $this->mutation, 'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader 'typeLoader' => $this->typeLoader,
]); ]);
$this->assertSame($withoutLoader->getQueryType(), $withLoader->getQueryType()); $this->assertSame($withoutLoader->getQueryType(), $withLoader->getQueryType());
@ -337,7 +332,7 @@ class TypeLoaderTest extends TestCase
$schema = new Schema([ $schema = new Schema([
'query' => $this->query, 'query' => $this->query,
'mutation' => $this->mutation, 'mutation' => $this->mutation,
'typeLoader' => $this->typeLoader 'typeLoader' => $this->typeLoader,
]); ]);
$type = $schema->getType('ID'); $type = $schema->getType('ID');

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -9,7 +12,6 @@ use PHPUnit\Framework\TestCase;
class AssertValidNameTest extends TestCase class AssertValidNameTest extends TestCase
{ {
// Describe: assertValidName() // Describe: assertValidName()
/** /**
* @see it('throws for use of leading double underscores') * @see it('throws for use of leading double underscores')
*/ */

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\BooleanValueNode;
@ -16,10 +19,12 @@ use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use stdClass;
class AstFromValueTest extends TestCase class AstFromValueTest extends TestCase
{ {
// Describe: astFromValue /** @var stdClass */
private $complexValue;
/** /**
* @see it('converts boolean values to ASTs') * @see it('converts boolean values to ASTs')
@ -31,8 +36,14 @@ class AstFromValueTest extends TestCase
$this->assertEquals(new NullValueNode([]), AST::astFromValue(null, Type::boolean())); $this->assertEquals(new NullValueNode([]), AST::astFromValue(null, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::boolean())); $this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => true]), AST::astFromValue(1, Type::boolean())); $this->assertEquals(new BooleanValueNode(['value' => true]), AST::astFromValue(1, Type::boolean()));
$this->assertEquals(new BooleanValueNode(['value' => false]), AST::astFromValue(0, Type::nonNull(Type::boolean()))); $this->assertEquals(
$this->assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::boolean()))); // Note: null means that AST cannot new BooleanValueNode(['value' => false]),
AST::astFromValue(0, Type::nonNull(Type::boolean()))
);
$this->assertEquals(
null,
AST::astFromValue(null, Type::nonNull(Type::boolean()))
); // Note: null means that AST cannot
} }
/** /**
@ -59,7 +70,10 @@ class AstFromValueTest extends TestCase
{ {
$this->expectException(\Throwable::class); $this->expectException(\Throwable::class);
$this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 1.0E+40'); $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 1.0E+40');
AST::astFromValue(1e40, Type::int()); // Note: js version will produce 1e+40, both values are valid GraphQL floats AST::astFromValue(
1e40,
Type::int()
); // Note: js version will produce 1e+40, both values are valid GraphQL floats
} }
/** /**
@ -112,7 +126,7 @@ class AstFromValueTest extends TestCase
*/ */
public function testDoesNotConvertsNonNullValuestoNullValue() : void public function testDoesNotConvertsNonNullValuestoNullValue() : void
{ {
$this->assertSame(null, AST::astFromValue(null, Type::nonNull(Type::boolean()))); $this->assertNull(AST::astFromValue(null, Type::nonNull(Type::boolean())));
} }
/** /**
@ -121,99 +135,16 @@ class AstFromValueTest extends TestCase
public function testConvertsStringValuesToEnumASTsIfPossible() : void public function testConvertsStringValuesToEnumASTsIfPossible() : void
{ {
$this->assertEquals(new EnumValueNode(['value' => 'HELLO']), AST::astFromValue('HELLO', $this->myEnum())); $this->assertEquals(new EnumValueNode(['value' => 'HELLO']), AST::astFromValue('HELLO', $this->myEnum()));
$this->assertEquals(new EnumValueNode(['value' => 'COMPLEX']), AST::astFromValue($this->complexValue(), $this->myEnum())); $this->assertEquals(
new EnumValueNode(['value' => 'COMPLEX']),
AST::astFromValue($this->complexValue(), $this->myEnum())
);
// Note: case sensitive // Note: case sensitive
$this->assertEquals(null, AST::astFromValue('hello', $this->myEnum())); $this->assertNull(AST::astFromValue('hello', $this->myEnum()));
// Note: Not a valid enum value // Note: Not a valid enum value
$this->assertEquals(null, AST::astFromValue('VALUE', $this->myEnum())); $this->assertNull(AST::astFromValue('VALUE', $this->myEnum()));
}
/**
* @see it('converts array values to List ASTs')
*/
public function testConvertsArrayValuesToListASTs() : void
{
$value1 = new ListValueNode([
'values' => [
new StringValueNode(['value' => 'FOO']),
new StringValueNode(['value' => 'BAR'])
]
]);
$this->assertEquals($value1, AST::astFromValue(['FOO', 'BAR'], Type::listOf(Type::string())));
$value2 = new ListValueNode([
'values' => [
new EnumValueNode(['value' => 'HELLO']),
new EnumValueNode(['value' => 'GOODBYE']),
]
]);
$this->assertEquals($value2, AST::astFromValue(['HELLO', 'GOODBYE'], Type::listOf($this->myEnum())));
}
/**
* @see it('converts list singletons')
*/
public function testConvertsListSingletons() : void
{
$this->assertEquals(new StringValueNode(['value' => 'FOO']), AST::astFromValue('FOO', Type::listOf(Type::string())));
}
/**
* @see it('converts input objects')
*/
public function testConvertsInputObjects() : void
{
$inputObj = new InputObjectType([
'name' => 'MyInputObj',
'fields' => [
'foo' => Type::float(),
'bar' => $this->myEnum()
]
]);
$expected = new ObjectValueNode([
'fields' => [
$this->objectField('foo', new IntValueNode(['value' => '3'])),
$this->objectField('bar', new EnumValueNode(['value' => 'HELLO']))
]
]);
$data = ['foo' => 3, 'bar' => 'HELLO'];
$this->assertEquals($expected, AST::astFromValue($data, $inputObj));
$this->assertEquals($expected, AST::astFromValue((object) $data, $inputObj));
}
/**
* @see it('converts input objects with explicit nulls')
*/
public function testConvertsInputObjectsWithExplicitNulls() : void
{
$inputObj = new InputObjectType([
'name' => 'MyInputObj',
'fields' => [
'foo' => Type::float(),
'bar' => $this->myEnum()
]
]);
$this->assertEquals(new ObjectValueNode([
'fields' => [
$this->objectField('foo', new NullValueNode([]))
]
]), AST::astFromValue(['foo' => null], $inputObj));
}
private $complexValue;
private function complexValue()
{
if (!$this->complexValue) {
$this->complexValue = new \stdClass();
$this->complexValue->someArbitrary = 'complexValue';
}
return $this->complexValue;
} }
/** /**
@ -226,21 +157,111 @@ class AstFromValueTest extends TestCase
'values' => [ 'values' => [
'HELLO' => [], 'HELLO' => [],
'GOODBYE' => [], 'GOODBYE' => [],
'COMPLEX' => ['value' => $this->complexValue()] 'COMPLEX' => ['value' => $this->complexValue()],
] ],
]);
}
private function complexValue()
{
if (! $this->complexValue) {
$this->complexValue = new \stdClass();
$this->complexValue->someArbitrary = 'complexValue';
}
return $this->complexValue;
}
/**
* @see it('converts array values to List ASTs')
*/
public function testConvertsArrayValuesToListASTs() : void
{
$value1 = new ListValueNode([
'values' => [
new StringValueNode(['value' => 'FOO']),
new StringValueNode(['value' => 'BAR']),
],
]);
$this->assertEquals($value1, AST::astFromValue(['FOO', 'BAR'], Type::listOf(Type::string())));
$value2 = new ListValueNode([
'values' => [
new EnumValueNode(['value' => 'HELLO']),
new EnumValueNode(['value' => 'GOODBYE']),
],
]);
$this->assertEquals($value2, AST::astFromValue(['HELLO', 'GOODBYE'], Type::listOf($this->myEnum())));
}
/**
* @see it('converts list singletons')
*/
public function testConvertsListSingletons() : void
{
$this->assertEquals(
new StringValueNode(['value' => 'FOO']),
AST::astFromValue('FOO', Type::listOf(Type::string()))
);
}
/**
* @see it('converts input objects')
*/
public function testConvertsInputObjects() : void
{
$inputObj = new InputObjectType([
'name' => 'MyInputObj',
'fields' => [
'foo' => Type::float(),
'bar' => $this->myEnum(),
],
]);
$expected = new ObjectValueNode([
'fields' => [
$this->objectField('foo', new IntValueNode(['value' => '3'])),
$this->objectField('bar', new EnumValueNode(['value' => 'HELLO'])),
],
]);
$data = ['foo' => 3, 'bar' => 'HELLO'];
$this->assertEquals($expected, AST::astFromValue($data, $inputObj));
$this->assertEquals($expected, AST::astFromValue((object) $data, $inputObj));
}
/**
* @param mixed $value
* @return ObjectFieldNode
*/
private function objectField(string $name, $value)
{
return new ObjectFieldNode([
'name' => new NameNode(['value' => $name]),
'value' => $value,
]); ]);
} }
/** /**
* @param $name * @see it('converts input objects with explicit nulls')
* @param $value
* @return ObjectFieldNode
*/ */
private function objectField($name, $value) public function testConvertsInputObjectsWithExplicitNulls() : void
{ {
return new ObjectFieldNode([ $inputObj = new InputObjectType([
'name' => new NameNode(['value' => $name]), 'name' => 'MyInputObj',
'value' => $value 'fields' => [
'foo' => Type::float(),
'bar' => $this->myEnum(),
],
]); ]);
$this->assertEquals(
new ObjectValueNode([
'fields' => [
$this->objectField('foo', new NullValueNode([])),
],
]),
AST::astFromValue(['foo' => null], $inputObj)
);
} }
} }

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
@ -8,14 +11,6 @@ use PHPUnit\Framework\TestCase;
class AstFromValueUntypedTest extends TestCase class AstFromValueUntypedTest extends TestCase
{ {
// Describe: valueFromASTUntyped // Describe: valueFromASTUntyped
private function assertTestCase($valueText, $expected, array $variables = null) {
$this->assertEquals(
$expected,
AST::valueFromASTUntyped(Parser::parseValue($valueText), $variables)
);
}
/** /**
* @see it('parses simple values') * @see it('parses simple values')
*/ */
@ -29,6 +24,17 @@ class AstFromValueUntypedTest extends TestCase
$this->assertTestCase('abc123', 'abc123'); $this->assertTestCase('abc123', 'abc123');
} }
/**
* @param mixed[]|null $variables
*/
private function assertTestCase($valueText, $expected, ?array $variables = null) : void
{
$this->assertEquals(
$expected,
AST::valueFromASTUntyped(Parser::parseValue($valueText), $variables)
);
}
/** /**
* @see it('parses lists of values') * @see it('parses lists of values')
*/ */

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
@ -8,24 +11,19 @@ use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Utils\BuildSchema; use GraphQL\Utils\BuildSchema;
use GraphQL\Utils\SchemaPrinter; use GraphQL\Utils\SchemaPrinter;
use GraphQL\Type\Definition\Directive;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use function array_keys;
use function count;
class BuildSchemaTest extends TestCase class BuildSchemaTest extends TestCase
{ {
// Describe: Schema Builder // Describe: Schema Builder
private function cycleOutput($body, $options = [])
{
$ast = Parser::parse($body);
$schema = BuildSchema::buildAST($ast, null, $options);
return "\n" . SchemaPrinter::doPrint($schema, $options);
}
/** /**
* @see it('can use built schema for limited execution') * @see it('can use built schema for limited execution')
*/ */
@ -46,16 +44,16 @@ class BuildSchemaTest extends TestCase
*/ */
public function testBuildSchemaDirectlyFromSource() : void public function testBuildSchemaDirectlyFromSource() : void
{ {
$schema = BuildSchema::build(" $schema = BuildSchema::build('
type Query { type Query {
add(x: Int, y: Int): Int add(x: Int, y: Int): Int
} }
"); ');
$root = [ $root = [
'add' => function ($root, $args) { 'add' => function ($root, $args) {
return $args['x'] + $args['y']; return $args['x'] + $args['y'];
} },
]; ];
$result = GraphQL::executeQuery( $result = GraphQL::executeQuery(
@ -84,6 +82,14 @@ type HelloScalars {
$this->assertEquals($output, $body); $this->assertEquals($output, $body);
} }
private function cycleOutput($body, $options = [])
{
$ast = Parser::parse($body);
$schema = BuildSchema::buildAST($ast, null, $options);
return "\n" . SchemaPrinter::doPrint($schema, $options);
}
/** /**
* @see it('With directives') * @see it('With directives')
*/ */
@ -159,7 +165,7 @@ type Query {
str: String str: String
} }
'; ';
$output = $this->cycleOutput($body, [ 'commentDescriptions' => true ]); $output = $this->cycleOutput($body, ['commentDescriptions' => true]);
$this->assertEquals($body, $output); $this->assertEquals($body, $output);
} }
@ -467,16 +473,16 @@ type WorldTwo {
[ [
'length' => 5, 'length' => 5,
'__typename' => 'Banana', '__typename' => 'Banana',
] ],
] ],
]; ];
$expected = [ $expected = [
'data' => [ 'data' => [
'fruits' => [ 'fruits' => [
['color' => 'green'], ['color' => 'green'],
['length' => 5], ['length' => 5],
] ],
] ],
]; ];
$result = GraphQL::executeQuery($schema, $query, $root); $result = GraphQL::executeQuery($schema, $query, $root);
@ -531,16 +537,16 @@ type WorldTwo {
'name' => 'R2-D2', 'name' => 'R2-D2',
'primaryFunction' => 'Astromech', 'primaryFunction' => 'Astromech',
'__typename' => 'Droid', '__typename' => 'Droid',
] ],
] ],
]; ];
$expected = [ $expected = [
'data' => [ 'data' => [
'characters' => [ 'characters' => [
['name' => 'Han Solo', 'totalCredits' => 10], ['name' => 'Han Solo', 'totalCredits' => 10],
['name' => 'R2-D2', 'primaryFunction' => 'Astromech'], ['name' => 'R2-D2', 'primaryFunction' => 'Astromech'],
] ],
] ],
]; ];
$result = GraphQL::executeQuery($schema, $query, $root); $result = GraphQL::executeQuery($schema, $query, $root);
@ -813,9 +819,15 @@ type Query {
$testField = $query->getField('testField'); $testField = $query->getField('testField');
$this->assertEquals('testField(testArg: TestInput): TestUnion', Printer::doPrint($testField->astNode)); $this->assertEquals('testField(testArg: TestInput): TestUnion', Printer::doPrint($testField->astNode));
$this->assertEquals('testArg: TestInput', Printer::doPrint($testField->args[0]->astNode)); $this->assertEquals('testArg: TestInput', Printer::doPrint($testField->args[0]->astNode));
$this->assertEquals('testInputField: TestEnum', Printer::doPrint($testInput->getField('testInputField')->astNode)); $this->assertEquals(
'testInputField: TestEnum',
Printer::doPrint($testInput->getField('testInputField')->astNode)
);
$this->assertEquals('TEST_VALUE', Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode)); $this->assertEquals('TEST_VALUE', Printer::doPrint($testEnum->getValue('TEST_VALUE')->astNode));
$this->assertEquals('interfaceField: String', Printer::doPrint($testInterface->getField('interfaceField')->astNode)); $this->assertEquals(
'interfaceField: String',
Printer::doPrint($testInterface->getField('interfaceField')->astNode)
);
$this->assertEquals('interfaceField: String', Printer::doPrint($testType->getField('interfaceField')->astNode)); $this->assertEquals('interfaceField: String', Printer::doPrint($testType->getField('interfaceField')->astNode));
$this->assertEquals('arg: TestScalar', Printer::doPrint($testDirective->args[0]->astNode)); $this->assertEquals('arg: TestScalar', Printer::doPrint($testDirective->args[0]->astNode));
} }
@ -1184,9 +1196,10 @@ interface Hello {
$decorated = []; $decorated = [];
$calls = []; $calls = [];
$typeConfigDecorator = function($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) { $typeConfigDecorator = function ($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
$decorated[] = $defaultConfig['name']; $decorated[] = $defaultConfig['name'];
$calls[] = [$defaultConfig, $node, $allNodesMap]; $calls[] = [$defaultConfig, $node, $allNodesMap];
return ['description' => 'My description of ' . $node->name->value] + $defaultConfig; return ['description' => 'My description of ' . $node->name->value] + $defaultConfig;
}; };
@ -1204,19 +1217,21 @@ interface Hello {
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']); $this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Query', $schema->getType('Query')->description); $this->assertEquals('My description of Query', $schema->getType('Query')->description);
list($defaultConfig, $node, $allNodesMap) = $calls[1]; list($defaultConfig, $node, $allNodesMap) = $calls[1];
$this->assertInstanceOf(EnumTypeDefinitionNode::class, $node); $this->assertInstanceOf(EnumTypeDefinitionNode::class, $node);
$this->assertEquals('Color', $defaultConfig['name']); $this->assertEquals('Color', $defaultConfig['name']);
$enumValue = [ $enumValue = [
'description' => '', 'description' => '',
'deprecationReason' => '' 'deprecationReason' => '',
]; ];
$this->assertArraySubset([ $this->assertArraySubset(
[
'RED' => $enumValue, 'RED' => $enumValue,
'GREEN' => $enumValue, 'GREEN' => $enumValue,
'BLUE' => $enumValue, 'BLUE' => $enumValue,
], $defaultConfig['values']); ],
$defaultConfig['values']
);
$this->assertCount(4, $defaultConfig); // 3 + astNode $this->assertCount(4, $defaultConfig); // 3 + astNode
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']); $this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
$this->assertEquals('My description of Color', $schema->getType('Color')->description); $this->assertEquals('My description of Color', $schema->getType('Color')->description);
@ -1261,8 +1276,9 @@ type World implements Hello {
$doc = Parser::parse($body); $doc = Parser::parse($body);
$created = []; $created = [];
$typeConfigDecorator = function($config, $node) use (&$created) { $typeConfigDecorator = function ($config, $node) use (&$created) {
$created[] = $node->name->value; $created[] = $node->name->value;
return $config; return $config;
}; };
@ -1282,5 +1298,4 @@ type World implements Hello {
$this->assertArrayHasKey('Hello', $types); $this->assertArrayHasKey('Hello', $types);
$this->assertArrayHasKey('World', $types); $this->assertArrayHasKey('World', $types);
} }
} }

View File

@ -1,8 +1,9 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Error\Error;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
@ -12,7 +13,10 @@ use PHPUnit\Framework\TestCase;
class CoerceValueTest extends TestCase class CoerceValueTest extends TestCase
{ {
/** @var EnumType */
private $testEnum; private $testEnum;
/** @var InputObjectType */
private $testInputObject; private $testInputObject;
public function setUp() public function setUp()
@ -34,9 +38,9 @@ class CoerceValueTest extends TestCase
]); ]);
} }
// Describe: coerceValue
/** /**
* Describe: coerceValue
*
* @see it('coercing an array to GraphQLString produces an error') * @see it('coercing an array to GraphQLString produces an error')
*/ */
public function testCoercingAnArrayToGraphQLStringProducesAnError() : void public function testCoercingAnArrayToGraphQLStringProducesAnError() : void
@ -53,7 +57,17 @@ class CoerceValueTest extends TestCase
); );
} }
// Describe: for GraphQLInt /**
* Describe: for GraphQLInt
*/
private function expectError($result, $expected)
{
$this->assertInternalType('array', $result);
$this->assertInternalType('array', $result['errors']);
$this->assertCount(1, $result['errors']);
$this->assertEquals($expected, $result['errors'][0]->getMessage());
$this->assertEquals(Utils::undefined(), $result['value']);
}
/** /**
* @see it('returns no error for int input') * @see it('returns no error for int input')
@ -64,6 +78,13 @@ class CoerceValueTest extends TestCase
$this->expectNoErrors($result); $this->expectNoErrors($result);
} }
private function expectNoErrors($result)
{
$this->assertInternalType('array', $result);
$this->assertNull($result['errors']);
$this->assertNotEquals(Utils::undefined(), $result['value']);
}
/** /**
* @see it('returns no error for negative int input') * @see it('returns no error for negative int input')
*/ */
@ -115,6 +136,8 @@ class CoerceValueTest extends TestCase
); );
} }
// Describe: for GraphQLFloat
/** /**
* @see it('returns a single error for char input') * @see it('returns a single error for char input')
*/ */
@ -139,8 +162,6 @@ class CoerceValueTest extends TestCase
); );
} }
// Describe: for GraphQLFloat
/** /**
* @see it('returns no error for int input') * @see it('returns no error for int input')
*/ */
@ -189,6 +210,8 @@ class CoerceValueTest extends TestCase
); );
} }
// DESCRIBE: for GraphQLEnum
/** /**
* @see it('returns a single error for char input') * @see it('returns a single error for char input')
*/ */
@ -213,8 +236,6 @@ class CoerceValueTest extends TestCase
); );
} }
// DESCRIBE: for GraphQLEnum
/** /**
* @see it('returns no error for a known enum name') * @see it('returns no error for a known enum name')
*/ */
@ -229,6 +250,8 @@ class CoerceValueTest extends TestCase
$this->assertEquals(123456789, $barResult['value']); $this->assertEquals(123456789, $barResult['value']);
} }
// DESCRIBE: for GraphQLInputObject
/** /**
* @see it('results error for misspelled enum value') * @see it('results error for misspelled enum value')
*/ */
@ -250,8 +273,6 @@ class CoerceValueTest extends TestCase
$this->expectError($result2, 'Expected type TestEnum.'); $this->expectError($result2, 'Expected type TestEnum.');
} }
// DESCRIBE: for GraphQLInputObject
/** /**
* @see it('returns no error for a valid input') * @see it('returns no error for a valid input')
*/ */
@ -277,7 +298,10 @@ class CoerceValueTest extends TestCase
public function testReturnErrorForAnInvalidField() : void public function testReturnErrorForAnInvalidField() : void
{ {
$result = Value::coerceValue(['foo' => 'abc'], $this->testInputObject); $result = Value::coerceValue(['foo' => 'abc'], $this->testInputObject);
$this->expectError($result, 'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc'); $this->expectError(
$result,
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc'
);
} }
/** /**
@ -286,10 +310,13 @@ class CoerceValueTest extends TestCase
public function testReturnsMultipleErrorsForMultipleInvalidFields() : void public function testReturnsMultipleErrorsForMultipleInvalidFields() : void
{ {
$result = Value::coerceValue(['foo' => 'abc', 'bar' => 'def'], $this->testInputObject); $result = Value::coerceValue(['foo' => 'abc', 'bar' => 'def'], $this->testInputObject);
$this->assertEquals([ $this->assertEquals(
[
'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc', 'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc',
'Expected type Int at value.bar; Int cannot represent non 32-bit signed integer value: def', 'Expected type Int at value.bar; Int cannot represent non 32-bit signed integer value: def',
], $result['errors']); ],
$result['errors']
);
} }
/** /**
@ -318,20 +345,4 @@ class CoerceValueTest extends TestCase
$result = Value::coerceValue(['foo' => 123, 'bart' => 123], $this->testInputObject); $result = Value::coerceValue(['foo' => 123, 'bart' => 123], $this->testInputObject);
$this->expectError($result, 'Field "bart" is not defined by type TestInputObject; did you mean bar?'); $this->expectError($result, 'Field "bart" is not defined by type TestInputObject; did you mean bar?');
} }
private function expectNoErrors($result)
{
$this->assertInternalType('array', $result);
$this->assertNull($result['errors']);
$this->assertNotEquals(Utils::undefined(), $result['value']);
}
private function expectError($result, $expected) {
$this->assertInternalType('array', $result);
$this->assertInternalType('array', $result['errors']);
$this->assertCount(1, $result['errors']);
$this->assertEquals($expected, $result['errors'][0]->getMessage());
$this->assertEquals(Utils::undefined(), $result['value']);
}
} }

View File

@ -1,5 +1,8 @@
<?php <?php
namespace Utils;
declare(strict_types=1);
namespace GraphQL\Tests\Utils;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
@ -12,72 +15,43 @@ use PHPUnit\Framework\TestCase;
class ExtractTypesTest extends TestCase class ExtractTypesTest extends TestCase
{ {
/** /** @var ObjectType */
* @var ObjectType
*/
private $query; private $query;
/** /** @var ObjectType */
* @var ObjectType
*/
private $mutation; private $mutation;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $node; private $node;
/** /** @var InterfaceType */
* @var InterfaceType
*/
private $content; private $content;
/** /** @var ObjectType */
* @var ObjectType
*/
private $blogStory; private $blogStory;
/** /** @var ObjectType */
* @var ObjectType
*/
private $link;
/**
* @var ObjectType
*/
private $video;
/**
* @var ObjectType
*/
private $videoMetadata;
/**
* @var ObjectType
*/
private $comment; private $comment;
/** /** @var ObjectType */
* @var ObjectType
*/
private $user; private $user;
/** /** @var ObjectType */
* @var ObjectType
*/
private $category; private $category;
/** /** @var UnionType */
* @var UnionType
*/
private $mention; private $mention;
/** @var ObjectType */
private $postStoryMutation; private $postStoryMutation;
/** @var InputObjectType */
private $postStoryMutationInput; private $postStoryMutationInput;
/** @var ObjectType */
private $postCommentMutation; private $postCommentMutation;
/** @var InputObjectType */
private $postCommentMutationInput; private $postCommentMutationInput;
public function setUp() public function setUp()
@ -85,136 +59,136 @@ class ExtractTypesTest extends TestCase
$this->node = new InterfaceType([ $this->node = new InterfaceType([
'name' => 'Node', 'name' => 'Node',
'fields' => [ 'fields' => [
'id' => Type::string() 'id' => Type::string(),
] ],
]); ]);
$this->content = new InterfaceType([ $this->content = new InterfaceType([
'name' => 'Content', 'name' => 'Content',
'fields' => function() { 'fields' => function () {
return [ return [
'title' => Type::string(), 'title' => Type::string(),
'body' => Type::string(), 'body' => Type::string(),
'author' => $this->user, 'author' => $this->user,
'comments' => Type::listOf($this->comment), 'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category) 'categories' => Type::listOf($this->category),
]; ];
} },
]); ]);
$this->blogStory = new ObjectType([ $this->blogStory = new ObjectType([
'name' => 'BlogStory', 'name' => 'BlogStory',
'interfaces' => [ 'interfaces' => [
$this->node, $this->node,
$this->content $this->content,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), $this->node->getField('id'),
$this->content->getField('title'), $this->content->getField('title'),
$this->content->getField('body'), $this->content->getField('body'),
$this->content->getField('author'), $this->content->getField('author'),
$this->content->getField('comments'), $this->content->getField('comments'),
$this->content->getField('categories') $this->content->getField('categories'),
]; ];
}, },
]); ]);
$this->link = new ObjectType([ new ObjectType([
'name' => 'Link', 'name' => 'Link',
'interfaces' => [ 'interfaces' => [
$this->node, $this->node,
$this->content $this->content,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
$this->content->getField('title'), 'title' => $this->content->getField('title'),
$this->content->getField('body'), 'body' => $this->content->getField('body'),
$this->content->getField('author'), 'author' => $this->content->getField('author'),
$this->content->getField('comments'), 'comments' => $this->content->getField('comments'),
$this->content->getField('categories'), 'categories' => $this->content->getField('categories'),
'url' => Type::string() 'url' => Type::string(),
]; ];
}, },
]); ]);
$this->video = new ObjectType([ new ObjectType([
'name' => 'Video', 'name' => 'Video',
'interfaces' => [ 'interfaces' => [
$this->node, $this->node,
$this->content $this->content,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
$this->content->getField('title'), 'title' => $this->content->getField('title'),
$this->content->getField('body'), 'body' => $this->content->getField('body'),
$this->content->getField('author'), 'author' => $this->content->getField('author'),
$this->content->getField('comments'), 'comments' => $this->content->getField('comments'),
$this->content->getField('categories'), 'categories' => $this->content->getField('categories'),
'streamUrl' => Type::string(), 'streamUrl' => Type::string(),
'downloadUrl' => Type::string(), 'downloadUrl' => Type::string(),
'metadata' => $this->videoMetadata = new ObjectType([ 'metadata' => new ObjectType([
'name' => 'VideoMetadata', 'name' => 'VideoMetadata',
'fields' => [ 'fields' => [
'lat' => Type::float(), 'lat' => Type::float(),
'lng' => Type::float() 'lng' => Type::float(),
] ],
]) ]),
]; ];
} },
]); ]);
$this->comment = new ObjectType([ $this->comment = new ObjectType([
'name' => 'Comment', 'name' => 'Comment',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'author' => $this->user, 'author' => $this->user,
'text' => Type::string(), 'text' => Type::string(),
'replies' => Type::listOf($this->comment), 'replies' => Type::listOf($this->comment),
'parent' => $this->comment, 'parent' => $this->comment,
'content' => $this->content 'content' => $this->content,
]; ];
} },
]); ]);
$this->user = new ObjectType([ $this->user = new ObjectType([
'name' => 'User', 'name' => 'User',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'name' => Type::string(), 'name' => Type::string(),
]; ];
} },
]); ]);
$this->category = new ObjectType([ $this->category = new ObjectType([
'name' => 'Category', 'name' => 'Category',
'interfaces' => [ 'interfaces' => [
$this->node $this->node,
], ],
'fields' => function() { 'fields' => function () {
return [ return [
$this->node->getField('id'), 'id' => $this->node->getField('id'),
'name' => Type::string() 'name' => Type::string(),
]; ];
} },
]); ]);
$this->mention = new UnionType([ $this->mention = new UnionType([
'name' => 'Mention', 'name' => 'Mention',
'types' => [ 'types' => [
$this->user, $this->user,
$this->category $this->category,
] ],
]); ]);
$this->query = new ObjectType([ $this->query = new ObjectType([
@ -223,8 +197,18 @@ class ExtractTypesTest extends TestCase
'viewer' => $this->user, 'viewer' => $this->user,
'latestContent' => $this->content, 'latestContent' => $this->content,
'node' => $this->node, 'node' => $this->node,
'mentions' => Type::listOf($this->mention) 'mentions' => Type::listOf($this->mention),
] ],
]);
$this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id(),
],
]); ]);
$this->mutation = new ObjectType([ $this->mutation = new ObjectType([
@ -234,28 +218,20 @@ class ExtractTypesTest extends TestCase
'type' => $this->postStoryMutation = new ObjectType([ 'type' => $this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation', 'name' => 'PostStoryMutation',
'fields' => [ 'fields' => [
'story' => $this->blogStory 'story' => $this->blogStory,
] ],
]), ]),
'args' => [ 'args' => [
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([ 'input' => Type::nonNull($this->postStoryMutationInput),
'name' => 'PostStoryMutationInput', 'clientRequestId' => Type::string(),
'fields' => [ ],
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
])),
'clientRequestId' => Type::string()
]
], ],
'postComment' => [ 'postComment' => [
'type' => $this->postCommentMutation = new ObjectType([ 'type' => $this->postCommentMutation = new ObjectType([
'name' => 'PostCommentMutation', 'name' => 'PostCommentMutation',
'fields' => [ 'fields' => [
'comment' => $this->comment 'comment' => $this->comment,
] ],
]), ]),
'args' => [ 'args' => [
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([ 'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
@ -264,13 +240,13 @@ class ExtractTypesTest extends TestCase
'text' => Type::nonNull(Type::string()), 'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()), 'author' => Type::nonNull(Type::id()),
'content' => Type::id(), 'content' => Type::id(),
'parent' => Type::id() 'parent' => Type::id(),
] ],
])), ])),
'clientRequestId' => Type::string() 'clientRequestId' => Type::string(),
] ],
] ],
] ],
]); ]);
} }
@ -317,15 +293,15 @@ class ExtractTypesTest extends TestCase
{ {
$otherUserType = new ObjectType([ $otherUserType = new ObjectType([
'name' => 'User', 'name' => 'User',
'fields' => ['a' => Type::string()] 'fields' => ['a' => Type::string()],
]); ]);
$queryType = new ObjectType([ $queryType = new ObjectType([
'name' => 'Test', 'name' => 'Test',
'fields' => [ 'fields' => [
'otherUser' => $otherUserType, 'otherUser' => $otherUserType,
'user' => $this->user 'user' => $this->user,
] ],
]); ]);
$this->expectException(InvariantViolation::class); $this->expectException(InvariantViolation::class);

View File

@ -1,17 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\SourceLocation; use GraphQL\Language\SourceLocation;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils;
use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\DocumentValidator;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class IsValidLiteralValueTest extends TestCase class IsValidLiteralValueTest extends TestCase
{ {
// DESCRIBE: isValidLiteralValue // DESCRIBE: isValidLiteralValue
/** /**
* @see it('Returns no errors for a valid value') * @see it('Returns no errors for a valid value')
*/ */

View File

@ -1,16 +1,16 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Utils\Utils;
use GraphQL\Utils\MixedStore; use GraphQL\Utils\MixedStore;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class MixedStoreTest extends TestCase class MixedStoreTest extends TestCase
{ {
/** /** @var MixedStore */
* @var MixedStore
*/
private $mixedStore; private $mixedStore;
public function setUp() public function setUp()
@ -18,6 +18,13 @@ class MixedStoreTest extends TestCase
$this->mixedStore = new MixedStore(); $this->mixedStore = new MixedStore();
} }
public function testAcceptsNullKeys() : void
{
foreach ($this->getPossibleValues() as $value) {
$this->assertAcceptsKeyValue(null, $value);
}
}
public function getPossibleValues() public function getPossibleValues()
{ {
return [ return [
@ -30,16 +37,38 @@ class MixedStoreTest extends TestCase
'a', 'a',
[], [],
new \stdClass(), new \stdClass(),
function() {}, function () {
new MixedStore() },
new MixedStore(),
]; ];
} }
public function testAcceptsNullKeys() : void private function assertAcceptsKeyValue($key, $value)
{ {
foreach ($this->getPossibleValues() as $value) { $err = 'Failed assertion that MixedStore accepts key ' .
$this->assertAcceptsKeyValue(null, $value); Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->mixedStore->offsetSet($key, $value);
$this->assertTrue($this->mixedStore->offsetExists($key), $err);
$this->assertSame($value, $this->mixedStore->offsetGet($key), $err);
$this->mixedStore->offsetUnset($key);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->assertProvidesArrayAccess($key, $value);
} }
private function assertProvidesArrayAccess($key, $value)
{
$err = 'Failed assertion that MixedStore provides array access for key ' .
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
$this->mixedStore[$key] = $value;
$this->assertTrue(isset($this->mixedStore[$key]), $err);
$this->assertEquals(! empty($value), ! empty($this->mixedStore[$key]), $err);
$this->assertSame($value, $this->mixedStore[$key], $err);
unset($this->mixedStore[$key]);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
} }
public function testAcceptsBoolKeys() : void public function testAcceptsBoolKeys() : void
@ -93,35 +122,11 @@ class MixedStoreTest extends TestCase
foreach ($this->getPossibleValues() as $value) { foreach ($this->getPossibleValues() as $value) {
$this->assertAcceptsKeyValue(new \stdClass(), $value); $this->assertAcceptsKeyValue(new \stdClass(), $value);
$this->assertAcceptsKeyValue(new MixedStore(), $value); $this->assertAcceptsKeyValue(new MixedStore(), $value);
$this->assertAcceptsKeyValue(function() {}, $value); $this->assertAcceptsKeyValue(
function () {
},
$value
);
} }
} }
private function assertAcceptsKeyValue($key, $value)
{
$err = 'Failed assertion that MixedStore accepts key ' .
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->mixedStore->offsetSet($key, $value);
$this->assertTrue($this->mixedStore->offsetExists($key), $err);
$this->assertSame($value, $this->mixedStore->offsetGet($key), $err);
$this->mixedStore->offsetUnset($key);
$this->assertFalse($this->mixedStore->offsetExists($key), $err);
$this->assertProvidesArrayAccess($key, $value);
}
private function assertProvidesArrayAccess($key, $value)
{
$err = 'Failed assertion that MixedStore provides array access for key ' .
Utils::printSafe($key) . ' with value ' . Utils::printSafe($value);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
$this->mixedStore[$key] = $value;
$this->assertTrue(isset($this->mixedStore[$key]), $err);
$this->assertEquals(!empty($value), !empty($this->mixedStore[$key]), $err);
$this->assertSame($value, $this->mixedStore[$key], $err);
unset($this->mixedStore[$key]);
$this->assertFalse(isset($this->mixedStore[$key]), $err);
}
} }

View File

@ -1,16 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class QuotedOrListTest extends TestCase class QuotedOrListTest extends TestCase
{ {
// DESCRIBE: quotedOrList // DESCRIBE: quotedOrList
/** /**
* @see it('Does not accept an empty list') * @see it('Does not accept an empty list')
*/ */

View File

@ -1,16 +1,19 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Schema;
use GraphQL\Utils\BuildSchema; use GraphQL\Utils\BuildSchema;
use GraphQL\Utils\SchemaPrinter; use GraphQL\Utils\SchemaPrinter;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -19,37 +22,40 @@ class SchemaPrinterTest extends TestCase
{ {
// Describe: Type System Printer // Describe: Type System Printer
private function printForTest($schema)
{
$schemaText = SchemaPrinter::doPrint($schema);
$this->assertEquals($schemaText, SchemaPrinter::doPrint(BuildSchema::build($schemaText)));
return "\n" . $schemaText;
}
private function printSingleFieldSchema($fieldConfig)
{
$query = new ObjectType([
'name' => 'Query',
'fields' => [
'singleField' => $fieldConfig
]
]);
return $this->printForTest(new Schema(['query' => $query]));
}
/** /**
* @see it('Prints String Field') * @see it('Prints String Field')
*/ */
public function testPrintsStringField() : void public function testPrintsStringField() : void
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::string() 'type' => Type::string(),
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField: String singleField: String
} }
', $output); ',
$output
);
}
private function printSingleFieldSchema($fieldConfig)
{
$query = new ObjectType([
'name' => 'Query',
'fields' => ['singleField' => $fieldConfig],
]);
return $this->printForTest(new Schema(['query' => $query]));
}
private function printForTest($schema)
{
$schemaText = SchemaPrinter::doPrint($schema);
$this->assertEquals($schemaText, SchemaPrinter::doPrint(BuildSchema::build($schemaText)));
return "\n" . $schemaText;
} }
/** /**
@ -58,13 +64,16 @@ type Query {
public function testPrintArrayStringField() : void public function testPrintArrayStringField() : void
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::listOf(Type::string()) 'type' => Type::listOf(Type::string()),
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField: [String] singleField: [String]
} }
', $output); ',
$output
);
} }
/** /**
@ -73,13 +82,16 @@ type Query {
public function testPrintNonNullStringField() : void public function testPrintNonNullStringField() : void
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::nonNull(Type::string()) 'type' => Type::nonNull(Type::string()),
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField: String! singleField: String!
} }
', $output); ',
$output
);
} }
/** /**
@ -88,13 +100,16 @@ type Query {
public function testPrintNonNullArrayStringField() : void public function testPrintNonNullArrayStringField() : void
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::nonNull(Type::listOf(Type::string())) 'type' => Type::nonNull(Type::listOf(Type::string())),
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField: [String]! singleField: [String]!
} }
', $output); ',
$output
);
} }
/** /**
@ -103,13 +118,16 @@ type Query {
public function testPrintArrayNonNullStringField() : void public function testPrintArrayNonNullStringField() : void
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::listOf(Type::nonNull(Type::string())) 'type' => Type::listOf(Type::nonNull(Type::string())),
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField: [String!] singleField: [String!]
} }
', $output); ',
$output
);
} }
/** /**
@ -118,13 +136,16 @@ type Query {
public function testPrintNonNullArrayNonNullStringField() : void public function testPrintNonNullArrayNonNullStringField() : void
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string()))) 'type' => Type::nonNull(Type::listOf(Type::nonNull(Type::string()))),
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField: [String!]! singleField: [String!]!
} }
', $output); ',
$output
);
} }
/** /**
@ -134,17 +155,18 @@ type Query {
{ {
$fooType = new ObjectType([ $fooType = new ObjectType([
'name' => 'Foo', 'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]] 'fields' => ['str' => ['type' => Type::string()]],
]); ]);
$root = new ObjectType([ $root = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => ['foo' => ['type' => $fooType]] 'fields' => ['foo' => ['type' => $fooType]],
]); ]);
$schema = new Schema(['query' => $root]); $schema = new Schema(['query' => $root]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
type Foo { type Foo {
str: String str: String
} }
@ -152,7 +174,9 @@ type Foo {
type Query { type Query {
foo: Foo foo: Foo
} }
', $output); ',
$output
);
} }
/** /**
@ -162,13 +186,16 @@ type Query {
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::string(), 'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::int()]] 'args' => ['argOne' => ['type' => Type::int()]],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int): String singleField(argOne: Int): String
} }
', $output); ',
$output
);
} }
/** /**
@ -178,13 +205,16 @@ type Query {
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::string(), 'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]] 'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => 2]],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int = 2): String singleField(argOne: Int = 2): String
} }
', $output); ',
$output
);
} }
/** /**
@ -196,11 +226,14 @@ type Query {
'type' => Type::string(), 'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::string(), 'defaultValue' => "tes\t de\fault"]], 'args' => ['argOne' => ['type' => Type::string(), 'defaultValue' => "tes\t de\fault"]],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: String = "tes\t de\fault"): String singleField(argOne: String = "tes\t de\fault"): String
} }
', $output); ',
$output
);
} }
/** /**
@ -210,13 +243,16 @@ type Query {
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::string(), 'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]] 'args' => ['argOne' => ['type' => Type::int(), 'defaultValue' => null]],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int = null): String singleField(argOne: Int = null): String
} }
', $output); ',
$output
);
} }
/** /**
@ -226,13 +262,16 @@ type Query {
{ {
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
'type' => Type::string(), 'type' => Type::string(),
'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]] 'args' => ['argOne' => ['type' => Type::nonNull(Type::int())]],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int!): String singleField(argOne: Int!): String
} }
', $output); ',
$output
);
} }
/** /**
@ -244,14 +283,17 @@ type Query {
'type' => Type::string(), 'type' => Type::string(),
'args' => [ 'args' => [
'argOne' => ['type' => Type::int()], 'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string()] 'argTwo' => ['type' => Type::string()],
] ],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int, argTwo: String): String singleField(argOne: Int, argTwo: String): String
} }
', $output); ',
$output
);
} }
/** /**
@ -264,14 +306,17 @@ type Query {
'args' => [ 'args' => [
'argOne' => ['type' => Type::int(), 'defaultValue' => 1], 'argOne' => ['type' => Type::int(), 'defaultValue' => 1],
'argTwo' => ['type' => Type::string()], 'argTwo' => ['type' => Type::string()],
'argThree' => ['type' => Type::boolean()] 'argThree' => ['type' => Type::boolean()],
] ],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String singleField(argOne: Int = 1, argTwo: String, argThree: Boolean): String
} }
', $output); ',
$output
);
} }
/** /**
@ -284,14 +329,17 @@ type Query {
'args' => [ 'args' => [
'argOne' => ['type' => Type::int()], 'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string(), 'defaultValue' => 'foo'], 'argTwo' => ['type' => Type::string(), 'defaultValue' => 'foo'],
'argThree' => ['type' => Type::boolean()] 'argThree' => ['type' => Type::boolean()],
] ],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String singleField(argOne: Int, argTwo: String = "foo", argThree: Boolean): String
} }
', $output); ',
$output
);
} }
/** /**
@ -304,14 +352,17 @@ type Query {
'args' => [ 'args' => [
'argOne' => ['type' => Type::int()], 'argOne' => ['type' => Type::int()],
'argTwo' => ['type' => Type::string()], 'argTwo' => ['type' => Type::string()],
'argThree' => ['type' => Type::boolean(), 'defaultValue' => false] 'argThree' => ['type' => Type::boolean(), 'defaultValue' => false],
] ],
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String singleField(argOne: Int, argTwo: String, argThree: Boolean = false): String
} }
', $output); ',
$output
);
} }
/** /**
@ -324,9 +375,7 @@ type Query {
'fields' => ['bar' => ['type' => Type::string()]], 'fields' => ['bar' => ['type' => Type::string()]],
]); ]);
$schema = new Schema([ $schema = new Schema(['query' => $customQueryType]);
'query' => $customQueryType,
]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$expected = ' $expected = '
schema { schema {
@ -347,26 +396,27 @@ type CustomQueryType {
{ {
$fooType = new InterfaceType([ $fooType = new InterfaceType([
'name' => 'Foo', 'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]] 'fields' => ['str' => ['type' => Type::string()]],
]); ]);
$barType = new ObjectType([ $barType = new ObjectType([
'name' => 'Bar', 'name' => 'Bar',
'fields' => ['str' => ['type' => Type::string()]], 'fields' => ['str' => ['type' => Type::string()]],
'interfaces' => [$fooType] 'interfaces' => [$fooType],
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => ['bar' => ['type' => $barType]] 'fields' => ['bar' => ['type' => $barType]],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => $query, 'query' => $query,
'types' => [$barType] 'types' => [$barType],
]); ]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
type Bar implements Foo { type Bar implements Foo {
str: String str: String
} }
@ -378,7 +428,9 @@ interface Foo {
type Query { type Query {
bar: Bar bar: Bar
} }
', $output); ',
$output
);
} }
/** /**
@ -388,34 +440,35 @@ type Query {
{ {
$fooType = new InterfaceType([ $fooType = new InterfaceType([
'name' => 'Foo', 'name' => 'Foo',
'fields' => ['str' => ['type' => Type::string()]] 'fields' => ['str' => ['type' => Type::string()]],
]); ]);
$baazType = new InterfaceType([ $baazType = new InterfaceType([
'name' => 'Baaz', 'name' => 'Baaz',
'fields' => ['int' => ['type' => Type::int()]] 'fields' => ['int' => ['type' => Type::int()]],
]); ]);
$barType = new ObjectType([ $barType = new ObjectType([
'name' => 'Bar', 'name' => 'Bar',
'fields' => [ 'fields' => [
'str' => ['type' => Type::string()], 'str' => ['type' => Type::string()],
'int' => ['type' => Type::int()] 'int' => ['type' => Type::int()],
], ],
'interfaces' => [$fooType, $baazType] 'interfaces' => [$fooType, $baazType],
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => ['bar' => ['type' => $barType]] 'fields' => ['bar' => ['type' => $barType]],
]); ]);
$schema = new Schema([ $schema = new Schema([
'query' => $query, 'query' => $query,
'types' => [$barType] 'types' => [$barType],
]); ]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
interface Baaz { interface Baaz {
int: Int int: Int
} }
@ -432,7 +485,9 @@ interface Foo {
type Query { type Query {
bar: Bar bar: Bar
} }
', $output); ',
$output
);
} }
/** /**
@ -442,35 +497,36 @@ type Query {
{ {
$fooType = new ObjectType([ $fooType = new ObjectType([
'name' => 'Foo', 'name' => 'Foo',
'fields' => ['bool' => ['type' => Type::boolean()]] 'fields' => ['bool' => ['type' => Type::boolean()]],
]); ]);
$barType = new ObjectType([ $barType = new ObjectType([
'name' => 'Bar', 'name' => 'Bar',
'fields' => ['str' => ['type' => Type::string()]] 'fields' => ['str' => ['type' => Type::string()]],
]); ]);
$singleUnion = new UnionType([ $singleUnion = new UnionType([
'name' => 'SingleUnion', 'name' => 'SingleUnion',
'types' => [$fooType] 'types' => [$fooType],
]); ]);
$multipleUnion = new UnionType([ $multipleUnion = new UnionType([
'name' => 'MultipleUnion', 'name' => 'MultipleUnion',
'types' => [$fooType, $barType] 'types' => [$fooType, $barType],
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'single' => ['type' => $singleUnion], 'single' => ['type' => $singleUnion],
'multiple' => ['type' => $multipleUnion] 'multiple' => ['type' => $multipleUnion],
] ],
]); ]);
$schema = new Schema(['query' => $query]); $schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
type Bar { type Bar {
str: String str: String
} }
@ -487,7 +543,9 @@ type Query {
} }
union SingleUnion = Foo union SingleUnion = Foo
', $output); ',
$output
);
} }
/** /**
@ -497,7 +555,7 @@ union SingleUnion = Foo
{ {
$inputType = new InputObjectType([ $inputType = new InputObjectType([
'name' => 'InputType', 'name' => 'InputType',
'fields' => ['int' => ['type' => Type::int()]] 'fields' => ['int' => ['type' => Type::int()]],
]); ]);
$query = new ObjectType([ $query = new ObjectType([
@ -505,14 +563,15 @@ union SingleUnion = Foo
'fields' => [ 'fields' => [
'str' => [ 'str' => [
'type' => Type::string(), 'type' => Type::string(),
'args' => ['argOne' => ['type' => $inputType]] 'args' => ['argOne' => ['type' => $inputType]],
] ],
] ],
]); ]);
$schema = new Schema(['query' => $query]); $schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
input InputType { input InputType {
int: Int int: Int
} }
@ -520,7 +579,9 @@ input InputType {
type Query { type Query {
str(argOne: InputType): String str(argOne: InputType): String
} }
', $output); ',
$output
);
} }
/** /**
@ -530,27 +591,30 @@ type Query {
{ {
$oddType = new CustomScalarType([ $oddType = new CustomScalarType([
'name' => 'Odd', 'name' => 'Odd',
'serialize' => function($value) { 'serialize' => function ($value) {
return $value % 2 === 1 ? $value : null; return $value % 2 === 1 ? $value : null;
} },
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'odd' => ['type' => $oddType] 'odd' => ['type' => $oddType],
] ],
]); ]);
$schema = new Schema(['query' => $query]); $schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
scalar Odd scalar Odd
type Query { type Query {
odd: Odd odd: Odd
} }
', $output); ',
$output
);
} }
/** /**
@ -563,20 +627,21 @@ type Query {
'values' => [ 'values' => [
'RED' => ['value' => 0], 'RED' => ['value' => 0],
'GREEN' => ['value' => 1], 'GREEN' => ['value' => 1],
'BLUE' => ['value' => 2] 'BLUE' => ['value' => 2],
] ],
]); ]);
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'rgb' => ['type' => $RGBType] 'rgb' => ['type' => $RGBType],
] ],
]); ]);
$schema = new Schema(['query' => $query]); $schema = new Schema(['query' => $query]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
rgb: RGB rgb: RGB
} }
@ -586,7 +651,9 @@ enum RGB {
GREEN GREEN
BLUE BLUE
} }
', $output); ',
$output
);
} }
/** /**
@ -598,14 +665,14 @@ enum RGB {
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'field' => ['type' => Type::string()], 'field' => ['type' => Type::string()],
] ],
]); ]);
$customDirectives = new Directive([ $customDirectives = new Directive([
'name' => 'customDirective', 'name' => 'customDirective',
'locations' => [ 'locations' => [
DirectiveLocation::FIELD DirectiveLocation::FIELD,
] ],
]); ]);
$schema = new Schema([ $schema = new Schema([
@ -614,13 +681,16 @@ enum RGB {
]); ]);
$output = $this->printForTest($schema); $output = $this->printForTest($schema);
$this->assertEquals(' $this->assertEquals(
'
directive @customDirective on FIELD directive @customDirective on FIELD
type Query { type Query {
field: String field: String
} }
', $output); ',
$output
);
} }
/** /**
@ -630,16 +700,19 @@ type Query {
{ {
$description = 'This field is awesome'; $description = 'This field is awesome';
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
"type" => Type::string(), 'type' => Type::string(),
"description" => $description 'description' => $description,
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
"""This field is awesome""" """This field is awesome"""
singleField: String singleField: String
} }
', $output); ',
$output
);
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query']; $recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedField = $recreatedRoot->getFields()['singleField']; $recreatedField = $recreatedRoot->getFields()['singleField'];
@ -653,18 +726,21 @@ type Query {
{ {
$description = 'This field is "awesome"'; $description = 'This field is "awesome"';
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
"type" => Type::string(), 'type' => Type::string(),
"description" => $description 'description' => $description,
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
""" """
This field is "awesome" This field is "awesome"
""" """
singleField: String singleField: String
} }
', $output); ',
$output
);
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query']; $recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedField = $recreatedRoot->getFields()['singleField']; $recreatedField = $recreatedRoot->getFields()['singleField'];
@ -678,17 +754,20 @@ type Query {
{ {
$description = ' This field is "awesome"'; $description = ' This field is "awesome"';
$output = $this->printSingleFieldSchema([ $output = $this->printSingleFieldSchema([
"type" => Type::string(), 'type' => Type::string(),
"description" => $description 'description' => $description,
]); ]);
$this->assertEquals(' $this->assertEquals(
'
type Query { type Query {
""" This field is "awesome" """ This field is "awesome"
""" """
singleField: String singleField: String
} }
', $output); ',
$output
);
$recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query']; $recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query'];
$recreatedField = $recreatedRoot->getFields()['singleField']; $recreatedField = $recreatedRoot->getFields()['singleField'];
@ -703,8 +782,8 @@ type Query {
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'onlyField' => ['type' => Type::string()] 'onlyField' => ['type' => Type::string()],
] ],
]); ]);
$schema = new Schema(['query' => $query]); $schema = new Schema(['query' => $query]);
@ -948,14 +1027,15 @@ EOT;
$query = new ObjectType([ $query = new ObjectType([
'name' => 'Query', 'name' => 'Query',
'fields' => [ 'fields' => [
'onlyField' => ['type' => Type::string()] 'onlyField' => ['type' => Type::string()],
] ],
]); ]);
$schema = new Schema(['query' => $query]); $schema = new Schema(['query' => $query]);
$output = SchemaPrinter::printIntrosepctionSchema($schema, [ $output = SchemaPrinter::printIntrosepctionSchema(
'commentDescriptions' => true $schema,
]); ['commentDescriptions' => true]
);
$introspectionSchema = <<<'EOT' $introspectionSchema = <<<'EOT'
# Directs the executor to include this field or fragment only when the `if` argument is true. # Directs the executor to include this field or fragment only when the `if` argument is true.
directive @include( directive @include(

View File

@ -1,16 +1,15 @@
<?php <?php
declare(strict_types=1);
namespace GraphQL\Tests\Utils; namespace GraphQL\Tests\Utils;
use GraphQL\Executor\Values;
use GraphQL\Type\Definition\Type;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class SuggestionListTest extends TestCase class SuggestionListTest extends TestCase
{ {
// DESCRIBE: suggestionList // DESCRIBE: suggestionList
/** /**
* @see it('Returns results when input is empty') * @see it('Returns results when input is empty')
*/ */

Some files were not shown because too many files have changed in this diff Show More