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 } 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('visits kitchen sink', () => { 481 const ast = parse(kitchenSinkQuery); 482 const visited = []; 483 const argsStack = []; 484 485 visit(ast, { 486 enter(node, key, parent) { 487 visited.push([ 488 'enter', 489 node.kind, 490 key, 491 isNode(parent) ? parent.kind : undefined, 492 ]); 493 494 checkVisitorFnArgs(ast, arguments); 495 argsStack.push([...arguments]); 496 }, 497 498 leave(node, key, parent) { 499 visited.push([ 500 'leave', 501 node.kind, 502 key, 503 isNode(parent) ? parent.kind : undefined, 504 ]); 505 506 expect(argsStack.pop()).toEqual([...arguments]); 507 }, 508 }); 509 510 expect(argsStack).toEqual([]); 511 expect(visited).toEqual([ 512 ['enter', 'Document', undefined, undefined], 513 ['enter', 'OperationDefinition', 0, undefined], 514 ['enter', 'Name', 'name', 'OperationDefinition'], 515 ['leave', 'Name', 'name', 'OperationDefinition'], 516 ['enter', 'VariableDefinition', 0, undefined], 517 ['enter', 'Variable', 'variable', 'VariableDefinition'], 518 ['enter', 'Name', 'name', 'Variable'], 519 ['leave', 'Name', 'name', 'Variable'], 520 ['leave', 'Variable', 'variable', 'VariableDefinition'], 521 ['enter', 'NamedType', 'type', 'VariableDefinition'], 522 ['enter', 'Name', 'name', 'NamedType'], 523 ['leave', 'Name', 'name', 'NamedType'], 524 ['leave', 'NamedType', 'type', 'VariableDefinition'], 525 ['leave', 'VariableDefinition', 0, undefined], 526 ['enter', 'VariableDefinition', 1, undefined], 527 ['enter', 'Variable', 'variable', 'VariableDefinition'], 528 ['enter', 'Name', 'name', 'Variable'], 529 ['leave', 'Name', 'name', 'Variable'], 530 ['leave', 'Variable', 'variable', 'VariableDefinition'], 531 ['enter', 'NamedType', 'type', 'VariableDefinition'], 532 ['enter', 'Name', 'name', 'NamedType'], 533 ['leave', 'Name', 'name', 'NamedType'], 534 ['leave', 'NamedType', 'type', 'VariableDefinition'], 535 ['enter', 'EnumValue', 'defaultValue', 'VariableDefinition'], 536 ['leave', 'EnumValue', 'defaultValue', 'VariableDefinition'], 537 ['leave', 'VariableDefinition', 1, undefined], 538 ['enter', 'Directive', 0, undefined], 539 ['enter', 'Name', 'name', 'Directive'], 540 ['leave', 'Name', 'name', 'Directive'], 541 ['leave', 'Directive', 0, undefined], 542 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 543 ['enter', 'Field', 0, undefined], 544 ['enter', 'Name', 'alias', 'Field'], 545 ['leave', 'Name', 'alias', 'Field'], 546 ['enter', 'Name', 'name', 'Field'], 547 ['leave', 'Name', 'name', 'Field'], 548 ['enter', 'Argument', 0, undefined], 549 ['enter', 'Name', 'name', 'Argument'], 550 ['leave', 'Name', 'name', 'Argument'], 551 ['enter', 'ListValue', 'value', 'Argument'], 552 ['enter', 'IntValue', 0, undefined], 553 ['leave', 'IntValue', 0, undefined], 554 ['enter', 'IntValue', 1, undefined], 555 ['leave', 'IntValue', 1, undefined], 556 ['leave', 'ListValue', 'value', 'Argument'], 557 ['leave', 'Argument', 0, undefined], 558 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 559 ['enter', 'Field', 0, undefined], 560 ['enter', 'Name', 'name', 'Field'], 561 ['leave', 'Name', 'name', 'Field'], 562 ['leave', 'Field', 0, undefined], 563 ['enter', 'InlineFragment', 1, undefined], 564 ['enter', 'NamedType', 'typeCondition', 'InlineFragment'], 565 ['enter', 'Name', 'name', 'NamedType'], 566 ['leave', 'Name', 'name', 'NamedType'], 567 ['leave', 'NamedType', 'typeCondition', 'InlineFragment'], 568 ['enter', 'Directive', 0, undefined], 569 ['enter', 'Name', 'name', 'Directive'], 570 ['leave', 'Name', 'name', 'Directive'], 571 ['leave', 'Directive', 0, undefined], 572 ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 573 ['enter', 'Field', 0, undefined], 574 ['enter', 'Name', 'name', 'Field'], 575 ['leave', 'Name', 'name', 'Field'], 576 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 577 ['enter', 'Field', 0, undefined], 578 ['enter', 'Name', 'name', 'Field'], 579 ['leave', 'Name', 'name', 'Field'], 580 ['leave', 'Field', 0, undefined], 581 ['enter', 'Field', 1, undefined], 582 ['enter', 'Name', 'alias', 'Field'], 583 ['leave', 'Name', 'alias', 'Field'], 584 ['enter', 'Name', 'name', 'Field'], 585 ['leave', 'Name', 'name', 'Field'], 586 ['enter', 'Argument', 0, undefined], 587 ['enter', 'Name', 'name', 'Argument'], 588 ['leave', 'Name', 'name', 'Argument'], 589 ['enter', 'IntValue', 'value', 'Argument'], 590 ['leave', 'IntValue', 'value', 'Argument'], 591 ['leave', 'Argument', 0, undefined], 592 ['enter', 'Argument', 1, undefined], 593 ['enter', 'Name', 'name', 'Argument'], 594 ['leave', 'Name', 'name', 'Argument'], 595 ['enter', 'Variable', 'value', 'Argument'], 596 ['enter', 'Name', 'name', 'Variable'], 597 ['leave', 'Name', 'name', 'Variable'], 598 ['leave', 'Variable', 'value', 'Argument'], 599 ['leave', 'Argument', 1, undefined], 600 ['enter', 'Directive', 0, undefined], 601 ['enter', 'Name', 'name', 'Directive'], 602 ['leave', 'Name', 'name', 'Directive'], 603 ['enter', 'Argument', 0, undefined], 604 ['enter', 'Name', 'name', 'Argument'], 605 ['leave', 'Name', 'name', 'Argument'], 606 ['enter', 'Variable', 'value', 'Argument'], 607 ['enter', 'Name', 'name', 'Variable'], 608 ['leave', 'Name', 'name', 'Variable'], 609 ['leave', 'Variable', 'value', 'Argument'], 610 ['leave', 'Argument', 0, undefined], 611 ['leave', 'Directive', 0, undefined], 612 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 613 ['enter', 'Field', 0, undefined], 614 ['enter', 'Name', 'name', 'Field'], 615 ['leave', 'Name', 'name', 'Field'], 616 ['leave', 'Field', 0, undefined], 617 ['enter', 'FragmentSpread', 1, undefined], 618 ['enter', 'Name', 'name', 'FragmentSpread'], 619 ['leave', 'Name', 'name', 'FragmentSpread'], 620 ['enter', 'Directive', 0, undefined], 621 ['enter', 'Name', 'name', 'Directive'], 622 ['leave', 'Name', 'name', 'Directive'], 623 ['leave', 'Directive', 0, undefined], 624 ['leave', 'FragmentSpread', 1, undefined], 625 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 626 ['leave', 'Field', 1, undefined], 627 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 628 ['leave', 'Field', 0, undefined], 629 ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 630 ['leave', 'InlineFragment', 1, undefined], 631 ['enter', 'InlineFragment', 2, undefined], 632 ['enter', 'Directive', 0, undefined], 633 ['enter', 'Name', 'name', 'Directive'], 634 ['leave', 'Name', 'name', 'Directive'], 635 ['enter', 'Argument', 0, undefined], 636 ['enter', 'Name', 'name', 'Argument'], 637 ['leave', 'Name', 'name', 'Argument'], 638 ['enter', 'Variable', 'value', 'Argument'], 639 ['enter', 'Name', 'name', 'Variable'], 640 ['leave', 'Name', 'name', 'Variable'], 641 ['leave', 'Variable', 'value', 'Argument'], 642 ['leave', 'Argument', 0, undefined], 643 ['leave', 'Directive', 0, undefined], 644 ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 645 ['enter', 'Field', 0, undefined], 646 ['enter', 'Name', 'name', 'Field'], 647 ['leave', 'Name', 'name', 'Field'], 648 ['leave', 'Field', 0, undefined], 649 ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 650 ['leave', 'InlineFragment', 2, undefined], 651 ['enter', 'InlineFragment', 3, undefined], 652 ['enter', 'SelectionSet', 'selectionSet', 'InlineFragment'], 653 ['enter', 'Field', 0, undefined], 654 ['enter', 'Name', 'name', 'Field'], 655 ['leave', 'Name', 'name', 'Field'], 656 ['leave', 'Field', 0, undefined], 657 ['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'], 658 ['leave', 'InlineFragment', 3, undefined], 659 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 660 ['leave', 'Field', 0, undefined], 661 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 662 ['leave', 'OperationDefinition', 0, undefined], 663 ['enter', 'OperationDefinition', 1, undefined], 664 ['enter', 'Name', 'name', 'OperationDefinition'], 665 ['leave', 'Name', 'name', 'OperationDefinition'], 666 ['enter', 'Directive', 0, undefined], 667 ['enter', 'Name', 'name', 'Directive'], 668 ['leave', 'Name', 'name', 'Directive'], 669 ['leave', 'Directive', 0, undefined], 670 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 671 ['enter', 'Field', 0, undefined], 672 ['enter', 'Name', 'name', 'Field'], 673 ['leave', 'Name', 'name', 'Field'], 674 ['enter', 'Argument', 0, undefined], 675 ['enter', 'Name', 'name', 'Argument'], 676 ['leave', 'Name', 'name', 'Argument'], 677 ['enter', 'IntValue', 'value', 'Argument'], 678 ['leave', 'IntValue', 'value', 'Argument'], 679 ['leave', 'Argument', 0, undefined], 680 ['enter', 'Directive', 0, undefined], 681 ['enter', 'Name', 'name', 'Directive'], 682 ['leave', 'Name', 'name', 'Directive'], 683 ['leave', 'Directive', 0, undefined], 684 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 685 ['enter', 'Field', 0, undefined], 686 ['enter', 'Name', 'name', 'Field'], 687 ['leave', 'Name', 'name', 'Field'], 688 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 689 ['enter', 'Field', 0, undefined], 690 ['enter', 'Name', 'name', 'Field'], 691 ['leave', 'Name', 'name', 'Field'], 692 ['enter', 'Directive', 0, undefined], 693 ['enter', 'Name', 'name', 'Directive'], 694 ['leave', 'Name', 'name', 'Directive'], 695 ['leave', 'Directive', 0, undefined], 696 ['leave', 'Field', 0, undefined], 697 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 698 ['leave', 'Field', 0, undefined], 699 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 700 ['leave', 'Field', 0, undefined], 701 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 702 ['leave', 'OperationDefinition', 1, undefined], 703 ['enter', 'OperationDefinition', 2, undefined], 704 ['enter', 'Name', 'name', 'OperationDefinition'], 705 ['leave', 'Name', 'name', 'OperationDefinition'], 706 ['enter', 'VariableDefinition', 0, undefined], 707 ['enter', 'Variable', 'variable', 'VariableDefinition'], 708 ['enter', 'Name', 'name', 'Variable'], 709 ['leave', 'Name', 'name', 'Variable'], 710 ['leave', 'Variable', 'variable', 'VariableDefinition'], 711 ['enter', 'NamedType', 'type', 'VariableDefinition'], 712 ['enter', 'Name', 'name', 'NamedType'], 713 ['leave', 'Name', 'name', 'NamedType'], 714 ['leave', 'NamedType', 'type', 'VariableDefinition'], 715 ['enter', 'Directive', 0, undefined], 716 ['enter', 'Name', 'name', 'Directive'], 717 ['leave', 'Name', 'name', 'Directive'], 718 ['leave', 'Directive', 0, undefined], 719 ['leave', 'VariableDefinition', 0, undefined], 720 ['enter', 'Directive', 0, undefined], 721 ['enter', 'Name', 'name', 'Directive'], 722 ['leave', 'Name', 'name', 'Directive'], 723 ['leave', 'Directive', 0, undefined], 724 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 725 ['enter', 'Field', 0, undefined], 726 ['enter', 'Name', 'name', 'Field'], 727 ['leave', 'Name', 'name', 'Field'], 728 ['enter', 'Argument', 0, undefined], 729 ['enter', 'Name', 'name', 'Argument'], 730 ['leave', 'Name', 'name', 'Argument'], 731 ['enter', 'Variable', 'value', 'Argument'], 732 ['enter', 'Name', 'name', 'Variable'], 733 ['leave', 'Name', 'name', 'Variable'], 734 ['leave', 'Variable', 'value', 'Argument'], 735 ['leave', 'Argument', 0, undefined], 736 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 737 ['enter', 'Field', 0, undefined], 738 ['enter', 'Name', 'name', 'Field'], 739 ['leave', 'Name', 'name', 'Field'], 740 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 741 ['enter', 'Field', 0, undefined], 742 ['enter', 'Name', 'name', 'Field'], 743 ['leave', 'Name', 'name', 'Field'], 744 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 745 ['enter', 'Field', 0, undefined], 746 ['enter', 'Name', 'name', 'Field'], 747 ['leave', 'Name', 'name', 'Field'], 748 ['leave', 'Field', 0, undefined], 749 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 750 ['leave', 'Field', 0, undefined], 751 ['enter', 'Field', 1, undefined], 752 ['enter', 'Name', 'name', 'Field'], 753 ['leave', 'Name', 'name', 'Field'], 754 ['enter', 'SelectionSet', 'selectionSet', 'Field'], 755 ['enter', 'Field', 0, undefined], 756 ['enter', 'Name', 'name', 'Field'], 757 ['leave', 'Name', 'name', 'Field'], 758 ['leave', 'Field', 0, undefined], 759 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 760 ['leave', 'Field', 1, undefined], 761 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 762 ['leave', 'Field', 0, undefined], 763 ['leave', 'SelectionSet', 'selectionSet', 'Field'], 764 ['leave', 'Field', 0, undefined], 765 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 766 ['leave', 'OperationDefinition', 2, undefined], 767 ['enter', 'FragmentDefinition', 3, undefined], 768 ['enter', 'Name', 'name', 'FragmentDefinition'], 769 ['leave', 'Name', 'name', 'FragmentDefinition'], 770 ['enter', 'NamedType', 'typeCondition', 'FragmentDefinition'], 771 ['enter', 'Name', 'name', 'NamedType'], 772 ['leave', 'Name', 'name', 'NamedType'], 773 ['leave', 'NamedType', 'typeCondition', 'FragmentDefinition'], 774 ['enter', 'Directive', 0, undefined], 775 ['enter', 'Name', 'name', 'Directive'], 776 ['leave', 'Name', 'name', 'Directive'], 777 ['leave', 'Directive', 0, undefined], 778 ['enter', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], 779 ['enter', 'Field', 0, undefined], 780 ['enter', 'Name', 'name', 'Field'], 781 ['leave', 'Name', 'name', 'Field'], 782 ['enter', 'Argument', 0, undefined], 783 ['enter', 'Name', 'name', 'Argument'], 784 ['leave', 'Name', 'name', 'Argument'], 785 ['enter', 'Variable', 'value', 'Argument'], 786 ['enter', 'Name', 'name', 'Variable'], 787 ['leave', 'Name', 'name', 'Variable'], 788 ['leave', 'Variable', 'value', 'Argument'], 789 ['leave', 'Argument', 0, undefined], 790 ['enter', 'Argument', 1, undefined], 791 ['enter', 'Name', 'name', 'Argument'], 792 ['leave', 'Name', 'name', 'Argument'], 793 ['enter', 'Variable', 'value', 'Argument'], 794 ['enter', 'Name', 'name', 'Variable'], 795 ['leave', 'Name', 'name', 'Variable'], 796 ['leave', 'Variable', 'value', 'Argument'], 797 ['leave', 'Argument', 1, undefined], 798 ['enter', 'Argument', 2, undefined], 799 ['enter', 'Name', 'name', 'Argument'], 800 ['leave', 'Name', 'name', 'Argument'], 801 ['enter', 'ObjectValue', 'value', 'Argument'], 802 ['enter', 'ObjectField', 0, undefined], 803 ['enter', 'Name', 'name', 'ObjectField'], 804 ['leave', 'Name', 'name', 'ObjectField'], 805 ['enter', 'StringValue', 'value', 'ObjectField'], 806 ['leave', 'StringValue', 'value', 'ObjectField'], 807 ['leave', 'ObjectField', 0, undefined], 808 ['enter', 'ObjectField', 1, undefined], 809 ['enter', 'Name', 'name', 'ObjectField'], 810 ['leave', 'Name', 'name', 'ObjectField'], 811 ['enter', 'StringValue', 'value', 'ObjectField'], 812 ['leave', 'StringValue', 'value', 'ObjectField'], 813 ['leave', 'ObjectField', 1, undefined], 814 ['leave', 'ObjectValue', 'value', 'Argument'], 815 ['leave', 'Argument', 2, undefined], 816 ['leave', 'Field', 0, undefined], 817 ['leave', 'SelectionSet', 'selectionSet', 'FragmentDefinition'], 818 ['leave', 'FragmentDefinition', 3, undefined], 819 ['enter', 'OperationDefinition', 4, undefined], 820 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 821 ['enter', 'Field', 0, undefined], 822 ['enter', 'Name', 'name', 'Field'], 823 ['leave', 'Name', 'name', 'Field'], 824 ['enter', 'Argument', 0, undefined], 825 ['enter', 'Name', 'name', 'Argument'], 826 ['leave', 'Name', 'name', 'Argument'], 827 ['enter', 'BooleanValue', 'value', 'Argument'], 828 ['leave', 'BooleanValue', 'value', 'Argument'], 829 ['leave', 'Argument', 0, undefined], 830 ['enter', 'Argument', 1, undefined], 831 ['enter', 'Name', 'name', 'Argument'], 832 ['leave', 'Name', 'name', 'Argument'], 833 ['enter', 'BooleanValue', 'value', 'Argument'], 834 ['leave', 'BooleanValue', 'value', 'Argument'], 835 ['leave', 'Argument', 1, undefined], 836 ['enter', 'Argument', 2, undefined], 837 ['enter', 'Name', 'name', 'Argument'], 838 ['leave', 'Name', 'name', 'Argument'], 839 ['enter', 'NullValue', 'value', 'Argument'], 840 ['leave', 'NullValue', 'value', 'Argument'], 841 ['leave', 'Argument', 2, undefined], 842 ['leave', 'Field', 0, undefined], 843 ['enter', 'Field', 1, undefined], 844 ['enter', 'Name', 'name', 'Field'], 845 ['leave', 'Name', 'name', 'Field'], 846 ['leave', 'Field', 1, undefined], 847 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 848 ['leave', 'OperationDefinition', 4, undefined], 849 ['enter', 'OperationDefinition', 5, undefined], 850 ['enter', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 851 ['enter', 'Field', 0, undefined], 852 ['enter', 'Name', 'name', 'Field'], 853 ['leave', 'Name', 'name', 'Field'], 854 ['leave', 'Field', 0, undefined], 855 ['leave', 'SelectionSet', 'selectionSet', 'OperationDefinition'], 856 ['leave', 'OperationDefinition', 5, undefined], 857 ['leave', 'Document', undefined, undefined], 858 ]); 859 }); 860 861 // TODO: Verify discrepancies 862 describe('visitInParallel', () => { 863 // Note: nearly identical to the above test of the same test but 864 // using visitInParallel. 865 it('allows skipping a sub-tree', () => { 866 const visited = []; 867 868 const ast = parse('{ a, b { x }, c }'); 869 visit( 870 ast, 871 visitInParallel([ 872 { 873 enter(node) { 874 checkVisitorFnArgs(ast, arguments); 875 visited.push(['enter', node.kind, getValue(node)]); 876 if (node.kind === 'Field' && node.name.value === 'b') { 877 return false; 878 } 879 }, 880 881 leave(node) { 882 checkVisitorFnArgs(ast, arguments); 883 visited.push(['leave', node.kind, getValue(node)]); 884 }, 885 }, 886 ]) 887 ); 888 889 expect(visited).toEqual([ 890 ['enter', 'Document', undefined], 891 ['enter', 'OperationDefinition', undefined], 892 ['enter', 'SelectionSet', undefined], 893 ['enter', 'Field', undefined], 894 ['enter', 'Name', 'a'], 895 ['leave', 'Name', 'a'], 896 ['leave', 'Field', undefined], 897 ['enter', 'Field', undefined], 898 ['enter', 'Field', undefined], 899 ['enter', 'Name', 'c'], 900 ['leave', 'Name', 'c'], 901 ['leave', 'Field', undefined], 902 ['leave', 'SelectionSet', undefined], 903 ['leave', 'OperationDefinition', undefined], 904 ['leave', 'Document', undefined], 905 ]); 906 }); 907 908 it('allows skipping different sub-trees', () => { 909 const visited = []; 910 911 const ast = parse('{ a { x }, b { y} }'); 912 visit( 913 ast, 914 visitInParallel([ 915 { 916 enter(node) { 917 checkVisitorFnArgs(ast, arguments); 918 visited.push(['no-a', 'enter', node.kind, getValue(node)]); 919 if (node.kind === 'Field' && node.name.value === 'a') { 920 return false; 921 } 922 }, 923 leave(node) { 924 checkVisitorFnArgs(ast, arguments); 925 visited.push(['no-a', 'leave', node.kind, getValue(node)]); 926 }, 927 }, 928 { 929 enter(node) { 930 checkVisitorFnArgs(ast, arguments); 931 visited.push(['no-b', 'enter', node.kind, getValue(node)]); 932 if (node.kind === 'Field' && node.name.value === 'b') { 933 return false; 934 } 935 }, 936 leave(node) { 937 checkVisitorFnArgs(ast, arguments); 938 visited.push(['no-b', 'leave', node.kind, getValue(node)]); 939 }, 940 }, 941 ]) 942 ); 943 944 expect(visited).toEqual([ 945 ['no-a', 'enter', 'Document', undefined], 946 ['no-b', 'enter', 'Document', undefined], 947 ['no-a', 'enter', 'OperationDefinition', undefined], 948 ['no-b', 'enter', 'OperationDefinition', undefined], 949 ['no-a', 'enter', 'SelectionSet', undefined], 950 ['no-b', 'enter', 'SelectionSet', undefined], 951 ['no-a', 'enter', 'Field', undefined], 952 ['no-b', 'enter', 'Field', undefined], 953 ['no-b', 'enter', 'Name', 'a'], 954 ['no-b', 'leave', 'Name', 'a'], 955 ['no-b', 'enter', 'SelectionSet', undefined], 956 ['no-b', 'enter', 'Field', undefined], 957 ['no-b', 'enter', 'Name', 'x'], 958 ['no-b', 'leave', 'Name', 'x'], 959 ['no-b', 'leave', 'Field', undefined], 960 ['no-b', 'leave', 'SelectionSet', undefined], 961 ['no-b', 'leave', 'Field', undefined], 962 ['no-a', 'enter', 'Field', undefined], 963 ['no-b', 'enter', 'Field', undefined], 964 ['no-a', 'enter', 'Name', 'b'], 965 ['no-a', 'leave', 'Name', 'b'], 966 ['no-a', 'enter', 'SelectionSet', undefined], 967 ['no-a', 'enter', 'Field', undefined], 968 ['no-a', 'enter', 'Name', 'y'], 969 ['no-a', 'leave', 'Name', 'y'], 970 ['no-a', 'leave', 'Field', undefined], 971 ['no-a', 'leave', 'SelectionSet', undefined], 972 ['no-a', 'leave', 'Field', undefined], 973 ['no-a', 'leave', 'SelectionSet', undefined], 974 ['no-b', 'leave', 'SelectionSet', undefined], 975 ['no-a', 'leave', 'OperationDefinition', undefined], 976 ['no-b', 'leave', 'OperationDefinition', undefined], 977 ['no-a', 'leave', 'Document', undefined], 978 ['no-b', 'leave', 'Document', undefined], 979 ]); 980 }); 981 982 // Note: nearly identical to the above test of the same test but 983 // using visitInParallel. 984 it('allows early exit while visiting', () => { 985 const visited = []; 986 987 const ast = parse('{ a, b { x }, c }'); 988 visit( 989 ast, 990 visitInParallel([ 991 { 992 enter(node) { 993 checkVisitorFnArgs(ast, arguments); 994 visited.push(['enter', node.kind, getValue(node)]); 995 if (node.kind === 'Name' && node.value === 'x') { 996 return BREAK; 997 } 998 }, 999 leave(node) { 1000 checkVisitorFnArgs(ast, arguments); 1001 visited.push(['leave', node.kind, getValue(node)]); 1002 }, 1003 }, 1004 ]) 1005 ); 1006 1007 expect(visited).toEqual([ 1008 ['enter', 'Document', undefined], 1009 ['enter', 'OperationDefinition', undefined], 1010 ['enter', 'SelectionSet', undefined], 1011 ['enter', 'Field', undefined], 1012 ['enter', 'Name', 'a'], 1013 ['leave', 'Name', 'a'], 1014 ['leave', 'Field', undefined], 1015 ['enter', 'Field', undefined], 1016 ['enter', 'Name', 'b'], 1017 ['leave', 'Name', 'b'], 1018 ['enter', 'SelectionSet', undefined], 1019 ['enter', 'Field', undefined], 1020 ['enter', 'Name', 'x'], 1021 ]); 1022 }); 1023 1024 it('allows early exit from different points', () => { 1025 const visited = []; 1026 1027 const ast = parse('{ a { y }, b { x } }'); 1028 visit( 1029 ast, 1030 visitInParallel([ 1031 { 1032 enter(node) { 1033 checkVisitorFnArgs(ast, arguments); 1034 visited.push(['break-a', 'enter', node.kind, getValue(node)]); 1035 if (node.kind === 'Name' && node.value === 'a') { 1036 return BREAK; 1037 } 1038 }, 1039 // istanbul ignore next (Never called and used as a placeholder) 1040 leave() { 1041 expect.fail('Should not be called'); 1042 }, 1043 }, 1044 { 1045 enter(node) { 1046 checkVisitorFnArgs(ast, arguments); 1047 visited.push(['break-b', 'enter', node.kind, getValue(node)]); 1048 if (node.kind === 'Name' && node.value === 'b') { 1049 return BREAK; 1050 } 1051 }, 1052 leave(node) { 1053 checkVisitorFnArgs(ast, arguments); 1054 visited.push(['break-b', 'leave', node.kind, getValue(node)]); 1055 }, 1056 }, 1057 ]) 1058 ); 1059 1060 expect(visited).toEqual([ 1061 ['break-a', 'enter', 'Document', undefined], 1062 ['break-b', 'enter', 'Document', undefined], 1063 ['break-a', 'enter', 'OperationDefinition', undefined], 1064 ['break-b', 'enter', 'OperationDefinition', undefined], 1065 ['break-a', 'enter', 'SelectionSet', undefined], 1066 ['break-b', 'enter', 'SelectionSet', undefined], 1067 ['break-a', 'enter', 'Field', undefined], 1068 ['break-b', 'enter', 'Field', undefined], 1069 ['break-a', 'enter', 'Name', 'a'], 1070 ['break-b', 'enter', 'Name', 'a'], 1071 ['break-b', 'leave', 'Name', 'a'], 1072 ['break-b', 'enter', 'SelectionSet', undefined], 1073 ['break-b', 'enter', 'Field', undefined], 1074 ['break-b', 'enter', 'Name', 'y'], 1075 ['break-b', 'leave', 'Name', 'y'], 1076 ['break-b', 'leave', 'Field', undefined], 1077 ['break-b', 'leave', 'SelectionSet', undefined], 1078 ['break-b', 'leave', 'Field', undefined], 1079 ['break-b', 'enter', 'Field', undefined], 1080 ['break-b', 'enter', 'Name', 'b'], 1081 ]); 1082 }); 1083 1084 // Note: nearly identical to the above test of the same test but 1085 // using visitInParallel. 1086 it('allows early exit while leaving', () => { 1087 const visited = []; 1088 1089 const ast = parse('{ a, b { x }, c }'); 1090 visit( 1091 ast, 1092 visitInParallel([ 1093 { 1094 enter(node) { 1095 checkVisitorFnArgs(ast, arguments); 1096 visited.push(['enter', node.kind, getValue(node)]); 1097 }, 1098 leave(node) { 1099 checkVisitorFnArgs(ast, arguments); 1100 visited.push(['leave', node.kind, getValue(node)]); 1101 if (node.kind === 'Name' && node.value === 'x') { 1102 return BREAK; 1103 } 1104 }, 1105 }, 1106 ]) 1107 ); 1108 1109 expect(visited).toEqual([ 1110 ['enter', 'Document', undefined], 1111 ['enter', 'OperationDefinition', undefined], 1112 ['enter', 'SelectionSet', undefined], 1113 ['enter', 'Field', undefined], 1114 ['enter', 'Name', 'a'], 1115 ['leave', 'Name', 'a'], 1116 ['leave', 'Field', undefined], 1117 ['enter', 'Field', undefined], 1118 ['enter', 'Name', 'b'], 1119 ['leave', 'Name', 'b'], 1120 ['enter', 'SelectionSet', undefined], 1121 ['enter', 'Field', undefined], 1122 ['enter', 'Name', 'x'], 1123 ['leave', 'Name', 'x'], 1124 ]); 1125 }); 1126 1127 it('allows early exit from leaving different points', () => { 1128 const visited = []; 1129 1130 const ast = parse('{ a { y }, b { x } }'); 1131 visit( 1132 ast, 1133 visitInParallel([ 1134 { 1135 enter(node) { 1136 checkVisitorFnArgs(ast, arguments); 1137 visited.push(['break-a', 'enter', node.kind, getValue(node)]); 1138 }, 1139 leave(node) { 1140 checkVisitorFnArgs(ast, arguments); 1141 visited.push(['break-a', 'leave', node.kind, getValue(node)]); 1142 if (node.kind === 'Field' && node.name.value === 'a') { 1143 return BREAK; 1144 } 1145 }, 1146 }, 1147 { 1148 enter(node) { 1149 checkVisitorFnArgs(ast, arguments); 1150 visited.push(['break-b', 'enter', node.kind, getValue(node)]); 1151 }, 1152 leave(node) { 1153 checkVisitorFnArgs(ast, arguments); 1154 visited.push(['break-b', 'leave', node.kind, getValue(node)]); 1155 if (node.kind === 'Field' && node.name.value === 'b') { 1156 return BREAK; 1157 } 1158 }, 1159 }, 1160 ]) 1161 ); 1162 1163 expect(visited).toEqual([ 1164 ['break-a', 'enter', 'Document', undefined], 1165 ['break-b', 'enter', 'Document', undefined], 1166 ['break-a', 'enter', 'OperationDefinition', undefined], 1167 ['break-b', 'enter', 'OperationDefinition', undefined], 1168 ['break-a', 'enter', 'SelectionSet', undefined], 1169 ['break-b', 'enter', 'SelectionSet', undefined], 1170 ['break-a', 'enter', 'Field', undefined], 1171 ['break-b', 'enter', 'Field', undefined], 1172 ['break-a', 'enter', 'Name', 'a'], 1173 ['break-b', 'enter', 'Name', 'a'], 1174 ['break-a', 'leave', 'Name', 'a'], 1175 ['break-b', 'leave', 'Name', 'a'], 1176 ['break-a', 'enter', 'SelectionSet', undefined], 1177 ['break-b', 'enter', 'SelectionSet', undefined], 1178 ['break-a', 'enter', 'Field', undefined], 1179 ['break-b', 'enter', 'Field', undefined], 1180 ['break-a', 'enter', 'Name', 'y'], 1181 ['break-b', 'enter', 'Name', 'y'], 1182 ['break-a', 'leave', 'Name', 'y'], 1183 ['break-b', 'leave', 'Name', 'y'], 1184 ['break-a', 'leave', 'Field', undefined], 1185 ['break-b', 'leave', 'Field', undefined], 1186 ['break-a', 'leave', 'SelectionSet', undefined], 1187 ['break-b', 'leave', 'SelectionSet', undefined], 1188 ['break-a', 'leave', 'Field', undefined], 1189 ['break-b', 'leave', 'Field', undefined], 1190 ['break-b', 'enter', 'Field', undefined], 1191 ['break-b', 'enter', 'Name', 'b'], 1192 ['break-b', 'leave', 'Name', 'b'], 1193 ['break-b', 'enter', 'SelectionSet', undefined], 1194 ['break-b', 'enter', 'Field', undefined], 1195 ['break-b', 'enter', 'Name', 'x'], 1196 ['break-b', 'leave', 'Name', 'x'], 1197 ['break-b', 'leave', 'Field', undefined], 1198 ['break-b', 'leave', 'SelectionSet', undefined], 1199 ['break-b', 'leave', 'Field', undefined], 1200 ]); 1201 }); 1202 1203 it('allows for editing on enter', () => { 1204 const visited = []; 1205 1206 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 1207 const editedAST = visit( 1208 ast, 1209 visitInParallel([ 1210 { 1211 enter(node) { 1212 checkVisitorFnArgs(ast, arguments); 1213 if (node.kind === 'Field' && node.name.value === 'b') { 1214 return null; 1215 } 1216 }, 1217 }, 1218 { 1219 enter(node) { 1220 checkVisitorFnArgs(ast, arguments); 1221 visited.push(['enter', node.kind, getValue(node)]); 1222 }, 1223 leave(node) { 1224 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1225 visited.push(['leave', node.kind, getValue(node)]); 1226 }, 1227 }, 1228 ]) 1229 ); 1230 1231 expect(ast).toEqual( 1232 parse('{ a, b, c { a, b, c } }', { noLocation: true }) 1233 ); 1234 1235 expect(editedAST).toEqual( 1236 parse('{ a, c { a, c } }', { noLocation: true }) 1237 ); 1238 1239 expect(visited).toEqual([ 1240 ['enter', 'Document', undefined], 1241 ['enter', 'OperationDefinition', undefined], 1242 ['enter', 'SelectionSet', undefined], 1243 ['enter', 'Field', undefined], 1244 ['enter', 'Name', 'a'], 1245 ['leave', 'Name', 'a'], 1246 ['leave', 'Field', undefined], 1247 ['enter', 'Field', undefined], 1248 ['enter', 'Name', 'c'], 1249 ['leave', 'Name', 'c'], 1250 ['enter', 'SelectionSet', undefined], 1251 ['enter', 'Field', undefined], 1252 ['enter', 'Name', 'a'], 1253 ['leave', 'Name', 'a'], 1254 ['leave', 'Field', undefined], 1255 ['enter', 'Field', undefined], 1256 ['enter', 'Name', 'c'], 1257 ['leave', 'Name', 'c'], 1258 ['leave', 'Field', undefined], 1259 ['leave', 'SelectionSet', undefined], 1260 ['leave', 'Field', undefined], 1261 ['leave', 'SelectionSet', undefined], 1262 ['leave', 'OperationDefinition', undefined], 1263 ['leave', 'Document', undefined], 1264 ]); 1265 }); 1266 1267 it('allows for editing on leave', () => { 1268 const visited = []; 1269 1270 const ast = parse('{ a, b, c { a, b, c } }', { noLocation: true }); 1271 const editedAST = visit( 1272 ast, 1273 visitInParallel([ 1274 { 1275 leave(node) { 1276 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1277 if (node.kind === 'Field' && node.name.value === 'b') { 1278 return null; 1279 } 1280 }, 1281 }, 1282 { 1283 enter(node) { 1284 checkVisitorFnArgs(ast, arguments); 1285 visited.push(['enter', node.kind, getValue(node)]); 1286 }, 1287 leave(node) { 1288 checkVisitorFnArgs(ast, arguments, /* isEdited */ true); 1289 visited.push(['leave', node.kind, getValue(node)]); 1290 }, 1291 }, 1292 ]) 1293 ); 1294 1295 expect(ast).toEqual( 1296 parse('{ a, b, c { a, b, c } }', { noLocation: true }) 1297 ); 1298 1299 expect(editedAST).toEqual( 1300 parse('{ a, c { a, c } }', { noLocation: true }) 1301 ); 1302 1303 expect(visited).toEqual([ 1304 ['enter', 'Document', undefined], 1305 ['enter', 'OperationDefinition', undefined], 1306 ['enter', 'SelectionSet', undefined], 1307 ['enter', 'Field', undefined], 1308 ['enter', 'Name', 'a'], 1309 ['leave', 'Name', 'a'], 1310 ['leave', 'Field', undefined], 1311 ['enter', 'Field', undefined], 1312 ['enter', 'Name', 'b'], 1313 ['leave', 'Name', 'b'], 1314 ['enter', 'Field', undefined], 1315 ['enter', 'Name', 'c'], 1316 ['leave', 'Name', 'c'], 1317 ['enter', 'SelectionSet', undefined], 1318 ['enter', 'Field', undefined], 1319 ['enter', 'Name', 'a'], 1320 ['leave', 'Name', 'a'], 1321 ['leave', 'Field', undefined], 1322 ['enter', 'Field', undefined], 1323 ['enter', 'Name', 'b'], 1324 ['leave', 'Name', 'b'], 1325 ['enter', 'Field', undefined], 1326 ['enter', 'Name', 'c'], 1327 ['leave', 'Name', 'c'], 1328 ['leave', 'Field', undefined], 1329 ['leave', 'SelectionSet', undefined], 1330 ['leave', 'Field', undefined], 1331 ['leave', 'SelectionSet', undefined], 1332 ['leave', 'OperationDefinition', undefined], 1333 ['leave', 'Document', undefined], 1334 ]); 1335 }); 1336 }); 1337});