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