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(__dirname, 'fixture-project');
11
12let server: TSServer;
13
14describe('simple', () => {
15 const testFile = path.join(projectPath, 'simple.ts');
16 const generatedFile = path.join(projectPath, 'simple.generated.ts');
17 const baseGeneratedFile = path.join(
18 projectPath,
19 '__generated__/baseGraphQLSP.ts'
20 );
21
22 beforeAll(async () => {
23 server = new TSServer(projectPath, { debugLog: false });
24 const fixtureFileContent = fs.readFileSync(
25 path.resolve(testFile, '../fixtures/simple.ts'),
26 'utf-8'
27 );
28
29 server.sendCommand('open', {
30 file: testFile,
31 fileContent: '// empty',
32 scriptKindName: 'TS',
33 } satisfies ts.server.protocol.OpenRequestArgs);
34
35 server.sendCommand('updateOpen', {
36 openFiles: [{ file: testFile, fileContent: fixtureFileContent }],
37 } satisfies ts.server.protocol.UpdateOpenRequestArgs);
38
39 server.sendCommand('saveto', {
40 file: testFile,
41 tmpfile: testFile,
42 } satisfies ts.server.protocol.SavetoRequestArgs);
43
44 await server.waitForResponse(
45 response => response.type === 'event' && response.event === 'setTypings'
46 );
47 });
48
49 afterAll(() => {
50 try {
51 fs.unlinkSync(testFile);
52 fs.unlinkSync(generatedFile);
53 fs.unlinkSync(baseGeneratedFile);
54 } catch {}
55 server.close();
56 });
57
58 it('Proposes suggestions for a selection-set', async () => {
59 server.send({
60 seq: 8,
61 type: 'request',
62 command: 'completionInfo',
63 arguments: {
64 file: testFile,
65 line: 7,
66 offset: 7,
67 includeExternalModuleExports: true,
68 includeInsertTextCompletions: true,
69 triggerKind: 1,
70 },
71 });
72
73 await server.waitForResponse(
74 response =>
75 response.type === 'response' && response.command === 'completionInfo'
76 );
77
78 const res = server.responses
79 .reverse()
80 .find(
81 resp => resp.type === 'response' && resp.command === 'completionInfo'
82 );
83
84 expect(res).toBeDefined();
85 expect(typeof res?.body.entries).toEqual('object');
86 const defaultAttrs = { kind: 'var', kindModifiers: 'declare' };
87 expect(res?.body.entries).toEqual([
88 {
89 ...defaultAttrs,
90 name: 'id',
91 sortText: '0id',
92 labelDetails: { detail: ' ID!' },
93 },
94 {
95 ...defaultAttrs,
96 name: 'content',
97 sortText: '2content',
98 labelDetails: { detail: ' String!' },
99 },
100 {
101 ...defaultAttrs,
102 name: '__typename',
103 sortText: '3__typename',
104 labelDetails: {
105 detail: ' String!',
106 description: 'The name of the current Object type at runtime.',
107 },
108 },
109 ]);
110 }, 7500);
111
112 it('Gives quick-info when hovering start (#15)', async () => {
113 server.send({
114 seq: 9,
115 type: 'request',
116 command: 'quickinfo',
117 arguments: {
118 file: testFile,
119 line: 5,
120 offset: 5,
121 },
122 });
123
124 await server.waitForResponse(
125 response =>
126 response.type === 'response' && response.command === 'quickinfo'
127 );
128
129 const res = server.responses
130 .reverse()
131 .find(resp => resp.type === 'response' && resp.command === 'quickinfo');
132 expect(res).toBeDefined();
133 expect(typeof res?.body).toEqual('object');
134 expect(res?.body.documentation).toEqual(
135 `Query.posts: [Post]\n\nList out all posts`
136 );
137 }, 7500);
138
139 it('Handles empty line (#190)', async () => {
140 server.send({
141 seq: 10,
142 type: 'request',
143 command: 'completionInfo',
144 arguments: {
145 file: testFile,
146 line: 14,
147 offset: 3,
148 includeExternalModuleExports: true,
149 includeInsertTextCompletions: true,
150 triggerKind: 1,
151 },
152 });
153
154 await server.waitForResponse(
155 response =>
156 response.type === 'response' && response.command === 'completionInfo'
157 );
158
159 const res = server.responses
160 .reverse()
161 .find(
162 resp => resp.type === 'response' && resp.command === 'completionInfo'
163 );
164
165 expect(res).toBeDefined();
166 expect(typeof res?.body.entries).toEqual('object');
167 const defaultAttrs = { kind: 'var', kindModifiers: 'declare' };
168 expect(res?.body.entries).toEqual([
169 {
170 ...defaultAttrs,
171 name: 'post',
172 sortText: '0post',
173 labelDetails: { detail: ' Post' },
174 },
175 {
176 ...defaultAttrs,
177 name: 'posts',
178 sortText: '1posts',
179 labelDetails: { detail: ' [Post]', description: 'List out all posts' },
180 },
181 {
182 ...defaultAttrs,
183 name: '__typename',
184 sortText: '2__typename',
185 labelDetails: {
186 detail: ' String!',
187 description: 'The name of the current Object type at runtime.',
188 },
189 },
190 {
191 ...defaultAttrs,
192 name: '__schema',
193 sortText: '3__schema',
194 labelDetails: {
195 detail: ' __Schema!',
196 description: 'Access the current type schema of this server.',
197 },
198 },
199 {
200 ...defaultAttrs,
201 name: '__type',
202 sortText: '4__type',
203 labelDetails: {
204 detail: ' __Type',
205 description: 'Request the type information of a single type.',
206 },
207 },
208 ]);
209 }, 7500);
210});