From 0a182ac53cf2c1aeac5870241d0d57d46a56b122 Mon Sep 17 00:00:00 2001
From: vladar
+ * An offset to check for.
+ *
+ * The return value will be casted to boolean if non-boolean was returned. + * @since 5.0.0 + */ + public function offsetExists($offset) + { + if (is_scalar($offset)) { + return array_key_exists($offset, $this->scalarStore); + } + if (is_object($offset)) { + return $this->objectStore->offsetExists($offset); + } + if (is_array($offset)) { + foreach ($this->arrayKeys as $index => $entry) { + if ($entry === $offset) { + $this->lastArrayKey = $offset; + $this->lastArrayValue = $this->arrayValues[$index]; + return true; + } + } + } + return false; + } + + /** + * Offset to retrieve + * @link http://php.net/manual/en/arrayaccess.offsetget.php + * @param mixed $offset
+ * The offset to retrieve. + *
+ * @return mixed Can return all value types. + * @since 5.0.0 + */ + public function offsetGet($offset) + { + if (is_scalar($offset)) { + return $this->scalarStore[$offset]; + } + if (is_object($offset)) { + return $this->objectStore->offsetGet($offset); + } + if (is_array($offset)) { + // offsetGet is often called directly after offsetExists, so optimize to avoid second loop: + if ($this->lastArrayKey === $offset) { + return $this->lastArrayValue; + } + foreach ($this->arrayKeys as $index => $entry) { + if ($entry === $offset) { + return $this->arrayValues[$index]; + } + } + } + return null; + } + + /** + * Offset to set + * @link http://php.net/manual/en/arrayaccess.offsetset.php + * @param mixed $offset+ * The offset to assign the value to. + *
+ * @param mixed $value+ * The value to set. + *
+ * @return void + * @since 5.0.0 + */ + public function offsetSet($offset, $value) + { + if (is_scalar($offset)) { + $this->scalarStore[$offset] = $value; + } else if (is_object($offset)) { + $this->objectStore[$offset] = $value; + } else if (is_array($offset)) { + $this->arrayKeys[] = $offset; + $this->arrayValues[] = $value; + } else { + throw new \InvalidArgumentException("Unexpected offset type: " . Utils::printSafe($offset)); + } + } + + /** + * Offset to unset + * @link http://php.net/manual/en/arrayaccess.offsetunset.php + * @param mixed $offset+ * The offset to unset. + *
+ * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + if (is_scalar($offset)) { + unset($this->scalarStore[$offset]); + } else if (is_object($offset)) { + $this->objectStore->offsetUnset($offset); + } else if (is_array($offset)) { + $index = array_search($offset, $this->arrayKeys, true); + + if (false !== $index) { + array_splice($this->arrayKeys, $index, 1); + array_splice($this->arrayValues, $index, 1); + } + } + } +} diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index 35eb8c5..1d637e0 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -3,15 +3,29 @@ namespace GraphQL\Tests\Type; use GraphQL\Error; use GraphQL\GraphQL; +use GraphQL\Language\SourceLocation; use GraphQL\Schema; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Introspection; class EnumTypeTest extends \PHPUnit_Framework_TestCase { + /** + * @var Schema + */ private $schema; + /** + * @var EnumType + */ + private $ComplexEnum; + + private $Complex1; + + private $Complex2; + public function setUp() { $ColorType = new EnumType([ @@ -23,6 +37,17 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase ] ]); + $Complex1 = ['someRandomFunction' => function() {}]; + $Complex2 = new \ArrayObject(['someRandomValue' => 123]); + + $ComplexEnum = new EnumType([ + 'name' => 'Complex', + 'values' => [ + 'ONE' => ['value' => $Complex1], + 'TWO' => ['value' => $Complex2] + ] + ]); + $QueryType = new ObjectType([ 'name' => 'Query', 'fields' => [ @@ -59,6 +84,36 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase return $args['fromEnum']; } } + ], + 'complexEnum' => [ + 'type' => $ComplexEnum, + 'args' => [ + 'fromEnum' => [ + 'type' => $ComplexEnum, + // Note: defaultValue is provided an *internal* representation for + // Enums, rather than the string name. + 'defaultValue' => $Complex1 + ], + 'provideGoodValue' => [ + 'type' => Type::boolean(), + ], + 'provideBadValue' => [ + 'type' => Type::boolean() + ] + ], + 'resolve' => function($value, $args) use ($Complex1, $Complex2) { + if (!empty($args['provideGoodValue'])) { + // Note: this is one of the references of the internal values which + // ComplexEnum allows. + return $Complex2; + } + if (!empty($args['provideBadValue'])) { + // Note: similar shape, but not the same *reference* + // as Complex2 above. Enum internal values require === equality. + return new \ArrayObject(['someRandomValue' => 123]); + } + return $args['fromEnum']; + } ] ] ]); @@ -89,6 +144,10 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase ] ]); + $this->Complex1 = $Complex1; + $this->Complex2 = $Complex2; + $this->ComplexEnum = $ComplexEnum; + $this->schema = new Schema([ 'query' => $QueryType, 'mutation' => $MutationType, @@ -139,7 +198,10 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase $this->expectFailure( '{ colorEnum(fromEnum: "GREEN") }', null, - "Argument \"fromEnum\" has invalid value \"GREEN\".\nExpected type \"Color\", found \"GREEN\"." + [ + 'message' => "Argument \"fromEnum\" has invalid value \"GREEN\".\nExpected type \"Color\", found \"GREEN\".", + 'locations' => [new SourceLocation(1, 23)] + ] ); } @@ -148,9 +210,13 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase */ public function testDoesNotAcceptIncorrectInternalValue() { - $this->assertEquals( - ['data' => ['colorEnum' => null]], - GraphQL::execute($this->schema, '{ colorEnum(fromString: "GREEN") }') + $this->expectFailure( + '{ colorEnum(fromString: "GREEN") }', + null, + [ + 'message' => 'Expected a value of type "Color" but received: GREEN', + 'locations' => [new SourceLocation(1, 3)] + ] ); } @@ -294,14 +360,78 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase ); } + /** + * @it may present a values API for complex enums + */ + public function testMayPresentValuesAPIForComplexEnums() + { + $ComplexEnum = $this->ComplexEnum; + $values = $ComplexEnum->getValues(); + + $this->assertEquals(2, count($values)); + $this->assertEquals('ONE', $values[0]->name); + $this->assertEquals($this->Complex1, $values[0]->value); + $this->assertEquals('TWO', $values[1]->name); + $this->assertEquals($this->Complex2, $values[1]->value); + } + + /** + * @it may be internally represented with complex values + */ + public function testMayBeInternallyRepresentedWithComplexValues() + { + $result = GraphQL::execute($this->schema, '{ + first: complexEnum + second: complexEnum(fromEnum: TWO) + good: complexEnum(provideGoodValue: true) + bad: complexEnum(provideBadValue: true) + }'); + + $expected = [ + 'data' => [ + 'first' => 'ONE', + 'second' => 'TWO', + 'good' => 'TWO', + 'bad' => null + ], + 'errors' => [[ + 'message' => + 'Expected a value of type "Complex" but received: instance of ArrayObject', + 'locations' => [['line' => 5, 'column' => 9]] + ]] + ]; + + $this->assertEquals($expected, $result); + } + + /** + * @it can be introspected without error + */ + public function testCanBeIntrospectedWithoutError() + { + $result = GraphQL::execute($this->schema, Introspection::getIntrospectionQuery()); + $this->assertArrayNotHasKey('errors', $result); + } + private function expectFailure($query, $vars, $err) { $result = GraphQL::executeAndReturnResult($this->schema, $query, null, null, $vars); $this->assertEquals(1, count($result->errors)); - $this->assertEquals( - $err, - $result->errors[0]->getMessage() - ); + 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() + ); + } } }