Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
Add type system definition tests
Browse files Browse the repository at this point in the history
  • Loading branch information
crisu83 committed Feb 9, 2018
1 parent 6e1748e commit dd42364
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 29 deletions.
28 changes: 20 additions & 8 deletions src/Type/Definition/Behavior/FieldsTrait.php
Expand Up @@ -2,10 +2,8 @@

namespace Digia\GraphQL\Type\Definition\Behavior;

use Digia\GraphQL\Type\Definition\Contract\NamedTypeInterface;
use Digia\GraphQL\Type\Definition\Contract\TypeInterface;
use Digia\GraphQL\Type\Definition\Field;
use function Digia\GraphQL\Util\instantiateAssocFromArray;
use function Digia\GraphQL\Type\resolveThunk;
use function Digia\GraphQL\Util\invariant;

trait FieldsTrait
Expand Down Expand Up @@ -72,16 +70,30 @@ protected function defineFields()
* @return array
* @throws \Exception
*/
function defineFieldMap($type, $fields): array
function defineFieldMap($type, $fieldsThunk): array
{
if (is_callable($fields)) {
$fields = $fields();
}
$fields = resolveThunk($fieldsThunk) ?: [];

invariant(
is_array($fields),
sprintf('%s fields must be an array or a callable which returns such an array.', $type->getName())
);

return instantiateAssocFromArray(Field::class, $fields);
$fieldMap = [];

foreach ($fields as $fieldName => $fieldConfig) {
invariant(
is_array($fieldConfig),
sprintf('%s.%s field config must be an object', $type->getName(), $fieldName)
);

invariant(
!isset($fieldConfig['isDeprecated']),
sprintf('%s.%s should provide "deprecationReason" instead of "isDeprecated".', $type->getName(), $fieldName)
);

$fieldMap[$fieldName] = new Field(array_merge($fieldConfig, ['name' => $fieldName]));
}

return $fieldMap;
}
53 changes: 37 additions & 16 deletions src/Type/Definition/UnionType.php
Expand Up @@ -12,16 +12,15 @@
use Digia\GraphQL\Type\Definition\Contract\CompositeTypeInterface;
use Digia\GraphQL\Type\Definition\Contract\OutputTypeInterface;
use Digia\GraphQL\Type\Definition\Contract\TypeInterface;
use function Digia\GraphQL\Type\resolveThunk;
use function Digia\GraphQL\Util\invariant;

/**
* Union Type Definition
*
* When a field can return one of a heterogeneous set of types, a Union type
* is used to describe what types are possible as well as providing a function
* to determine which type is actually used when the field is resolved.
*
* Example:
*
* const PetType = new GraphQLUnionType({
* name: 'Pet',
* types: [ DogType, CatType ],
Expand All @@ -34,7 +33,6 @@
* }
* }
* });
*
*/

/**
Expand All @@ -53,10 +51,15 @@ class UnionType implements AbstractTypeInterface, CompositeTypeInterface, Output
use ConfigTrait;

/**
* @var TypeInterface[]
* @var array|callable
*/
private $types;

/**
* @var TypeInterface[]
*/
private $_types;

/**
* @inheritdoc
*/
Expand All @@ -67,33 +70,51 @@ protected function beforeConfig(): void

/**
* @return TypeInterface[]
* @throws \Exception
*/
public function getTypes(): array
{
return $this->types;
$this->defineTypes();

return $this->_types;
}

/**
* @param TypeInterface $type
* @param array|callable $types
* @return $this
*/
public function addType(TypeInterface $type)
protected function setTypes($types)
{
$this->types[] = $type;
$this->types = $types;

return $this;
}

/**
* @param TypeInterface[] $types
* @return $this
* @throws \Exception
*/
protected function setTypes(array $types)
protected function defineTypes()
{
foreach ($types as $type) {
$this->addType($type);
if ($this->_types === null) {
$this->_types = defineTypes($this, $this->types);
}

return $this;
}
}

/**
* @param UnionType $type
* @param mixed $typesThunk
* @return array
* @throws \Exception
*/
function defineTypes(UnionType $type, $typesThunk): array
{
$types = resolveThunk($typesThunk) ?: [];

invariant(
is_array($types),
sprintf('Must provide Array of types or a function which returns such an array for Union %s.', $type->getName())
);

return $types;
}
9 changes: 8 additions & 1 deletion src/Type/functions.php
Expand Up @@ -32,7 +32,14 @@
use function Digia\GraphQL\Util\invariant;
use function Digia\GraphQL\Util\toString;


/**
* @param $thunk
* @return array
*/
function resolveThunk($thunk): array
{
return is_callable($thunk) ? $thunk() : $thunk;
}

/**
* @param $type
Expand Down
4 changes: 0 additions & 4 deletions src/Util/functions.php
Expand Up @@ -38,10 +38,6 @@ function instantiateFromArray(string $className, array $array): array
function instantiateAssocFromArray(string $className, array $array, string $keyName = 'name'): array
{
$objects = array_map(function ($item, $key) use ($className, $keyName) {
if ($item instanceof $className) {
return $item;
}

return new $className(array_merge($item, [$keyName => $key]));
}, $array, array_keys($array));

Expand Down
80 changes: 80 additions & 0 deletions tests/Functional/Type/DefinitionTest.php
Expand Up @@ -421,6 +421,7 @@ public function testIdentifiesInputTypes($type, $answer)

public function identifiesInputTypesDataProvider(): array
{
// We cannot use the class fields here because they do not get instantiated for data providers.
return [
[GraphQLInt(), true],
[GraphQLObjectType(), true],
Expand All @@ -438,4 +439,83 @@ public function testProhibitsNestingNonNullInsideNonNull()
{
GraphQLNonNull(GraphQLNonNull(GraphQLInt()));
}

/**
* @throws \Exception
*/
public function testAllowsAThunkForUnionMemberTypes()
{
$union = GraphQLUnionType([
'name' => 'ThunkUnion',
'types' => function () {
return [$this->objectType];
}
]);

$types = $union->getTypes();

$this->assertEquals(1, count($types));
$this->assertEquals($this->objectType, $types[0]);
}

/**
* @throws \Exception
*/
public function testDoesNotMutatePassedFieldDefinitions()
{
$fields = [
'field1' => ['type' => GraphQLString()],
'field2' => [
'type' => GraphQLString(),
'args' => [
'id' => ['type' => GraphQLString()],
],
],
];

$testObject1 = GraphQLObjectType([
'name' => 'Test1',
'fields' => $fields,
]);

$testObject2 = GraphQLObjectType([
'name' => 'Test2',
'fields' => $fields,
]);

$this->assertEquals($testObject2->getFields(), $testObject1->getFields());

$testInputObject1 = GraphQLInputObjectType([
'name' => 'Test1',
'fields' => $fields,
]);

$testInputObject2 = GraphQLInputObjectType([
'name' => 'Test2',
'fields' => $fields,
]);

$this->assertEquals($testInputObject2->getFields(), $testInputObject1->getFields());

$this->assertInstanceOf(StringType::class, $fields['field1']['type']);
$this->assertInstanceOf(StringType::class, $fields['field2']['type']);
$this->assertInstanceOf(StringType::class, $fields['field2']['args']['id']['type']);
}

// TODO: Assess if we want to test 'accepts an Object type with a field function'.

/**
* @expectedException \Exception
*/
public function testRejectsFieldWithNullConfig()
{
$objType = GraphQLObjectType([
'name' => 'SomeObject',
'fields' => [
'f' => null,
],
]);

$objType->getFields();
}
}

0 comments on commit dd42364

Please sign in to comment.