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