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});