Mirror: TypeScript LSP plugin that finds GraphQL documents in your code and provides diagnostics, auto-complete and hover-information.
1import { expect, afterAll, beforeAll, it, describe } from 'vitest'; 2import { TSServer } from './server'; 3import path from 'node:path'; 4import fs from 'node:fs'; 5import url from 'node:url'; 6import ts from 'typescript/lib/tsserverlibrary'; 7 8const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 9 10const projectPath = path.resolve( 11 __dirname, 12 'fixture-project-tada-multi-schema' 13); 14describe('Multiple schemas', () => { 15 const outfilePokemonTest = path.join(projectPath, 'simple-pokemon.ts'); 16 const outfileTodoTest = path.join(projectPath, 'simple-todo.ts'); 17 18 let server: TSServer; 19 beforeAll(async () => { 20 server = new TSServer(projectPath, { debugLog: false }); 21 22 server.sendCommand('open', { 23 file: outfilePokemonTest, 24 fileContent: '// empty', 25 scriptKindName: 'TS', 26 } satisfies ts.server.protocol.OpenRequestArgs); 27 server.sendCommand('open', { 28 file: outfileTodoTest, 29 fileContent: '// empty', 30 scriptKindName: 'TS', 31 } satisfies ts.server.protocol.OpenRequestArgs); 32 33 server.sendCommand('updateOpen', { 34 openFiles: [ 35 { 36 file: outfilePokemonTest, 37 fileContent: fs.readFileSync( 38 path.join(projectPath, 'fixtures/simple-pokemon.ts'), 39 'utf-8' 40 ), 41 }, 42 { 43 file: outfileTodoTest, 44 fileContent: fs.readFileSync( 45 path.join(projectPath, 'fixtures/simple-todo.ts'), 46 'utf-8' 47 ), 48 }, 49 ], 50 } satisfies ts.server.protocol.UpdateOpenRequestArgs); 51 52 server.sendCommand('saveto', { 53 file: outfilePokemonTest, 54 tmpfile: outfilePokemonTest, 55 } satisfies ts.server.protocol.SavetoRequestArgs); 56 server.sendCommand('saveto', { 57 file: outfileTodoTest, 58 tmpfile: outfileTodoTest, 59 } satisfies ts.server.protocol.SavetoRequestArgs); 60 61 // Give TS some time to figure this out... 62 await new Promise(resolve => setTimeout(resolve, 1000)); 63 }); 64 65 afterAll(() => { 66 try { 67 fs.unlinkSync(outfilePokemonTest); 68 fs.unlinkSync(outfileTodoTest); 69 } catch {} 70 }); 71 72 it('gives diagnostics about unused fields', async () => { 73 await server.waitForResponse( 74 e => e.type === 'event' && e.event === 'semanticDiag' 75 ); 76 const res = server.responses.filter( 77 resp => 78 resp.type === 'event' && 79 resp.event === 'semanticDiag' && 80 resp.body?.file === outfilePokemonTest 81 ); 82 83 expect(res).toBeDefined(); 84 expect(res).toHaveLength(1); 85 expect(res[0].body.diagnostics).toHaveLength(1); 86 expect(res[0].body.diagnostics[0]).toMatchInlineSnapshot(` 87 { 88 "category": "warning", 89 "code": 52004, 90 "end": { 91 "line": 12, 92 "offset": 1, 93 }, 94 "start": { 95 "line": 11, 96 "offset": 7, 97 }, 98 "text": "The field Pokemon.classification is deprecated. And this is the reason why", 99 } 100 `); 101 }, 30000); 102 103 it('gives quick-info for the pokemon document', async () => { 104 server.send({ 105 seq: 9, 106 type: 'request', 107 command: 'quickinfo', 108 arguments: { 109 file: outfilePokemonTest, 110 line: 8, 111 offset: 8, 112 }, 113 }); 114 115 await server.waitForResponse( 116 response => 117 response.type === 'response' && response.command === 'quickinfo' 118 ); 119 120 const res = server.responses 121 .reverse() 122 .find(resp => resp.type === 'response' && resp.command === 'quickinfo'); 123 124 expect(res).toBeDefined(); 125 expect(typeof res?.body).toEqual('object'); 126 expect(res?.body.documentation).toEqual(`Pokemon.name: String!`); 127 }, 30000); 128 129 it('gives quick-info for the todo document', async () => { 130 server.send({ 131 seq: 10, 132 type: 'request', 133 command: 'quickinfo', 134 arguments: { 135 file: outfileTodoTest, 136 line: 7, 137 offset: 8, 138 }, 139 }); 140 141 await server.waitForResponse( 142 response => 143 response.type === 'response' && response.command === 'quickinfo' 144 ); 145 146 const res = server.responses 147 .reverse() 148 .find(resp => resp.type === 'response' && resp.command === 'quickinfo'); 149 150 expect(res).toBeDefined(); 151 expect(typeof res?.body).toEqual('object'); 152 expect(res?.body.documentation).toEqual(`Todo.id: ID!`); 153 }, 30000); 154 155 it('gives completion-info for the pokemon document', async () => { 156 server.send({ 157 seq: 11, 158 type: 'request', 159 command: 'completionInfo', 160 arguments: { 161 file: outfilePokemonTest, 162 line: 9, 163 offset: 7, 164 includeExternalModuleExports: true, 165 includeInsertTextCompletions: true, 166 triggerKind: 1, 167 }, 168 }); 169 170 await server.waitForResponse( 171 response => 172 response.type === 'response' && response.command === 'completionInfo' 173 ); 174 175 const res = server.responses 176 .reverse() 177 .find( 178 resp => resp.type === 'response' && resp.command === 'completionInfo' 179 ); 180 181 expect(res).toBeDefined(); 182 expect(res).toMatchInlineSnapshot(` 183 { 184 "body": { 185 "entries": [ 186 { 187 "kind": "var", 188 "kindModifiers": "declare", 189 "labelDetails": { 190 "detail": " AttacksConnection", 191 }, 192 "name": "attacks", 193 "sortText": "0attacks", 194 }, 195 { 196 "kind": "var", 197 "kindModifiers": "declare", 198 "labelDetails": { 199 "detail": " [EvolutionRequirement]", 200 }, 201 "name": "evolutionRequirements", 202 "sortText": "2evolutionRequirements", 203 }, 204 { 205 "kind": "var", 206 "kindModifiers": "declare", 207 "labelDetails": { 208 "detail": " [Pokemon]", 209 }, 210 "name": "evolutions", 211 "sortText": "3evolutions", 212 }, 213 { 214 "kind": "var", 215 "kindModifiers": "declare", 216 "labelDetails": { 217 "detail": " PokemonDimension", 218 }, 219 "name": "height", 220 "sortText": "5height", 221 }, 222 { 223 "kind": "var", 224 "kindModifiers": "declare", 225 "labelDetails": { 226 "description": "Maximum combat power a Pokémon may achieve at max level.", 227 "detail": " Int", 228 }, 229 "name": "maxCP", 230 "sortText": "7maxCP", 231 }, 232 { 233 "kind": "var", 234 "kindModifiers": "declare", 235 "labelDetails": { 236 "description": "Maximum health points a Pokémon may achieve at max level.", 237 "detail": " Int", 238 }, 239 "name": "maxHP", 240 "sortText": "8maxHP", 241 }, 242 { 243 "kind": "var", 244 "kindModifiers": "declare", 245 "labelDetails": { 246 "detail": " [PokemonType]", 247 }, 248 "name": "resistant", 249 "sortText": "10resistant", 250 }, 251 { 252 "kind": "var", 253 "kindModifiers": "declare", 254 "labelDetails": { 255 "detail": " [PokemonType]", 256 }, 257 "name": "types", 258 "sortText": "11types", 259 }, 260 { 261 "kind": "var", 262 "kindModifiers": "declare", 263 "labelDetails": { 264 "detail": " [PokemonType]", 265 }, 266 "name": "weaknesses", 267 "sortText": "12weaknesses", 268 }, 269 { 270 "kind": "var", 271 "kindModifiers": "declare", 272 "labelDetails": { 273 "detail": " PokemonDimension", 274 }, 275 "name": "weight", 276 "sortText": "13weight", 277 }, 278 ], 279 "isGlobalCompletion": false, 280 "isMemberCompletion": false, 281 "isNewIdentifierLocation": false, 282 }, 283 "command": "completionInfo", 284 "request_seq": 11, 285 "seq": 0, 286 "success": true, 287 "type": "response", 288 } 289 `); 290 }, 30000); 291 292 it('gives completion-info for the todo document', async () => { 293 server.send({ 294 seq: 11, 295 type: 'request', 296 command: 'completionInfo', 297 arguments: { 298 file: outfileTodoTest, 299 line: 8, 300 offset: 7, 301 includeExternalModuleExports: true, 302 includeInsertTextCompletions: true, 303 triggerKind: 1, 304 }, 305 }); 306 307 await server.waitForResponse( 308 response => 309 response.type === 'response' && response.command === 'completionInfo' 310 ); 311 312 const res = server.responses 313 .reverse() 314 .find( 315 resp => resp.type === 'response' && resp.command === 'completionInfo' 316 ); 317 318 expect(res).toBeDefined(); 319 expect(res).toMatchInlineSnapshot(` 320 { 321 "body": { 322 "entries": [ 323 { 324 "kind": "var", 325 "kindModifiers": "declare", 326 "labelDetails": { 327 "detail": " String!", 328 }, 329 "name": "text", 330 "sortText": "1text", 331 }, 332 { 333 "kind": "var", 334 "kindModifiers": "declare", 335 "labelDetails": { 336 "detail": " Boolean!", 337 }, 338 "name": "completed", 339 "sortText": "2completed", 340 }, 341 { 342 "kind": "var", 343 "kindModifiers": "declare", 344 "labelDetails": { 345 "description": "The name of the current Object type at runtime.", 346 "detail": " String!", 347 }, 348 "name": "__typename", 349 "sortText": "3__typename", 350 }, 351 ], 352 "isGlobalCompletion": false, 353 "isMemberCompletion": false, 354 "isNewIdentifierLocation": false, 355 }, 356 "command": "completionInfo", 357 "request_seq": 11, 358 "seq": 0, 359 "success": true, 360 "type": "response", 361 } 362 `); 363 }, 30000); 364});