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