1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace GraphQL\Tests\Type; |
6
|
|
|
|
7
|
|
|
use GraphQL\Error\InvariantViolation; |
8
|
|
|
use GraphQL\Tests\Type\TestClasses\MyCustomType; |
9
|
|
|
use GraphQL\Tests\Type\TestClasses\OtherCustom; |
10
|
|
|
use GraphQL\Type\Definition\BooleanType; |
11
|
|
|
use GraphQL\Type\Definition\CustomScalarType; |
12
|
|
|
use GraphQL\Type\Definition\EnumType; |
13
|
|
|
use GraphQL\Type\Definition\FloatType; |
14
|
|
|
use GraphQL\Type\Definition\IDType; |
15
|
|
|
use GraphQL\Type\Definition\InputObjectType; |
16
|
|
|
use GraphQL\Type\Definition\InterfaceType; |
17
|
|
|
use GraphQL\Type\Definition\IntType; |
18
|
|
|
use GraphQL\Type\Definition\ListOfType; |
19
|
|
|
use GraphQL\Type\Definition\NonNull; |
20
|
|
|
use GraphQL\Type\Definition\ObjectType; |
21
|
|
|
use GraphQL\Type\Definition\Type; |
22
|
|
|
use GraphQL\Type\Definition\UnionType; |
23
|
|
|
use GraphQL\Type\Schema; |
24
|
|
|
use GraphQL\Utils\Utils; |
25
|
|
|
use PHPUnit\Framework\TestCase; |
26
|
|
|
use stdClass; |
27
|
|
|
use Throwable; |
28
|
|
|
use function count; |
29
|
|
|
use function get_class; |
30
|
|
|
use function json_encode; |
31
|
|
|
use function sprintf; |
32
|
|
|
|
33
|
|
|
class DefinitionTest extends TestCase |
34
|
|
|
{ |
35
|
|
|
/** @var ObjectType */ |
36
|
|
|
public $blogImage; |
37
|
|
|
|
38
|
|
|
/** @var ObjectType */ |
39
|
|
|
public $blogArticle; |
40
|
|
|
|
41
|
|
|
/** @var ObjectType */ |
42
|
|
|
public $blogAuthor; |
43
|
|
|
|
44
|
|
|
/** @var ObjectType */ |
45
|
|
|
public $blogMutation; |
46
|
|
|
|
47
|
|
|
/** @var ObjectType */ |
48
|
|
|
public $blogQuery; |
49
|
|
|
|
50
|
|
|
/** @var ObjectType */ |
51
|
|
|
public $blogSubscription; |
52
|
|
|
|
53
|
|
|
/** @var ObjectType */ |
54
|
|
|
public $objectType; |
55
|
|
|
|
56
|
|
|
/** @var ObjectType */ |
57
|
|
|
public $objectWithIsTypeOf; |
58
|
|
|
|
59
|
|
|
/** @var InterfaceType */ |
60
|
|
|
public $interfaceType; |
61
|
|
|
|
62
|
|
|
/** @var UnionType */ |
63
|
|
|
public $unionType; |
64
|
|
|
|
65
|
|
|
/** @var EnumType */ |
66
|
|
|
public $enumType; |
67
|
|
|
|
68
|
|
|
/** @var InputObjectType */ |
69
|
|
|
public $inputObjectType; |
70
|
|
|
|
71
|
|
|
/** @var CustomScalarType */ |
72
|
|
|
public $scalarType; |
73
|
|
|
|
74
|
|
|
public function setUp() |
75
|
|
|
{ |
76
|
|
|
$this->objectType = new ObjectType(['name' => 'Object', 'fields' => ['tmp' => Type::string()]]); |
77
|
|
|
$this->interfaceType = new InterfaceType(['name' => 'Interface']); |
78
|
|
|
$this->unionType = new UnionType(['name' => 'Union', 'types' => [$this->objectType]]); |
79
|
|
|
$this->enumType = new EnumType(['name' => 'Enum']); |
80
|
|
|
$this->inputObjectType = new InputObjectType(['name' => 'InputObject']); |
81
|
|
|
|
82
|
|
|
$this->objectWithIsTypeOf = new ObjectType([ |
83
|
|
|
'name' => 'ObjectWithIsTypeOf', |
84
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
85
|
|
|
]); |
86
|
|
|
|
87
|
|
|
$this->scalarType = new CustomScalarType([ |
88
|
|
|
'name' => 'Scalar', |
89
|
|
|
'serialize' => static function () { |
90
|
|
|
}, |
91
|
|
|
'parseValue' => static function () { |
92
|
|
|
}, |
93
|
|
|
'parseLiteral' => static function () { |
94
|
|
|
}, |
95
|
|
|
]); |
96
|
|
|
|
97
|
|
|
$this->blogImage = new ObjectType([ |
98
|
|
|
'name' => 'Image', |
99
|
|
|
'fields' => [ |
100
|
|
|
'url' => ['type' => Type::string()], |
101
|
|
|
'width' => ['type' => Type::int()], |
102
|
|
|
'height' => ['type' => Type::int()], |
103
|
|
|
], |
104
|
|
|
]); |
105
|
|
|
|
106
|
|
|
$this->blogAuthor = new ObjectType([ |
107
|
|
|
'name' => 'Author', |
108
|
|
|
'fields' => function () { |
109
|
|
|
return [ |
110
|
|
|
'id' => ['type' => Type::string()], |
111
|
|
|
'name' => ['type' => Type::string()], |
112
|
|
|
'pic' => [ |
113
|
|
|
'type' => $this->blogImage, |
114
|
|
|
'args' => [ |
115
|
|
|
'width' => ['type' => Type::int()], |
116
|
|
|
'height' => ['type' => Type::int()], |
117
|
|
|
], |
118
|
|
|
], |
119
|
|
|
'recentArticle' => $this->blogArticle, |
120
|
|
|
]; |
121
|
|
|
}, |
122
|
|
|
]); |
123
|
|
|
|
124
|
|
|
$this->blogArticle = new ObjectType([ |
125
|
|
|
'name' => 'Article', |
126
|
|
|
'fields' => [ |
127
|
|
|
'id' => ['type' => Type::string()], |
128
|
|
|
'isPublished' => ['type' => Type::boolean()], |
129
|
|
|
'author' => ['type' => $this->blogAuthor], |
130
|
|
|
'title' => ['type' => Type::string()], |
131
|
|
|
'body' => ['type' => Type::string()], |
132
|
|
|
], |
133
|
|
|
]); |
134
|
|
|
|
135
|
|
|
$this->blogQuery = new ObjectType([ |
136
|
|
|
'name' => 'Query', |
137
|
|
|
'fields' => [ |
138
|
|
|
'article' => [ |
139
|
|
|
'type' => $this->blogArticle, |
140
|
|
|
'args' => [ |
141
|
|
|
'id' => ['type' => Type::string()], |
142
|
|
|
], |
143
|
|
|
], |
144
|
|
|
'feed' => ['type' => new ListOfType($this->blogArticle)], |
145
|
|
|
], |
146
|
|
|
]); |
147
|
|
|
|
148
|
|
|
$this->blogMutation = new ObjectType([ |
149
|
|
|
'name' => 'Mutation', |
150
|
|
|
'fields' => [ |
151
|
|
|
'writeArticle' => ['type' => $this->blogArticle], |
152
|
|
|
], |
153
|
|
|
]); |
154
|
|
|
|
155
|
|
|
$this->blogSubscription = new ObjectType([ |
156
|
|
|
'name' => 'Subscription', |
157
|
|
|
'fields' => [ |
158
|
|
|
'articleSubscribe' => [ |
159
|
|
|
'args' => ['id' => ['type' => Type::string()]], |
160
|
|
|
'type' => $this->blogArticle, |
161
|
|
|
], |
162
|
|
|
], |
163
|
|
|
]); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// Type System: Example |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @see it('defines a query only schema') |
170
|
|
|
*/ |
171
|
|
|
public function testDefinesAQueryOnlySchema() : void |
172
|
|
|
{ |
173
|
|
|
$blogSchema = new Schema([ |
174
|
|
|
'query' => $this->blogQuery, |
175
|
|
|
]); |
176
|
|
|
|
177
|
|
|
self::assertSame($blogSchema->getQueryType(), $this->blogQuery); |
178
|
|
|
|
179
|
|
|
$articleField = $this->blogQuery->getField('article'); |
180
|
|
|
self::assertSame($articleField->getType(), $this->blogArticle); |
181
|
|
|
self::assertSame($articleField->getType()->name, 'Article'); |
182
|
|
|
self::assertSame($articleField->name, 'article'); |
183
|
|
|
|
184
|
|
|
/** @var ObjectType $articleFieldType */ |
185
|
|
|
$articleFieldType = $articleField->getType(); |
186
|
|
|
$titleField = $articleFieldType->getField('title'); |
187
|
|
|
|
188
|
|
|
self::assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $titleField); |
189
|
|
|
self::assertSame('title', $titleField->name); |
190
|
|
|
self::assertSame(Type::string(), $titleField->getType()); |
191
|
|
|
|
192
|
|
|
$authorField = $articleFieldType->getField('author'); |
193
|
|
|
self::assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $authorField); |
194
|
|
|
|
195
|
|
|
/** @var ObjectType $authorFieldType */ |
196
|
|
|
$authorFieldType = $authorField->getType(); |
197
|
|
|
self::assertSame($this->blogAuthor, $authorFieldType); |
198
|
|
|
|
199
|
|
|
$recentArticleField = $authorFieldType->getField('recentArticle'); |
200
|
|
|
self::assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $recentArticleField); |
201
|
|
|
self::assertSame($this->blogArticle, $recentArticleField->getType()); |
202
|
|
|
|
203
|
|
|
$feedField = $this->blogQuery->getField('feed'); |
204
|
|
|
self::assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $feedField); |
205
|
|
|
|
206
|
|
|
/** @var ListOfType $feedFieldType */ |
207
|
|
|
$feedFieldType = $feedField->getType(); |
208
|
|
|
self::assertInstanceOf('GraphQL\Type\Definition\ListOfType', $feedFieldType); |
209
|
|
|
self::assertSame($this->blogArticle, $feedFieldType->getWrappedType()); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* @see it('defines a mutation schema') |
214
|
|
|
*/ |
215
|
|
|
public function testDefinesAMutationSchema() : void |
216
|
|
|
{ |
217
|
|
|
$schema = new Schema([ |
218
|
|
|
'query' => $this->blogQuery, |
219
|
|
|
'mutation' => $this->blogMutation, |
220
|
|
|
]); |
221
|
|
|
|
222
|
|
|
self::assertSame($this->blogMutation, $schema->getMutationType()); |
223
|
|
|
$writeMutation = $this->blogMutation->getField('writeArticle'); |
224
|
|
|
|
225
|
|
|
self::assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $writeMutation); |
226
|
|
|
self::assertSame($this->blogArticle, $writeMutation->getType()); |
227
|
|
|
self::assertSame('Article', $writeMutation->getType()->name); |
228
|
|
|
self::assertSame('writeArticle', $writeMutation->name); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* @see it('defines a subscription schema') |
233
|
|
|
*/ |
234
|
|
|
public function testDefinesSubscriptionSchema() : void |
235
|
|
|
{ |
236
|
|
|
$schema = new Schema([ |
237
|
|
|
'query' => $this->blogQuery, |
238
|
|
|
'subscription' => $this->blogSubscription, |
239
|
|
|
]); |
240
|
|
|
|
241
|
|
|
self::assertEquals($this->blogSubscription, $schema->getSubscriptionType()); |
242
|
|
|
|
243
|
|
|
$sub = $this->blogSubscription->getField('articleSubscribe'); |
244
|
|
|
self::assertEquals($sub->getType(), $this->blogArticle); |
245
|
|
|
self::assertEquals($sub->getType()->name, 'Article'); |
246
|
|
|
self::assertEquals($sub->name, 'articleSubscribe'); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* @see it('defines an enum type with deprecated value') |
251
|
|
|
*/ |
252
|
|
|
public function testDefinesEnumTypeWithDeprecatedValue() : void |
253
|
|
|
{ |
254
|
|
|
$enumTypeWithDeprecatedValue = new EnumType([ |
255
|
|
|
'name' => 'EnumWithDeprecatedValue', |
256
|
|
|
'values' => [ |
257
|
|
|
'foo' => ['deprecationReason' => 'Just because'], |
258
|
|
|
], |
259
|
|
|
]); |
260
|
|
|
|
261
|
|
|
$value = $enumTypeWithDeprecatedValue->getValues()[0]; |
262
|
|
|
|
263
|
|
|
self::assertArraySubset( |
|
|
|
|
264
|
|
|
[ |
265
|
|
|
'name' => 'foo', |
266
|
|
|
'description' => null, |
267
|
|
|
'deprecationReason' => 'Just because', |
268
|
|
|
'value' => 'foo', |
269
|
|
|
'astNode' => null, |
270
|
|
|
], |
271
|
|
|
(array) $value |
272
|
|
|
); |
273
|
|
|
|
274
|
|
|
self::assertEquals(true, $value->isDeprecated()); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* @see it('defines an enum type with a value of `null` and `undefined`') |
279
|
|
|
*/ |
280
|
|
|
public function testDefinesAnEnumTypeWithAValueOfNullAndUndefined() : void |
281
|
|
|
{ |
282
|
|
|
$EnumTypeWithNullishValue = new EnumType([ |
283
|
|
|
'name' => 'EnumWithNullishValue', |
284
|
|
|
'values' => [ |
285
|
|
|
'NULL' => ['value' => null], |
286
|
|
|
'UNDEFINED' => ['value' => null], |
287
|
|
|
], |
288
|
|
|
]); |
289
|
|
|
|
290
|
|
|
$expected = [ |
291
|
|
|
[ |
292
|
|
|
'name' => 'NULL', |
293
|
|
|
'description' => null, |
294
|
|
|
'deprecationReason' => null, |
295
|
|
|
'value' => null, |
296
|
|
|
'astNode' => null, |
297
|
|
|
], |
298
|
|
|
[ |
299
|
|
|
'name' => 'UNDEFINED', |
300
|
|
|
'description' => null, |
301
|
|
|
'deprecationReason' => null, |
302
|
|
|
'value' => null, |
303
|
|
|
'astNode' => null, |
304
|
|
|
], |
305
|
|
|
]; |
306
|
|
|
|
307
|
|
|
$actual = $EnumTypeWithNullishValue->getValues(); |
308
|
|
|
|
309
|
|
|
self::assertEquals(count($expected), count($actual)); |
310
|
|
|
self::assertArraySubset($expected[0], (array) $actual[0]); |
|
|
|
|
311
|
|
|
self::assertArraySubset($expected[1], (array) $actual[1]); |
|
|
|
|
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* @see it('defines an object type with deprecated field') |
316
|
|
|
*/ |
317
|
|
|
public function testDefinesAnObjectTypeWithDeprecatedField() : void |
318
|
|
|
{ |
319
|
|
|
$TypeWithDeprecatedField = new ObjectType([ |
320
|
|
|
'name' => 'foo', |
321
|
|
|
'fields' => [ |
322
|
|
|
'bar' => [ |
323
|
|
|
'type' => Type::string(), |
324
|
|
|
'deprecationReason' => 'A terrible reason', |
325
|
|
|
], |
326
|
|
|
], |
327
|
|
|
]); |
328
|
|
|
|
329
|
|
|
$field = $TypeWithDeprecatedField->getField('bar'); |
330
|
|
|
|
331
|
|
|
self::assertEquals(Type::string(), $field->getType()); |
332
|
|
|
self::assertEquals(true, $field->isDeprecated()); |
333
|
|
|
self::assertEquals('A terrible reason', $field->deprecationReason); |
334
|
|
|
self::assertEquals('bar', $field->name); |
335
|
|
|
self::assertEquals([], $field->args); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* @see it('includes nested input objects in the map') |
340
|
|
|
*/ |
341
|
|
|
public function testIncludesNestedInputObjectInTheMap() : void |
342
|
|
|
{ |
343
|
|
|
$nestedInputObject = new InputObjectType([ |
344
|
|
|
'name' => 'NestedInputObject', |
345
|
|
|
'fields' => ['value' => ['type' => Type::string()]], |
346
|
|
|
]); |
347
|
|
|
$someInputObject = new InputObjectType([ |
348
|
|
|
'name' => 'SomeInputObject', |
349
|
|
|
'fields' => ['nested' => ['type' => $nestedInputObject]], |
350
|
|
|
]); |
351
|
|
|
$someMutation = new ObjectType([ |
352
|
|
|
'name' => 'SomeMutation', |
353
|
|
|
'fields' => [ |
354
|
|
|
'mutateSomething' => [ |
355
|
|
|
'type' => $this->blogArticle, |
356
|
|
|
'args' => ['input' => ['type' => $someInputObject]], |
357
|
|
|
], |
358
|
|
|
], |
359
|
|
|
]); |
360
|
|
|
|
361
|
|
|
$schema = new Schema([ |
362
|
|
|
'query' => $this->blogQuery, |
363
|
|
|
'mutation' => $someMutation, |
364
|
|
|
]); |
365
|
|
|
self::assertSame($nestedInputObject, $schema->getType('NestedInputObject')); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* @see it('includes interface possible types in the type map') |
370
|
|
|
*/ |
371
|
|
|
public function testIncludesInterfaceSubtypesInTheTypeMap() : void |
372
|
|
|
{ |
373
|
|
|
$someInterface = new InterfaceType([ |
374
|
|
|
'name' => 'SomeInterface', |
375
|
|
|
'fields' => [ |
376
|
|
|
'f' => ['type' => Type::int()], |
377
|
|
|
], |
378
|
|
|
]); |
379
|
|
|
|
380
|
|
|
$someSubtype = new ObjectType([ |
381
|
|
|
'name' => 'SomeSubtype', |
382
|
|
|
'fields' => [ |
383
|
|
|
'f' => ['type' => Type::int()], |
384
|
|
|
], |
385
|
|
|
'interfaces' => [$someInterface], |
386
|
|
|
]); |
387
|
|
|
|
388
|
|
|
$schema = new Schema([ |
389
|
|
|
'query' => new ObjectType([ |
390
|
|
|
'name' => 'Query', |
391
|
|
|
'fields' => [ |
392
|
|
|
'iface' => ['type' => $someInterface], |
393
|
|
|
], |
394
|
|
|
]), |
395
|
|
|
'types' => [$someSubtype], |
396
|
|
|
]); |
397
|
|
|
self::assertSame($someSubtype, $schema->getType('SomeSubtype')); |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
/** |
401
|
|
|
* @see it('includes interfaces' thunk subtypes in the type map') |
402
|
|
|
*/ |
403
|
|
|
public function testIncludesInterfacesThunkSubtypesInTheTypeMap() : void |
404
|
|
|
{ |
405
|
|
|
$someInterface = null; |
406
|
|
|
|
407
|
|
|
$someSubtype = new ObjectType([ |
408
|
|
|
'name' => 'SomeSubtype', |
409
|
|
|
'fields' => [ |
410
|
|
|
'f' => ['type' => Type::int()], |
411
|
|
|
], |
412
|
|
|
'interfaces' => static function () use (&$someInterface) { |
413
|
|
|
return [$someInterface]; |
414
|
|
|
}, |
415
|
|
|
]); |
416
|
|
|
|
417
|
|
|
$someInterface = new InterfaceType([ |
418
|
|
|
'name' => 'SomeInterface', |
419
|
|
|
'fields' => [ |
420
|
|
|
'f' => ['type' => Type::int()], |
421
|
|
|
], |
422
|
|
|
]); |
423
|
|
|
|
424
|
|
|
$schema = new Schema([ |
425
|
|
|
'query' => new ObjectType([ |
426
|
|
|
'name' => 'Query', |
427
|
|
|
'fields' => [ |
428
|
|
|
'iface' => ['type' => $someInterface], |
429
|
|
|
], |
430
|
|
|
]), |
431
|
|
|
'types' => [$someSubtype], |
432
|
|
|
]); |
433
|
|
|
|
434
|
|
|
self::assertSame($someSubtype, $schema->getType('SomeSubtype')); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* @see it('stringifies simple types') |
439
|
|
|
*/ |
440
|
|
|
public function testStringifiesSimpleTypes() : void |
441
|
|
|
{ |
442
|
|
|
self::assertSame('Int', (string) Type::int()); |
443
|
|
|
self::assertSame('Article', (string) $this->blogArticle); |
444
|
|
|
|
445
|
|
|
self::assertSame('Interface', (string) $this->interfaceType); |
446
|
|
|
self::assertSame('Union', (string) $this->unionType); |
447
|
|
|
self::assertSame('Enum', (string) $this->enumType); |
448
|
|
|
self::assertSame('InputObject', (string) $this->inputObjectType); |
449
|
|
|
self::assertSame('Object', (string) $this->objectType); |
450
|
|
|
|
451
|
|
|
self::assertSame('Int!', (string) new NonNull(Type::int())); |
452
|
|
|
self::assertSame('[Int]', (string) new ListOfType(Type::int())); |
453
|
|
|
self::assertSame('[Int]!', (string) new NonNull(new ListOfType(Type::int()))); |
454
|
|
|
self::assertSame('[Int!]', (string) new ListOfType(new NonNull(Type::int()))); |
455
|
|
|
self::assertSame('[[Int]]', (string) new ListOfType(new ListOfType(Type::int()))); |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* @see it('JSON stringifies simple types') |
460
|
|
|
*/ |
461
|
|
|
public function testJSONStringifiesSimpleTypes() : void |
462
|
|
|
{ |
463
|
|
|
self::assertEquals('"Int"', json_encode(Type::int())); |
464
|
|
|
self::assertEquals('"Article"', json_encode($this->blogArticle)); |
465
|
|
|
self::assertEquals('"Interface"', json_encode($this->interfaceType)); |
466
|
|
|
self::assertEquals('"Union"', json_encode($this->unionType)); |
467
|
|
|
self::assertEquals('"Enum"', json_encode($this->enumType)); |
468
|
|
|
self::assertEquals('"InputObject"', json_encode($this->inputObjectType)); |
469
|
|
|
self::assertEquals('"Int!"', json_encode(Type::nonNull(Type::int()))); |
470
|
|
|
self::assertEquals('"[Int]"', json_encode(Type::listOf(Type::int()))); |
471
|
|
|
self::assertEquals('"[Int]!"', json_encode(Type::nonNull(Type::listOf(Type::int())))); |
472
|
|
|
self::assertEquals('"[Int!]"', json_encode(Type::listOf(Type::nonNull(Type::int())))); |
473
|
|
|
self::assertEquals('"[[Int]]"', json_encode(Type::listOf(Type::listOf(Type::int())))); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
/** |
477
|
|
|
* @see it('identifies input types') |
478
|
|
|
*/ |
479
|
|
|
public function testIdentifiesInputTypes() : void |
480
|
|
|
{ |
481
|
|
|
$expected = [ |
482
|
|
|
[Type::int(), true], |
483
|
|
|
[$this->objectType, false], |
484
|
|
|
[$this->interfaceType, false], |
485
|
|
|
[$this->unionType, false], |
486
|
|
|
[$this->enumType, true], |
487
|
|
|
[$this->inputObjectType, true], |
488
|
|
|
|
489
|
|
|
[Type::boolean(), true], |
490
|
|
|
[Type::float(),true ], |
491
|
|
|
[Type::id(), true], |
492
|
|
|
[Type::int(), true], |
493
|
|
|
[Type::listOf(Type::string()), true], |
494
|
|
|
[Type::listOf($this->objectType), false], |
495
|
|
|
[Type::nonNull(Type::string()), true], |
496
|
|
|
[Type::nonNull($this->objectType), false], |
497
|
|
|
[Type::string(), true], |
498
|
|
|
]; |
499
|
|
|
|
500
|
|
|
foreach ($expected as $index => $entry) { |
501
|
|
|
self::assertSame( |
502
|
|
|
$entry[1], |
503
|
|
|
Type::isInputType($entry[0]), |
504
|
|
|
sprintf('Type %s was detected incorrectly', $entry[0]) |
505
|
|
|
); |
506
|
|
|
} |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
/** |
510
|
|
|
* @see it('identifies output types') |
511
|
|
|
*/ |
512
|
|
|
public function testIdentifiesOutputTypes() : void |
513
|
|
|
{ |
514
|
|
|
$expected = [ |
515
|
|
|
[Type::int(), true], |
516
|
|
|
[$this->objectType, true], |
517
|
|
|
[$this->interfaceType, true], |
518
|
|
|
[$this->unionType, true], |
519
|
|
|
[$this->enumType, true], |
520
|
|
|
[$this->inputObjectType, false], |
521
|
|
|
|
522
|
|
|
[Type::boolean(), true], |
523
|
|
|
[Type::float(),true ], |
524
|
|
|
[Type::id(), true], |
525
|
|
|
[Type::int(), true], |
526
|
|
|
[Type::listOf(Type::string()), true], |
527
|
|
|
[Type::listOf($this->objectType), true], |
528
|
|
|
[Type::nonNull(Type::string()), true], |
529
|
|
|
[Type::nonNull($this->objectType), true], |
530
|
|
|
[Type::string(), true], |
531
|
|
|
]; |
532
|
|
|
|
533
|
|
|
foreach ($expected as $index => $entry) { |
534
|
|
|
self::assertSame( |
535
|
|
|
$entry[1], |
536
|
|
|
Type::isOutputType($entry[0]), |
537
|
|
|
sprintf('Type %s was detected incorrectly', $entry[0]) |
538
|
|
|
); |
539
|
|
|
} |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* @see it('allows a thunk for Union member types') |
544
|
|
|
*/ |
545
|
|
|
public function testAllowsThunkForUnionTypes() : void |
546
|
|
|
{ |
547
|
|
|
$union = new UnionType([ |
548
|
|
|
'name' => 'ThunkUnion', |
549
|
|
|
'types' => function () { |
550
|
|
|
return [$this->objectType]; |
551
|
|
|
}, |
552
|
|
|
]); |
553
|
|
|
|
554
|
|
|
$types = $union->getTypes(); |
555
|
|
|
self::assertEquals(1, count($types)); |
556
|
|
|
self::assertSame($this->objectType, $types[0]); |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
public function testAllowsRecursiveDefinitions() : void |
560
|
|
|
{ |
561
|
|
|
// See https://github.com/webonyx/graphql-php/issues/16 |
562
|
|
|
$node = new InterfaceType([ |
563
|
|
|
'name' => 'Node', |
564
|
|
|
'fields' => [ |
565
|
|
|
'id' => ['type' => Type::nonNull(Type::id())], |
566
|
|
|
], |
567
|
|
|
]); |
568
|
|
|
|
569
|
|
|
$blog = null; |
570
|
|
|
$called = false; |
571
|
|
|
|
572
|
|
|
$user = new ObjectType([ |
573
|
|
|
'name' => 'User', |
574
|
|
|
'fields' => static function () use (&$blog, &$called) { |
575
|
|
|
self::assertNotNull($blog, 'Blog type is expected to be defined at this point, but it is null'); |
576
|
|
|
$called = true; |
577
|
|
|
|
578
|
|
|
return [ |
579
|
|
|
'id' => ['type' => Type::nonNull(Type::id())], |
580
|
|
|
'blogs' => ['type' => Type::nonNull(Type::listOf(Type::nonNull($blog)))], |
581
|
|
|
]; |
582
|
|
|
}, |
583
|
|
|
'interfaces' => static function () use ($node) { |
584
|
|
|
return [$node]; |
585
|
|
|
}, |
586
|
|
|
]); |
587
|
|
|
|
588
|
|
|
$blog = new ObjectType([ |
589
|
|
|
'name' => 'Blog', |
590
|
|
|
'fields' => static function () use ($user) { |
591
|
|
|
return [ |
592
|
|
|
'id' => ['type' => Type::nonNull(Type::id())], |
593
|
|
|
'owner' => ['type' => Type::nonNull($user)], |
594
|
|
|
]; |
595
|
|
|
}, |
596
|
|
|
'interfaces' => static function () use ($node) { |
597
|
|
|
return [$node]; |
598
|
|
|
}, |
599
|
|
|
]); |
600
|
|
|
|
601
|
|
|
$schema = new Schema([ |
602
|
|
|
'query' => new ObjectType([ |
603
|
|
|
'name' => 'Query', |
604
|
|
|
'fields' => [ |
605
|
|
|
'node' => ['type' => $node], |
606
|
|
|
], |
607
|
|
|
]), |
608
|
|
|
'types' => [$user, $blog], |
609
|
|
|
]); |
610
|
|
|
|
611
|
|
|
self::assertTrue($called); |
612
|
|
|
$schema->getType('Blog'); |
613
|
|
|
|
614
|
|
|
self::assertEquals([$node], $blog->getInterfaces()); |
615
|
|
|
self::assertEquals([$node], $user->getInterfaces()); |
616
|
|
|
|
617
|
|
|
self::assertNotNull($user->getField('blogs')); |
618
|
|
|
/** @var NonNull $blogFieldReturnType */ |
619
|
|
|
$blogFieldReturnType = $user->getField('blogs')->getType(); |
620
|
|
|
self::assertSame($blog, $blogFieldReturnType->getWrappedType(true)); |
621
|
|
|
|
622
|
|
|
self::assertNotNull($blog->getField('owner')); |
623
|
|
|
/** @var NonNull $ownerFieldReturnType */ |
624
|
|
|
$ownerFieldReturnType = $blog->getField('owner')->getType(); |
625
|
|
|
self::assertSame($user, $ownerFieldReturnType->getWrappedType(true)); |
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
public function testInputObjectTypeAllowsRecursiveDefinitions() : void |
629
|
|
|
{ |
630
|
|
|
$called = false; |
631
|
|
|
$inputObject = new InputObjectType([ |
632
|
|
|
'name' => 'InputObject', |
633
|
|
|
'fields' => static function () use (&$inputObject, &$called) { |
634
|
|
|
$called = true; |
635
|
|
|
|
636
|
|
|
return [ |
637
|
|
|
'value' => ['type' => Type::string()], |
638
|
|
|
'nested' => ['type' => $inputObject], |
639
|
|
|
]; |
640
|
|
|
}, |
641
|
|
|
]); |
642
|
|
|
|
643
|
|
|
$someMutation = new ObjectType([ |
644
|
|
|
'name' => 'SomeMutation', |
645
|
|
|
'fields' => [ |
646
|
|
|
'mutateSomething' => [ |
647
|
|
|
'type' => $this->blogArticle, |
648
|
|
|
'args' => ['input' => ['type' => $inputObject]], |
649
|
|
|
], |
650
|
|
|
], |
651
|
|
|
]); |
652
|
|
|
|
653
|
|
|
$schema = new Schema([ |
654
|
|
|
'query' => $this->blogQuery, |
655
|
|
|
'mutation' => $someMutation, |
656
|
|
|
]); |
657
|
|
|
|
658
|
|
|
self::assertSame($inputObject, $schema->getType('InputObject')); |
659
|
|
|
self::assertTrue($called); |
660
|
|
|
self::assertEquals(count($inputObject->getFields()), 2); |
661
|
|
|
self::assertSame($inputObject->getField('nested')->getType(), $inputObject); |
662
|
|
|
self::assertSame($someMutation->getField('mutateSomething')->getArg('input')->getType(), $inputObject); |
663
|
|
|
} |
664
|
|
|
|
665
|
|
|
public function testInterfaceTypeAllowsRecursiveDefinitions() : void |
666
|
|
|
{ |
667
|
|
|
$called = false; |
668
|
|
|
$interface = new InterfaceType([ |
669
|
|
|
'name' => 'SomeInterface', |
670
|
|
|
'fields' => static function () use (&$interface, &$called) { |
671
|
|
|
$called = true; |
672
|
|
|
|
673
|
|
|
return [ |
674
|
|
|
'value' => ['type' => Type::string()], |
675
|
|
|
'nested' => ['type' => $interface], |
676
|
|
|
]; |
677
|
|
|
}, |
678
|
|
|
]); |
679
|
|
|
|
680
|
|
|
$query = new ObjectType([ |
681
|
|
|
'name' => 'Query', |
682
|
|
|
'fields' => [ |
683
|
|
|
'test' => ['type' => $interface], |
684
|
|
|
], |
685
|
|
|
]); |
686
|
|
|
|
687
|
|
|
$schema = new Schema(['query' => $query]); |
688
|
|
|
|
689
|
|
|
self::assertSame($interface, $schema->getType('SomeInterface')); |
690
|
|
|
self::assertTrue($called); |
691
|
|
|
self::assertEquals(count($interface->getFields()), 2); |
692
|
|
|
self::assertSame($interface->getField('nested')->getType(), $interface); |
693
|
|
|
self::assertSame($interface->getField('value')->getType(), Type::string()); |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
public function testAllowsShorthandFieldDefinition() : void |
697
|
|
|
{ |
698
|
|
|
$interface = new InterfaceType([ |
699
|
|
|
'name' => 'SomeInterface', |
700
|
|
|
'fields' => static function () use (&$interface) { |
701
|
|
|
return [ |
702
|
|
|
'value' => Type::string(), |
703
|
|
|
'nested' => $interface, |
704
|
|
|
'withArg' => [ |
705
|
|
|
'type' => Type::string(), |
706
|
|
|
'args' => [ |
707
|
|
|
'arg1' => Type::int(), |
708
|
|
|
], |
709
|
|
|
], |
710
|
|
|
]; |
711
|
|
|
}, |
712
|
|
|
]); |
713
|
|
|
|
714
|
|
|
$query = new ObjectType([ |
715
|
|
|
'name' => 'Query', |
716
|
|
|
'fields' => ['test' => $interface], |
717
|
|
|
]); |
718
|
|
|
|
719
|
|
|
$schema = new Schema(['query' => $query]); |
720
|
|
|
|
721
|
|
|
/** @var InterfaceType $SomeInterface */ |
722
|
|
|
$SomeInterface = $schema->getType('SomeInterface'); |
723
|
|
|
|
724
|
|
|
$valueField = $SomeInterface->getField('value'); |
725
|
|
|
self::assertEquals(Type::string(), $valueField->getType()); |
726
|
|
|
|
727
|
|
|
$nestedField = $SomeInterface->getField('nested'); |
728
|
|
|
self::assertEquals($interface, $nestedField->getType()); |
729
|
|
|
|
730
|
|
|
$withArg = $SomeInterface->getField('withArg'); |
731
|
|
|
self::assertEquals(Type::string(), $withArg->getType()); |
732
|
|
|
|
733
|
|
|
self::assertEquals('arg1', $withArg->args[0]->name); |
734
|
|
|
self::assertEquals(Type::int(), $withArg->args[0]->getType()); |
735
|
|
|
|
736
|
|
|
/** @var ObjectType $Query */ |
737
|
|
|
$Query = $schema->getType('Query'); |
738
|
|
|
$testField = $Query->getField('test'); |
739
|
|
|
self::assertEquals($interface, $testField->getType()); |
740
|
|
|
self::assertEquals('test', $testField->name); |
741
|
|
|
} |
742
|
|
|
|
743
|
|
|
public function testInfersNameFromClassname() : void |
744
|
|
|
{ |
745
|
|
|
$myObj = new MyCustomType(); |
746
|
|
|
self::assertEquals('MyCustom', $myObj->name); |
747
|
|
|
|
748
|
|
|
$otherCustom = new OtherCustom(); |
749
|
|
|
self::assertEquals('OtherCustom', $otherCustom->name); |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
public function testAllowsOverridingInternalTypes() : void |
753
|
|
|
{ |
754
|
|
|
$idType = new CustomScalarType([ |
755
|
|
|
'name' => 'ID', |
756
|
|
|
'serialize' => static function () { |
757
|
|
|
}, |
758
|
|
|
'parseValue' => static function () { |
759
|
|
|
}, |
760
|
|
|
'parseLiteral' => static function () { |
761
|
|
|
}, |
762
|
|
|
]); |
763
|
|
|
|
764
|
|
|
$schema = new Schema([ |
765
|
|
|
'query' => new ObjectType(['name' => 'Query', 'fields' => []]), |
766
|
|
|
'types' => [$idType], |
767
|
|
|
]); |
768
|
|
|
|
769
|
|
|
self::assertSame($idType, $schema->getType('ID')); |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
// Field config must be object |
773
|
|
|
|
774
|
|
|
/** |
775
|
|
|
* @see it('accepts an Object type with a field function') |
776
|
|
|
*/ |
777
|
|
|
public function testAcceptsAnObjectTypeWithAFieldFunction() : void |
778
|
|
|
{ |
779
|
|
|
$objType = new ObjectType([ |
780
|
|
|
'name' => 'SomeObject', |
781
|
|
|
'fields' => static function () { |
782
|
|
|
return [ |
783
|
|
|
'f' => ['type' => Type::string()], |
784
|
|
|
]; |
785
|
|
|
}, |
786
|
|
|
]); |
787
|
|
|
$objType->assertValid(); |
788
|
|
|
self::assertSame(Type::string(), $objType->getField('f')->getType()); |
789
|
|
|
} |
790
|
|
|
|
791
|
|
|
/** |
792
|
|
|
* @see it('rejects an Object type field with undefined config') |
793
|
|
|
*/ |
794
|
|
|
public function testRejectsAnObjectTypeFieldWithUndefinedConfig() : void |
795
|
|
|
{ |
796
|
|
|
$objType = new ObjectType([ |
797
|
|
|
'name' => 'SomeObject', |
798
|
|
|
'fields' => ['f' => null], |
799
|
|
|
]); |
800
|
|
|
$this->expectException(InvariantViolation::class); |
801
|
|
|
$this->expectExceptionMessage( |
802
|
|
|
'SomeObject.f field config must be an array, but got: null' |
803
|
|
|
); |
804
|
|
|
$objType->getFields(); |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
/** |
808
|
|
|
* @see it('rejects an Object type with incorrectly typed fields') |
809
|
|
|
*/ |
810
|
|
|
public function testRejectsAnObjectTypeWithIncorrectlyTypedFields() : void |
811
|
|
|
{ |
812
|
|
|
$objType = new ObjectType([ |
813
|
|
|
'name' => 'SomeObject', |
814
|
|
|
'fields' => [['field' => Type::string()]], |
815
|
|
|
]); |
816
|
|
|
$this->expectException(InvariantViolation::class); |
817
|
|
|
$this->expectExceptionMessage( |
818
|
|
|
'SomeObject fields must be an associative array with field names as keys or a ' . |
819
|
|
|
'function which returns such an array.' |
820
|
|
|
); |
821
|
|
|
$objType->getFields(); |
822
|
|
|
} |
823
|
|
|
|
824
|
|
|
/** |
825
|
|
|
* @see it('rejects an Object type with a field function that returns incorrect type') |
826
|
|
|
*/ |
827
|
|
|
public function testRejectsAnObjectTypeWithAFieldFunctionThatReturnsIncorrectType() : void |
828
|
|
|
{ |
829
|
|
|
$objType = new ObjectType([ |
830
|
|
|
'name' => 'SomeObject', |
831
|
|
|
'fields' => static function () { |
832
|
|
|
return [['field' => Type::string()]]; |
833
|
|
|
}, |
834
|
|
|
]); |
835
|
|
|
$this->expectException(InvariantViolation::class); |
836
|
|
|
$this->expectExceptionMessage( |
837
|
|
|
'SomeObject fields must be an associative array with field names as keys or a ' . |
838
|
|
|
'function which returns such an array.' |
839
|
|
|
); |
840
|
|
|
$objType->getFields(); |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
// Field arg config must be object |
844
|
|
|
|
845
|
|
|
/** |
846
|
|
|
* @see it('accepts an Object type with field args') |
847
|
|
|
*/ |
848
|
|
|
public function testAcceptsAnObjectTypeWithFieldArgs() : void |
849
|
|
|
{ |
850
|
|
|
$this->expectNotToPerformAssertions(); |
851
|
|
|
$objType = new ObjectType([ |
852
|
|
|
'name' => 'SomeObject', |
853
|
|
|
'fields' => [ |
854
|
|
|
'goodField' => [ |
855
|
|
|
'type' => Type::string(), |
856
|
|
|
'args' => [ |
857
|
|
|
'goodArg' => ['type' => Type::string()], |
858
|
|
|
], |
859
|
|
|
], |
860
|
|
|
], |
861
|
|
|
]); |
862
|
|
|
// Should not throw: |
863
|
|
|
$objType->assertValid(); |
864
|
|
|
} |
865
|
|
|
|
866
|
|
|
// rejects an Object type with incorrectly typed field args |
867
|
|
|
|
868
|
|
|
/** |
869
|
|
|
* @see it('does not allow isDeprecated without deprecationReason on field') |
870
|
|
|
*/ |
871
|
|
|
public function testDoesNotAllowIsDeprecatedWithoutDeprecationReasonOnField() : void |
872
|
|
|
{ |
873
|
|
|
$OldObject = new ObjectType([ |
874
|
|
|
'name' => 'OldObject', |
875
|
|
|
'fields' => [ |
876
|
|
|
'field' => [ |
877
|
|
|
'type' => Type::string(), |
878
|
|
|
'isDeprecated' => true, |
879
|
|
|
], |
880
|
|
|
], |
881
|
|
|
]); |
882
|
|
|
|
883
|
|
|
$this->expectException(InvariantViolation::class); |
884
|
|
|
$this->expectExceptionMessage( |
885
|
|
|
'OldObject.field should provide "deprecationReason" instead of "isDeprecated".' |
886
|
|
|
); |
887
|
|
|
$OldObject->assertValid(); |
888
|
|
|
} |
889
|
|
|
|
890
|
|
|
// Object interfaces must be array |
891
|
|
|
|
892
|
|
|
/** |
893
|
|
|
* @see it('accepts an Object type with array interfaces') |
894
|
|
|
*/ |
895
|
|
|
public function testAcceptsAnObjectTypeWithArrayInterfaces() : void |
896
|
|
|
{ |
897
|
|
|
$objType = new ObjectType([ |
898
|
|
|
'name' => 'SomeObject', |
899
|
|
|
'interfaces' => [$this->interfaceType], |
900
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
901
|
|
|
]); |
902
|
|
|
self::assertSame($this->interfaceType, $objType->getInterfaces()[0]); |
903
|
|
|
} |
904
|
|
|
|
905
|
|
|
/** |
906
|
|
|
* @see it('accepts an Object type with interfaces as a function returning an array') |
907
|
|
|
*/ |
908
|
|
|
public function testAcceptsAnObjectTypeWithInterfacesAsAFunctionReturningAnArray() : void |
909
|
|
|
{ |
910
|
|
|
$objType = new ObjectType([ |
911
|
|
|
'name' => 'SomeObject', |
912
|
|
|
'interfaces' => function () { |
913
|
|
|
return [$this->interfaceType]; |
914
|
|
|
}, |
915
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
916
|
|
|
]); |
917
|
|
|
self::assertSame($this->interfaceType, $objType->getInterfaces()[0]); |
918
|
|
|
} |
919
|
|
|
|
920
|
|
|
/** |
921
|
|
|
* @see it('rejects an Object type with incorrectly typed interfaces') |
922
|
|
|
*/ |
923
|
|
|
public function testRejectsAnObjectTypeWithIncorrectlyTypedInterfaces() : void |
924
|
|
|
{ |
925
|
|
|
$objType = new ObjectType([ |
926
|
|
|
'name' => 'SomeObject', |
927
|
|
|
'interfaces' => new stdClass(), |
928
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
929
|
|
|
]); |
930
|
|
|
$this->expectException(InvariantViolation::class); |
931
|
|
|
$this->expectExceptionMessage( |
932
|
|
|
'SomeObject interfaces must be an Array or a callable which returns an Array.' |
933
|
|
|
); |
934
|
|
|
$objType->getInterfaces(); |
935
|
|
|
} |
936
|
|
|
|
937
|
|
|
/** |
938
|
|
|
* @see it('rejects an Object type with interfaces as a function returning an incorrect type') |
939
|
|
|
*/ |
940
|
|
|
public function testRejectsAnObjectTypeWithInterfacesAsAFunctionReturningAnIncorrectType() : void |
941
|
|
|
{ |
942
|
|
|
$objType = new ObjectType([ |
943
|
|
|
'name' => 'SomeObject', |
944
|
|
|
'interfaces' => static function () { |
945
|
|
|
return new stdClass(); |
946
|
|
|
}, |
947
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
948
|
|
|
]); |
949
|
|
|
$this->expectException(InvariantViolation::class); |
950
|
|
|
$this->expectExceptionMessage( |
951
|
|
|
'SomeObject interfaces must be an Array or a callable which returns an Array.' |
952
|
|
|
); |
953
|
|
|
$objType->getInterfaces(); |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
// Type System: Object fields must have valid resolve values |
957
|
|
|
|
958
|
|
|
/** |
959
|
|
|
* @see it('accepts a lambda as an Object field resolver') |
960
|
|
|
*/ |
961
|
|
|
public function testAcceptsALambdaAsAnObjectFieldResolver() : void |
962
|
|
|
{ |
963
|
|
|
$this->expectNotToPerformAssertions(); |
964
|
|
|
// should not throw: |
965
|
|
|
$this->schemaWithObjectWithFieldResolver(static function () { |
966
|
|
|
}); |
967
|
|
|
} |
968
|
|
|
|
969
|
|
|
private function schemaWithObjectWithFieldResolver($resolveValue) |
970
|
|
|
{ |
971
|
|
|
$BadResolverType = new ObjectType([ |
972
|
|
|
'name' => 'BadResolver', |
973
|
|
|
'fields' => [ |
974
|
|
|
'badField' => [ |
975
|
|
|
'type' => Type::string(), |
976
|
|
|
'resolve' => $resolveValue, |
977
|
|
|
], |
978
|
|
|
], |
979
|
|
|
]); |
980
|
|
|
|
981
|
|
|
$schema = new Schema([ |
982
|
|
|
'query' => new ObjectType([ |
983
|
|
|
'name' => 'Query', |
984
|
|
|
'fields' => [ |
985
|
|
|
'f' => ['type' => $BadResolverType], |
986
|
|
|
], |
987
|
|
|
]), |
988
|
|
|
]); |
989
|
|
|
$schema->assertValid(); |
990
|
|
|
|
991
|
|
|
return $schema; |
992
|
|
|
} |
993
|
|
|
|
994
|
|
|
/** |
995
|
|
|
* @see it('rejects an empty Object field resolver') |
996
|
|
|
*/ |
997
|
|
|
public function testRejectsAnEmptyObjectFieldResolver() : void |
998
|
|
|
{ |
999
|
|
|
$this->expectException(InvariantViolation::class); |
1000
|
|
|
$this->expectExceptionMessage( |
1001
|
|
|
'BadResolver.badField field resolver must be a function if provided, but got: []' |
1002
|
|
|
); |
1003
|
|
|
$this->schemaWithObjectWithFieldResolver([]); |
1004
|
|
|
} |
1005
|
|
|
|
1006
|
|
|
/** |
1007
|
|
|
* @see it('rejects a constant scalar value resolver') |
1008
|
|
|
*/ |
1009
|
|
|
public function testRejectsAConstantScalarValueResolver() : void |
1010
|
|
|
{ |
1011
|
|
|
$this->expectException(InvariantViolation::class); |
1012
|
|
|
$this->expectExceptionMessage( |
1013
|
|
|
'BadResolver.badField field resolver must be a function if provided, but got: 0' |
1014
|
|
|
); |
1015
|
|
|
$this->schemaWithObjectWithFieldResolver(0); |
1016
|
|
|
} |
1017
|
|
|
|
1018
|
|
|
// Type System: Interface types must be resolvable |
1019
|
|
|
|
1020
|
|
|
/** |
1021
|
|
|
* @see it('accepts an Interface type defining resolveType') |
1022
|
|
|
*/ |
1023
|
|
|
public function testAcceptsAnInterfaceTypeDefiningResolveType() : void |
1024
|
|
|
{ |
1025
|
|
|
$this->expectNotToPerformAssertions(); |
1026
|
|
|
$AnotherInterfaceType = new InterfaceType([ |
1027
|
|
|
'name' => 'AnotherInterface', |
1028
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1029
|
|
|
]); |
1030
|
|
|
|
1031
|
|
|
// Should not throw: |
1032
|
|
|
$this->schemaWithFieldType( |
1033
|
|
|
new ObjectType([ |
1034
|
|
|
'name' => 'SomeObject', |
1035
|
|
|
'interfaces' => [$AnotherInterfaceType], |
1036
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1037
|
|
|
]) |
1038
|
|
|
); |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
|
|
private function schemaWithFieldType($type) |
1042
|
|
|
{ |
1043
|
|
|
$schema = new Schema([ |
1044
|
|
|
'query' => new ObjectType([ |
1045
|
|
|
'name' => 'Query', |
1046
|
|
|
'fields' => ['field' => ['type' => $type]], |
1047
|
|
|
]), |
1048
|
|
|
'types' => [$type], |
1049
|
|
|
]); |
1050
|
|
|
$schema->assertValid(); |
1051
|
|
|
|
1052
|
|
|
return $schema; |
1053
|
|
|
} |
1054
|
|
|
|
1055
|
|
|
/** |
1056
|
|
|
* @see it('accepts an Interface with implementing type defining isTypeOf') |
1057
|
|
|
*/ |
1058
|
|
|
public function testAcceptsAnInterfaceWithImplementingTypeDefiningIsTypeOf() : void |
1059
|
|
|
{ |
1060
|
|
|
$this->expectNotToPerformAssertions(); |
1061
|
|
|
$InterfaceTypeWithoutResolveType = new InterfaceType([ |
1062
|
|
|
'name' => 'InterfaceTypeWithoutResolveType', |
1063
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1064
|
|
|
]); |
1065
|
|
|
|
1066
|
|
|
// Should not throw: |
1067
|
|
|
$this->schemaWithFieldType( |
1068
|
|
|
new ObjectType([ |
1069
|
|
|
'name' => 'SomeObject', |
1070
|
|
|
'interfaces' => [$InterfaceTypeWithoutResolveType], |
1071
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1072
|
|
|
]) |
1073
|
|
|
); |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
|
|
/** |
1077
|
|
|
* @see it('accepts an Interface type defining resolveType with implementing type defining isTypeOf') |
1078
|
|
|
*/ |
1079
|
|
|
public function testAcceptsAnInterfaceTypeDefiningResolveTypeWithImplementingTypeDefiningIsTypeOf() : void |
1080
|
|
|
{ |
1081
|
|
|
$this->expectNotToPerformAssertions(); |
1082
|
|
|
$AnotherInterfaceType = new InterfaceType([ |
1083
|
|
|
'name' => 'AnotherInterface', |
1084
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1085
|
|
|
]); |
1086
|
|
|
|
1087
|
|
|
// Should not throw: |
1088
|
|
|
$this->schemaWithFieldType( |
1089
|
|
|
new ObjectType([ |
1090
|
|
|
'name' => 'SomeObject', |
1091
|
|
|
'interfaces' => [$AnotherInterfaceType], |
1092
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1093
|
|
|
]) |
1094
|
|
|
); |
1095
|
|
|
} |
1096
|
|
|
|
1097
|
|
|
/** |
1098
|
|
|
* @see it('rejects an Interface type with an incorrect type for resolveType') |
1099
|
|
|
*/ |
1100
|
|
|
public function testRejectsAnInterfaceTypeWithAnIncorrectTypeForResolveType() : void |
1101
|
|
|
{ |
1102
|
|
|
$this->expectException(InvariantViolation::class); |
1103
|
|
|
$this->expectExceptionMessage( |
1104
|
|
|
'AnotherInterface must provide "resolveType" as a function, but got: instance of stdClass' |
1105
|
|
|
); |
1106
|
|
|
|
1107
|
|
|
$type = new InterfaceType([ |
1108
|
|
|
'name' => 'AnotherInterface', |
1109
|
|
|
'resolveType' => new stdClass(), |
1110
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1111
|
|
|
]); |
1112
|
|
|
$type->assertValid(); |
1113
|
|
|
} |
1114
|
|
|
|
1115
|
|
|
// Type System: Union types must be resolvable |
1116
|
|
|
|
1117
|
|
|
/** |
1118
|
|
|
* @see it('accepts a Union type defining resolveType') |
1119
|
|
|
*/ |
1120
|
|
|
public function testAcceptsAUnionTypeDefiningResolveType() : void |
1121
|
|
|
{ |
1122
|
|
|
$this->expectNotToPerformAssertions(); |
1123
|
|
|
// Should not throw: |
1124
|
|
|
$this->schemaWithFieldType( |
1125
|
|
|
new UnionType([ |
1126
|
|
|
'name' => 'SomeUnion', |
1127
|
|
|
'types' => [$this->objectType], |
1128
|
|
|
]) |
1129
|
|
|
); |
1130
|
|
|
} |
1131
|
|
|
|
1132
|
|
|
/** |
1133
|
|
|
* @see it('accepts a Union of Object types defining isTypeOf') |
1134
|
|
|
*/ |
1135
|
|
|
public function testAcceptsAUnionOfObjectTypesDefiningIsTypeOf() : void |
1136
|
|
|
{ |
1137
|
|
|
$this->expectNotToPerformAssertions(); |
1138
|
|
|
// Should not throw: |
1139
|
|
|
$this->schemaWithFieldType( |
1140
|
|
|
new UnionType([ |
1141
|
|
|
'name' => 'SomeUnion', |
1142
|
|
|
'types' => [$this->objectWithIsTypeOf], |
1143
|
|
|
]) |
1144
|
|
|
); |
1145
|
|
|
} |
1146
|
|
|
|
1147
|
|
|
/** |
1148
|
|
|
* @see it('accepts a Union type defining resolveType of Object types defining isTypeOf') |
1149
|
|
|
*/ |
1150
|
|
|
public function testAcceptsAUnionTypeDefiningResolveTypeOfObjectTypesDefiningIsTypeOf() : void |
1151
|
|
|
{ |
1152
|
|
|
$this->expectNotToPerformAssertions(); |
1153
|
|
|
// Should not throw: |
1154
|
|
|
$this->schemaWithFieldType( |
1155
|
|
|
new UnionType([ |
1156
|
|
|
'name' => 'SomeUnion', |
1157
|
|
|
'types' => [$this->objectWithIsTypeOf], |
1158
|
|
|
]) |
1159
|
|
|
); |
1160
|
|
|
} |
1161
|
|
|
|
1162
|
|
|
/** |
1163
|
|
|
* @see it('rejects an Union type with an incorrect type for resolveType') |
1164
|
|
|
*/ |
1165
|
|
|
public function testRejectsAnUnionTypeWithAnIncorrectTypeForResolveType() : void |
1166
|
|
|
{ |
1167
|
|
|
$this->expectException(InvariantViolation::class); |
1168
|
|
|
$this->expectExceptionMessage( |
1169
|
|
|
'SomeUnion must provide "resolveType" as a function, but got: instance of stdClass' |
1170
|
|
|
); |
1171
|
|
|
$this->schemaWithFieldType( |
1172
|
|
|
new UnionType([ |
1173
|
|
|
'name' => 'SomeUnion', |
1174
|
|
|
'resolveType' => new stdClass(), |
1175
|
|
|
'types' => [$this->objectWithIsTypeOf], |
1176
|
|
|
]) |
1177
|
|
|
); |
1178
|
|
|
} |
1179
|
|
|
|
1180
|
|
|
/** |
1181
|
|
|
* @see it('accepts a Scalar type defining serialize') |
1182
|
|
|
*/ |
1183
|
|
|
public function testAcceptsAScalarTypeDefiningSerialize() : void |
1184
|
|
|
{ |
1185
|
|
|
$this->expectNotToPerformAssertions(); |
1186
|
|
|
// Should not throw |
1187
|
|
|
$this->schemaWithFieldType( |
1188
|
|
|
new CustomScalarType([ |
1189
|
|
|
'name' => 'SomeScalar', |
1190
|
|
|
'serialize' => static function () { |
1191
|
|
|
return null; |
1192
|
|
|
}, |
1193
|
|
|
]) |
1194
|
|
|
); |
1195
|
|
|
} |
1196
|
|
|
|
1197
|
|
|
// Type System: Scalar types must be serializable |
1198
|
|
|
|
1199
|
|
|
/** |
1200
|
|
|
* @see it('rejects a Scalar type not defining serialize') |
1201
|
|
|
*/ |
1202
|
|
|
public function testRejectsAScalarTypeNotDefiningSerialize() : void |
1203
|
|
|
{ |
1204
|
|
|
$this->expectException(InvariantViolation::class); |
1205
|
|
|
$this->expectExceptionMessage( |
1206
|
|
|
'SomeScalar must provide "serialize" function. If this custom Scalar ' . |
1207
|
|
|
'is also used as an input type, ensure "parseValue" and "parseLiteral" ' . |
1208
|
|
|
'functions are also provided.' |
1209
|
|
|
); |
1210
|
|
|
$this->schemaWithFieldType( |
1211
|
|
|
new CustomScalarType(['name' => 'SomeScalar']) |
1212
|
|
|
); |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
|
|
/** |
1216
|
|
|
* @see it('rejects a Scalar type defining serialize with an incorrect type') |
1217
|
|
|
*/ |
1218
|
|
|
public function testRejectsAScalarTypeDefiningSerializeWithAnIncorrectType() : void |
1219
|
|
|
{ |
1220
|
|
|
$this->expectException(InvariantViolation::class); |
1221
|
|
|
$this->expectExceptionMessage( |
1222
|
|
|
'SomeScalar must provide "serialize" function. If this custom Scalar ' . |
1223
|
|
|
'is also used as an input type, ensure "parseValue" and "parseLiteral" ' . |
1224
|
|
|
'functions are also provided.' |
1225
|
|
|
); |
1226
|
|
|
$this->schemaWithFieldType( |
1227
|
|
|
new CustomScalarType([ |
1228
|
|
|
'name' => 'SomeScalar', |
1229
|
|
|
'serialize' => new stdClass(), |
1230
|
|
|
]) |
1231
|
|
|
); |
1232
|
|
|
} |
1233
|
|
|
|
1234
|
|
|
/** |
1235
|
|
|
* @see it('accepts a Scalar type defining parseValue and parseLiteral') |
1236
|
|
|
*/ |
1237
|
|
|
public function testAcceptsAScalarTypeDefiningParseValueAndParseLiteral() : void |
1238
|
|
|
{ |
1239
|
|
|
$this->expectNotToPerformAssertions(); |
1240
|
|
|
// Should not throw: |
1241
|
|
|
$this->schemaWithFieldType( |
1242
|
|
|
new CustomScalarType([ |
1243
|
|
|
'name' => 'SomeScalar', |
1244
|
|
|
'serialize' => static function () { |
1245
|
|
|
}, |
1246
|
|
|
'parseValue' => static function () { |
1247
|
|
|
}, |
1248
|
|
|
'parseLiteral' => static function () { |
1249
|
|
|
}, |
1250
|
|
|
]) |
1251
|
|
|
); |
1252
|
|
|
} |
1253
|
|
|
|
1254
|
|
|
/** |
1255
|
|
|
* @see it('rejects a Scalar type defining parseValue but not parseLiteral') |
1256
|
|
|
*/ |
1257
|
|
|
public function testRejectsAScalarTypeDefiningParseValueButNotParseLiteral() : void |
1258
|
|
|
{ |
1259
|
|
|
$this->expectException(InvariantViolation::class); |
1260
|
|
|
$this->expectExceptionMessage( |
1261
|
|
|
'SomeScalar must provide both "parseValue" and "parseLiteral" functions.' |
1262
|
|
|
); |
1263
|
|
|
$this->schemaWithFieldType( |
1264
|
|
|
new CustomScalarType([ |
1265
|
|
|
'name' => 'SomeScalar', |
1266
|
|
|
'serialize' => static function () { |
1267
|
|
|
}, |
1268
|
|
|
'parseValue' => static function () { |
1269
|
|
|
}, |
1270
|
|
|
]) |
1271
|
|
|
); |
1272
|
|
|
} |
1273
|
|
|
|
1274
|
|
|
/** |
1275
|
|
|
* @see it('rejects a Scalar type defining parseLiteral but not parseValue') |
1276
|
|
|
*/ |
1277
|
|
|
public function testRejectsAScalarTypeDefiningParseLiteralButNotParseValue() : void |
1278
|
|
|
{ |
1279
|
|
|
$this->expectException(InvariantViolation::class); |
1280
|
|
|
$this->expectExceptionMessage( |
1281
|
|
|
'SomeScalar must provide both "parseValue" and "parseLiteral" functions.' |
1282
|
|
|
); |
1283
|
|
|
$this->schemaWithFieldType( |
1284
|
|
|
new CustomScalarType([ |
1285
|
|
|
'name' => 'SomeScalar', |
1286
|
|
|
'serialize' => static function () { |
1287
|
|
|
}, |
1288
|
|
|
'parseLiteral' => static function () { |
1289
|
|
|
}, |
1290
|
|
|
]) |
1291
|
|
|
); |
1292
|
|
|
} |
1293
|
|
|
|
1294
|
|
|
/** |
1295
|
|
|
* @see it('rejects a Scalar type defining parseValue and parseLiteral with an incorrect type') |
1296
|
|
|
*/ |
1297
|
|
|
public function testRejectsAScalarTypeDefiningParseValueAndParseLiteralWithAnIncorrectType() : void |
1298
|
|
|
{ |
1299
|
|
|
$this->expectException(InvariantViolation::class); |
1300
|
|
|
$this->expectExceptionMessage( |
1301
|
|
|
'SomeScalar must provide both "parseValue" and "parseLiteral" functions.' |
1302
|
|
|
); |
1303
|
|
|
$this->schemaWithFieldType( |
1304
|
|
|
new CustomScalarType([ |
1305
|
|
|
'name' => 'SomeScalar', |
1306
|
|
|
'serialize' => static function () { |
1307
|
|
|
}, |
1308
|
|
|
'parseValue' => new stdClass(), |
1309
|
|
|
'parseLiteral' => new stdClass(), |
1310
|
|
|
]) |
1311
|
|
|
); |
1312
|
|
|
} |
1313
|
|
|
|
1314
|
|
|
/** |
1315
|
|
|
* @see it('accepts an Object type with an isTypeOf function') |
1316
|
|
|
*/ |
1317
|
|
|
public function testAcceptsAnObjectTypeWithAnIsTypeOfFunction() : void |
1318
|
|
|
{ |
1319
|
|
|
$this->expectNotToPerformAssertions(); |
1320
|
|
|
// Should not throw |
1321
|
|
|
$this->schemaWithFieldType( |
1322
|
|
|
new ObjectType([ |
1323
|
|
|
'name' => 'AnotherObject', |
1324
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1325
|
|
|
]) |
1326
|
|
|
); |
1327
|
|
|
} |
1328
|
|
|
|
1329
|
|
|
// Type System: Object types must be assertable |
1330
|
|
|
|
1331
|
|
|
/** |
1332
|
|
|
* @see it('rejects an Object type with an incorrect type for isTypeOf') |
1333
|
|
|
*/ |
1334
|
|
|
public function testRejectsAnObjectTypeWithAnIncorrectTypeForIsTypeOf() : void |
1335
|
|
|
{ |
1336
|
|
|
$this->expectException(InvariantViolation::class); |
1337
|
|
|
$this->expectExceptionMessage( |
1338
|
|
|
'AnotherObject must provide "isTypeOf" as a function, but got: instance of stdClass' |
1339
|
|
|
); |
1340
|
|
|
$this->schemaWithFieldType( |
1341
|
|
|
new ObjectType([ |
1342
|
|
|
'name' => 'AnotherObject', |
1343
|
|
|
'isTypeOf' => new stdClass(), |
1344
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1345
|
|
|
]) |
1346
|
|
|
); |
1347
|
|
|
} |
1348
|
|
|
|
1349
|
|
|
/** |
1350
|
|
|
* @see it('accepts a Union type with array types') |
1351
|
|
|
*/ |
1352
|
|
|
public function testAcceptsAUnionTypeWithArrayTypes() : void |
1353
|
|
|
{ |
1354
|
|
|
$this->expectNotToPerformAssertions(); |
1355
|
|
|
// Should not throw: |
1356
|
|
|
$this->schemaWithFieldType( |
1357
|
|
|
new UnionType([ |
1358
|
|
|
'name' => 'SomeUnion', |
1359
|
|
|
'types' => [$this->objectType], |
1360
|
|
|
]) |
1361
|
|
|
); |
1362
|
|
|
} |
1363
|
|
|
|
1364
|
|
|
// Type System: Union types must be array |
1365
|
|
|
|
1366
|
|
|
/** |
1367
|
|
|
* @see it('accepts a Union type with function returning an array of types') |
1368
|
|
|
*/ |
1369
|
|
|
public function testAcceptsAUnionTypeWithFunctionReturningAnArrayOfTypes() : void |
1370
|
|
|
{ |
1371
|
|
|
$this->expectNotToPerformAssertions(); |
1372
|
|
|
$this->schemaWithFieldType( |
1373
|
|
|
new UnionType([ |
1374
|
|
|
'name' => 'SomeUnion', |
1375
|
|
|
'types' => function () { |
1376
|
|
|
return [$this->objectType]; |
1377
|
|
|
}, |
1378
|
|
|
]) |
1379
|
|
|
); |
1380
|
|
|
} |
1381
|
|
|
|
1382
|
|
|
/** |
1383
|
|
|
* @see it('rejects a Union type without types') |
1384
|
|
|
*/ |
1385
|
|
|
public function testRejectsAUnionTypeWithoutTypes() : void |
1386
|
|
|
{ |
1387
|
|
|
$this->expectException(InvariantViolation::class); |
1388
|
|
|
$this->expectExceptionMessage( |
1389
|
|
|
'Must provide Array of types or a callable which returns such an array for Union SomeUnion' |
1390
|
|
|
); |
1391
|
|
|
$this->schemaWithFieldType( |
1392
|
|
|
new UnionType(['name' => 'SomeUnion']) |
1393
|
|
|
); |
1394
|
|
|
} |
1395
|
|
|
|
1396
|
|
|
/** |
1397
|
|
|
* @see it('rejects a Union type with incorrectly typed types') |
1398
|
|
|
*/ |
1399
|
|
|
public function testRejectsAUnionTypeWithIncorrectlyTypedTypes() : void |
1400
|
|
|
{ |
1401
|
|
|
$this->expectException(InvariantViolation::class); |
1402
|
|
|
$this->expectExceptionMessage( |
1403
|
|
|
'Must provide Array of types or a callable which returns such an array for Union SomeUnion' |
1404
|
|
|
); |
1405
|
|
|
$this->schemaWithFieldType( |
1406
|
|
|
new UnionType([ |
1407
|
|
|
'name' => 'SomeUnion', |
1408
|
|
|
'types' => (object) ['test' => $this->objectType], |
1409
|
|
|
]) |
1410
|
|
|
); |
1411
|
|
|
} |
1412
|
|
|
|
1413
|
|
|
/** |
1414
|
|
|
* @see it('accepts an Input Object type with fields') |
1415
|
|
|
*/ |
1416
|
|
|
public function testAcceptsAnInputObjectTypeWithFields() : void |
1417
|
|
|
{ |
1418
|
|
|
$inputObjType = new InputObjectType([ |
1419
|
|
|
'name' => 'SomeInputObject', |
1420
|
|
|
'fields' => [ |
1421
|
|
|
'f' => ['type' => Type::string()], |
1422
|
|
|
], |
1423
|
|
|
]); |
1424
|
|
|
$inputObjType->assertValid(); |
1425
|
|
|
self::assertSame(Type::string(), $inputObjType->getField('f')->getType()); |
1426
|
|
|
} |
1427
|
|
|
|
1428
|
|
|
// Type System: Input Objects must have fields |
1429
|
|
|
|
1430
|
|
|
/** |
1431
|
|
|
* @see it('accepts an Input Object type with a field function') |
1432
|
|
|
*/ |
1433
|
|
|
public function testAcceptsAnInputObjectTypeWithAFieldFunction() : void |
1434
|
|
|
{ |
1435
|
|
|
$inputObjType = new InputObjectType([ |
1436
|
|
|
'name' => 'SomeInputObject', |
1437
|
|
|
'fields' => static function () { |
1438
|
|
|
return [ |
1439
|
|
|
'f' => ['type' => Type::string()], |
1440
|
|
|
]; |
1441
|
|
|
}, |
1442
|
|
|
]); |
1443
|
|
|
$inputObjType->assertValid(); |
1444
|
|
|
self::assertSame(Type::string(), $inputObjType->getField('f')->getType()); |
1445
|
|
|
} |
1446
|
|
|
|
1447
|
|
|
/** |
1448
|
|
|
* @see it('rejects an Input Object type with incorrect fields') |
1449
|
|
|
*/ |
1450
|
|
|
public function testRejectsAnInputObjectTypeWithIncorrectFields() : void |
1451
|
|
|
{ |
1452
|
|
|
$inputObjType = new InputObjectType([ |
1453
|
|
|
'name' => 'SomeInputObject', |
1454
|
|
|
'fields' => [], |
1455
|
|
|
]); |
1456
|
|
|
$this->expectException(InvariantViolation::class); |
1457
|
|
|
$this->expectExceptionMessage( |
1458
|
|
|
'SomeInputObject fields must be an associative array with field names as keys or a callable ' . |
1459
|
|
|
'which returns such an array.' |
1460
|
|
|
); |
1461
|
|
|
$inputObjType->assertValid(); |
1462
|
|
|
} |
1463
|
|
|
|
1464
|
|
|
/** |
1465
|
|
|
* @see it('rejects an Input Object type with fields function that returns incorrect type') |
1466
|
|
|
*/ |
1467
|
|
|
public function testRejectsAnInputObjectTypeWithFieldsFunctionThatReturnsIncorrectType() : void |
1468
|
|
|
{ |
1469
|
|
|
$inputObjType = new InputObjectType([ |
1470
|
|
|
'name' => 'SomeInputObject', |
1471
|
|
|
'fields' => static function () { |
1472
|
|
|
return []; |
1473
|
|
|
}, |
1474
|
|
|
]); |
1475
|
|
|
$this->expectException(InvariantViolation::class); |
1476
|
|
|
$this->expectExceptionMessage( |
1477
|
|
|
'SomeInputObject fields must be an associative array with field names as keys or a ' . |
1478
|
|
|
'callable which returns such an array.' |
1479
|
|
|
); |
1480
|
|
|
$inputObjType->assertValid(); |
1481
|
|
|
} |
1482
|
|
|
|
1483
|
|
|
/** |
1484
|
|
|
* @see it('rejects an Input Object type with resolvers') |
1485
|
|
|
*/ |
1486
|
|
|
public function testRejectsAnInputObjectTypeWithResolvers() : void |
1487
|
|
|
{ |
1488
|
|
|
$inputObjType = new InputObjectType([ |
1489
|
|
|
'name' => 'SomeInputObject', |
1490
|
|
|
'fields' => [ |
1491
|
|
|
'f' => [ |
1492
|
|
|
'type' => Type::string(), |
1493
|
|
|
'resolve' => static function () { |
1494
|
|
|
return 0; |
1495
|
|
|
}, |
1496
|
|
|
], |
1497
|
|
|
], |
1498
|
|
|
]); |
1499
|
|
|
$this->expectException(InvariantViolation::class); |
1500
|
|
|
$this->expectExceptionMessage( |
1501
|
|
|
'SomeInputObject.f field type has a resolve property, ' . |
1502
|
|
|
'but Input Types cannot define resolvers.' |
1503
|
|
|
); |
1504
|
|
|
$inputObjType->assertValid(); |
1505
|
|
|
} |
1506
|
|
|
|
1507
|
|
|
// Type System: Input Object fields must not have resolvers |
1508
|
|
|
|
1509
|
|
|
/** |
1510
|
|
|
* @see it('rejects an Input Object type with resolver constant') |
1511
|
|
|
*/ |
1512
|
|
|
public function testRejectsAnInputObjectTypeWithResolverConstant() : void |
1513
|
|
|
{ |
1514
|
|
|
$inputObjType = new InputObjectType([ |
1515
|
|
|
'name' => 'SomeInputObject', |
1516
|
|
|
'fields' => [ |
1517
|
|
|
'f' => [ |
1518
|
|
|
'type' => Type::string(), |
1519
|
|
|
'resolve' => new stdClass(), |
1520
|
|
|
], |
1521
|
|
|
], |
1522
|
|
|
]); |
1523
|
|
|
$this->expectException(InvariantViolation::class); |
1524
|
|
|
$this->expectExceptionMessage( |
1525
|
|
|
'SomeInputObject.f field type has a resolve property, ' . |
1526
|
|
|
'but Input Types cannot define resolvers.' |
1527
|
|
|
); |
1528
|
|
|
$inputObjType->assertValid(); |
1529
|
|
|
} |
1530
|
|
|
|
1531
|
|
|
/** |
1532
|
|
|
* @see it('accepts a well defined Enum type with empty value definition') |
1533
|
|
|
*/ |
1534
|
|
|
public function testAcceptsAWellDefinedEnumTypeWithEmptyValueDefinition() : void |
1535
|
|
|
{ |
1536
|
|
|
$enumType = new EnumType([ |
1537
|
|
|
'name' => 'SomeEnum', |
1538
|
|
|
'values' => [ |
1539
|
|
|
'FOO' => [], |
1540
|
|
|
'BAR' => [], |
1541
|
|
|
], |
1542
|
|
|
]); |
1543
|
|
|
self::assertEquals('FOO', $enumType->getValue('FOO')->value); |
1544
|
|
|
self::assertEquals('BAR', $enumType->getValue('BAR')->value); |
1545
|
|
|
} |
1546
|
|
|
|
1547
|
|
|
// Type System: Enum types must be well defined |
1548
|
|
|
|
1549
|
|
|
/** |
1550
|
|
|
* @see it('accepts a well defined Enum type with internal value definition') |
1551
|
|
|
*/ |
1552
|
|
|
public function testAcceptsAWellDefinedEnumTypeWithInternalValueDefinition() : void |
1553
|
|
|
{ |
1554
|
|
|
$enumType = new EnumType([ |
1555
|
|
|
'name' => 'SomeEnum', |
1556
|
|
|
'values' => [ |
1557
|
|
|
'FOO' => ['value' => 10], |
1558
|
|
|
'BAR' => ['value' => 20], |
1559
|
|
|
], |
1560
|
|
|
]); |
1561
|
|
|
self::assertEquals(10, $enumType->getValue('FOO')->value); |
1562
|
|
|
self::assertEquals(20, $enumType->getValue('BAR')->value); |
1563
|
|
|
} |
1564
|
|
|
|
1565
|
|
|
/** |
1566
|
|
|
* @see it('rejects an Enum type with incorrectly typed values') |
1567
|
|
|
*/ |
1568
|
|
|
public function testRejectsAnEnumTypeWithIncorrectlyTypedValues() : void |
1569
|
|
|
{ |
1570
|
|
|
$enumType = new EnumType([ |
1571
|
|
|
'name' => 'SomeEnum', |
1572
|
|
|
'values' => [['FOO' => 10]], |
1573
|
|
|
]); |
1574
|
|
|
$this->expectException(InvariantViolation::class); |
1575
|
|
|
$this->expectExceptionMessage( |
1576
|
|
|
'SomeEnum values must be an array with value names as keys.' |
1577
|
|
|
); |
1578
|
|
|
$enumType->assertValid(); |
1579
|
|
|
} |
1580
|
|
|
|
1581
|
|
|
/** |
1582
|
|
|
* @see it('does not allow isDeprecated without deprecationReason on enum') |
1583
|
|
|
*/ |
1584
|
|
|
public function testDoesNotAllowIsDeprecatedWithoutDeprecationReasonOnEnum() : void |
1585
|
|
|
{ |
1586
|
|
|
$enumType = new EnumType([ |
1587
|
|
|
'name' => 'SomeEnum', |
1588
|
|
|
'values' => [ |
1589
|
|
|
'FOO' => ['isDeprecated' => true], |
1590
|
|
|
], |
1591
|
|
|
]); |
1592
|
|
|
$this->expectException(InvariantViolation::class); |
1593
|
|
|
$this->expectExceptionMessage( |
1594
|
|
|
'SomeEnum.FOO should provide "deprecationReason" instead ' . |
1595
|
|
|
'of "isDeprecated".' |
1596
|
|
|
); |
1597
|
|
|
$enumType->assertValid(); |
1598
|
|
|
} |
1599
|
|
|
|
1600
|
|
|
/** |
1601
|
|
|
* @see it('rejects a Schema which redefines a built-in type') |
1602
|
|
|
*/ |
1603
|
|
|
public function testRejectsASchemaWhichRedefinesABuiltInType() : void |
1604
|
|
|
{ |
1605
|
|
|
$FakeString = new CustomScalarType([ |
1606
|
|
|
'name' => 'String', |
1607
|
|
|
'serialize' => static function () { |
1608
|
|
|
}, |
1609
|
|
|
]); |
1610
|
|
|
|
1611
|
|
|
$QueryType = new ObjectType([ |
1612
|
|
|
'name' => 'Query', |
1613
|
|
|
'fields' => [ |
1614
|
|
|
'normal' => ['type' => Type::string()], |
1615
|
|
|
'fake' => ['type' => $FakeString], |
1616
|
|
|
], |
1617
|
|
|
]); |
1618
|
|
|
|
1619
|
|
|
$this->expectException(InvariantViolation::class); |
1620
|
|
|
$this->expectExceptionMessage( |
1621
|
|
|
'Schema must contain unique named types but contains multiple types named "String" ' . |
1622
|
|
|
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).' |
1623
|
|
|
); |
1624
|
|
|
$schema = new Schema(['query' => $QueryType]); |
1625
|
|
|
$schema->assertValid(); |
1626
|
|
|
} |
1627
|
|
|
|
1628
|
|
|
// Type System: A Schema must contain uniquely named types |
1629
|
|
|
|
1630
|
|
|
/** |
1631
|
|
|
* @see it('rejects a Schema which defines an object type twice') |
1632
|
|
|
*/ |
1633
|
|
|
public function testRejectsASchemaWhichDefinesAnObjectTypeTwice() : void |
1634
|
|
|
{ |
1635
|
|
|
$A = new ObjectType([ |
1636
|
|
|
'name' => 'SameName', |
1637
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1638
|
|
|
]); |
1639
|
|
|
|
1640
|
|
|
$B = new ObjectType([ |
1641
|
|
|
'name' => 'SameName', |
1642
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1643
|
|
|
]); |
1644
|
|
|
|
1645
|
|
|
$QueryType = new ObjectType([ |
1646
|
|
|
'name' => 'Query', |
1647
|
|
|
'fields' => [ |
1648
|
|
|
'a' => ['type' => $A], |
1649
|
|
|
'b' => ['type' => $B], |
1650
|
|
|
], |
1651
|
|
|
]); |
1652
|
|
|
$this->expectException(InvariantViolation::class); |
1653
|
|
|
$this->expectExceptionMessage( |
1654
|
|
|
'Schema must contain unique named types but contains multiple types named "SameName" ' . |
1655
|
|
|
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).' |
1656
|
|
|
); |
1657
|
|
|
$schema = new Schema(['query' => $QueryType]); |
1658
|
|
|
$schema->assertValid(); |
1659
|
|
|
} |
1660
|
|
|
|
1661
|
|
|
/** |
1662
|
|
|
* @see it('rejects a Schema which have same named objects implementing an interface') |
1663
|
|
|
*/ |
1664
|
|
|
public function testRejectsASchemaWhichHaveSameNamedObjectsImplementingAnInterface() : void |
1665
|
|
|
{ |
1666
|
|
|
$AnotherInterface = new InterfaceType([ |
1667
|
|
|
'name' => 'AnotherInterface', |
1668
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1669
|
|
|
]); |
1670
|
|
|
|
1671
|
|
|
$FirstBadObject = new ObjectType([ |
1672
|
|
|
'name' => 'BadObject', |
1673
|
|
|
'interfaces' => [$AnotherInterface], |
1674
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1675
|
|
|
]); |
1676
|
|
|
|
1677
|
|
|
$SecondBadObject = new ObjectType([ |
1678
|
|
|
'name' => 'BadObject', |
1679
|
|
|
'interfaces' => [$AnotherInterface], |
1680
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1681
|
|
|
]); |
1682
|
|
|
|
1683
|
|
|
$QueryType = new ObjectType([ |
1684
|
|
|
'name' => 'Query', |
1685
|
|
|
'fields' => [ |
1686
|
|
|
'iface' => ['type' => $AnotherInterface], |
1687
|
|
|
], |
1688
|
|
|
]); |
1689
|
|
|
|
1690
|
|
|
$this->expectException(InvariantViolation::class); |
1691
|
|
|
$this->expectExceptionMessage( |
1692
|
|
|
'Schema must contain unique named types but contains multiple types named "BadObject" ' . |
1693
|
|
|
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).' |
1694
|
|
|
); |
1695
|
|
|
$schema = new Schema([ |
1696
|
|
|
'query' => $QueryType, |
1697
|
|
|
'types' => [$FirstBadObject, $SecondBadObject], |
1698
|
|
|
]); |
1699
|
|
|
$schema->assertValid(); |
1700
|
|
|
} |
1701
|
|
|
|
1702
|
|
|
public function objectWithIsTypeOf() : ObjectType |
1703
|
|
|
{ |
1704
|
|
|
return new ObjectType([ |
1705
|
|
|
'name' => 'ObjectWithIsTypeOf', |
1706
|
|
|
'fields' => ['f' => ['type' => Type::string()]], |
1707
|
|
|
]); |
1708
|
|
|
} |
1709
|
|
|
} |
1710
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.