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__/printer-test.ts
2
3import { describe, it, expect } from 'vitest';
4import { parse } from '../parser';
5import { print } from '../printer';
6
7describe('Printer: Query document', () => {
8 it('prints minimal ast', () => {
9 const ast = {
10 kind: 'Field',
11 name: { kind: 'Name', value: 'foo' },
12 };
13 expect(print(ast)).toBe('foo');
14 });
15
16 // NOTE: The shim won't throw for invalid AST nodes
17 it('returns empty strings for invalid AST', () => {
18 const badAST = { random: 'Data' };
19 expect(print(badAST)).toBe('');
20 });
21
22 it('correctly prints non-query operations without name', () => {
23 const queryASTShorthanded = parse('query { id, name }');
24 expect(print(queryASTShorthanded)).toBe(dedent`
25 {
26 id
27 name
28 }
29 `);
30
31 const mutationAST = parse('mutation { id, name }');
32 expect(print(mutationAST)).toBe(dedent`
33 mutation {
34 id
35 name
36 }
37 `);
38
39 const queryASTWithArtifacts = parse('query ($foo: TestType) @testDirective { id, name }');
40 expect(print(queryASTWithArtifacts)).toBe(dedent`
41 query ($foo: TestType) @testDirective {
42 id
43 name
44 }
45 `);
46
47 const mutationASTWithArtifacts = parse('mutation ($foo: TestType) @testDirective { id, name }');
48 expect(print(mutationASTWithArtifacts)).toBe(dedent`
49 mutation ($foo: TestType) @testDirective {
50 id
51 name
52 }
53 `);
54 });
55
56 it('prints query with variable directives', () => {
57 const queryASTWithVariableDirective = parse(
58 'query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { id }'
59 );
60 expect(print(queryASTWithVariableDirective)).toBe(dedent`
61 query ($foo: TestType = {a: 123} @testDirective(if: true) @test) {
62 id
63 }
64 `);
65 });
66
67 it('keeps arguments on one line if line is short (<= 80 chars)', () => {
68 const printed = print(parse('{trip(wheelchair:false arriveBy:false){dateTime}}'));
69
70 expect(printed).toBe(
71 dedent`
72 {
73 trip(wheelchair: false, arriveBy: false) {
74 dateTime
75 }
76 }
77 `
78 );
79 });
80
81 it('prints kitchen sink without altering ast', () => {
82 const ast = parse(kitchenSinkQuery, { noLocation: true });
83
84 const astBeforePrintCall = JSON.stringify(ast);
85 const printed = print(ast);
86 const printedAST = parse(printed, { noLocation: true });
87
88 expect(printedAST).toEqual(ast);
89 expect(JSON.stringify(ast)).toBe(astBeforePrintCall);
90
91 expect(printed).toBe(
92 dedentString(String.raw`
93 query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
94 whoever123is: node(id: [123, 456]) {
95 id
96 ... on User @onInlineFragment {
97 field2 {
98 id
99 alias: field1(first: 10, after: $foo) @include(if: $foo) {
100 id
101 ...frag @onFragmentSpread
102 }
103 }
104 }
105 ... @skip(unless: $foo) {
106 id
107 }
108 ... {
109 id
110 }
111 }
112 }
113
114 mutation likeStory @onMutation {
115 like(story: 123) @onField {
116 story {
117 id @onField
118 }
119 }
120 }
121
122 subscription StoryLikeSubscription($input: StoryLikeSubscribeInput @onVariableDefinition) @onSubscription {
123 storyLikeSubscribe(input: $input) {
124 story {
125 likers {
126 count
127 }
128 likeSentence {
129 text
130 }
131 }
132 }
133 }
134
135 fragment frag on Friend @onFragmentDefinition {
136 foo(size: $size, bar: $b, obj: {key: "value"})
137 }
138
139 {
140 unnamed(truthy: true, falsy: false, nullish: null)
141 query
142 }
143
144 {
145 __typename
146 }
147 `) + '\n'
148 );
149 });
150});
151
152const kitchenSinkQuery = String.raw`
153query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
154 whoever123is: node(id: [123, 456]) {
155 id
156 ... on User @onInlineFragment {
157 field2 {
158 id
159 alias: field1(first: 10, after: $foo) @include(if: $foo) {
160 id
161 ...frag @onFragmentSpread
162 }
163 }
164 }
165 ... @skip(unless: $foo) {
166 id
167 }
168 ... {
169 id
170 }
171 }
172}
173mutation likeStory @onMutation {
174 like(story: 123) @onField {
175 story {
176 id @onField
177 }
178 }
179}
180subscription StoryLikeSubscription(
181 $input: StoryLikeSubscribeInput @onVariableDefinition
182)
183 @onSubscription {
184 storyLikeSubscribe(input: $input) {
185 story {
186 likers {
187 count
188 }
189 likeSentence {
190 text
191 }
192 }
193 }
194}
195fragment frag on Friend @onFragmentDefinition {
196 foo(
197 size: $size
198 bar: $b
199 obj: { key: "value" }
200 )
201}
202{
203 unnamed(truthy: true, falsy: false, nullish: null)
204 query
205}
206query {
207 __typename
208}
209`;
210
211function dedentString(string) {
212 const trimmedStr = string
213 .replace(/^\n*/m, '') // remove leading newline
214 .replace(/[ \t\n]*$/, ''); // remove trailing spaces and tabs
215 // fixes indentation by removing leading spaces and tabs from each line
216 let indent = '';
217 for (const char of trimmedStr) {
218 if (char !== ' ' && char !== '\t') {
219 break;
220 }
221 indent += char;
222 }
223
224 return trimmedStr.replace(RegExp('^' + indent, 'mg'), ''); // remove indent
225}
226
227function dedent(strings, ...values) {
228 let str = strings[0];
229 for (let i = 1; i < strings.length; ++i) str += values[i - 1] + strings[i]; // interpolation
230 return dedentString(str) + '\n';
231}