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
59 it('prints query with variable directives', () => {
60 const queryASTWithVariableDirective = parse(
61 'query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { id }'
62 );
63 expect(print(queryASTWithVariableDirective)).toBe(dedent`
64 query ($foo: TestType = {a: 123} @testDirective(if: true) @test) {
65 id
66 }
67 `);
68 });
69
70 it('keeps arguments on one line if line is short (<= 80 chars)', () => {
71 const printed = print(
72 parse('{trip(wheelchair:false arriveBy:false){dateTime}}')
73 );
74
75 expect(printed).toBe(
76 dedent`
77 {
78 trip(wheelchair: false, arriveBy: false) {
79 dateTime
80 }
81 }
82 `
83 );
84 });
85
86 it('prints kitchen sink without altering ast', () => {
87 const ast = parse(kitchenSinkQuery, { noLocation: true });
88
89 const astBeforePrintCall = JSON.stringify(ast);
90 const printed = print(ast);
91 const printedAST = parse(printed, { noLocation: true });
92
93 expect(printedAST).toEqual(ast);
94 expect(JSON.stringify(ast)).toBe(astBeforePrintCall);
95
96 expect(printed).toBe(
97 dedentString(String.raw`
98 query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
99 whoever123is: node(id: [123, 456]) {
100 id
101 ... on User @onInlineFragment {
102 field2 {
103 id
104 alias: field1(first: 10, after: $foo) @include(if: $foo) {
105 id
106 ...frag @onFragmentSpread
107 }
108 }
109 }
110 ... @skip(unless: $foo) {
111 id
112 }
113 ... {
114 id
115 }
116 }
117 }
118
119 mutation likeStory @onMutation {
120 like(story: 123) @onField {
121 story {
122 id @onField
123 }
124 }
125 }
126
127 subscription StoryLikeSubscription($input: StoryLikeSubscribeInput @onVariableDefinition) @onSubscription {
128 storyLikeSubscribe(input: $input) {
129 story {
130 likers {
131 count
132 }
133 likeSentence {
134 text
135 }
136 }
137 }
138 }
139
140 fragment frag on Friend @onFragmentDefinition {
141 foo(size: $size, bar: $b, obj: {key: "value"})
142 }
143
144 {
145 unnamed(truthy: true, falsy: false, nullish: null)
146 query
147 }
148
149 {
150 __typename
151 }
152 `) + '\n'
153 );
154 });
155});
156
157const kitchenSinkQuery = String.raw`
158query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
159 whoever123is: node(id: [123, 456]) {
160 id
161 ... on User @onInlineFragment {
162 field2 {
163 id
164 alias: field1(first: 10, after: $foo) @include(if: $foo) {
165 id
166 ...frag @onFragmentSpread
167 }
168 }
169 }
170 ... @skip(unless: $foo) {
171 id
172 }
173 ... {
174 id
175 }
176 }
177}
178mutation likeStory @onMutation {
179 like(story: 123) @onField {
180 story {
181 id @onField
182 }
183 }
184}
185subscription StoryLikeSubscription(
186 $input: StoryLikeSubscribeInput @onVariableDefinition
187)
188 @onSubscription {
189 storyLikeSubscribe(input: $input) {
190 story {
191 likers {
192 count
193 }
194 likeSentence {
195 text
196 }
197 }
198 }
199}
200fragment frag on Friend @onFragmentDefinition {
201 foo(
202 size: $size
203 bar: $b
204 obj: { key: "value" }
205 )
206}
207{
208 unnamed(truthy: true, falsy: false, nullish: null)
209 query
210}
211query {
212 __typename
213}
214`;
215
216function dedentString(string) {
217 const trimmedStr = string
218 .replace(/^\n*/m, '') // remove leading newline
219 .replace(/[ \t\n]*$/, ''); // remove trailing spaces and tabs
220 // fixes indentation by removing leading spaces and tabs from each line
221 let indent = '';
222 for (const char of trimmedStr) {
223 if (char !== ' ' && char !== '\t') {
224 break;
225 }
226 indent += char;
227 }
228
229 return trimmedStr.replace(RegExp('^' + indent, 'mg'), ''); // remove indent
230}
231
232function dedent(strings, ...values) {
233 let str = strings[0];
234 for (let i = 1; i < strings.length; ++i) str += values[i - 1] + strings[i]; // interpolation
235 return dedentString(str) + '\n';
236}