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__/visitor-test.ts 2 3import { Kind, parse, print } from 'graphql'; 4import { visit, visitInParallel, BREAK } from '../visitor'; 5 6const kitchenSinkQuery = String.raw` 7query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery { 8 whoever123is: node(id: [123, 456]) { 9 id 10 ... on User @onInlineFragment { 11 field2 { 12 id 13 alias: field1(first: 10, after: $foo) @include(if: $foo) { 14 id 15 ...frag @onFragmentSpread 16 } 17 } 18 } 19 ... @skip(unless: $foo) { 20 id 21 } 22 ... { 23 id 24 } 25 } 26} 27mutation likeStory @onMutation { 28 like(story: 123) @onField { 29 story { 30 id @onField 31 } 32 } 33} 34subscription StoryLikeSubscription( 35 $input: StoryLikeSubscribeInput @onVariableDefinition 36) 37 @onSubscription { 38 storyLikeSubscribe(input: $input) { 39 story { 40 likers { 41 count 42 } 43 likeSentence { 44 text 45 } 46 } 47 } 48} 49fragment frag on Friend @onFragmentDefinition { 50 foo( 51 size: $size 52 bar: $b 53 obj: { 54 key: "value" 55 block: """ 56 block string uses \""" 57 """ 58 } 59 ) 60} 61{ 62 unnamed(truthy: true, falsy: false, nullish: null) 63 query 64} 65query { 66 __typename 67} 68`; 69 70function checkVisitorFnArgs(ast, args, isEdited = false) { 71 const [node, key, parent, path, ancestors] = args; 72 73 expect(node).toBeInstanceOf(Object); 74 expect(Object.values(Kind)).toContain(node.kind); 75 76 const isRoot = key === undefined; 77 if (isRoot) { 78 if (!isEdited) { 79 expect(node).toEqual(ast); 80 } 81 expect(parent).toEqual(undefined); 82 expect(path).toEqual([]); 83 expect(ancestors).toEqual([]); 84 return; 85 } 86 87 expect(typeof key).toMatch(/number|string/); 88 89 expect(parent).toHaveProperty([key]); 90 91 expect(path).toBeInstanceOf(Array); 92 expect(path[path.length - 1]).toEqual(key); 93 94 expect(ancestors).toBeInstanceOf(Array); 95 expect(ancestors.length).toEqual(path.length - 1); 96 97 if (!isEdited) { 98 let currentNode = ast; 99 for (let i = 0; i < ancestors.length; ++i) { 100 expect(ancestors[i]).toEqual(currentNode); 101 102 currentNode = currentNode[path[i]]; 103 expect(currentNode).not.toEqual(undefined); 104 } 105 106 // expect(parent).toEqual(currentNode); 107 // expect(parent[key]).toEqual(node); 108 } 109} 110 111function isNode(node) { 112 return node != null && typeof node.kind === 'string'; 113} 114 115function getValue(node) { 116 return 'value' in node ? node.value : undefined; 117} 118 119describe('Visitor', () => { 120 it('handles empty visitor', () => { 121 const ast = parse('{ a }', { noLocation: true }); 122 expect(() => visit(ast, {})).not.toThrow(); 123 }); 124 125 it('validates path argument', () => { 126 const visited = []; 127 128 const ast = parse('{ a }', { noLocation: true }); 129 130 visit(ast, { 131 enter(_node, _key, _parent, path) { 132 checkVisitorFnArgs(ast, arguments); 133 visited.push(['enter', path.slice()]); 134 }, 135 leave(_node, _key, _parent, path) { 136 checkVisitorFnArgs(ast, arguments); 137 visited.push(['leave', path.slice()]); 138 }, 139 }); 140 141 expect(visited).toEqual([ 142 ['enter', []], 143 ['enter', ['definitions', 0]], 144 ['enter', ['definitions', 0, 'selectionSet']], 145 ['enter', ['definitions', 0, 'selectionSet', 'selections', 0]], 146 ['enter', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], 147 ['leave', ['definitions', 0, 'selectionSet', 'selections', 0, 'name']], 148 ['leave', ['definitions', 0, 'selectionSet', 'selections', 0]], 149 ['leave', ['definitions', 0, 'selectionSet']], 150 ['leave', ['definitions', 0]], 151 ['leave', []], 152 ]); 153 }); 154 155 it('validates ancestors argument', () => { 156 const ast = parse('{ a }', { noLocation: true }); 157 const visitedNodes = []; 158 159 visit(ast, { 160 enter(node, key, parent, _path, ancestors) { 161 const inArray = typeof key === 'number'; 162 if (inArray) { 163 visitedNodes.push(parent); 164 } 165 visitedNodes.push(node); 166 167 const expectedAncestors = visitedNodes.slice(0, -2); 168 expect(ancestors).toEqual(expectedAncestors); 169 }, 170 leave(_node, key, _parent, _path, ancestors) { 171 const expectedAncestors = visitedNodes.slice(0, -2); 172 expect(ancestors).toEqual(expectedAncestors); 173 174 const inArray = typeof key === 'number'; 175 if (inArray) { 176 visitedNodes.pop(); 177 } 178 visitedNodes.pop(); 179 }, 180 }); 181 }); 182 183 it('allows editing a node both on enter and on leave', () => { 184 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 185 186 let selectionSet; 187 188 const editedAST = visit(ast, { 189 OperationDefinition: { 190 enter(node) { 191 checkVisitorFnArgs(ast, arguments); 192 selectionSet = node.selectionSet; 193 return { 194 ...node, 195 selectionSet: { 196 kind: 'SelectionSet', 197 selections: [], 198 }, 199 didEnter: true, 200 }; 201 }, 202 leave(node) { 203 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 204 return { 205 ...node, 206 selectionSet, 207 didLeave: true, 208 }; 209 }, 210 }, 211 }); 212 213 expect(editedAST).toEqual({ 214 ...ast, 215 definitions: [ 216 { 217 ...ast.definitions[0], 218 didEnter: true, 219 didLeave: true, 220 }, 221 ], 222 }); 223 }); 224 225 it('allows editing the root node on enter and on leave', () => { 226 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 227 228 const { definitions } = ast; 229 230 const editedAST = visit(ast, { 231 Document: { 232 enter(node) { 233 checkVisitorFnArgs(ast, arguments); 234 return { 235 ...node, 236 definitions: [], 237 didEnter: true, 238 }; 239 }, 240 leave(node) { 241 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 242 return { 243 ...node, 244 definitions, 245 didLeave: true, 246 }; 247 }, 248 }, 249 }); 250 251 expect(editedAST).toEqual({ 252 ...ast, 253 didEnter: true, 254 didLeave: true, 255 }); 256 }); 257 258 it('allows for editing on enter', () => { 259 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 260 const editedAST = visit(ast, { 261 enter(node) { 262 checkVisitorFnArgs(ast, arguments); 263 if (node.kind === 'Field' && node.name.value === 'b') { 264 return null; 265 } 266 }, 267 }); 268 269 expect(ast).toEqual(parse('{ a, b, c { a, b, c } }', { noLocation: true })); 270 271 expect(editedAST).toEqual( 272 parse('{ a, c { a, c } }', { noLocation: true }) 273 ); 274 }); 275 276 it('allows for editing on leave', () => { 277 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 278 const editedAST = visit(ast, { 279 leave(node) { 280 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 281 if (node.kind === 'Field' && node.name.value === 'b') { 282 return null; 283 } 284 }, 285 }); 286 287 expect(ast).toEqual(parse('{ a, b, c { a, b, c } }', { noLocation: true })); 288 289 expect(editedAST).toEqual( 290 parse('{ a, c { a, c } }', { noLocation: true }) 291 ); 292 }); 293 294 it('ignores false returned on leave', () => { 295 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 296 const returnedAST = visit(ast, { 297 leave() { 298 return false; 299 }, 300 }); 301 302 expect(returnedAST).toEqual( 303 parse('{ a, b, c { a, b, c } }', { noLocation: true }) 304 ); 305 }); 306 307 it('visits edited node', () => { 308 const addedField = { 309 kind: 'Field', 310 name: { 311 kind: 'Name', 312 value: '__typename', 313 }, 314 }; 315 316 let didVisitAddedField; 317 318 const ast = parse('{ a { x } }', { noLocation: true }); 319 visit(ast, { 320 enter(node) { 321 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 322 if (node.kind === 'Field' && node.name.value === 'a') { 323 return { 324 kind: 'Field', 325 selectionSet: [addedField, node.selectionSet], 326 }; 327 } 328 if (node === addedField) { 329 didVisitAddedField = true; 330 } 331 }, 332 }); 333 334 expect(didVisitAddedField).toEqual(true); 335 }); 336 337 it('allows skipping a sub-tree', () => { 338 const visited = []; 339 340 const ast = parse('{ a, b { x }, c }', { noLocation: true }); 341 visit(ast, { 342 enter(node) { 343 checkVisitorFnArgs(ast, arguments); 344 visited.push(['enter', node.kind, getValue(node)]); 345 if (node.kind === 'Field' && node.name.value === 'b') { 346 return false; 347 } 348 }, 349 350 leave(node) { 351 checkVisitorFnArgs(ast, arguments); 352 visited.push(['leave', node.kind, getValue(node)]); 353 }, 354 }); 355 356 expect(visited).toEqual([ 357 ['enter', 'Document', undefined], 358 ['enter', 'OperationDefinition', undefined], 359 ['enter', 'SelectionSet', undefined], 360 ['enter', 'Field', undefined], 361 ['enter', 'Name', 'a'], 362 ['leave', 'Name', 'a'], 363 ['leave', 'Field', undefined], 364 ['enter', 'Field', undefined], 365 ['enter', 'Field', undefined], 366 ['enter', 'Name', 'c'], 367 ['leave', 'Name', 'c'], 368 ['leave', 'Field', undefined], 369 ['leave', 'SelectionSet', undefined], 370 ['leave', 'OperationDefinition', undefined], 371 ['leave', 'Document', undefined], 372 ]); 373 }); 374 375 it('allows early exit while visiting', () => { 376 const visited = []; 377 378 const ast = parse('{ a, b { x }, c }', { noLocation: true }); 379 visit(ast, { 380 enter(node) { 381 checkVisitorFnArgs(ast, arguments); 382 visited.push(['enter', node.kind, getValue(node)]); 383 if (node.kind === 'Name' && node.value === 'x') { 384 return BREAK; 385 } 386 }, 387 leave(node) { 388 checkVisitorFnArgs(ast, arguments); 389 visited.push(['leave', node.kind, getValue(node)]); 390 }, 391 }); 392 393 expect(visited).toEqual([ 394 ['enter', 'Document', undefined], 395 ['enter', 'OperationDefinition', undefined], 396 ['enter', 'SelectionSet', undefined], 397 ['enter', 'Field', undefined], 398 ['enter', 'Name', 'a'], 399 ['leave', 'Name', 'a'], 400 ['leave', 'Field', undefined], 401 ['enter', 'Field', undefined], 402 ['enter', 'Name', 'b'], 403 ['leave', 'Name', 'b'], 404 ['enter', 'SelectionSet', undefined], 405 ['enter', 'Field', undefined], 406 ['enter', 'Name', 'x'], 407 ]); 408 }); 409 410 it('allows early exit while leaving', () => { 411 const visited = []; 412 413 const ast = parse('{ a, b { x }, c }', { noLocation: true }); 414 visit(ast, { 415 enter(node) { 416 checkVisitorFnArgs(ast, arguments); 417 visited.push(['enter', node.kind, getValue(node)]); 418 }, 419 420 leave(node) { 421 checkVisitorFnArgs(ast, arguments); 422 visited.push(['leave', node.kind, getValue(node)]); 423 if (node.kind === 'Name' && node.value === 'x') { 424 return BREAK; 425 } 426 }, 427 }); 428 429 expect(visited).toEqual([ 430 ['enter', 'Document', undefined], 431 ['enter', 'OperationDefinition', undefined], 432 ['enter', 'SelectionSet', undefined], 433 ['enter', 'Field', undefined], 434 ['enter', 'Name', 'a'], 435 ['leave', 'Name', 'a'], 436 ['leave', 'Field', undefined], 437 ['enter', 'Field', undefined], 438 ['enter', 'Name', 'b'], 439 ['leave', 'Name', 'b'], 440 ['enter', 'SelectionSet', undefined], 441 ['enter', 'Field', undefined], 442 ['enter', 'Name', 'x'], 443 ['leave', 'Name', 'x'], 444 ]); 445 }); 446 447 it('allows a named functions visitor API', () => { 448 const visited = []; 449 450 const ast = parse('{ a, b { x }, c }', { noLocation: true }); 451 visit(ast, { 452 Name(node) { 453 checkVisitorFnArgs(ast, arguments); 454 visited.push(['enter', node.kind, getValue(node)]); 455 }, 456 SelectionSet: { 457 enter(node) { 458 checkVisitorFnArgs(ast, arguments); 459 visited.push(['enter', node.kind, getValue(node)]); 460 }, 461 leave(node) { 462 checkVisitorFnArgs(ast, arguments); 463 visited.push(['leave', node.kind, getValue(node)]); 464 }, 465 }, 466 }); 467 468 expect(visited).toEqual([ 469 ['enter', 'SelectionSet', undefined], 470 ['enter', 'Name', 'a'], 471 ['enter', 'Name', 'b'], 472 ['enter', 'SelectionSet', undefined], 473 ['enter', 'Name', 'x'], 474 ['leave', 'SelectionSet', undefined], 475 ['enter', 'Name', 'c'], 476 ['leave', 'SelectionSet', undefined], 477 ]); 478 }); 479 480 it('handles deep immutable edits correctly when using "enter"', () => { 481 const formatNode = (node) => { 482 if ( 483 node.selectionSet && 484 !node.selectionSet.selections.some( 485 (node) => 486 node.kind === Kind.FIELD && 487 node.name.value === '__typename' && 488 !node.alias 489 ) 490 ) { 491 return { 492 ...node, 493 selectionSet: { 494 ...node.selectionSet, 495 selections: [ 496 ...node.selectionSet.selections, 497 { 498 kind: Kind.FIELD, 499 name: { 500 kind: Kind.NAME, 501 value: '__typename', 502 }, 503 }, 504 ], 505 }, 506 }; 507 } 508 }; 509 const ast = parse('{ players { nodes { id } } }'); 510 const expected = parse( 511 '{ players { nodes { id __typename } __typename } }' 512 ); 513 const visited = visit(ast, { 514 Field: formatNode, 515 InlineFragment: formatNode, 516 }); 517 518 expect(print(visited)).toEqual(print(expected)); 519 }); 520 521 it('visits kitchen sink', () => { 522 const ast = parse(kitchenSinkQuery); 523 const visited = []; 524 const argsStack = []; 525 526 visit(ast, { 527 enter(node, key, parent) { 528 visited.push([ 529 'enter', 530 node.kind, 531 key, 532 isNode(parent) ? parent.kind : undefined, 533 ]); 534 535 checkVisitorFnArgs(ast, arguments); 536 argsStack.push([...arguments]); 537 }, 538 539 leave(node, key, parent) { 540 visited.push([ 541 'leave', 542 node.kind, 543 key, 544 isNode(parent) ? parent.kind : undefined, 545 ]); 546 547 expect(argsStack.pop()).toEqual([...arguments]); 548 }, 549 }); 550 551 expect(argsStack).toEqual([]); 552 expect(visited).toEqual([ 553 ['enter', 'Document', undefined, undefined], 554 ['enter', 'OperationDefinition', 0, undefined], 555 ['enter', 'Name', 'name', 'OperationDefinition'], 556 ['leave', 'Name', 'name', 'OperationDefinition'], 557 ['enter', 'VariableDefinition', 0, undefined], 558 ['enter', 'Variable', 'variable', 'VariableDefinition'], 559 ['enter', 'Name', 'name', 'Variable'], 560 ['leave', 'Name', 'name', 'Variable'], 561 ['leave', 'Variable', 'variable', 'VariableDefinition'], 562 ['enter', 'NamedType', 'type', 'VariableDefinition'], 563 ['enter', 'Name', 'name', 'NamedType'], 564 ['leave', 'Name', 'name', 'NamedType'], 565 ['leave', 'NamedType', 'type', 'VariableDefinition'], 566 ['leave', 'VariableDefinition', 0, undefined], 567 ['enter', 'VariableDefinition', 1, undefined], 568 ['enter', 'Variable', 'variable', 'VariableDefinition'], 569 ['enter', 'Name', 'name', 'Variable'], 570 ['leave', 'Name', 'name', 'Variable'], 571 ['leave', 'Variable', 'variable', 'VariableDefinition'], 572 ['enter', 'NamedType', 'type', 'VariableDefinition'], 573 ['enter', 'Name', 'name', 'NamedType'], 574 ['leave', 'Name', 'name', 'NamedType'], 575 ['leave', 'NamedType', 'type', 'VariableDefinition'], 576 ['enter', 'EnumValue', 'defaultValue', 'VariableDefinition'], 577 ['leave', 'EnumValue', 'defaultValue', 'VariableDefinition'], 578 ['leave', 'VariableDefinition', 1, undefined], 579 ['enter', 'Directive', 0, undefined], 580 ['enter', 'Name', 'name', 'Directive'], 581 ['leave', 'Name', 'name', 'Directive'], 582 ['leave', 'Directive', 0, undefined], 583 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 584 ['enter', 'Field', 0, undefined], 585 ['enter', 'Name', 'alias', 'Field'], 586 ['leave', 'Name', 'alias', 'Field'], 587 ['enter', 'Name', 'name', 'Field'], 588 ['leave', 'Name', 'name', 'Field'], 589 ['enter', 'Argument', 0, undefined], 590 ['enter', 'Name', 'name', 'Argument'], 591 ['leave', 'Name', 'name', 'Argument'], 592 ['enter', 'ListValue', 'value', 'Argument'], 593 ['enter', 'IntValue', 0, undefined], 594 ['leave', 'IntValue', 0, undefined], 595 ['enter', 'IntValue', 1, undefined], 596 ['leave', 'IntValue', 1, undefined], 597 ['leave', 'ListValue', 'value', 'Argument'], 598 ['leave', 'Argument', 0, undefined], 599 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 600 ['enter', 'Field', 0, undefined], 601 ['enter', 'Name', 'name', 'Field'], 602 ['leave', 'Name', 'name', 'Field'], 603 ['leave', 'Field', 0, undefined], 604 ['enter', 'InlineFragment', 1, undefined], 605 ['enter', 'NamedType', 'typeCondition', 'InlineFragment'], 606 ['enter', 'Name', 'name', 'NamedType'], 607 ['leave', 'Name', 'name', 'NamedType'], 608 ['leave', 'NamedType', 'typeCondition', 'InlineFragment'], 609 ['enter', 'Directive', 0, undefined], 610 ['enter', 'Name', 'name', 'Directive'], 611 ['leave', 'Name', 'name', 'Directive'], 612 ['leave', 'Directive', 0, undefined], 613 ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 614 ['enter', 'Field', 0, undefined], 615 ['enter', 'Name', 'name', 'Field'], 616 ['leave', 'Name', 'name', 'Field'], 617 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 618 ['enter', 'Field', 0, undefined], 619 ['enter', 'Name', 'name', 'Field'], 620 ['leave', 'Name', 'name', 'Field'], 621 ['leave', 'Field', 0, undefined], 622 ['enter', 'Field', 1, undefined], 623 ['enter', 'Name', 'alias', 'Field'], 624 ['leave', 'Name', 'alias', 'Field'], 625 ['enter', 'Name', 'name', 'Field'], 626 ['leave', 'Name', 'name', 'Field'], 627 ['enter', 'Argument', 0, undefined], 628 ['enter', 'Name', 'name', 'Argument'], 629 ['leave', 'Name', 'name', 'Argument'], 630 ['enter', 'IntValue', 'value', 'Argument'], 631 ['leave', 'IntValue', 'value', 'Argument'], 632 ['leave', 'Argument', 0, undefined], 633 ['enter', 'Argument', 1, undefined], 634 ['enter', 'Name', 'name', 'Argument'], 635 ['leave', 'Name', 'name', 'Argument'], 636 ['enter', 'Variable', 'value', 'Argument'], 637 ['enter', 'Name', 'name', 'Variable'], 638 ['leave', 'Name', 'name', 'Variable'], 639 ['leave', 'Variable', 'value', 'Argument'], 640 ['leave', 'Argument', 1, undefined], 641 ['enter', 'Directive', 0, undefined], 642 ['enter', 'Name', 'name', 'Directive'], 643 ['leave', 'Name', 'name', 'Directive'], 644 ['enter', 'Argument', 0, undefined], 645 ['enter', 'Name', 'name', 'Argument'], 646 ['leave', 'Name', 'name', 'Argument'], 647 ['enter', 'Variable', 'value', 'Argument'], 648 ['enter', 'Name', 'name', 'Variable'], 649 ['leave', 'Name', 'name', 'Variable'], 650 ['leave', 'Variable', 'value', 'Argument'], 651 ['leave', 'Argument', 0, undefined], 652 ['leave', 'Directive', 0, undefined], 653 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 654 ['enter', 'Field', 0, undefined], 655 ['enter', 'Name', 'name', 'Field'], 656 ['leave', 'Name', 'name', 'Field'], 657 ['leave', 'Field', 0, undefined], 658 ['enter', 'FragmentSpread', 1, undefined], 659 ['enter', 'Name', 'name', 'FragmentSpread'], 660 ['leave', 'Name', 'name', 'FragmentSpread'], 661 ['enter', 'Directive', 0, undefined], 662 ['enter', 'Name', 'name', 'Directive'], 663 ['leave', 'Name', 'name', 'Directive'], 664 ['leave', 'Directive', 0, undefined], 665 ['leave', 'FragmentSpread', 1, undefined], 666 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 667 ['leave', 'Field', 1, undefined], 668 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 669 ['leave', 'Field', 0, undefined], 670 ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 671 ['leave', 'InlineFragment', 1, undefined], 672 ['enter', 'InlineFragment', 2, undefined], 673 ['enter', 'Directive', 0, undefined], 674 ['enter', 'Name', 'name', 'Directive'], 675 ['leave', 'Name', 'name', 'Directive'], 676 ['enter', 'Argument', 0, undefined], 677 ['enter', 'Name', 'name', 'Argument'], 678 ['leave', 'Name', 'name', 'Argument'], 679 ['enter', 'Variable', 'value', 'Argument'], 680 ['enter', 'Name', 'name', 'Variable'], 681 ['leave', 'Name', 'name', 'Variable'], 682 ['leave', 'Variable', 'value', 'Argument'], 683 ['leave', 'Argument', 0, undefined], 684 ['leave', 'Directive', 0, undefined], 685 ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 686 ['enter', 'Field', 0, undefined], 687 ['enter', 'Name', 'name', 'Field'], 688 ['leave', 'Name', 'name', 'Field'], 689 ['leave', 'Field', 0, undefined], 690 ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 691 ['leave', 'InlineFragment', 2, undefined], 692 ['enter', 'InlineFragment', 3, undefined], 693 ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 694 ['enter', 'Field', 0, undefined], 695 ['enter', 'Name', 'name', 'Field'], 696 ['leave', 'Name', 'name', 'Field'], 697 ['leave', 'Field', 0, undefined], 698 ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 699 ['leave', 'InlineFragment', 3, undefined], 700 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 701 ['leave', 'Field', 0, undefined], 702 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 703 ['leave', 'OperationDefinition', 0, undefined], 704 ['enter', 'OperationDefinition', 1, undefined], 705 ['enter', 'Name', 'name', 'OperationDefinition'], 706 ['leave', 'Name', 'name', 'OperationDefinition'], 707 ['enter', 'Directive', 0, undefined], 708 ['enter', 'Name', 'name', 'Directive'], 709 ['leave', 'Name', 'name', 'Directive'], 710 ['leave', 'Directive', 0, undefined], 711 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 712 ['enter', 'Field', 0, undefined], 713 ['enter', 'Name', 'name', 'Field'], 714 ['leave', 'Name', 'name', 'Field'], 715 ['enter', 'Argument', 0, undefined], 716 ['enter', 'Name', 'name', 'Argument'], 717 ['leave', 'Name', 'name', 'Argument'], 718 ['enter', 'IntValue', 'value', 'Argument'], 719 ['leave', 'IntValue', 'value', 'Argument'], 720 ['leave', 'Argument', 0, undefined], 721 ['enter', 'Directive', 0, undefined], 722 ['enter', 'Name', 'name', 'Directive'], 723 ['leave', 'Name', 'name', 'Directive'], 724 ['leave', 'Directive', 0, undefined], 725 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 726 ['enter', 'Field', 0, undefined], 727 ['enter', 'Name', 'name', 'Field'], 728 ['leave', 'Name', 'name', 'Field'], 729 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 730 ['enter', 'Field', 0, undefined], 731 ['enter', 'Name', 'name', 'Field'], 732 ['leave', 'Name', 'name', 'Field'], 733 ['enter', 'Directive', 0, undefined], 734 ['enter', 'Name', 'name', 'Directive'], 735 ['leave', 'Name', 'name', 'Directive'], 736 ['leave', 'Directive', 0, undefined], 737 ['leave', 'Field', 0, undefined], 738 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 739 ['leave', 'Field', 0, undefined], 740 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 741 ['leave', 'Field', 0, undefined], 742 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 743 ['leave', 'OperationDefinition', 1, undefined], 744 ['enter', 'OperationDefinition', 2, undefined], 745 ['enter', 'Name', 'name', 'OperationDefinition'], 746 ['leave', 'Name', 'name', 'OperationDefinition'], 747 ['enter', 'VariableDefinition', 0, undefined], 748 ['enter', 'Variable', 'variable', 'VariableDefinition'], 749 ['enter', 'Name', 'name', 'Variable'], 750 ['leave', 'Name', 'name', 'Variable'], 751 ['leave', 'Variable', 'variable', 'VariableDefinition'], 752 ['enter', 'NamedType', 'type', 'VariableDefinition'], 753 ['enter', 'Name', 'name', 'NamedType'], 754 ['leave', 'Name', 'name', 'NamedType'], 755 ['leave', 'NamedType', 'type', 'VariableDefinition'], 756 ['enter', 'Directive', 0, undefined], 757 ['enter', 'Name', 'name', 'Directive'], 758 ['leave', 'Name', 'name', 'Directive'], 759 ['leave', 'Directive', 0, undefined], 760 ['leave', 'VariableDefinition', 0, undefined], 761 ['enter', 'Directive', 0, undefined], 762 ['enter', 'Name', 'name', 'Directive'], 763 ['leave', 'Name', 'name', 'Directive'], 764 ['leave', 'Directive', 0, undefined], 765 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 766 ['enter', 'Field', 0, undefined], 767 ['enter', 'Name', 'name', 'Field'], 768 ['leave', 'Name', 'name', 'Field'], 769 ['enter', 'Argument', 0, undefined], 770 ['enter', 'Name', 'name', 'Argument'], 771 ['leave', 'Name', 'name', 'Argument'], 772 ['enter', 'Variable', 'value', 'Argument'], 773 ['enter', 'Name', 'name', 'Variable'], 774 ['leave', 'Name', 'name', 'Variable'], 775 ['leave', 'Variable', 'value', 'Argument'], 776 ['leave', 'Argument', 0, undefined], 777 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 778 ['enter', 'Field', 0, undefined], 779 ['enter', 'Name', 'name', 'Field'], 780 ['leave', 'Name', 'name', 'Field'], 781 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 782 ['enter', 'Field', 0, undefined], 783 ['enter', 'Name', 'name', 'Field'], 784 ['leave', 'Name', 'name', 'Field'], 785 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 786 ['enter', 'Field', 0, undefined], 787 ['enter', 'Name', 'name', 'Field'], 788 ['leave', 'Name', 'name', 'Field'], 789 ['leave', 'Field', 0, undefined], 790 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 791 ['leave', 'Field', 0, undefined], 792 ['enter', 'Field', 1, undefined], 793 ['enter', 'Name', 'name', 'Field'], 794 ['leave', 'Name', 'name', 'Field'], 795 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 796 ['enter', 'Field', 0, undefined], 797 ['enter', 'Name', 'name', 'Field'], 798 ['leave', 'Name', 'name', 'Field'], 799 ['leave', 'Field', 0, undefined], 800 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 801 ['leave', 'Field', 1, undefined], 802 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 803 ['leave', 'Field', 0, undefined], 804 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 805 ['leave', 'Field', 0, undefined], 806 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 807 ['leave', 'OperationDefinition', 2, undefined], 808 ['enter', 'FragmentDefinition', 3, undefined], 809 ['enter', 'Name', 'name', 'FragmentDefinition'], 810 ['leave', 'Name', 'name', 'FragmentDefinition'], 811 ['enter', 'NamedType', 'typeCondition', 'FragmentDefinition'], 812 ['enter', 'Name', 'name', 'NamedType'], 813 ['leave', 'Name', 'name', 'NamedType'], 814 ['leave', 'NamedType', 'typeCondition', 'FragmentDefinition'], 815 ['enter', 'Directive', 0, undefined], 816 ['enter', 'Name', 'name', 'Directive'], 817 ['leave', 'Name', 'name', 'Directive'], 818 ['leave', 'Directive', 0, undefined], 819 ['enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], 820 ['enter', 'Field', 0, undefined], 821 ['enter', 'Name', 'name', 'Field'], 822 ['leave', 'Name', 'name', 'Field'], 823 ['enter', 'Argument', 0, undefined], 824 ['enter', 'Name', 'name', 'Argument'], 825 ['leave', 'Name', 'name', 'Argument'], 826 ['enter', 'Variable', 'value', 'Argument'], 827 ['enter', 'Name', 'name', 'Variable'], 828 ['leave', 'Name', 'name', 'Variable'], 829 ['leave', 'Variable', 'value', 'Argument'], 830 ['leave', 'Argument', 0, undefined], 831 ['enter', 'Argument', 1, undefined], 832 ['enter', 'Name', 'name', 'Argument'], 833 ['leave', 'Name', 'name', 'Argument'], 834 ['enter', 'Variable', 'value', 'Argument'], 835 ['enter', 'Name', 'name', 'Variable'], 836 ['leave', 'Name', 'name', 'Variable'], 837 ['leave', 'Variable', 'value', 'Argument'], 838 ['leave', 'Argument', 1, undefined], 839 ['enter', 'Argument', 2, undefined], 840 ['enter', 'Name', 'name', 'Argument'], 841 ['leave', 'Name', 'name', 'Argument'], 842 ['enter', 'ObjectValue', 'value', 'Argument'], 843 ['enter', 'ObjectField', 0, undefined], 844 ['enter', 'Name', 'name', 'ObjectField'], 845 ['leave', 'Name', 'name', 'ObjectField'], 846 ['enter', 'StringValue', 'value', 'ObjectField'], 847 ['leave', 'StringValue', 'value', 'ObjectField'], 848 ['leave', 'ObjectField', 0, undefined], 849 ['enter', 'ObjectField', 1, undefined], 850 ['enter', 'Name', 'name', 'ObjectField'], 851 ['leave', 'Name', 'name', 'ObjectField'], 852 ['enter', 'StringValue', 'value', 'ObjectField'], 853 ['leave', 'StringValue', 'value', 'ObjectField'], 854 ['leave', 'ObjectField', 1, undefined], 855 ['leave', 'ObjectValue', 'value', 'Argument'], 856 ['leave', 'Argument', 2, undefined], 857 ['leave', 'Field', 0, undefined], 858 ['leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], 859 ['leave', 'FragmentDefinition', 3, undefined], 860 ['enter', 'OperationDefinition', 4, undefined], 861 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 862 ['enter', 'Field', 0, undefined], 863 ['enter', 'Name', 'name', 'Field'], 864 ['leave', 'Name', 'name', 'Field'], 865 ['enter', 'Argument', 0, undefined], 866 ['enter', 'Name', 'name', 'Argument'], 867 ['leave', 'Name', 'name', 'Argument'], 868 ['enter', 'BooleanValue', 'value', 'Argument'], 869 ['leave', 'BooleanValue', 'value', 'Argument'], 870 ['leave', 'Argument', 0, undefined], 871 ['enter', 'Argument', 1, undefined], 872 ['enter', 'Name', 'name', 'Argument'], 873 ['leave', 'Name', 'name', 'Argument'], 874 ['enter', 'BooleanValue', 'value', 'Argument'], 875 ['leave', 'BooleanValue', 'value', 'Argument'], 876 ['leave', 'Argument', 1, undefined], 877 ['enter', 'Argument', 2, undefined], 878 ['enter', 'Name', 'name', 'Argument'], 879 ['leave', 'Name', 'name', 'Argument'], 880 ['enter', 'NullValue', 'value', 'Argument'], 881 ['leave', 'NullValue', 'value', 'Argument'], 882 ['leave', 'Argument', 2, undefined], 883 ['leave', 'Field', 0, undefined], 884 ['enter', 'Field', 1, undefined], 885 ['enter', 'Name', 'name', 'Field'], 886 ['leave', 'Name', 'name', 'Field'], 887 ['leave', 'Field', 1, undefined], 888 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 889 ['leave', 'OperationDefinition', 4, undefined], 890 ['enter', 'OperationDefinition', 5, undefined], 891 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 892 ['enter', 'Field', 0, undefined], 893 ['enter', 'Name', 'name', 'Field'], 894 ['leave', 'Name', 'name', 'Field'], 895 ['leave', 'Field', 0, undefined], 896 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 897 ['leave', 'OperationDefinition', 5, undefined], 898 ['leave', 'Document', undefined, undefined], 899 ]); 900 }); 901 902 // TODO: Verify discrepancies 903 describe('visitInParallel', () => { 904 // Note: nearly identical to the above test of the same test but 905 // using visitInParallel. 906 it('allows skipping a sub-tree', () => { 907 const visited = []; 908 909 const ast = parse('{ a, b { x }, c }'); 910 visit( 911 ast, 912 visitInParallel([ 913 { 914 enter(node) { 915 checkVisitorFnArgs(ast, arguments); 916 visited.push(['enter', node.kind, getValue(node)]); 917 if (node.kind === 'Field' && node.name.value === 'b') { 918 return false; 919 } 920 }, 921 922 leave(node) { 923 checkVisitorFnArgs(ast, arguments); 924 visited.push(['leave', node.kind, getValue(node)]); 925 }, 926 }, 927 ]) 928 ); 929 930 expect(visited).toEqual([ 931 ['enter', 'Document', undefined], 932 ['enter', 'OperationDefinition', undefined], 933 ['enter', 'SelectionSet', undefined], 934 ['enter', 'Field', undefined], 935 ['enter', 'Name', 'a'], 936 ['leave', 'Name', 'a'], 937 ['leave', 'Field', undefined], 938 ['enter', 'Field', undefined], 939 ['enter', 'Field', undefined], 940 ['enter', 'Name', 'c'], 941 ['leave', 'Name', 'c'], 942 ['leave', 'Field', undefined], 943 ['leave', 'SelectionSet', undefined], 944 ['leave', 'OperationDefinition', undefined], 945 ['leave', 'Document', undefined], 946 ]); 947 }); 948 949 it('allows skipping different sub-trees', () => { 950 const visited = []; 951 952 const ast = parse('{ a { x }, b { y} }'); 953 visit( 954 ast, 955 visitInParallel([ 956 { 957 enter(node) { 958 checkVisitorFnArgs(ast, arguments); 959 visited.push(['no-a', 'enter', node.kind, getValue(node)]); 960 if (node.kind === 'Field' && node.name.value === 'a') { 961 return false; 962 } 963 }, 964 leave(node) { 965 checkVisitorFnArgs(ast, arguments); 966 visited.push(['no-a', 'leave', node.kind, getValue(node)]); 967 }, 968 }, 969 { 970 enter(node) { 971 checkVisitorFnArgs(ast, arguments); 972 visited.push(['no-b', 'enter', node.kind, getValue(node)]); 973 if (node.kind === 'Field' && node.name.value === 'b') { 974 return false; 975 } 976 }, 977 leave(node) { 978 checkVisitorFnArgs(ast, arguments); 979 visited.push(['no-b', 'leave', node.kind, getValue(node)]); 980 }, 981 }, 982 ]) 983 ); 984 985 expect(visited).toEqual([ 986 ['no-a', 'enter', 'Document', undefined], 987 ['no-b', 'enter', 'Document', undefined], 988 ['no-a', 'enter', 'OperationDefinition', undefined], 989 ['no-b', 'enter', 'OperationDefinition', undefined], 990 ['no-a', 'enter', 'SelectionSet', undefined], 991 ['no-b', 'enter', 'SelectionSet', undefined], 992 ['no-a', 'enter', 'Field', undefined], 993 ['no-b', 'enter', 'Field', undefined], 994 ['no-b', 'enter', 'Name', 'a'], 995 ['no-b', 'leave', 'Name', 'a'], 996 ['no-b', 'enter', 'SelectionSet', undefined], 997 ['no-b', 'enter', 'Field', undefined], 998 ['no-b', 'enter', 'Name', 'x'], 999 ['no-b', 'leave', 'Name', 'x'], 1000 ['no-b', 'leave', 'Field', undefined], 1001 ['no-b', 'leave', 'SelectionSet', undefined], 1002 ['no-b', 'leave', 'Field', undefined], 1003 ['no-a', 'enter', 'Field', undefined], 1004 ['no-b', 'enter', 'Field', undefined], 1005 ['no-a', 'enter', 'Name', 'b'], 1006 ['no-a', 'leave', 'Name', 'b'], 1007 ['no-a', 'enter', 'SelectionSet', undefined], 1008 ['no-a', 'enter', 'Field', undefined], 1009 ['no-a', 'enter', 'Name', 'y'], 1010 ['no-a', 'leave', 'Name', 'y'], 1011 ['no-a', 'leave', 'Field', undefined], 1012 ['no-a', 'leave', 'SelectionSet', undefined], 1013 ['no-a', 'leave', 'Field', undefined], 1014 ['no-a', 'leave', 'SelectionSet', undefined], 1015 ['no-b', 'leave', 'SelectionSet', undefined], 1016 ['no-a', 'leave', 'OperationDefinition', undefined], 1017 ['no-b', 'leave', 'OperationDefinition', undefined], 1018 ['no-a', 'leave', 'Document', undefined], 1019 ['no-b', 'leave', 'Document', undefined], 1020 ]); 1021 }); 1022 1023 // Note: nearly identical to the above test of the same test but 1024 // using visitInParallel. 1025 it('allows early exit while visiting', () => { 1026 const visited = []; 1027 1028 const ast = parse('{ a, b { x }, c }'); 1029 visit( 1030 ast, 1031 visitInParallel([ 1032 { 1033 enter(node) { 1034 checkVisitorFnArgs(ast, arguments); 1035 visited.push(['enter', node.kind, getValue(node)]); 1036 if (node.kind === 'Name' && node.value === 'x') { 1037 return BREAK; 1038 } 1039 }, 1040 leave(node) { 1041 checkVisitorFnArgs(ast, arguments); 1042 visited.push(['leave', node.kind, getValue(node)]); 1043 }, 1044 }, 1045 ]) 1046 ); 1047 1048 expect(visited).toEqual([ 1049 ['enter', 'Document', undefined], 1050 ['enter', 'OperationDefinition', undefined], 1051 ['enter', 'SelectionSet', undefined], 1052 ['enter', 'Field', undefined], 1053 ['enter', 'Name', 'a'], 1054 ['leave', 'Name', 'a'], 1055 ['leave', 'Field', undefined], 1056 ['enter', 'Field', undefined], 1057 ['enter', 'Name', 'b'], 1058 ['leave', 'Name', 'b'], 1059 ['enter', 'SelectionSet', undefined], 1060 ['enter', 'Field', undefined], 1061 ['enter', 'Name', 'x'], 1062 ]); 1063 }); 1064 1065 it('allows early exit from different points', () => { 1066 const visited = []; 1067 1068 const ast = parse('{ a { y }, b { x } }'); 1069 visit( 1070 ast, 1071 visitInParallel([ 1072 { 1073 enter(node) { 1074 checkVisitorFnArgs(ast, arguments); 1075 visited.push(['break-a', 'enter', node.kind, getValue(node)]); 1076 if (node.kind === 'Name' && node.value === 'a') { 1077 return BREAK; 1078 } 1079 }, 1080 // istanbul ignore next (Never called and used as a placeholder) 1081 leave() { 1082 expect.fail('Should not be called'); 1083 }, 1084 }, 1085 { 1086 enter(node) { 1087 checkVisitorFnArgs(ast, arguments); 1088 visited.push(['break-b', 'enter', node.kind, getValue(node)]); 1089 if (node.kind === 'Name' && node.value === 'b') { 1090 return BREAK; 1091 } 1092 }, 1093 leave(node) { 1094 checkVisitorFnArgs(ast, arguments); 1095 visited.push(['break-b', 'leave', node.kind, getValue(node)]); 1096 }, 1097 }, 1098 ]) 1099 ); 1100 1101 expect(visited).toEqual([ 1102 ['break-a', 'enter', 'Document', undefined], 1103 ['break-b', 'enter', 'Document', undefined], 1104 ['break-a', 'enter', 'OperationDefinition', undefined], 1105 ['break-b', 'enter', 'OperationDefinition', undefined], 1106 ['break-a', 'enter', 'SelectionSet', undefined], 1107 ['break-b', 'enter', 'SelectionSet', undefined], 1108 ['break-a', 'enter', 'Field', undefined], 1109 ['break-b', 'enter', 'Field', undefined], 1110 ['break-a', 'enter', 'Name', 'a'], 1111 ['break-b', 'enter', 'Name', 'a'], 1112 ['break-b', 'leave', 'Name', 'a'], 1113 ['break-b', 'enter', 'SelectionSet', undefined], 1114 ['break-b', 'enter', 'Field', undefined], 1115 ['break-b', 'enter', 'Name', 'y'], 1116 ['break-b', 'leave', 'Name', 'y'], 1117 ['break-b', 'leave', 'Field', undefined], 1118 ['break-b', 'leave', 'SelectionSet', undefined], 1119 ['break-b', 'leave', 'Field', undefined], 1120 ['break-b', 'enter', 'Field', undefined], 1121 ['break-b', 'enter', 'Name', 'b'], 1122 ]); 1123 }); 1124 1125 // Note: nearly identical to the above test of the same test but 1126 // using visitInParallel. 1127 it('allows early exit while leaving', () => { 1128 const visited = []; 1129 1130 const ast = parse('{ a, b { x }, c }'); 1131 visit( 1132 ast, 1133 visitInParallel([ 1134 { 1135 enter(node) { 1136 checkVisitorFnArgs(ast, arguments); 1137 visited.push(['enter', node.kind, getValue(node)]); 1138 }, 1139 leave(node) { 1140 checkVisitorFnArgs(ast, arguments); 1141 visited.push(['leave', node.kind, getValue(node)]); 1142 if (node.kind === 'Name' && node.value === 'x') { 1143 return BREAK; 1144 } 1145 }, 1146 }, 1147 ]) 1148 ); 1149 1150 expect(visited).toEqual([ 1151 ['enter', 'Document', undefined], 1152 ['enter', 'OperationDefinition', undefined], 1153 ['enter', 'SelectionSet', undefined], 1154 ['enter', 'Field', undefined], 1155 ['enter', 'Name', 'a'], 1156 ['leave', 'Name', 'a'], 1157 ['leave', 'Field', undefined], 1158 ['enter', 'Field', undefined], 1159 ['enter', 'Name', 'b'], 1160 ['leave', 'Name', 'b'], 1161 ['enter', 'SelectionSet', undefined], 1162 ['enter', 'Field', undefined], 1163 ['enter', 'Name', 'x'], 1164 ['leave', 'Name', 'x'], 1165 ]); 1166 }); 1167 1168 it('allows early exit from leaving different points', () => { 1169 const visited = []; 1170 1171 const ast = parse('{ a { y }, b { x } }'); 1172 visit( 1173 ast, 1174 visitInParallel([ 1175 { 1176 enter(node) { 1177 checkVisitorFnArgs(ast, arguments); 1178 visited.push(['break-a', 'enter', node.kind, getValue(node)]); 1179 }, 1180 leave(node) { 1181 checkVisitorFnArgs(ast, arguments); 1182 visited.push(['break-a', 'leave', node.kind, getValue(node)]); 1183 if (node.kind === 'Field' && node.name.value === 'a') { 1184 return BREAK; 1185 } 1186 }, 1187 }, 1188 { 1189 enter(node) { 1190 checkVisitorFnArgs(ast, arguments); 1191 visited.push(['break-b', 'enter', node.kind, getValue(node)]); 1192 }, 1193 leave(node) { 1194 checkVisitorFnArgs(ast, arguments); 1195 visited.push(['break-b', 'leave', node.kind, getValue(node)]); 1196 if (node.kind === 'Field' && node.name.value === 'b') { 1197 return BREAK; 1198 } 1199 }, 1200 }, 1201 ]) 1202 ); 1203 1204 expect(visited).toEqual([ 1205 ['break-a', 'enter', 'Document', undefined], 1206 ['break-b', 'enter', 'Document', undefined], 1207 ['break-a', 'enter', 'OperationDefinition', undefined], 1208 ['break-b', 'enter', 'OperationDefinition', undefined], 1209 ['break-a', 'enter', 'SelectionSet', undefined], 1210 ['break-b', 'enter', 'SelectionSet', undefined], 1211 ['break-a', 'enter', 'Field', undefined], 1212 ['break-b', 'enter', 'Field', undefined], 1213 ['break-a', 'enter', 'Name', 'a'], 1214 ['break-b', 'enter', 'Name', 'a'], 1215 ['break-a', 'leave', 'Name', 'a'], 1216 ['break-b', 'leave', 'Name', 'a'], 1217 ['break-a', 'enter', 'SelectionSet', undefined], 1218 ['break-b', 'enter', 'SelectionSet', undefined], 1219 ['break-a', 'enter', 'Field', undefined], 1220 ['break-b', 'enter', 'Field', undefined], 1221 ['break-a', 'enter', 'Name', 'y'], 1222 ['break-b', 'enter', 'Name', 'y'], 1223 ['break-a', 'leave', 'Name', 'y'], 1224 ['break-b', 'leave', 'Name', 'y'], 1225 ['break-a', 'leave', 'Field', undefined], 1226 ['break-b', 'leave', 'Field', undefined], 1227 ['break-a', 'leave', 'SelectionSet', undefined], 1228 ['break-b', 'leave', 'SelectionSet', undefined], 1229 ['break-a', 'leave', 'Field', undefined], 1230 ['break-b', 'leave', 'Field', undefined], 1231 ['break-b', 'enter', 'Field', undefined], 1232 ['break-b', 'enter', 'Name', 'b'], 1233 ['break-b', 'leave', 'Name', 'b'], 1234 ['break-b', 'enter', 'SelectionSet', undefined], 1235 ['break-b', 'enter', 'Field', undefined], 1236 ['break-b', 'enter', 'Name', 'x'], 1237 ['break-b', 'leave', 'Name', 'x'], 1238 ['break-b', 'leave', 'Field', undefined], 1239 ['break-b', 'leave', 'SelectionSet', undefined], 1240 ['break-b', 'leave', 'Field', undefined], 1241 ]); 1242 }); 1243 1244 it('allows for editing on enter', () => { 1245 const visited = []; 1246 1247 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 1248 const editedAST = visit( 1249 ast, 1250 visitInParallel([ 1251 { 1252 enter(node) { 1253 checkVisitorFnArgs(ast, arguments); 1254 if (node.kind === 'Field' && node.name.value === 'b') { 1255 return null; 1256 } 1257 }, 1258 }, 1259 { 1260 enter(node) { 1261 checkVisitorFnArgs(ast, arguments); 1262 visited.push(['enter', node.kind, getValue(node)]); 1263 }, 1264 leave(node) { 1265 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1266 visited.push(['leave', node.kind, getValue(node)]); 1267 }, 1268 }, 1269 ]) 1270 ); 1271 1272 expect(ast).toEqual( 1273 parse('{ a, b, c { a, b, c } }', { noLocation: true }) 1274 ); 1275 1276 expect(editedAST).toEqual( 1277 parse('{ a, c { a, c } }', { noLocation: true }) 1278 ); 1279 1280 expect(visited).toEqual([ 1281 ['enter', 'Document', undefined], 1282 ['enter', 'OperationDefinition', undefined], 1283 ['enter', 'SelectionSet', undefined], 1284 ['enter', 'Field', undefined], 1285 ['enter', 'Name', 'a'], 1286 ['leave', 'Name', 'a'], 1287 ['leave', 'Field', undefined], 1288 ['enter', 'Field', undefined], 1289 ['enter', 'Name', 'c'], 1290 ['leave', 'Name', 'c'], 1291 ['enter', 'SelectionSet', undefined], 1292 ['enter', 'Field', undefined], 1293 ['enter', 'Name', 'a'], 1294 ['leave', 'Name', 'a'], 1295 ['leave', 'Field', undefined], 1296 ['enter', 'Field', undefined], 1297 ['enter', 'Name', 'c'], 1298 ['leave', 'Name', 'c'], 1299 ['leave', 'Field', undefined], 1300 ['leave', 'SelectionSet', undefined], 1301 ['leave', 'Field', undefined], 1302 ['leave', 'SelectionSet', undefined], 1303 ['leave', 'OperationDefinition', undefined], 1304 ['leave', 'Document', undefined], 1305 ]); 1306 }); 1307 1308 it('allows for editing on leave', () => { 1309 const visited = []; 1310 1311 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 1312 const editedAST = visit( 1313 ast, 1314 visitInParallel([ 1315 { 1316 leave(node) { 1317 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1318 if (node.kind === 'Field' && node.name.value === 'b') { 1319 return null; 1320 } 1321 }, 1322 }, 1323 { 1324 enter(node) { 1325 checkVisitorFnArgs(ast, arguments); 1326 visited.push(['enter', node.kind, getValue(node)]); 1327 }, 1328 leave(node) { 1329 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1330 visited.push(['leave', node.kind, getValue(node)]); 1331 }, 1332 }, 1333 ]) 1334 ); 1335 1336 expect(ast).toEqual( 1337 parse('{ a, b, c { a, b, c } }', { noLocation: true }) 1338 ); 1339 1340 expect(editedAST).toEqual( 1341 parse('{ a, c { a, c } }', { noLocation: true }) 1342 ); 1343 1344 expect(visited).toEqual([ 1345 ['enter', 'Document', undefined], 1346 ['enter', 'OperationDefinition', undefined], 1347 ['enter', 'SelectionSet', undefined], 1348 ['enter', 'Field', undefined], 1349 ['enter', 'Name', 'a'], 1350 ['leave', 'Name', 'a'], 1351 ['leave', 'Field', undefined], 1352 ['enter', 'Field', undefined], 1353 ['enter', 'Name', 'b'], 1354 ['leave', 'Name', 'b'], 1355 ['enter', 'Field', undefined], 1356 ['enter', 'Name', 'c'], 1357 ['leave', 'Name', 'c'], 1358 ['enter', 'SelectionSet', undefined], 1359 ['enter', 'Field', undefined], 1360 ['enter', 'Name', 'a'], 1361 ['leave', 'Name', 'a'], 1362 ['leave', 'Field', undefined], 1363 ['enter', 'Field', undefined], 1364 ['enter', 'Name', 'b'], 1365 ['leave', 'Name', 'b'], 1366 ['enter', 'Field', undefined], 1367 ['enter', 'Name', 'c'], 1368 ['leave', 'Name', 'c'], 1369 ['leave', 'Field', undefined], 1370 ['leave', 'SelectionSet', undefined], 1371 ['leave', 'Field', undefined], 1372 ['leave', 'SelectionSet', undefined], 1373 ['leave', 'OperationDefinition', undefined], 1374 ['leave', 'Document', undefined], 1375 ]); 1376 }); 1377 }); 1378});