Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/configuration-editing/src/test/completion.test.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as vscode from 'vscode';
7
import * as assert from 'assert';
8
import { promises as fs } from 'fs';
9
import * as path from 'path';
10
import * as os from 'os';
11
import 'mocha';
12
13
14
const testFolder = fs.mkdtemp(path.join(os.tmpdir(), 'conf-editing-'));
15
16
suite('Completions in settings.json', () => {
17
const testFile = 'settings.json';
18
19
test('window.title', async () => {
20
{ // inserting after text
21
const content = [
22
'{',
23
' "window.title": "custom|"',
24
'}',
25
].join('\n');
26
const resultText = [
27
'{',
28
' "window.title": "custom${activeEditorShort}"',
29
'}',
30
].join('\n');
31
const expected = { label: '${activeEditorShort}', resultText };
32
await testCompletion(testFile, 'jsonc', content, expected);
33
}
34
{ // inserting before a variable
35
const content = [
36
'{',
37
' "window.title": "|${activeEditorShort}"',
38
'}',
39
].join('\n');
40
const resultText = [
41
'{',
42
' "window.title": "${folderPath}${activeEditorShort}"',
43
'}',
44
].join('\n');
45
const expected = { label: '${folderPath}', resultText };
46
await testCompletion(testFile, 'jsonc', content, expected);
47
}
48
{ // inserting after a variable
49
const content = [
50
'{',
51
' "window.title": "${activeEditorShort}|"',
52
'}',
53
].join('\n');
54
const resultText = [
55
'{',
56
' "window.title": "${activeEditorShort}${folderPath}"',
57
'}',
58
].join('\n');
59
const expected = { label: '${folderPath}', resultText };
60
await testCompletion(testFile, 'jsonc', content, expected);
61
}
62
{ // replacing an variable
63
const content = [
64
'{',
65
' "window.title": "${a|ctiveEditorShort}"',
66
'}',
67
].join('\n');
68
const resultText = [
69
'{',
70
' "window.title": "${activeEditorMedium}"',
71
'}',
72
].join('\n');
73
const expected = { label: '${activeEditorMedium}', resultText };
74
await testCompletion(testFile, 'jsonc', content, expected);
75
}
76
{ // replacing a partial variable
77
const content = [
78
'{',
79
' "window.title": "${a|"',
80
'}',
81
].join('\n');
82
const resultText = [
83
'{',
84
' "window.title": "${dirty}"',
85
'}',
86
].join('\n');
87
const expected = { label: '${dirty}', resultText };
88
await testCompletion(testFile, 'jsonc', content, expected);
89
}
90
{ // inserting a literal
91
const content = [
92
'{',
93
' "window.title": |',
94
'}',
95
].join('\n');
96
const resultText = [
97
'{',
98
' "window.title": "${activeEditorMedium}"',
99
'}',
100
].join('\n');
101
const expected = { label: '"${activeEditorMedium}"', resultText };
102
await testCompletion(testFile, 'jsonc', content, expected);
103
}
104
{ // no proposals after literal
105
const content = [
106
'{',
107
' "window.title": "${activeEditorShort}" |',
108
'}',
109
].join('\n');
110
const expected = { label: '${activeEditorMedium}', notAvailable: true };
111
await testCompletion(testFile, 'jsonc', content, expected);
112
}
113
});
114
115
test('files.associations', async () => {
116
{
117
const content = [
118
'{',
119
' "files.associations": {',
120
' |',
121
' }',
122
'}',
123
].join('\n');
124
const resultText = [
125
'{',
126
' "files.associations": {',
127
' "*.${1:extension}": "${2:language}"',
128
' }',
129
'}',
130
].join('\n');
131
const expected = { label: 'Files with Extension', resultText };
132
await testCompletion(testFile, 'jsonc', content, expected);
133
}
134
{
135
const content = [
136
'{',
137
' "files.associations": {',
138
' |',
139
' }',
140
'}',
141
].join('\n');
142
const resultText = [
143
'{',
144
' "files.associations": {',
145
' "/${1:path to file}/*.${2:extension}": "${3:language}"',
146
' }',
147
'}',
148
].join('\n');
149
const expected = { label: 'Files with Path', resultText };
150
await testCompletion(testFile, 'jsonc', content, expected);
151
}
152
{
153
const content = [
154
'{',
155
' "files.associations": {',
156
' "*.extension": "|bat"',
157
' }',
158
'}',
159
].join('\n');
160
const resultText = [
161
'{',
162
' "files.associations": {',
163
' "*.extension": "json"',
164
' }',
165
'}',
166
].join('\n');
167
const expected = { label: '"json"', resultText };
168
await testCompletion(testFile, 'jsonc', content, expected);
169
}
170
{
171
const content = [
172
'{',
173
' "files.associations": {',
174
' "*.extension": "bat"|',
175
' }',
176
'}',
177
].join('\n');
178
const resultText = [
179
'{',
180
' "files.associations": {',
181
' "*.extension": "json"',
182
' }',
183
'}',
184
].join('\n');
185
const expected = { label: '"json"', resultText };
186
await testCompletion(testFile, 'jsonc', content, expected);
187
}
188
{
189
const content = [
190
'{',
191
' "files.associations": {',
192
' "*.extension": "bat" |',
193
' }',
194
'}',
195
].join('\n');
196
const expected = { label: '"json"', notAvailable: true };
197
await testCompletion(testFile, 'jsonc', content, expected);
198
}
199
});
200
test('files.exclude', async () => {
201
{
202
const content = [
203
'{',
204
' "files.exclude": {',
205
' |',
206
' }',
207
'}',
208
].join('\n');
209
const resultText = [
210
'{',
211
' "files.exclude": {',
212
' "**/*.${1:extension}": true',
213
' }',
214
'}',
215
].join('\n');
216
const expected = { label: 'Files by Extension', resultText };
217
await testCompletion(testFile, 'jsonc', content, expected);
218
}
219
{
220
const content = [
221
'{',
222
' "files.exclude": {',
223
' "**/*.extension": |true',
224
' }',
225
'}',
226
].join('\n');
227
const resultText = [
228
'{',
229
' "files.exclude": {',
230
' "**/*.extension": { "when": "$(basename).${1:extension}" }',
231
' }',
232
'}',
233
].join('\n');
234
const expected = { label: 'Files with Siblings by Name', resultText };
235
await testCompletion(testFile, 'jsonc', content, expected);
236
}
237
});
238
test('files.defaultLanguage', async () => {
239
{
240
const content = [
241
'{',
242
' "files.defaultLanguage": "json|"',
243
'}',
244
].join('\n');
245
const resultText = [
246
'{',
247
' "files.defaultLanguage": "jsonc"',
248
'}',
249
].join('\n');
250
const expected = { label: '"jsonc"', resultText };
251
await testCompletion(testFile, 'jsonc', content, expected);
252
}
253
{
254
const content = [
255
'{',
256
' "files.defaultLanguage": |',
257
'}',
258
].join('\n');
259
const resultText = [
260
'{',
261
' "files.defaultLanguage": "jsonc"',
262
'}',
263
].join('\n');
264
const expected = { label: '"jsonc"', resultText };
265
await testCompletion(testFile, 'jsonc', content, expected);
266
}
267
});
268
test('remote.extensionKind', async () => {
269
{
270
const content = [
271
'{',
272
'\t"remote.extensionKind": {',
273
'\t\t|',
274
'\t}',
275
'}',
276
].join('\n');
277
const expected = { label: 'vscode.npm' };
278
await testCompletion(testFile, 'jsonc', content, expected);
279
}
280
});
281
test('remote.portsAttributes', async () => {
282
{
283
const content = [
284
'{',
285
' "remote.portsAttributes": {',
286
' |',
287
' }',
288
'}',
289
].join('\n');
290
const expected = { label: '"3000"' };
291
await testCompletion(testFile, 'jsonc', content, expected);
292
}
293
});
294
});
295
296
suite('Completions in extensions.json', () => {
297
const testFile = 'extensions.json';
298
test('change recommendation', async () => {
299
{
300
const content = [
301
'{',
302
' "recommendations": [',
303
' "|a.b"',
304
' ]',
305
'}',
306
].join('\n');
307
const resultText = [
308
'{',
309
' "recommendations": [',
310
' "ms-vscode.js-debug"',
311
' ]',
312
'}',
313
].join('\n');
314
const expected = { label: 'ms-vscode.js-debug', resultText };
315
await testCompletion(testFile, 'jsonc', content, expected);
316
}
317
});
318
test('add recommendation', async () => {
319
{
320
const content = [
321
'{',
322
' "recommendations": [',
323
' |',
324
' ]',
325
'}',
326
].join('\n');
327
const resultText = [
328
'{',
329
' "recommendations": [',
330
' "ms-vscode.js-debug"',
331
' ]',
332
'}',
333
].join('\n');
334
const expected = { label: 'ms-vscode.js-debug', resultText };
335
await testCompletion(testFile, 'jsonc', content, expected);
336
}
337
});
338
});
339
340
suite('Completions in launch.json', () => {
341
const testFile = 'launch.json';
342
test('variable completions', async () => {
343
{
344
const content = [
345
'{',
346
' "version": "0.2.0",',
347
' "configurations": [',
348
' {',
349
' "name": "Run Extension",',
350
' "type": "extensionHost",',
351
' "preLaunchTask": "${|defaultBuildTask}"',
352
' }',
353
' ]',
354
'}',
355
].join('\n');
356
const resultText = [
357
'{',
358
' "version": "0.2.0",',
359
' "configurations": [',
360
' {',
361
' "name": "Run Extension",',
362
' "type": "extensionHost",',
363
' "preLaunchTask": "${cwd}"',
364
' }',
365
' ]',
366
'}',
367
].join('\n');
368
const expected = { label: '${cwd}', resultText };
369
await testCompletion(testFile, 'jsonc', content, expected);
370
}
371
{
372
const content = [
373
'{',
374
' "version": "0.2.0",',
375
' "configurations": [',
376
' {',
377
' "name": "Run Extension",',
378
' "type": "extensionHost",',
379
' "preLaunchTask": "|${defaultBuildTask}"',
380
' }',
381
' ]',
382
'}',
383
].join('\n');
384
const resultText = [
385
'{',
386
' "version": "0.2.0",',
387
' "configurations": [',
388
' {',
389
' "name": "Run Extension",',
390
' "type": "extensionHost",',
391
' "preLaunchTask": "${cwd}${defaultBuildTask}"',
392
' }',
393
' ]',
394
'}',
395
].join('\n');
396
const expected = { label: '${cwd}', resultText };
397
await testCompletion(testFile, 'jsonc', content, expected);
398
}
399
{
400
const content = [
401
'{',
402
' "version": "0.2.0",',
403
' "configurations": [',
404
' {',
405
' "name": "Do It",',
406
' "program": "${workspace|"',
407
' }',
408
' ]',
409
'}',
410
].join('\n');
411
const resultText = [
412
'{',
413
' "version": "0.2.0",',
414
' "configurations": [',
415
' {',
416
' "name": "Do It",',
417
' "program": "${cwd}"',
418
' }',
419
' ]',
420
'}',
421
].join('\n');
422
const expected = { label: '${cwd}', resultText };
423
await testCompletion(testFile, 'jsonc', content, expected);
424
}
425
});
426
});
427
428
suite('Completions in tasks.json', () => {
429
const testFile = 'tasks.json';
430
test('variable completions', async () => {
431
{
432
const content = [
433
'{',
434
' "version": "0.2.0",',
435
' "tasks": [',
436
' {',
437
' "type": "shell",',
438
' "command": "${|defaultBuildTask}"',
439
' }',
440
' ]',
441
'}',
442
].join('\n');
443
const resultText = [
444
'{',
445
' "version": "0.2.0",',
446
' "tasks": [',
447
' {',
448
' "type": "shell",',
449
' "command": "${cwd}"',
450
' }',
451
' ]',
452
'}',
453
].join('\n');
454
const expected = { label: '${cwd}', resultText };
455
await testCompletion(testFile, 'jsonc', content, expected);
456
}
457
{
458
const content = [
459
'{',
460
' "version": "0.2.0",',
461
' "tasks": [',
462
' {',
463
' "type": "shell",',
464
' "command": "${defaultBuildTask}|"',
465
' }',
466
' ]',
467
'}',
468
].join('\n');
469
const resultText = [
470
'{',
471
' "version": "0.2.0",',
472
' "tasks": [',
473
' {',
474
' "type": "shell",',
475
' "command": "${defaultBuildTask}${cwd}"',
476
' }',
477
' ]',
478
'}',
479
].join('\n');
480
const expected = { label: '${cwd}', resultText };
481
await testCompletion(testFile, 'jsonc', content, expected);
482
}
483
});
484
});
485
486
suite('Completions in keybindings.json', () => {
487
const testFile = 'keybindings.json';
488
test('context key insertion', async () => {
489
{
490
const content = [
491
'[',
492
' {',
493
' "key": "ctrl+k ctrl+,",',
494
' "command": "editor.jumpToNextFold",',
495
' "when": "|"',
496
' }',
497
']',
498
].join('\n');
499
const resultText = [
500
'[',
501
' {',
502
' "key": "ctrl+k ctrl+,",',
503
' "command": "editor.jumpToNextFold",',
504
' "when": "resourcePath"',
505
' }',
506
']',
507
].join('\n');
508
const expected = { label: 'resourcePath', resultText };
509
await testCompletion(testFile, 'jsonc', content, expected);
510
}
511
});
512
513
test('context key replace', async () => {
514
{
515
const content = [
516
'[',
517
' {',
518
' "key": "ctrl+k ctrl+,",',
519
' "command": "editor.jumpToNextFold",',
520
' "when": "resou|rcePath"',
521
' }',
522
']',
523
].join('\n');
524
const resultText = [
525
'[',
526
' {',
527
' "key": "ctrl+k ctrl+,",',
528
' "command": "editor.jumpToNextFold",',
529
' "when": "resource"',
530
' }',
531
']',
532
].join('\n');
533
const expected = { label: 'resource', resultText };
534
await testCompletion(testFile, 'jsonc', content, expected);
535
}
536
});
537
});
538
539
interface ItemDescription {
540
label: string;
541
resultText?: string;
542
notAvailable?: boolean;
543
}
544
545
async function testCompletion(testFileName: string, languageId: string, content: string, expected: ItemDescription) {
546
547
const offset = content.indexOf('|');
548
content = content.substring(0, offset) + content.substring(offset + 1);
549
550
const docUri = vscode.Uri.file(path.join(await testFolder, testFileName));
551
await fs.writeFile(docUri.fsPath, content);
552
553
const editor = await setTestContent(docUri, languageId, content);
554
const position = editor.document.positionAt(offset);
555
556
// Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion
557
const actualCompletions = (await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', docUri, position)) as vscode.CompletionList;
558
559
const matches = actualCompletions.items.filter(completion => {
560
return completion.label === expected.label;
561
});
562
if (expected.notAvailable) {
563
assert.strictEqual(matches.length, 0, `${expected.label} should not existing is results`);
564
} else {
565
assert.strictEqual(matches.length, 1, `${expected.label} should only existing once: Actual: ${actualCompletions.items.map(c => c.label).join(', ')}`);
566
567
if (expected.resultText) {
568
const match = matches[0];
569
if (match.range && match.insertText) {
570
const range = match.range instanceof vscode.Range ? match.range : match.range.replacing;
571
const text = typeof match.insertText === 'string' ? match.insertText : match.insertText.value;
572
573
await editor.edit(eb => eb.replace(range, text));
574
assert.strictEqual(editor.document.getText(), expected.resultText);
575
} else {
576
assert.fail(`Range or insertText missing`);
577
}
578
}
579
}
580
}
581
582
async function setTestContent(docUri: vscode.Uri, languageId: string, content: string): Promise<vscode.TextEditor> {
583
const ext = vscode.extensions.getExtension('vscode.configuration-editing')!;
584
await ext.activate();
585
586
const doc = await vscode.workspace.openTextDocument(docUri);
587
await vscode.languages.setTextDocumentLanguage(doc, languageId);
588
const editor = await vscode.window.showTextDocument(doc);
589
590
const fullRange = new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length));
591
await editor.edit(eb => eb.replace(fullRange, content));
592
return editor;
593
594
}
595
596