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});