Mirror: The spec-compliant minimum of client-side GraphQL.
1import { describe, it, expect } from 'vitest';
2import { parse } from '../parser';
3import { print } from '../printer';
4import type {
5 OperationDefinitionNode,
6 VariableDefinitionNode,
7 FragmentDefinitionNode,
8} from '../ast';
9
10describe('GraphQL descriptions', () => {
11 describe('OperationDefinition descriptions', () => {
12 it('parses operation with description', () => {
13 const source = `
14 """
15 Request the current status of a time machine and its operator.
16 """
17 query GetTimeMachineStatus {
18 timeMachine {
19 id
20 status
21 }
22 }
23 `;
24
25 const doc = parse(source, { noLocation: true });
26 const operation = doc.definitions[0] as OperationDefinitionNode;
27
28 expect(operation.description).toBeDefined();
29 expect(operation.description?.value).toBe(
30 'Request the current status of a time machine and its operator.'
31 );
32 expect(operation.description?.block).toBe(true);
33 });
34
35 it('parses operation with single-line description', () => {
36 const source = `
37 "Simple query description"
38 query SimpleQuery {
39 field
40 }
41 `;
42
43 const doc = parse(source, { noLocation: true });
44 const operation = doc.definitions[0] as OperationDefinitionNode;
45
46 expect(operation.description).toBeDefined();
47 expect(operation.description?.value).toBe('Simple query description');
48 expect(operation.description?.block).toBe(false);
49 });
50
51 it('does not allow description on anonymous operations', () => {
52 const source = `
53 "This should fail"
54 {
55 field
56 }
57 `;
58
59 expect(() => parse(source)).toThrow();
60 });
61
62 it('parses mutation with description', () => {
63 const source = `
64 """
65 Create a new time machine entry.
66 """
67 mutation CreateTimeMachine($input: TimeMachineInput!) {
68 createTimeMachine(input: $input) {
69 id
70 }
71 }
72 `;
73
74 const doc = parse(source, { noLocation: true });
75 const operation = doc.definitions[0] as OperationDefinitionNode;
76
77 expect(operation.description).toBeDefined();
78 expect(operation.description?.value).toBe('Create a new time machine entry.');
79 });
80 });
81
82 describe('VariableDefinition descriptions', () => {
83 it('parses variable with description', () => {
84 const source = `
85 query GetTimeMachineStatus(
86 "The unique serial number of the time machine to inspect."
87 $machineId: ID!
88
89 """
90 The year to check the status for.
91 **Warning:** certain years may trigger an anomaly in the space-time continuum.
92 """
93 $year: Int
94 ) {
95 timeMachine(id: $machineId) {
96 status(year: $year)
97 }
98 }
99 `;
100
101 const doc = parse(source, { noLocation: true });
102 const operation = doc.definitions[0] as OperationDefinitionNode;
103 const variables = operation.variableDefinitions as VariableDefinitionNode[];
104
105 expect(variables[0].description).toBeDefined();
106 expect(variables[0].description?.value).toBe(
107 'The unique serial number of the time machine to inspect.'
108 );
109 expect(variables[0].description?.block).toBe(false);
110
111 expect(variables[1].description).toBeDefined();
112 expect(variables[1].description?.value).toBe(
113 'The year to check the status for.\n**Warning:** certain years may trigger an anomaly in the space-time continuum.'
114 );
115 expect(variables[1].description?.block).toBe(true);
116 });
117
118 it('parses mixed variables with and without descriptions', () => {
119 const source = `
120 query Mixed(
121 "Described variable"
122 $described: String
123 $undescribed: Int
124 ) {
125 field
126 }
127 `;
128
129 const doc = parse(source, { noLocation: true });
130 const operation = doc.definitions[0] as OperationDefinitionNode;
131 const variables = operation.variableDefinitions as VariableDefinitionNode[];
132
133 expect(variables[0].description).toBeDefined();
134 expect(variables[0].description?.value).toBe('Described variable');
135 expect(variables[1].description).toBeUndefined();
136 });
137 });
138
139 describe('FragmentDefinition descriptions', () => {
140 it('parses fragment with description', () => {
141 const source = `
142 "Time machine details."
143 fragment TimeMachineDetails on TimeMachine {
144 id
145 model
146 lastMaintenance
147 }
148 `;
149
150 const doc = parse(source, { noLocation: true });
151 const fragment = doc.definitions[0] as FragmentDefinitionNode;
152
153 expect(fragment.description).toBeDefined();
154 expect(fragment.description?.value).toBe('Time machine details.');
155 expect(fragment.description?.block).toBe(false);
156 });
157
158 it('parses fragment with block description', () => {
159 const source = `
160 """
161 Comprehensive time machine information
162 including maintenance history and operational status.
163 """
164 fragment FullTimeMachineInfo on TimeMachine {
165 id
166 model
167 lastMaintenance
168 operationalStatus
169 }
170 `;
171
172 const doc = parse(source, { noLocation: true });
173 const fragment = doc.definitions[0] as FragmentDefinitionNode;
174
175 expect(fragment.description).toBeDefined();
176 expect(fragment.description?.value).toBe(
177 'Comprehensive time machine information\nincluding maintenance history and operational status.'
178 );
179 expect(fragment.description?.block).toBe(true);
180 });
181 });
182
183 describe('print with descriptions', () => {
184 it('prints operation description correctly', () => {
185 const source = `"""
186Request the current status of a time machine and its operator.
187"""
188query GetTimeMachineStatus {
189 timeMachine {
190 id
191 }
192}`;
193
194 const doc = parse(source, { noLocation: true });
195 const printed = print(doc);
196
197 expect(printed).toContain('"""');
198 expect(printed).toContain('Request the current status of a time machine and its operator.');
199 });
200
201 it('prints variable descriptions correctly', () => {
202 const source = `query GetStatus(
203 "Machine ID"
204 $id: ID!
205) {
206 field
207}`;
208
209 const doc = parse(source, { noLocation: true });
210 const printed = print(doc);
211
212 expect(printed).toContain('"Machine ID"');
213 });
214
215 it('prints fragment description correctly', () => {
216 const source = `"Details fragment"
217fragment Details on Type {
218 field
219}`;
220
221 const doc = parse(source, { noLocation: true });
222 const printed = print(doc);
223
224 expect(printed).toContain('"Details fragment"');
225 });
226 });
227
228 describe('roundtrip parsing and printing', () => {
229 it('maintains descriptions through parse and print cycle', () => {
230 const source = `"""
231Request the current status of a time machine and its operator.
232"""
233query GetTimeMachineStatus(
234 "The unique serial number of the time machine to inspect."
235 $machineId: ID!
236
237 """
238 The year to check the status for.
239 **Warning:** certain years may trigger an anomaly in the space-time continuum.
240 """
241 $year: Int
242) {
243 timeMachine(id: $machineId) {
244 ...TimeMachineDetails
245 operator {
246 name
247 licenseLevel
248 }
249 status(year: $year)
250 }
251}
252
253"Time machine details."
254fragment TimeMachineDetails on TimeMachine {
255 id
256 model
257 lastMaintenance
258}`;
259
260 const doc = parse(source, { noLocation: true });
261 const printed = print(doc);
262 const reparsed = parse(printed, { noLocation: true });
263
264 const operation = doc.definitions[0] as OperationDefinitionNode;
265 const reparsedOperation = reparsed.definitions[0] as OperationDefinitionNode;
266
267 // The printed/reparsed cycle may have slightly different formatting but same content
268 expect(reparsedOperation.description?.value?.trim()).toBe(
269 operation.description?.value?.trim()
270 );
271
272 const fragment = doc.definitions[1] as FragmentDefinitionNode;
273 const reparsedFragment = reparsed.definitions[1] as FragmentDefinitionNode;
274
275 expect(reparsedFragment.description?.value).toBe(fragment.description?.value);
276 });
277 });
278});