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