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