Mirror: The small sibling of the graphql package, slimmed down for client-side libraries.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add tests for parser

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