Mirror: The small sibling of the graphql package, slimmed down for client-side libraries.
1// See: https://github.com/graphql/graphql-js/blob/976d64b/src/language/__tests__/parser-test.ts
2// Note: Tests regarding reserved keywords have been removed.
3
4import { Kind } from 'graphql';
5import { parse, parseValue, parseType } from '../parser';
6
7describe('Parser', () => {
8 it('parse provides errors', () => {
9 expect(() => parse('{')).toThrow();
10 });
11
12 it('parses variable inline values', () => {
13 expect(() => {
14 return parse('{ field(complex: { a: { b: [ $var ] } }) }');
15 }).not.toThrow();
16 });
17
18 it('parses constant default values', () => {
19 expect(() => {
20 return parse('query Foo($x: Complex = { a: { b: [ $var ] } }) { field }');
21 }).not.toThrow();
22 });
23
24 it('parses variable definition directives', () => {
25 expect(() => {
26 return parse('query Foo($x: Boolean = false @bar) { field }');
27 }).not.toThrow();
28 });
29
30 it('does not accept fragments spread of "on"', () => {
31 expect(() => {
32 return parse('{ ...on }');
33 }).toThrow();
34 });
35
36 it('parses multi-byte characters', () => {
37 // Note: \u0A0A could be naively interpreted as two line-feed chars.
38 const ast = parse(`
39 # This comment has a \u0A0A multi-byte character.
40 { field(arg: "Has a \u0A0A multi-byte character.") }
41 `);
42
43 expect(ast).toHaveProperty(
44 'definitions.0.selectionSet.selections.0.arguments.0.value.value',
45 'Has a \u0A0A multi-byte character.'
46 );
47 });
48
49 it('parses kitchen sink', () => {
50 let query;
51 expect(() => {
52 return (query = parse(kitchenSinkQuery));
53 }).not.toThrow();
54
55 expect(query.definitions.length).toBe(6);
56 });
57
58 it('parses anonymous mutation operations', () => {
59 expect(() => {
60 return parse(`
61 mutation {
62 mutationField
63 }
64 `);
65 }).not.toThrow();
66 });
67
68 it('parses anonymous subscription operations', () => {
69 expect(() => {
70 return parse(`
71 subscription {
72 subscriptionField
73 }
74 `);
75 }).not.toThrow();
76 });
77
78 it('parses named mutation operations', () => {
79 expect(() => {
80 return parse(`
81 mutation Foo {
82 mutationField
83 }
84 `);
85 }).not.toThrow();
86 });
87
88 it('parses named subscription operations', () => {
89 expect(() => {
90 return parse(`
91 subscription Foo {
92 subscriptionField
93 }
94 `);
95 }).not.toThrow();
96 });
97
98 it('creates ast', () => {
99 const result = parse(`
100 {
101 node(id: 4) {
102 id,
103 name
104 }
105 }
106 `);
107
108 expect(result).toMatchObject({
109 kind: Kind.DOCUMENT,
110 definitions: [
111 {
112 kind: Kind.OPERATION_DEFINITION,
113 operation: 'query',
114 name: undefined,
115 variableDefinitions: [],
116 directives: [],
117 selectionSet: {
118 kind: Kind.SELECTION_SET,
119 selections: [
120 {
121 kind: Kind.FIELD,
122 alias: undefined,
123 name: {
124 kind: Kind.NAME,
125 value: 'node',
126 },
127 arguments: [
128 {
129 kind: Kind.ARGUMENT,
130 name: {
131 kind: Kind.NAME,
132 value: 'id',
133 },
134 value: {
135 kind: Kind.INT,
136 value: '4',
137 },
138 },
139 ],
140 directives: [],
141 selectionSet: {
142 kind: Kind.SELECTION_SET,
143 selections: [
144 {
145 kind: Kind.FIELD,
146 alias: undefined,
147 name: {
148 kind: Kind.NAME,
149 value: 'id',
150 },
151 arguments: [],
152 directives: [],
153 selectionSet: undefined,
154 },
155 {
156 kind: Kind.FIELD,
157 alias: undefined,
158 name: {
159 kind: Kind.NAME,
160 value: 'name',
161 },
162 arguments: [],
163 directives: [],
164 selectionSet: undefined,
165 },
166 ],
167 },
168 },
169 ],
170 },
171 },
172 ],
173 });
174 });
175
176 it('creates ast from nameless query without variables', () => {
177 const result = parse(`
178 query {
179 node {
180 id
181 }
182 }
183 `);
184
185 expect(result).toMatchObject({
186 kind: Kind.DOCUMENT,
187 definitions: [
188 {
189 kind: Kind.OPERATION_DEFINITION,
190 operation: 'query',
191 name: undefined,
192 variableDefinitions: [],
193 directives: [],
194 selectionSet: {
195 kind: Kind.SELECTION_SET,
196 selections: [
197 {
198 kind: Kind.FIELD,
199 alias: undefined,
200 name: {
201 kind: Kind.NAME,
202 value: 'node',
203 },
204 arguments: [],
205 directives: [],
206 selectionSet: {
207 kind: Kind.SELECTION_SET,
208 selections: [
209 {
210 kind: Kind.FIELD,
211 alias: undefined,
212 name: {
213 kind: Kind.NAME,
214 value: 'id',
215 },
216 arguments: [],
217 directives: [],
218 selectionSet: undefined,
219 },
220 ],
221 },
222 },
223 ],
224 },
225 },
226 ],
227 });
228 });
229
230 it('allows parsing without source location information', () => {
231 const result = parse('{ id }', { noLocation: true });
232 expect('loc' in result).toBe(false);
233 });
234
235 describe('parseValue', () => {
236 it('parses null value', () => {
237 const result = parseValue('null');
238 expect(result).toEqual({ kind: Kind.NULL });
239 });
240
241 it('parses list values', () => {
242 const result = parseValue('[123 "abc"]');
243 expect(result).toEqual({
244 kind: Kind.LIST,
245 values: [
246 {
247 kind: Kind.INT,
248 value: '123',
249 },
250 {
251 kind: Kind.STRING,
252 value: 'abc',
253 },
254 ],
255 });
256 });
257
258 it('parses block strings', () => {
259 const result = parseValue('["""long""" "short"]');
260 expect(result).toEqual({
261 kind: Kind.LIST,
262 values: [
263 {
264 kind: Kind.STRING,
265 value: 'long',
266 },
267 {
268 kind: Kind.STRING,
269 value: 'short',
270 },
271 ],
272 });
273 });
274
275 it('allows variables', () => {
276 const result = parseValue('{ field: $var }');
277 expect(result).toEqual({
278 kind: Kind.OBJECT,
279 fields: [
280 {
281 kind: Kind.OBJECT_FIELD,
282 name: {
283 kind: Kind.NAME,
284 value: 'field',
285 },
286 value: {
287 kind: Kind.VARIABLE,
288 name: {
289 kind: Kind.NAME,
290 value: 'var',
291 },
292 },
293 },
294 ],
295 });
296 });
297
298 it('correct message for incomplete variable', () => {
299 expect(() => {
300 return parseValue('$');
301 }).toThrow();
302 });
303
304 it('correct message for unexpected token', () => {
305 expect(() => {
306 return parseValue(':');
307 }).toThrow();
308 });
309 });
310
311 describe('parseType', () => {
312 it('parses well known types', () => {
313 const result = parseType('String');
314 expect(result).toEqual({
315 kind: Kind.NAMED_TYPE,
316 name: {
317 kind: Kind.NAME,
318 value: 'String',
319 },
320 });
321 });
322
323 it('parses custom types', () => {
324 const result = parseType('MyType');
325 expect(result).toEqual({
326 kind: Kind.NAMED_TYPE,
327 name: {
328 kind: Kind.NAME,
329 value: 'MyType',
330 },
331 });
332 });
333
334 it('parses list types', () => {
335 const result = parseType('[MyType]');
336 expect(result).toEqual({
337 kind: Kind.LIST_TYPE,
338 type: {
339 kind: Kind.NAMED_TYPE,
340 name: {
341 kind: Kind.NAME,
342 value: 'MyType',
343 },
344 },
345 });
346 });
347
348 it('parses non-null types', () => {
349 const result = parseType('MyType!');
350 expect(result).toEqual({
351 kind: Kind.NON_NULL_TYPE,
352 type: {
353 kind: Kind.NAMED_TYPE,
354 name: {
355 kind: Kind.NAME,
356 value: 'MyType',
357 },
358 },
359 });
360 });
361
362 it('parses nested types', () => {
363 const result = parseType('[MyType!]');
364 expect(result).toEqual({
365 kind: Kind.LIST_TYPE,
366 type: {
367 kind: Kind.NON_NULL_TYPE,
368 type: {
369 kind: Kind.NAMED_TYPE,
370 name: {
371 kind: Kind.NAME,
372 value: 'MyType',
373 },
374 },
375 },
376 });
377 });
378 });
379});
380
381const kitchenSinkQuery = String.raw`
382query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
383 whoever123is: node(id: [123, 456]) {
384 id
385 ... on User @onInlineFragment {
386 field2 {
387 id
388 alias: field1(first: 10, after: $foo) @include(if: $foo) {
389 id
390 ...frag @onFragmentSpread
391 }
392 }
393 }
394 ... @skip(unless: $foo) {
395 id
396 }
397 ... {
398 id
399 }
400 }
401}
402mutation likeStory @onMutation {
403 like(story: 123) @onField {
404 story {
405 id @onField
406 }
407 }
408}
409subscription StoryLikeSubscription(
410 $input: StoryLikeSubscribeInput @onVariableDefinition
411)
412 @onSubscription {
413 storyLikeSubscribe(input: $input) {
414 story {
415 likers {
416 count
417 }
418 likeSentence {
419 text
420 }
421 }
422 }
423}
424fragment frag on Friend @onFragmentDefinition {
425 foo(
426 size: $size
427 bar: $b
428 obj: { key: "value" }
429 )
430}
431{
432 unnamed(truthy: true, falsy: false, nullish: null)
433 query
434}
435query {
436 __typename
437}
438`;