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