···11+---
22+"@0no-co/graphql.web": minor
33+---
44+55+Add support for executable definitions as defined in https://github.com/graphql/graphql-spec/pull/1170
+278
src/__tests__/description.test.ts
···11+import { describe, it, expect } from 'vitest';
22+import { parse } from '../parser';
33+import { print } from '../printer';
44+import type {
55+ OperationDefinitionNode,
66+ VariableDefinitionNode,
77+ FragmentDefinitionNode,
88+} from '../ast';
99+1010+describe('GraphQL descriptions', () => {
1111+ describe('OperationDefinition descriptions', () => {
1212+ it('parses operation with description', () => {
1313+ const source = `
1414+ """
1515+ Request the current status of a time machine and its operator.
1616+ """
1717+ query GetTimeMachineStatus {
1818+ timeMachine {
1919+ id
2020+ status
2121+ }
2222+ }
2323+ `;
2424+2525+ const doc = parse(source, { noLocation: true });
2626+ const operation = doc.definitions[0] as OperationDefinitionNode;
2727+2828+ expect(operation.description).toBeDefined();
2929+ expect(operation.description?.value).toBe(
3030+ 'Request the current status of a time machine and its operator.'
3131+ );
3232+ expect(operation.description?.block).toBe(true);
3333+ });
3434+3535+ it('parses operation with single-line description', () => {
3636+ const source = `
3737+ "Simple query description"
3838+ query SimpleQuery {
3939+ field
4040+ }
4141+ `;
4242+4343+ const doc = parse(source, { noLocation: true });
4444+ const operation = doc.definitions[0] as OperationDefinitionNode;
4545+4646+ expect(operation.description).toBeDefined();
4747+ expect(operation.description?.value).toBe('Simple query description');
4848+ expect(operation.description?.block).toBe(false);
4949+ });
5050+5151+ it('does not allow description on anonymous operations', () => {
5252+ const source = `
5353+ "This should fail"
5454+ {
5555+ field
5656+ }
5757+ `;
5858+5959+ expect(() => parse(source)).toThrow();
6060+ });
6161+6262+ it('parses mutation with description', () => {
6363+ const source = `
6464+ """
6565+ Create a new time machine entry.
6666+ """
6767+ mutation CreateTimeMachine($input: TimeMachineInput!) {
6868+ createTimeMachine(input: $input) {
6969+ id
7070+ }
7171+ }
7272+ `;
7373+7474+ const doc = parse(source, { noLocation: true });
7575+ const operation = doc.definitions[0] as OperationDefinitionNode;
7676+7777+ expect(operation.description).toBeDefined();
7878+ expect(operation.description?.value).toBe('Create a new time machine entry.');
7979+ });
8080+ });
8181+8282+ describe('VariableDefinition descriptions', () => {
8383+ it('parses variable with description', () => {
8484+ const source = `
8585+ query GetTimeMachineStatus(
8686+ "The unique serial number of the time machine to inspect."
8787+ $machineId: ID!
8888+8989+ """
9090+ The year to check the status for.
9191+ **Warning:** certain years may trigger an anomaly in the space-time continuum.
9292+ """
9393+ $year: Int
9494+ ) {
9595+ timeMachine(id: $machineId) {
9696+ status(year: $year)
9797+ }
9898+ }
9999+ `;
100100+101101+ const doc = parse(source, { noLocation: true });
102102+ const operation = doc.definitions[0] as OperationDefinitionNode;
103103+ const variables = operation.variableDefinitions as VariableDefinitionNode[];
104104+105105+ expect(variables[0].description).toBeDefined();
106106+ expect(variables[0].description?.value).toBe(
107107+ 'The unique serial number of the time machine to inspect.'
108108+ );
109109+ expect(variables[0].description?.block).toBe(false);
110110+111111+ expect(variables[1].description).toBeDefined();
112112+ expect(variables[1].description?.value).toBe(
113113+ 'The year to check the status for.\n**Warning:** certain years may trigger an anomaly in the space-time continuum.'
114114+ );
115115+ expect(variables[1].description?.block).toBe(true);
116116+ });
117117+118118+ it('parses mixed variables with and without descriptions', () => {
119119+ const source = `
120120+ query Mixed(
121121+ "Described variable"
122122+ $described: String
123123+ $undescribed: Int
124124+ ) {
125125+ field
126126+ }
127127+ `;
128128+129129+ const doc = parse(source, { noLocation: true });
130130+ const operation = doc.definitions[0] as OperationDefinitionNode;
131131+ const variables = operation.variableDefinitions as VariableDefinitionNode[];
132132+133133+ expect(variables[0].description).toBeDefined();
134134+ expect(variables[0].description?.value).toBe('Described variable');
135135+ expect(variables[1].description).toBeUndefined();
136136+ });
137137+ });
138138+139139+ describe('FragmentDefinition descriptions', () => {
140140+ it('parses fragment with description', () => {
141141+ const source = `
142142+ "Time machine details."
143143+ fragment TimeMachineDetails on TimeMachine {
144144+ id
145145+ model
146146+ lastMaintenance
147147+ }
148148+ `;
149149+150150+ const doc = parse(source, { noLocation: true });
151151+ const fragment = doc.definitions[0] as FragmentDefinitionNode;
152152+153153+ expect(fragment.description).toBeDefined();
154154+ expect(fragment.description?.value).toBe('Time machine details.');
155155+ expect(fragment.description?.block).toBe(false);
156156+ });
157157+158158+ it('parses fragment with block description', () => {
159159+ const source = `
160160+ """
161161+ Comprehensive time machine information
162162+ including maintenance history and operational status.
163163+ """
164164+ fragment FullTimeMachineInfo on TimeMachine {
165165+ id
166166+ model
167167+ lastMaintenance
168168+ operationalStatus
169169+ }
170170+ `;
171171+172172+ const doc = parse(source, { noLocation: true });
173173+ const fragment = doc.definitions[0] as FragmentDefinitionNode;
174174+175175+ expect(fragment.description).toBeDefined();
176176+ expect(fragment.description?.value).toBe(
177177+ 'Comprehensive time machine information\nincluding maintenance history and operational status.'
178178+ );
179179+ expect(fragment.description?.block).toBe(true);
180180+ });
181181+ });
182182+183183+ describe('print with descriptions', () => {
184184+ it('prints operation description correctly', () => {
185185+ const source = `"""
186186+Request the current status of a time machine and its operator.
187187+"""
188188+query GetTimeMachineStatus {
189189+ timeMachine {
190190+ id
191191+ }
192192+}`;
193193+194194+ const doc = parse(source, { noLocation: true });
195195+ const printed = print(doc);
196196+197197+ expect(printed).toContain('"""');
198198+ expect(printed).toContain('Request the current status of a time machine and its operator.');
199199+ });
200200+201201+ it('prints variable descriptions correctly', () => {
202202+ const source = `query GetStatus(
203203+ "Machine ID"
204204+ $id: ID!
205205+) {
206206+ field
207207+}`;
208208+209209+ const doc = parse(source, { noLocation: true });
210210+ const printed = print(doc);
211211+212212+ expect(printed).toContain('"Machine ID"');
213213+ });
214214+215215+ it('prints fragment description correctly', () => {
216216+ const source = `"Details fragment"
217217+fragment Details on Type {
218218+ field
219219+}`;
220220+221221+ const doc = parse(source, { noLocation: true });
222222+ const printed = print(doc);
223223+224224+ expect(printed).toContain('"Details fragment"');
225225+ });
226226+ });
227227+228228+ describe('roundtrip parsing and printing', () => {
229229+ it('maintains descriptions through parse and print cycle', () => {
230230+ const source = `"""
231231+Request the current status of a time machine and its operator.
232232+"""
233233+query GetTimeMachineStatus(
234234+ "The unique serial number of the time machine to inspect."
235235+ $machineId: ID!
236236+237237+ """
238238+ The year to check the status for.
239239+ **Warning:** certain years may trigger an anomaly in the space-time continuum.
240240+ """
241241+ $year: Int
242242+) {
243243+ timeMachine(id: $machineId) {
244244+ ...TimeMachineDetails
245245+ operator {
246246+ name
247247+ licenseLevel
248248+ }
249249+ status(year: $year)
250250+ }
251251+}
252252+253253+"Time machine details."
254254+fragment TimeMachineDetails on TimeMachine {
255255+ id
256256+ model
257257+ lastMaintenance
258258+}`;
259259+260260+ const doc = parse(source, { noLocation: true });
261261+ const printed = print(doc);
262262+ const reparsed = parse(printed, { noLocation: true });
263263+264264+ const operation = doc.definitions[0] as OperationDefinitionNode;
265265+ const reparsedOperation = reparsed.definitions[0] as OperationDefinitionNode;
266266+267267+ // The printed/reparsed cycle may have slightly different formatting but same content
268268+ expect(reparsedOperation.description?.value?.trim()).toBe(
269269+ operation.description?.value?.trim()
270270+ );
271271+272272+ const fragment = doc.definitions[1] as FragmentDefinitionNode;
273273+ const reparsedFragment = reparsed.definitions[1] as FragmentDefinitionNode;
274274+275275+ expect(reparsedFragment.description?.value).toBe(fragment.description?.value);
276276+ });
277277+ });
278278+});