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