1 module graphql.parsertests; 2 3 import std.format : format; 4 import std.experimental.allocator; 5 import std.experimental.allocator.mallocator : Mallocator; 6 import std.stdio; 7 8 import graphql.lexer; 9 import graphql.parser; 10 11 private struct TestCase { 12 int id; 13 QueryParser qp; 14 string str; 15 } 16 17 unittest { 18 TestCase[] tests; 19 tests ~= TestCase(0, QueryParser.yes, 20 ` 21 mutation updateUser($userId: String! $name: String!) { 22 updateUser(id: $userId name: $name) { 23 name 24 } 25 } 26 `); 27 28 tests ~= TestCase(1, QueryParser.yes, ` 29 query inlineFragmentNoType($expandedInfo: Boolean) { 30 user(handle: "zuck") { 31 id 32 name 33 ... @include(if: $expandedInfo) { 34 firstName 35 lastName 36 birthday 37 } 38 } 39 } 40 `); 41 42 tests ~= TestCase(2, QueryParser.yes, ` 43 query HeroForEpisode($ep: Episode!) { 44 hero(episode: $ep) { 45 name 46 ... on Droid { 47 primaryFunction 48 } 49 ... on Human { 50 height 51 } 52 } 53 } 54 `); 55 56 tests ~= TestCase(3, QueryParser.yes, ` 57 mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { 58 createReview(episode: $ep, review: $review) { 59 stars 60 commentary 61 } 62 } 63 `); 64 65 tests ~= TestCase(4, QueryParser.yes, ` 66 query HeroNameAndFriends($episode: Episode = "JEDI") { 67 hero(episode: $episode) { 68 name 69 friends { 70 name 71 } 72 } 73 }`); 74 75 tests ~= TestCase(5, QueryParser.yes, ` 76 query hero { 77 name 78 # Queries can have comments! 79 80 # Queries can have comments Another! 81 friends { 82 name 83 } 84 }`); 85 86 tests ~= TestCase(6, QueryParser.no, ` 87 enum DogCommand { SIT, DOWN, HEEL } 88 89 type Dog implements Pet { 90 name: String! 91 nickname: String 92 barkVolume: Int 93 doesKnowCommand(dogCommand: DogCommand!): Boolean! 94 isHousetrained(atOtherHomes: Boolean): Boolean! 95 owner: Human 96 } 97 98 interface Sentient { 99 name: String! 100 } 101 102 interface Pet { 103 name: String! 104 } 105 106 type Alien implements Sentient { 107 name: String! 108 homePlanet: String 109 } 110 111 type Human implements Sentient { 112 name: String! 113 } 114 115 enum CatCommand { JUMP } 116 117 type Cat implements Pet { 118 name: String! 119 nickname: String 120 doesKnowCommand(catCommand: CatCommand!): Boolean! 121 meowVolume: Int 122 } 123 124 union CatOrDog = Cat | Dog 125 union DogOrHuman = Dog | Human 126 union HumanOrAlien = Human | Alien 127 128 type QueryRoot { 129 dog: Dog 130 } 131 `); 132 133 tests ~= TestCase(7, QueryParser.no, ` 134 union SearchResult = Photo | Person 135 union SearchResult = Photo Person 136 137 type Person { 138 name: String 139 age: Int 140 } 141 142 type Photo { 143 height: Int 144 width: Int 145 } 146 147 type SearchQuery { 148 firstSearchResult: SearchResult 149 }`); 150 151 tests ~= TestCase(8, QueryParser.no, ` 152 interface NamedEntity { 153 name: String 154 } 155 156 type Person implements NamedEntity { 157 name: String 158 age: Int 159 } 160 161 type Business implements NamedEntity { 162 name: String 163 employeeCount: Int 164 }`); 165 166 tests ~= TestCase(9, QueryParser.no, `type Person { 167 name: String 168 age: Int 169 picture: Url 170 }`); 171 172 tests ~= TestCase(10, QueryParser.yes, ` 173 query myQuery($someTest: Boolean) { 174 experimentalField @skip(if: $someTest) 175 }`); 176 177 tests ~= TestCase(11, QueryParser.no, ` 178 subscription sub { 179 newMessage { 180 body 181 sender 182 } 183 } 184 185 fragment newMessageFields on Message { 186 body 187 sender 188 } 189 190 subscription sub { 191 newMessage { 192 ... newMessageFields 193 } 194 195 }`); 196 197 tests ~= TestCase(11, QueryParser.yes, ` 198 query HeroNameAndFriends($episode: Episode) { 199 hero(episode: $episode) { 200 name, 201 friends { 202 name 203 } 204 } 205 }`); 206 207 tests ~= TestCase(12, QueryParser.yes, ` 208 query name { 209 leftComparison: hero(episode: $EMPIRE) { 210 ...comparisonFields 211 } 212 rightComparison: hero(episode: $JEDI) { 213 ...comparisonFields 214 } 215 } 216 fragment comparisonFields on Character { 217 name 218 appearsIn 219 friends { 220 name 221 } 222 } 223 224 `); 225 226 tests ~= TestCase(13, QueryParser.yes, ` 227 query name{ 228 builds(first: 54) { 229 all: number 230 } 231 } 232 `); 233 234 tests ~= TestCase(14, QueryParser.yes, ` 235 query foo { 236 viewer { 237 user { 238 name 239 builds(first: 1) { 240 edges { 241 node { 242 number 243 branch 244 message 245 } 246 } 247 } 248 } 249 } 250 } 251 `); 252 253 tests ~= TestCase(15, QueryParser.yes, ` 254 query foo { 255 name 256 builds(first: 1) { 257 abc 258 } 259 }`); 260 261 tests ~= TestCase(16, QueryParser.yes, ` 262 query h { 263 name 264 builds 265 } 266 `); 267 268 tests ~= TestCase(17, QueryParser.yes, ` 269 query human($id: H, $limit: lim, $gender: Gen) { 270 name 271 height 272 friends { 273 id 274 gender 275 income 276 } 277 }` ); 278 279 tests ~= TestCase(18, QueryParser.yes, ` 280 query human($id: Var) { 281 name 282 height 283 }`); 284 285 tests ~= TestCase(19, QueryParser.yes, ` 286 query human() { 287 name 288 height 289 }`); 290 291 tests ~= TestCase(20, QueryParser.yes, ` 292 query name { 293 foo 294 }`); 295 296 /*tests ~= TestCase(21, `{ 297 query n { 298 __type(name: "User") { 299 name 300 fields { 301 name 302 type { 303 name 304 } 305 } 306 } 307 } 308 }`);*/ 309 310 tests ~= TestCase(21, QueryParser.yes, `{ 311 user(id: 1) { 312 friends { 313 name 314 } 315 } 316 }`); 317 318 tests ~= TestCase(22, QueryParser.yes, `query IntrospectionQueryTypeQuery { 319 __schema { 320 queryType { 321 fields { 322 name 323 args { 324 name 325 description 326 type { 327 name 328 kind 329 ofType { 330 name 331 kind 332 } 333 } 334 defaultValue 335 } 336 } 337 } 338 } 339 }`); 340 341 tests ~= TestCase(23, QueryParser.yes, `query IntrospectionDroidFieldsQuery { 342 __type(name: "Droid") { 343 name 344 fields { 345 name 346 type { 347 name 348 kind 349 } 350 } 351 } 352 }`); 353 354 tests ~= TestCase(24, QueryParser.yes, ` 355 query IntrospectionCharacterKindQuery { 356 __type(name: "Character") { 357 name 358 kind 359 } 360 }`); 361 362 363 tests ~= TestCase(25, QueryParser.yes, `query IntrospectionDroidKindQuery { 364 __type(name: "Droid") { 365 name 366 kind 367 } 368 }`); 369 370 tests ~= TestCase(26, QueryParser.yes, `query IntrospectionDroidTypeQuery { 371 __type(name: "Droid") { 372 name 373 } 374 }`); 375 376 tests ~= TestCase(27, QueryParser.yes, `query IntrospectionQueryTypeQuery { 377 __schema { 378 queryType { 379 name 380 } 381 } 382 }`); 383 384 tests ~= TestCase(28, QueryParser.yes, `query IntrospectionTypeQuery { 385 __schema { 386 types { 387 name 388 } 389 } 390 }`); 391 392 tests ~= TestCase(29, QueryParser.yes, 393 `query IntrospectionDroidDescriptionQuery { 394 __type(name: "Droid") { 395 name 396 description 397 } 398 }`); 399 400 // Issue 20 401 tests ~= TestCase(30, QueryParser.yes, 402 `# a stupid comment that crashed Steven's tests 403 # more comments 404 query IntrospectionDroidDescriptionQuery { 405 __type(name: "Droid") { 406 name 407 description 408 } 409 }`); 410 411 tests ~= TestCase(31, QueryParser.yes, 412 `# Welcome to GraphiQL 413 # 414 # GraphiQL is an in-browser tool for writing, validating, and 415 # testing GraphQL queries. 416 # 417 # Type queries into this side of the screen, and you will see intelligent 418 # typeaheads aware of the current GraphQL type schema and live syntax and 419 # validation errors highlighted within the text. 420 # 421 # GraphQL queries typically start with a "{" character. Lines that starts 422 # with a # are ignored. 423 # 424 # An example GraphQL query might look like: 425 # 426 # { 427 # field(arg: "value") { 428 # subField 429 # } 430 # } 431 # 432 # Keyboard shortcuts: 433 # 434 # Prettify Query: Shift-Ctrl-P (or press the prettify button above) 435 # 436 # Run Query: Ctrl-Enter (or press the play button above) 437 # 438 # Auto Complete: Ctrl-Space (or just start typing) 439 # 440 441 442 {allEmployees 443 { 444 info 445 { 446 addressInstance 447 { 448 line1 449 } 450 } 451 }} 452 `); 453 454 foreach(test; tests) { 455 auto l = Lexer(test.str, test.qp); 456 auto p = Parser(l); 457 try { 458 auto d = p.parseDocument(); 459 } catch(Throwable e) { 460 writeln(e.toString()); 461 while(e.next) { 462 writeln(e.next.toString()); 463 e = e.next; 464 } 465 assert(false, format("Test %d", test.id)); 466 } 467 assert(p.lex.empty, format("%d %s", test.id, p.lex.getRestOfInput())); 468 } 469 }