Merge pull request #339 from simPod/cs-language-test

Fix CS in test/Language
This commit is contained in:
Vladimir Razuvaev 2018-09-02 21:22:39 +07:00 committed by GitHub
commit a7af4663b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2157 additions and 1836 deletions

View File

@ -1,13 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\Lexer;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Language\Token;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase;
use function count;
use function json_decode;
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')
*/
@ -33,7 +66,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME,
'start' => 2,
'end' => 5,
'value' => 'foo'
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne($bom . ' foo'));
@ -50,7 +83,7 @@ class LexerTest extends TestCase
'end' => 11,
'line' => 4,
'column' => 3,
'value' => 'foo'
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne("\n \r\n \r foo\n"));
}
@ -70,7 +103,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME,
'start' => 6,
'end' => 9,
'value' => 'foo'
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne($example1));
@ -83,7 +116,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME,
'start' => 18,
'end' => 21,
'value' => 'foo'
'value' => 'foo',
];
$this->assertArraySubset($expected, (array) $this->lexOne($example2));
@ -91,7 +124,7 @@ class LexerTest extends TestCase
'kind' => Token::NAME,
'start' => 3,
'end' => 6,
'value' => 'foo'
'value' => 'foo',
];
$example3 = ',,,foo,,,';
@ -181,63 +214,86 @@ class LexerTest extends TestCase
*/
public function testLexesStrings() : void
{
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 8,
'value' => 'simple'
], (array) $this->lexOne('"simple"'));
'value' => 'simple',
],
(array) $this->lexOne('"simple"')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 15,
'value' => ' white space '
], (array) $this->lexOne('" white space "'));
'value' => ' white space ',
],
(array) $this->lexOne('" white space "')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 10,
'value' => 'quote "'
], (array) $this->lexOne('"quote \\""'));
'value' => 'quote "',
],
(array) $this->lexOne('"quote \\""')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 25,
'value' => 'escaped \n\r\b\t\f'
], (array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"'));
'value' => 'escaped \n\r\b\t\f',
],
(array) $this->lexOne('"escaped \\\\n\\\\r\\\\b\\\\t\\\\f"')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 16,
'value' => 'slashes \\ \/'
], (array) $this->lexOne('"slashes \\\\ \\\\/"'));
'value' => 'slashes \\ \/',
],
(array) $this->lexOne('"slashes \\\\ \\\\/"')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 13,
'value' => 'unicode яуц'
], (array) $this->lexOne('"unicode яуц"'));
'value' => 'unicode яуц',
],
(array) $this->lexOne('"unicode яуц"')
);
$unicode = json_decode('"\u1234\u5678\u90AB\uCDEF"');
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 34,
'value' => 'unicode ' . $unicode
], (array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"'));
'value' => 'unicode ' . $unicode,
],
(array) $this->lexOne('"unicode \u1234\u5678\u90AB\uCDEF"')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::STRING,
'start' => 0,
'end' => 26,
'value' => $unicode
], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"'));
'value' => $unicode,
],
(array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"')
);
}
/**
@ -245,86 +301,128 @@ class LexerTest extends TestCase
*/
public function testLexesBlockString() : void
{
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 12,
'value' => 'simple'
], (array) $this->lexOne('"""simple"""'));
'value' => 'simple',
],
(array) $this->lexOne('"""simple"""')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 19,
'value' => ' white space '
], (array) $this->lexOne('""" white space """'));
'value' => ' white space ',
],
(array) $this->lexOne('""" white space """')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 22,
'value' => 'contains " quote'
], (array) $this->lexOne('"""contains " quote"""'));
'value' => 'contains " quote',
],
(array) $this->lexOne('"""contains " quote"""')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 31,
'value' => 'contains """ triplequote'
], (array) $this->lexOne('"""contains \\""" triplequote"""'));
'value' => 'contains """ triplequote',
],
(array) $this->lexOne('"""contains \\""" triplequote"""')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 16,
'value' => "multi\nline"
], (array) $this->lexOne("\"\"\"multi\nline\"\"\""));
'value' => "multi\nline",
],
(array) $this->lexOne("\"\"\"multi\nline\"\"\"")
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 28,
'value' => "multi\nline\nnormalized"
], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\""));
'value' => "multi\nline\nnormalized",
],
(array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\"")
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 32,
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234'
], (array) $this->lexOne('"""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"""')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 19,
'value' => 'slashes \\\\ \\/'
], (array) $this->lexOne('"""slashes \\\\ \\/"""'));
'value' => 'slashes \\\\ \\/',
],
(array) $this->lexOne('"""slashes \\\\ \\/"""')
);
$this->assertArraySubset([
$this->assertArraySubset(
[
'kind' => Token::BLOCK_STRING,
'start' => 0,
'end' => 68,
'value' => "spans\n multiple\n lines"
], (array) $this->lexOne("\"\"\"
'value' => "spans\n multiple\n lines",
],
(array) $this->lexOne('"""
spans
multiple
lines
\"\"\""));
"""')
);
}
public function reportsUsefulStringErrors() {
public function reportsUsefulStringErrors()
{
return [
['"', "Unterminated string.", $this->loc(1, 2)],
['"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)],
['"', 'Unterminated string.', $this->loc(1, 2)],
['"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
),
],
['"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' . "\r" . 'line"', "Unterminated string.", $this->loc(1, 7)],
['"bad \\z esc"', "Invalid character escape sequence: \\z", $this->loc(1, 7)],
['"multi' . "\n" . '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 \\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 \\u0XX1 esc"', "Invalid character escape sequence: \\u0XX1", $this->loc(1, 7)],
@ -343,12 +441,27 @@ class LexerTest extends TestCase
$this->expectSyntaxError($str, $expectedMessage, $location);
}
public function reportsUsefulBlockStringErrors() {
public function reportsUsefulBlockStringErrors()
{
return [
['"""', "Unterminated string.", $this->loc(1, 4)],
['"""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)],
['"""', 'Unterminated string.', $this->loc(1, 4)],
['"""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
),
],
];
}
@ -435,15 +548,15 @@ class LexerTest extends TestCase
public function reportsUsefulNumberErrors()
{
return [
[ '00', "Invalid number, unexpected digit after 0: \"0\"", $this->loc(1, 2)],
[ '+1', "Cannot parse the unexpected character \"+\".", $this->loc(1, 1)],
[ '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)],
[ '.123', "Cannot parse the unexpected character \".\".", $this->loc(1, 1)],
[ '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)],
[ '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)],
['00', 'Invalid number, unexpected digit after 0: "0"', $this->loc(1, 2)],
['+1', 'Cannot parse the unexpected character "+".', $this->loc(1, 1)],
['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)],
['.123', 'Cannot parse the unexpected character ".".', $this->loc(1, 1)],
['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)],
['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)],
];
}
@ -521,8 +634,8 @@ class LexerTest extends TestCase
$unicode2 = json_decode('"\u200b"');
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)],
[$unicode2, "Cannot parse the unexpected character \"\\u200b\".", $this->loc(1, 1)],
];
@ -544,7 +657,10 @@ class LexerTest extends TestCase
{
$q = 'a-b';
$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->expectExceptionMessage('Syntax Error: Invalid number, expected digit but got: "b"');
@ -587,42 +703,21 @@ class LexerTest extends TestCase
$tokens[] = $tok;
}
$this->assertEquals([
$this->assertEquals(
[
'<SOF>',
'{',
'Comment',
'Name',
'}',
'<EOF>'
], Utils::map($tokens, function ($tok) {
'<EOF>',
],
Utils::map(
$tokens,
function ($tok) {
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
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\SyntaxError;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NameNode;
@ -13,9 +17,10 @@ use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Error\SyntaxError;
use GraphQL\Utils\Utils;
use PHPUnit\Framework\TestCase;
use function file_get_contents;
use function sprintf;
class ParserTest extends TestCase
{
@ -43,13 +48,26 @@ class ParserTest extends TestCase
public function parseProvidesUsefulErrors()
{
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
', "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",],
['{ 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"],
', '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",
],
['{ 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
* @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 {
Parser::parse($str);
$this->fail('Expected exception not thrown');
@ -114,6 +137,23 @@ fragment MissingOn Type
);
}
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"')
*/
@ -160,14 +200,14 @@ HEREDOC;
'arguments' => new NodeList([
new ArgumentNode([
'name' => new NameNode(['value' => 'arg']),
'value' => new StringValueNode([
'value' => "Has a $char multi-byte character."
])
])
'value' => new StringValueNode(
['value' => sprintf('Has a %s multi-byte character.', $char)]
),
]),
]),
'directives' => new NodeList([]),
]),
]),
'directives' => new NodeList([])
])
])
]);
$this->assertEquals($expected, $result->definitions[0]->selectionSet);
@ -196,7 +236,7 @@ HEREDOC;
'mutation',
'subscription',
'true',
'false'
'false',
];
foreach ($nonKeywords as $keyword) {
$fragmentName = $keyword;
@ -205,14 +245,19 @@ HEREDOC;
}
// Expected not to throw:
$result = Parser::parse("query $keyword {
$result = Parser::parse(<<<GRAPHQL
query $keyword {
... $fragmentName
... on $keyword { field }
}
fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
}
");
fragment $fragmentName on Type {
$keyword($keyword: \$$keyword) @$keyword($keyword: $keyword)
}
GRAPHQL
);
$this->assertNotEmpty($result);
}
}
@ -289,7 +334,7 @@ fragment $fragmentName on Type {
$loc = function ($start, $end) use ($source) {
return [
'start' => $start,
'end' => $end
'end' => $end,
];
};
@ -315,7 +360,7 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(4, 8),
'value' => 'node'
'value' => 'node',
],
'arguments' => [
[
@ -323,15 +368,15 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(9, 11),
'value' => 'id'
'value' => 'id',
],
'value' => [
'kind' => NodeKind::INT,
'loc' => $loc(13, 14),
'value' => '4'
'value' => '4',
],
'loc' => $loc(9, 14, $source),
],
'loc' => $loc(9, 14, $source)
]
],
'directives' => [],
'selectionSet' => [
@ -345,11 +390,11 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(22, 24),
'value' => 'id'
'value' => 'id',
],
'arguments' => [],
'directives' => [],
'selectionSet' => null
'selectionSet' => null,
],
[
'kind' => NodeKind::FIELD,
@ -358,22 +403,30 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(30, 34),
'value' => 'name'
'value' => 'name',
],
'arguments' => [],
'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);
}
/**
@ -392,7 +445,7 @@ fragment $fragmentName on Type {
$loc = function ($start, $end) use ($source) {
return [
'start' => $start,
'end' => $end
'end' => $end,
];
};
@ -418,7 +471,7 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(10, 14),
'value' => 'node'
'value' => 'node',
],
'arguments' => [],
'directives' => [],
@ -433,19 +486,19 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => $loc(21, 23),
'value' => 'id'
'value' => 'id',
],
'arguments' => [],
'directives' => [],
'selectionSet' => null
]
]
]
]
]
]
]
]
'selectionSet' => null,
],
],
],
],
],
],
],
],
];
$this->assertEquals($expected, $this->nodeToArray($result));
@ -475,6 +528,8 @@ fragment $fragmentName on Type {
Parser::parse($source);
}
// Describe: parseValue
/**
* @see it('contains location information that only stringifys start/end')
*/
@ -495,6 +550,8 @@ fragment $fragmentName on Type {
$this->assertEquals($source, $result->loc->source);
}
// Describe: parseType
/**
* @see it('contains references to start and end tokens')
*/
@ -506,17 +563,18 @@ fragment $fragmentName on Type {
$this->assertEquals('<EOF>', $result->loc->endToken->kind);
}
// Describe: parseValue
/**
* @see it('parses null value')
*/
public function testParsesNullValues() : void
{
$this->assertEquals([
$this->assertEquals(
[
'kind' => NodeKind::NULL,
'loc' => ['start' => 0, 'end' => 4]
], $this->nodeToArray(Parser::parseValue('null')));
'loc' => ['start' => 0, 'end' => 4],
],
$this->nodeToArray(Parser::parseValue('null'))
);
}
/**
@ -524,41 +582,45 @@ fragment $fragmentName on Type {
*/
public function testParsesListValues() : void
{
$this->assertEquals([
$this->assertEquals(
[
'kind' => NodeKind::LST,
'loc' => ['start' => 0, 'end' => 11],
'values' => [
[
'kind' => NodeKind::INT,
'loc' => ['start' => 1, 'end' => 4],
'value' => '123'
'value' => '123',
],
[
'kind' => NodeKind::STRING,
'loc' => ['start' => 5, 'end' => 10],
'value' => 'abc',
'block' => false
]
]
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
'block' => false,
],
],
],
$this->nodeToArray(Parser::parseValue('[123 "abc"]'))
);
}
// Describe: parseType
/**
* @see it('parses well known types')
*/
public function testParsesWellKnownTypes() : void
{
$this->assertEquals([
$this->assertEquals(
[
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'String'
]
], $this->nodeToArray(Parser::parseType('String')));
'value' => 'String',
],
],
$this->nodeToArray(Parser::parseType('String'))
);
}
/**
@ -566,15 +628,18 @@ fragment $fragmentName on Type {
*/
public function testParsesCustomTypes() : void
{
$this->assertEquals([
$this->assertEquals(
[
'kind' => NodeKind::NAMED_TYPE,
'loc' => ['start' => 0, 'end' => 6],
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType'
]
], $this->nodeToArray(Parser::parseType('MyType')));
'value' => 'MyType',
],
],
$this->nodeToArray(Parser::parseType('MyType'))
);
}
/**
@ -582,7 +647,8 @@ fragment $fragmentName on Type {
*/
public function testParsesListTypes() : void
{
$this->assertEquals([
$this->assertEquals(
[
'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 8],
'type' => [
@ -591,10 +657,12 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7],
'value' => 'MyType'
]
]
], $this->nodeToArray(Parser::parseType('[MyType]')));
'value' => 'MyType',
],
],
],
$this->nodeToArray(Parser::parseType('[MyType]'))
);
}
/**
@ -602,7 +670,8 @@ fragment $fragmentName on Type {
*/
public function testParsesNonNullTypes() : void
{
$this->assertEquals([
$this->assertEquals(
[
'kind' => NodeKind::NON_NULL_TYPE,
'loc' => ['start' => 0, 'end' => 7],
'type' => [
@ -611,10 +680,12 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 0, 'end' => 6],
'value' => 'MyType'
]
]
], $this->nodeToArray(Parser::parseType('MyType!')));
'value' => 'MyType',
],
],
],
$this->nodeToArray(Parser::parseType('MyType!'))
);
}
/**
@ -622,7 +693,8 @@ fragment $fragmentName on Type {
*/
public function testParsesNestedTypes() : void
{
$this->assertEquals([
$this->assertEquals(
[
'kind' => NodeKind::LIST_TYPE,
'loc' => ['start' => 0, 'end' => 9],
'type' => [
@ -634,36 +706,12 @@ fragment $fragmentName on Type {
'name' => [
'kind' => NodeKind::NAME,
'loc' => ['start' => 1, 'end' => 7],
'value' => '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;
}
'value' => 'MyType',
],
],
],
],
$this->nodeToArray(Parser::parseType('[MyType!]'))
);
}
}

View File

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

View File

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Error\SyntaxError;
@ -10,7 +13,6 @@ use PHPUnit\Framework\TestCase;
class SchemaParserTest extends TestCase
{
// Describe: Schema Parser
/**
* @see it('Simple type')
*/
@ -21,7 +23,9 @@ type Hello {
world: String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -36,17 +40,53 @@ type Hello {
$this->nameNode('world', $loc(16, 21)),
$this->typeNode('String', $loc(23, 29)),
$loc(16, 29)
)
),
],
'loc' => $loc(1, 31),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 31)
],
'loc' => $loc(0, 31),
];
$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')
*/
@ -58,7 +98,9 @@ type Hello {
world: String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -73,18 +115,18 @@ type Hello {
$this->nameNode('world', $loc(30, 35)),
$this->typeNode('String', $loc(37, 43)),
$loc(30, 43)
)
),
],
'loc' => $loc(1, 45),
'description' => [
'kind' => NodeKind::STRING,
'value' => 'Description',
'loc' => $loc(1, 14),
'block' => false
]
]
'block' => false,
],
'loc' => $loc(0, 45)
],
],
'loc' => $loc(0, 45),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
@ -103,7 +145,9 @@ type Hello {
world: String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -118,18 +162,18 @@ type Hello {
$this->nameNode('world', $loc(70, 75)),
$this->typeNode('String', $loc(77, 83)),
$loc(70, 83)
)
),
],
'loc' => $loc(1, 85),
'description' => [
'kind' => NodeKind::STRING,
'value' => 'Description',
'loc' => $loc(1, 20),
'block' => true
]
]
'block' => true,
],
'loc' => $loc(0, 85)
],
],
'loc' => $loc(0, 85),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
@ -148,6 +192,7 @@ extend type Hello {
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
'definitions' => [
@ -161,12 +206,12 @@ extend type Hello {
$this->nameNode('world', $loc(23, 28)),
$this->typeNode('String', $loc(30, 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));
}
@ -181,6 +226,7 @@ extend type Hello {
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
'definitions' => [
@ -192,10 +238,10 @@ extend type Hello {
],
'directives' => [],
'fields' => [],
'loc' => $loc(0, 37)
]
'loc' => $loc(0, 37),
],
'loc' => $loc(0, 37)
],
'loc' => $loc(0, 37),
];
$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')
*/
@ -291,10 +354,11 @@ extend type Hello {
type Hello {
world: String!
}';
$doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$doc = Parser::parse($body);
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -310,16 +374,16 @@ type Hello {
[
'kind' => NodeKind::NON_NULL_TYPE,
'type' => $this->typeNode('String', $loc(23, 29)),
'loc' => $loc(23, 30)
'loc' => $loc(23, 30),
],
$loc(16, 30)
)
),
],
'loc' => $loc(1, 32),
'description' => null
]
'description' => null,
],
'loc' => $loc(0,32)
],
'loc' => $loc(0, 32),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -331,8 +395,10 @@ type Hello {
public function testSimpleTypeInheritingInterface() : void
{
$body = 'type Hello implements World { field: String }';
$loc = function($start, $end) { return TestUtils::locArray($start, $end); };
$doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -341,7 +407,7 @@ type Hello {
'kind' => NodeKind::OBJECT_TYPE_DEFINITION,
'name' => $this->nameNode('Hello', $loc(5, 10)),
'interfaces' => [
$this->typeNode('World', $loc(22, 27))
$this->typeNode('World', $loc(22, 27)),
],
'directives' => [],
'fields' => [
@ -349,13 +415,13 @@ type Hello {
$this->nameNode('field', $loc(30, 35)),
$this->typeNode('String', $loc(37, 43)),
$loc(30, 43)
)
),
],
'loc' => $loc(0, 45),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 45)
],
'loc' => $loc(0, 45),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -367,8 +433,10 @@ type Hello {
public function testSimpleTypeInheritingMultipleInterfaces() : void
{
$body = 'type Hello implements Wo & rld { field: String }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -378,7 +446,7 @@ type Hello {
'name' => $this->nameNode('Hello', $loc(5, 10)),
'interfaces' => [
$this->typeNode('Wo', $loc(22, 24)),
$this->typeNode('rld', $loc(27, 30))
$this->typeNode('rld', $loc(27, 30)),
],
'directives' => [],
'fields' => [
@ -386,13 +454,13 @@ type Hello {
$this->nameNode('field', $loc(33, 38)),
$this->typeNode('String', $loc(40, 46)),
$loc(33, 46)
)
),
],
'loc' => $loc(0, 48),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 48)
],
'loc' => $loc(0, 48),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -404,8 +472,11 @@ type Hello {
public function testSimpleTypeInheritingMultipleInterfacesWithLeadingAmpersand() : void
{
$body = 'type Hello implements & Wo & rld { field: String }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => 'Document',
'definitions' => [
@ -439,8 +510,10 @@ type Hello {
public function testSingleValueEnum() : void
{
$body = 'enum Hello { WORLD }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -451,23 +524,36 @@ type Hello {
'directives' => [],
'values' => [$this->enumValueNode('WORLD', $loc(13, 18))],
'loc' => $loc(0, 20),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 20)
],
'loc' => $loc(0, 20),
];
$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')
*/
public function testDoubleValueEnum() : void
{
$body = 'enum Hello { WO, RLD }';
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$doc = Parser::parse($body);
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -478,13 +564,13 @@ type Hello {
'directives' => [],
'values' => [
$this->enumValueNode('WO', $loc(13, 15)),
$this->enumValueNode('RLD', $loc(17, 20))
$this->enumValueNode('RLD', $loc(17, 20)),
],
'loc' => $loc(0, 22),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 22)
],
'loc' => $loc(0, 22),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -500,7 +586,9 @@ interface Hello {
world: String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -514,13 +602,13 @@ interface Hello {
$this->nameNode('world', $loc(21, 26)),
$this->typeNode('String', $loc(28, 34)),
$loc(21, 34)
)
),
],
'loc' => $loc(1, 36),
'description' => null
]
'description' => null,
],
'loc' => $loc(0,36)
],
'loc' => $loc(0, 36),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
@ -535,7 +623,9 @@ type Hello {
world(flag: Boolean): String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -555,21 +645,34 @@ type Hello {
$this->typeNode('Boolean', $loc(28, 35)),
null,
$loc(22, 35)
)
),
],
$loc(16, 44)
)
),
],
'loc' => $loc(1, 46),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 46)
],
'loc' => $loc(0, 46),
];
$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')
*/
@ -580,7 +683,9 @@ type Hello {
world(flag: Boolean = true): String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -600,16 +705,16 @@ type Hello {
$this->typeNode('Boolean', $loc(28, 35)),
['kind' => NodeKind::BOOLEAN, 'value' => true, 'loc' => $loc(38, 42)],
$loc(22, 42)
)
),
],
$loc(16, 51)
)
),
],
'loc' => $loc(1, 53),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 53)
],
'loc' => $loc(0, 53),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
@ -624,7 +729,9 @@ type Hello {
world(things: [String]): String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -641,19 +748,25 @@ type Hello {
[
$this->inputValueNode(
$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,
$loc(22, 38)
)
),
],
$loc(16, 47)
)
),
],
'loc' => $loc(1, 49),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 49)
],
'loc' => $loc(0, 49),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -669,7 +782,9 @@ type Hello {
world(argOne: Boolean, argTwo: Int): String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -695,16 +810,16 @@ type Hello {
$this->typeNode('Int', $loc(47, 50)),
null,
$loc(39, 50)
)
),
],
$loc(16, 59)
)
),
],
'loc' => $loc(1, 61),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 61)
],
'loc' => $loc(0, 61),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -717,7 +832,10 @@ type Hello {
{
$body = 'union Hello = World';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
'definitions' => [
@ -727,10 +845,10 @@ type Hello {
'directives' => [],
'types' => [$this->typeNode('World', $loc(14, 19))],
'loc' => $loc(0, 19),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 19)
],
'loc' => $loc(0, 19),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
@ -743,7 +861,9 @@ type Hello {
{
$body = 'union Hello = Wo | Rld';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -754,18 +874,17 @@ type Hello {
'directives' => [],
'types' => [
$this->typeNode('Wo', $loc(14, 16)),
$this->typeNode('Rld', $loc(19, 22))
$this->typeNode('Rld', $loc(19, 22)),
],
'loc' => $loc(0, 22),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 22)
],
'loc' => $loc(0, 22),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
/**
* @see it('Union with two types and leading pipe')
*/
@ -785,8 +904,8 @@ type Hello {
$this->typeNode('Rld', ['start' => 21, 'end' => 24]),
],
'loc' => ['start' => 0, 'end' => 24],
'description' => null
]
'description' => null,
],
],
'loc' => ['start' => 0, 'end' => 24],
];
@ -848,7 +967,10 @@ type Hello {
{
$body = 'scalar Hello';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
'definitions' => [
@ -857,10 +979,10 @@ type Hello {
'name' => $this->nameNode('Hello', $loc(7, 12)),
'directives' => [],
'loc' => $loc(0, 12),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 12)
],
'loc' => $loc(0, 12),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
@ -875,7 +997,9 @@ input Hello {
world: String
}';
$doc = Parser::parse($body);
$loc = function($start, $end) {return TestUtils::locArray($start, $end);};
$loc = function ($start, $end) {
return TestUtils::locArray($start, $end);
};
$expected = [
'kind' => NodeKind::DOCUMENT,
@ -890,13 +1014,13 @@ input Hello {
$this->typeNode('String', $loc(24, 30)),
null,
$loc(17, 30)
)
),
],
'loc' => $loc(1, 32),
'description' => null
]
'description' => null,
],
'loc' => $loc(0, 32)
],
'loc' => $loc(0, 32),
];
$this->assertEquals($expected, TestUtils::nodeToArray($doc));
}
@ -980,81 +1104,4 @@ input Hello {
];
$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,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests;
use GraphQL\Language\AST\NameNode;
@ -7,6 +10,7 @@ use GraphQL\Language\Parser;
use GraphQL\Language\Printer;
use PHPUnit\Framework\TestCase;
use Throwable;
use function file_get_contents;
class SchemaPrinterTest extends TestCase
{
@ -16,7 +20,7 @@ class SchemaPrinterTest extends TestCase
public function testPrintsMinimalAst() : void
{
$ast = new ScalarTypeDefinitionNode([
'name' => new NameNode(['value' => 'foo'])
'name' => new NameNode(['value' => 'foo']),
]);
$this->assertEquals('scalar foo', Printer::doPrint($ast));
}

View File

@ -1,5 +1,8 @@
<?php
namespace GraphQL\Tests;
declare(strict_types=1);
namespace GraphQL\Tests\Language;
use GraphQL\Language\AST\Location;
use GraphQL\Language\AST\Node;
@ -7,6 +10,13 @@ use GraphQL\Language\AST\NodeList;
use GraphQL\Language\Parser;
use GraphQL\Utils\AST;
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
{
@ -27,33 +37,14 @@ class SerializationTest extends TestCase
$this->assertNodesAreEqual($parsedAst, $actualAst);
}
public function testSerializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink, ['noLocation' => true]);
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$this->assertEquals($expectedAst, $ast->toArray(true));
}
public function testUnserializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]);
$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
* @param string[] $path
*/
private function assertNodesAreEqual($expected, $actual, $path = [])
private function assertNodesAreEqual(Node $expected, Node $actual, array $path = []) : void
{
$err = "Mismatch at AST path: " . implode(', ', $path);
$err = 'Mismatch at AST path: ' . implode(', ', $path);
$this->assertInstanceOf(Node::class, $actual, $err);
$this->assertEquals(get_class($expected), get_class($actual), $err);
@ -67,7 +58,7 @@ class SerializationTest extends TestCase
$actualValue = $actualVars[$name];
$tmpPath = $path;
$tmpPath[] = $name;
$err = "Mismatch at AST path: " . implode(', ', $tmpPath);
$err = 'Mismatch at AST path: ' . implode(', ', $tmpPath);
if ($expectedValue instanceof Node) {
$this->assertNodesAreEqual($expectedValue, $actualValue, $tmpPath);
@ -89,4 +80,21 @@ class SerializationTest extends TestCase
}
}
}
public function testSerializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$ast = Parser::parse($kitchenSink, ['noLocation' => true]);
$expectedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$this->assertEquals($expectedAst, $ast->toArray(true));
}
public function testUnserializeSupportsNoLocationOption() : void
{
$kitchenSink = file_get_contents(__DIR__ . '/kitchen-sink.graphql');
$serializedAst = json_decode(file_get_contents(__DIR__ . '/kitchen-sink-noloc.ast'), true);
$actualAst = AST::fromArray($serializedAst);
$parsedAst = Parser::parse($kitchenSink, ['noLocation' => true]);
$this->assertNodesAreEqual($parsedAst, $actualAst);
}
}

View File

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

View File

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

View File

@ -29,15 +29,40 @@ use function iterator_to_array;
class VisitorTest extends ValidatorTestCase
{
private function getNodeByPath(DocumentNode $ast, $path)
public function testValidatesPathArgument() : void
{
$result = $ast;
foreach ($path as $key) {
$resultArray = $result instanceof NodeList ? iterator_to_array($result) : $result->toArray();
$this->assertArrayHasKey($key, $resultArray);
$result = $resultArray[$key];
}
return $result;
$visited = [];
$ast = Parser::parse('{ a }', ['noLocation' => true]);
Visitor::visit(
$ast,
[
'enter' => function ($node, $key, $parent, $path) use ($ast, &$visited) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $path];
},
'leave' => function ($node, $key, $parent, $path) use ($ast, &$visited) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['leave', $path];
},
]
);
$expected = [
['enter', []],
['enter', ['definitions', 0]],
['enter', ['definitions', 0, 'selectionSet']],
['enter', ['definitions', 0, 'selectionSet', 'selections', 0]],
['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
['leave', ['definitions', 0, 'selectionSet', 'selections', 0]],
['leave', ['definitions', 0, 'selectionSet']],
['leave', ['definitions', 0]],
['leave', []],
];
$this->assertEquals($expected, $visited);
}
private function checkVisitorFnArgs($ast, $args, $isEdited = false)
@ -58,6 +83,7 @@ class VisitorTest extends ValidatorTestCase
$this->assertEquals(null, $parent);
$this->assertEquals([], $path);
$this->assertEquals([], $ancestors);
return;
}
@ -84,37 +110,16 @@ class VisitorTest extends ValidatorTestCase
}
}
public function testValidatesPathArgument() : void
private function getNodeByPath(DocumentNode $ast, $path)
{
$visited = [];
$result = $ast;
foreach ($path as $key) {
$resultArray = $result instanceof NodeList ? iterator_to_array($result) : $result->toArray();
$this->assertArrayHasKey($key, $resultArray);
$result = $resultArray[$key];
}
$ast = Parser::parse('{ a }', ['noLocation' => true]);
Visitor::visit($ast, [
'enter' => function ($node, $key, $parent, $path) use ($ast, &$visited) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $path];
},
'leave' => function ($node, $key, $parent, $path) use ($ast, &$visited) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['leave', $path];
},
]);
$expected = [
['enter', []],
['enter', ['definitions', 0]],
['enter', ['definitions', 0, 'selectionSet']],
['enter', ['definitions', 0, 'selectionSet', 'selections', 0]],
['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']],
['leave', ['definitions', 0, 'selectionSet', 'selections', 0]],
['leave', ['definitions', 0, 'selectionSet']],
['leave', ['definitions', 0]],
['leave', []],
];
$this->assertEquals($expected, $visited);
return $result;
}
public function testAllowsEditingNodeOnEnterAndOnLeave() : void
@ -122,7 +127,9 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$selectionSet = null;
$editedAst = Visitor::visit($ast, [
$editedAst = Visitor::visit(
$ast,
[
NodeKind::OPERATION_DEFINITION => [
'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
@ -133,6 +140,7 @@ class VisitorTest extends ValidatorTestCase
'selections' => [],
]);
$newNode->didEnter = true;
return $newNode;
},
'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) {
@ -140,10 +148,12 @@ class VisitorTest extends ValidatorTestCase
$newNode = clone $node;
$newNode->selectionSet = $selectionSet;
$newNode->didLeave = true;
return $newNode;
},
],
]);
]
);
$this->assertNotEquals($ast, $editedAst);
@ -159,13 +169,16 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$definitions = $ast->definitions;
$editedAst = Visitor::visit($ast, [
$editedAst = Visitor::visit(
$ast,
[
NodeKind::DOCUMENT => [
'enter' => function (DocumentNode $node) use ($ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$tmp = clone $node;
$tmp->definitions = [];
$tmp->didEnter = true;
return $tmp;
},
'leave' => function (DocumentNode $node) use ($definitions, $ast) {
@ -174,7 +187,8 @@ class VisitorTest extends ValidatorTestCase
$node->didLeave = true;
},
],
]);
]
);
$this->assertNotEquals($ast, $editedAst);
@ -188,14 +202,17 @@ class VisitorTest extends ValidatorTestCase
public function testAllowsForEditingOnEnter() : void
{
$ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, [
$editedAst = Visitor::visit(
$ast,
[
'enter' => function ($node) use ($ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
if ($node instanceof FieldNode && $node->name->value === 'b') {
return Visitor::removeNode();
}
},
]);
]
);
$this->assertEquals(
Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
@ -210,14 +227,17 @@ class VisitorTest extends ValidatorTestCase
public function testAllowsForEditingOnLeave() : void
{
$ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, [
$editedAst = Visitor::visit(
$ast,
[
'leave' => function ($node) use ($ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
if ($node instanceof FieldNode && $node->name->value === 'b') {
return Visitor::removeNode();
}
},
]);
]
);
$this->assertEquals(
Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
@ -240,7 +260,9 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse('{ a { x } }', ['noLocation' => true]);
Visitor::visit($ast, [
Visitor::visit(
$ast,
[
'enter' => function ($node) use ($addedField, &$didVisitAddedField, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
if ($node instanceof FieldNode && $node->name->value === 'a') {
@ -256,7 +278,8 @@ class VisitorTest extends ValidatorTestCase
$didVisitAddedField = true;
},
]);
]
);
$this->assertTrue($didVisitAddedField);
}
@ -266,7 +289,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
Visitor::visit(
$ast,
[
'enter' => function (Node $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, $node->value ?? null];
@ -278,7 +303,8 @@ class VisitorTest extends ValidatorTestCase
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['leave', $node->kind, $node->value ?? null];
},
]);
]
);
$expected = [
['enter', 'Document', null],
@ -306,7 +332,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
Visitor::visit(
$ast,
[
'enter' => function (Node $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, $node->value ?? null];
@ -318,7 +346,8 @@ class VisitorTest extends ValidatorTestCase
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['leave', $node->kind, $node->value ?? null];
},
]);
]
);
$expected = [
['enter', 'Document', null],
@ -344,7 +373,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
Visitor::visit(
$ast,
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, $node->value ?? null];
@ -357,9 +388,12 @@ class VisitorTest extends ValidatorTestCase
return Visitor::stop();
}
},
]);
]
);
$this->assertEquals($visited, [
$this->assertEquals(
$visited,
[
['enter', 'Document', null],
['enter', 'OperationDefinition', null],
['enter', 'SelectionSet', null],
@ -374,7 +408,8 @@ class VisitorTest extends ValidatorTestCase
['enter', 'Field', null],
['enter', 'Name', 'x'],
['leave', 'Name', 'x'],
]);
]
);
}
public function testAllowsANamedFunctionsVisitorAPI() : void
@ -382,7 +417,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }', ['noLocation' => true]);
Visitor::visit($ast, [
Visitor::visit(
$ast,
[
NodeKind::NAME => function (NameNode $node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, $node->value];
@ -397,7 +434,8 @@ class VisitorTest extends ValidatorTestCase
$visited[] = ['leave', $node->kind, null];
},
],
]);
]
);
$expected = [
['enter', 'SelectionSet', null],
@ -424,7 +462,9 @@ class VisitorTest extends ValidatorTestCase
);
$visited = [];
Visitor::visit($ast, [
Visitor::visit(
$ast,
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, $node->value ?? null];
@ -433,7 +473,8 @@ class VisitorTest extends ValidatorTestCase
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['leave', $node->kind, $node->value ?? null];
},
]);
]
);
$expected = [
['enter', 'Document', null],
@ -475,7 +516,9 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse($kitchenSink);
$visited = [];
Visitor::visit($ast, [
Visitor::visit(
$ast,
[
'enter' => function (Node $node, $key, $parent) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$r = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
@ -486,7 +529,8 @@ class VisitorTest extends ValidatorTestCase
$r = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null];
$visited[] = $r;
},
]);
]
);
$expected = [
['enter', 'Document', null, null],
@ -813,7 +857,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }');
Visitor::visit($ast, Visitor::visitInParallel([
Visitor::visit(
$ast,
Visitor::visitInParallel([
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
@ -829,9 +875,11 @@ class VisitorTest extends ValidatorTestCase
$visited[] = ['leave', $node->kind, $node->value ?? null];
},
],
]));
])
);
$this->assertEquals([
$this->assertEquals(
[
['enter', 'Document', null],
['enter', 'OperationDefinition', null],
['enter', 'SelectionSet', null],
@ -847,7 +895,9 @@ class VisitorTest extends ValidatorTestCase
['leave', 'SelectionSet', null],
['leave', 'OperationDefinition', null],
['leave', 'Document', null],
], $visited);
],
$visited
);
}
public function testAllowsSkippingDifferentSubTrees() : void
@ -855,7 +905,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a { x }, b { y} }');
Visitor::visit($ast, Visitor::visitInParallel([
Visitor::visit(
$ast,
Visitor::visitInParallel([
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
@ -882,9 +934,11 @@ class VisitorTest extends ValidatorTestCase
$visited[] = ['no-b', 'leave', $node->kind, $node->value ?? null];
},
],
]));
])
);
$this->assertEquals([
$this->assertEquals(
[
['no-a', 'enter', 'Document', null],
['no-b', 'enter', 'Document', null],
['no-a', 'enter', 'OperationDefinition', null],
@ -919,7 +973,9 @@ class VisitorTest extends ValidatorTestCase
['no-b', 'leave', 'OperationDefinition', null],
['no-a', 'leave', 'Document', null],
['no-b', 'leave', 'Document', null],
], $visited);
],
$visited
);
}
public function testAllowsEarlyExitWhileVisiting2() : void
@ -927,7 +983,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }');
Visitor::visit($ast, Visitor::visitInParallel([ [
Visitor::visit(
$ast,
Visitor::visitInParallel([[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$value = $node->value ?? null;
@ -941,9 +999,11 @@ class VisitorTest extends ValidatorTestCase
$visited[] = ['leave', $node->kind, $node->value ?? null];
},
],
]));
])
);
$this->assertEquals([
$this->assertEquals(
[
['enter', 'Document', null],
['enter', 'OperationDefinition', null],
['enter', 'SelectionSet', null],
@ -957,7 +1017,9 @@ class VisitorTest extends ValidatorTestCase
['enter', 'SelectionSet', null],
['enter', 'Field', null],
['enter', 'Name', 'x'],
], $visited);
],
$visited
);
}
public function testAllowsEarlyExitFromDifferentPoints() : void
@ -965,7 +1027,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a { y }, b { x } }');
Visitor::visit($ast, Visitor::visitInParallel([
Visitor::visit(
$ast,
Visitor::visitInParallel([
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
@ -994,9 +1058,11 @@ class VisitorTest extends ValidatorTestCase
$visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null];
},
],
]));
])
);
$this->assertEquals([
$this->assertEquals(
[
['break-a', 'enter', 'Document', null],
['break-b', 'enter', 'Document', null],
['break-a', 'enter', 'OperationDefinition', null],
@ -1017,7 +1083,9 @@ class VisitorTest extends ValidatorTestCase
['break-b', 'leave', 'Field', null],
['break-b', 'enter', 'Field', null],
['break-b', 'enter', 'Name', 'b'],
], $visited);
],
$visited
);
}
public function testAllowsEarlyExitWhileLeaving2() : void
@ -1025,7 +1093,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b { x }, c }');
Visitor::visit($ast, Visitor::visitInParallel([ [
Visitor::visit(
$ast,
Visitor::visitInParallel([[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$visited[] = ['enter', $node->kind, $node->value ?? null];
@ -1039,9 +1109,11 @@ class VisitorTest extends ValidatorTestCase
}
},
],
]));
])
);
$this->assertEquals([
$this->assertEquals(
[
['enter', 'Document', null],
['enter', 'OperationDefinition', null],
['enter', 'SelectionSet', null],
@ -1056,7 +1128,9 @@ class VisitorTest extends ValidatorTestCase
['enter', 'Field', null],
['enter', 'Name', 'x'],
['leave', 'Name', 'x'],
], $visited);
],
$visited
);
}
public function testAllowsEarlyExitFromLeavingDifferentPoints() : void
@ -1064,7 +1138,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a { y }, b { x } }');
Visitor::visit($ast, Visitor::visitInParallel([
Visitor::visit(
$ast,
Visitor::visitInParallel([
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
@ -1091,9 +1167,11 @@ class VisitorTest extends ValidatorTestCase
}
},
],
]));
])
);
$this->assertEquals([
$this->assertEquals(
[
['break-a', 'enter', 'Document', null],
['break-b', 'enter', 'Document', null],
['break-a', 'enter', 'OperationDefinition', null],
@ -1130,7 +1208,9 @@ class VisitorTest extends ValidatorTestCase
['break-b', 'leave', 'Field', null],
['break-b', 'leave', 'SelectionSet', null],
['break-b', 'leave', 'Field', null],
], $visited);
],
$visited
);
}
public function testAllowsForEditingOnEnter2() : void
@ -1138,7 +1218,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, Visitor::visitInParallel([
$editedAst = Visitor::visit(
$ast,
Visitor::visitInParallel([
[
'enter' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
@ -1157,7 +1239,8 @@ class VisitorTest extends ValidatorTestCase
$visited[] = ['leave', $node->kind, $node->value ?? null];
},
],
]));
])
);
$this->assertEquals(
Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
@ -1169,7 +1252,8 @@ class VisitorTest extends ValidatorTestCase
$editedAst
);
$this->assertEquals([
$this->assertEquals(
[
['enter', 'Document', null],
['enter', 'OperationDefinition', null],
['enter', 'SelectionSet', null],
@ -1194,7 +1278,9 @@ class VisitorTest extends ValidatorTestCase
['leave', 'SelectionSet', null],
['leave', 'OperationDefinition', null],
['leave', 'Document', null],
], $visited);
],
$visited
);
}
public function testAllowsForEditingOnLeave2() : void
@ -1202,7 +1288,9 @@ class VisitorTest extends ValidatorTestCase
$visited = [];
$ast = Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]);
$editedAst = Visitor::visit($ast, Visitor::visitInParallel([
$editedAst = Visitor::visit(
$ast,
Visitor::visitInParallel([
[
'leave' => function ($node) use (&$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
@ -1221,7 +1309,8 @@ class VisitorTest extends ValidatorTestCase
$visited[] = ['leave', $node->kind, $node->value ?? null];
},
],
]));
])
);
$this->assertEquals(
Parser::parse('{ a, b, c { a, b, c } }', ['noLocation' => true]),
@ -1233,7 +1322,8 @@ class VisitorTest extends ValidatorTestCase
$editedAst
);
$this->assertEquals([
$this->assertEquals(
[
['enter', 'Document', null],
['enter', 'OperationDefinition', null],
['enter', 'SelectionSet', null],
@ -1264,10 +1354,11 @@ class VisitorTest extends ValidatorTestCase
['leave', 'SelectionSet', null],
['leave', 'OperationDefinition', null],
['leave', 'Document', null],
], $visited);
],
$visited
);
}
/**
* Describe: visitWithTypeInfo
*/
@ -1278,7 +1369,11 @@ class VisitorTest extends ValidatorTestCase
$typeInfo = new TypeInfo(ValidatorTestCase::getTestSchema());
$ast = Parser::parse('{ human(id: 4) { name, pets { ... { name } }, unknown } }');
Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, [
Visitor::visit(
$ast,
Visitor::visitWithTypeInfo(
$typeInfo,
[
'enter' => function ($node) use ($typeInfo, &$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args());
$parentType = $typeInfo->getParentType();
@ -1307,9 +1402,12 @@ class VisitorTest extends ValidatorTestCase
$inputType ? (string) $inputType : null,
];
},
]));
]
)
);
$this->assertEquals([
$this->assertEquals(
[
['enter', 'Document', null, null, null, null],
['enter', 'OperationDefinition', null, null, 'QueryRoot', null],
['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
@ -1350,7 +1448,9 @@ class VisitorTest extends ValidatorTestCase
['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
['leave', 'OperationDefinition', null, null, 'QueryRoot', null],
['leave', 'Document', null, null, null, null],
], $visited);
],
$visited
);
}
public function testMaintainsTypeInfoDuringEdit() : void
@ -1361,7 +1461,11 @@ class VisitorTest extends ValidatorTestCase
$ast = Parser::parse(
'{ human(id: 4) { name, pets }, alien }'
);
$editedAst = Visitor::visit($ast, Visitor::visitWithTypeInfo($typeInfo, [
$editedAst = Visitor::visit(
$ast,
Visitor::visitWithTypeInfo(
$typeInfo,
[
'enter' => function ($node) use ($typeInfo, &$visited, $ast) {
$this->checkVisitorFnArgs($ast, func_get_args(), true);
$parentType = $typeInfo->getParentType();
@ -1410,17 +1514,26 @@ class VisitorTest extends ValidatorTestCase
$inputType ? (string) $inputType : null,
];
},
]));
]
)
);
$this->assertEquals(Printer::doPrint(Parser::parse(
$this->assertEquals(
Printer::doPrint(Parser::parse(
'{ human(id: 4) { name, pets }, alien }'
)), Printer::doPrint($ast));
)),
Printer::doPrint($ast)
);
$this->assertEquals(Printer::doPrint(Parser::parse(
$this->assertEquals(
Printer::doPrint(Parser::parse(
'{ human(id: 4) { name, pets { __typename } }, alien { __typename } }'
)), Printer::doPrint($editedAst));
)),
Printer::doPrint($editedAst)
);
$this->assertEquals([
$this->assertEquals(
[
['enter', 'Document', null, null, null, null],
['enter', 'OperationDefinition', null, null, 'QueryRoot', null],
['enter', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
@ -1463,6 +1576,8 @@ class VisitorTest extends ValidatorTestCase
['leave', 'SelectionSet', null, 'QueryRoot', 'QueryRoot', null],
['leave', 'OperationDefinition', null, null, 'QueryRoot', null],
['leave', 'Document', null, null, null, null],
], $visited);
],
$visited
);
}
}