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`;