Archived
1
0

Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export function activate(_context: vscode.ExtensionContext) {
// Set context as a global as some tests depend on it
(global as any).testExtensionContext = _context;
}

View File

@ -0,0 +1,242 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as vscode from 'vscode';
class File implements vscode.FileStat {
type: vscode.FileType;
ctime: number;
mtime: number;
size: number;
name: string;
data?: Uint8Array;
constructor(name: string) {
this.type = vscode.FileType.File;
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
}
}
class Directory implements vscode.FileStat {
type: vscode.FileType;
ctime: number;
mtime: number;
size: number;
name: string;
entries: Map<string, File | Directory>;
constructor(name: string) {
this.type = vscode.FileType.Directory;
this.ctime = Date.now();
this.mtime = Date.now();
this.size = 0;
this.name = name;
this.entries = new Map();
}
}
export type Entry = File | Directory;
export class TestFS implements vscode.FileSystemProvider {
constructor(
readonly scheme: string,
readonly isCaseSensitive: boolean
) { }
readonly root = new Directory('');
// --- manage file metadata
stat(uri: vscode.Uri): vscode.FileStat {
return this._lookup(uri, false);
}
readDirectory(uri: vscode.Uri): [string, vscode.FileType][] {
const entry = this._lookupAsDirectory(uri, false);
let result: [string, vscode.FileType][] = [];
for (const [name, child] of entry.entries) {
result.push([name, child.type]);
}
return result;
}
// --- manage file contents
readFile(uri: vscode.Uri): Uint8Array {
const data = this._lookupAsFile(uri, false).data;
if (data) {
return data;
}
throw vscode.FileSystemError.FileNotFound();
}
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void {
let basename = path.posix.basename(uri.path);
let parent = this._lookupParentDirectory(uri);
let entry = parent.entries.get(basename);
if (entry instanceof Directory) {
throw vscode.FileSystemError.FileIsADirectory(uri);
}
if (!entry && !options.create) {
throw vscode.FileSystemError.FileNotFound(uri);
}
if (entry && options.create && !options.overwrite) {
throw vscode.FileSystemError.FileExists(uri);
}
if (!entry) {
entry = new File(basename);
parent.entries.set(basename, entry);
this._fireSoon({ type: vscode.FileChangeType.Created, uri });
}
entry.mtime = Date.now();
entry.size = content.byteLength;
entry.data = content;
this._fireSoon({ type: vscode.FileChangeType.Changed, uri });
}
// --- manage files/folders
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void {
if (!options.overwrite && this._lookup(newUri, true)) {
throw vscode.FileSystemError.FileExists(newUri);
}
let entry = this._lookup(oldUri, false);
let oldParent = this._lookupParentDirectory(oldUri);
let newParent = this._lookupParentDirectory(newUri);
let newName = path.posix.basename(newUri.path);
oldParent.entries.delete(entry.name);
entry.name = newName;
newParent.entries.set(newName, entry);
this._fireSoon(
{ type: vscode.FileChangeType.Deleted, uri: oldUri },
{ type: vscode.FileChangeType.Created, uri: newUri }
);
}
delete(uri: vscode.Uri): void {
let dirname = uri.with({ path: path.posix.dirname(uri.path) });
let basename = path.posix.basename(uri.path);
let parent = this._lookupAsDirectory(dirname, false);
if (!parent.entries.has(basename)) {
throw vscode.FileSystemError.FileNotFound(uri);
}
parent.entries.delete(basename);
parent.mtime = Date.now();
parent.size -= 1;
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted });
}
createDirectory(uri: vscode.Uri): void {
let basename = path.posix.basename(uri.path);
let dirname = uri.with({ path: path.posix.dirname(uri.path) });
let parent = this._lookupAsDirectory(dirname, false);
let entry = new Directory(basename);
parent.entries.set(entry.name, entry);
parent.mtime = Date.now();
parent.size += 1;
this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri });
}
// --- lookup
private _lookup(uri: vscode.Uri, silent: false): Entry;
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined;
private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined {
let parts = uri.path.split('/');
let entry: Entry = this.root;
for (const part of parts) {
const partLow = part.toLowerCase();
if (!part) {
continue;
}
let child: Entry | undefined;
if (entry instanceof Directory) {
if (this.isCaseSensitive) {
child = entry.entries.get(part);
} else {
for (let [key, value] of entry.entries) {
if (key.toLowerCase() === partLow) {
child = value;
break;
}
}
}
}
if (!child) {
if (!silent) {
throw vscode.FileSystemError.FileNotFound(uri);
} else {
return undefined;
}
}
entry = child;
}
return entry;
}
private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory {
let entry = this._lookup(uri, silent);
if (entry instanceof Directory) {
return entry;
}
throw vscode.FileSystemError.FileNotADirectory(uri);
}
private _lookupAsFile(uri: vscode.Uri, silent: boolean): File {
let entry = this._lookup(uri, silent);
if (entry instanceof File) {
return entry;
}
throw vscode.FileSystemError.FileIsADirectory(uri);
}
private _lookupParentDirectory(uri: vscode.Uri): Directory {
const dirname = uri.with({ path: path.posix.dirname(uri.path) });
return this._lookupAsDirectory(dirname, false);
}
// --- manage file events
private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
private _bufferedEvents: vscode.FileChangeEvent[] = [];
private _fireSoonHandle?: NodeJS.Timer;
readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this._emitter.event;
watch(_resource: vscode.Uri): vscode.Disposable {
// ignore, fires for all changes...
return new vscode.Disposable(() => { });
}
private _fireSoon(...events: vscode.FileChangeEvent[]): void {
this._bufferedEvents.push(...events);
if (this._fireSoonHandle) {
clearTimeout(this._fireSoonHandle);
}
this._fireSoonHandle = setTimeout(() => {
this._emitter.fire(this._bufferedEvents);
this._bufferedEvents.length = 0;
}, 5);
}
}

View File

@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import { join } from 'path';
import { commands, workspace, window, Uri, Range, Position, ViewColumn } from 'vscode';
suite('vscode API - commands', () => {
test('getCommands', function (done) {
let p1 = commands.getCommands().then(commands => {
let hasOneWithUnderscore = false;
for (let command of commands) {
if (command[0] === '_') {
hasOneWithUnderscore = true;
break;
}
}
assert.ok(hasOneWithUnderscore);
}, done);
let p2 = commands.getCommands(true).then(commands => {
let hasOneWithUnderscore = false;
for (let command of commands) {
if (command[0] === '_') {
hasOneWithUnderscore = true;
break;
}
}
assert.ok(!hasOneWithUnderscore);
}, done);
Promise.all([p1, p2]).then(() => {
done();
}, done);
});
test('command with args', async function () {
let args: IArguments;
let registration = commands.registerCommand('t1', function () {
args = arguments;
});
await commands.executeCommand('t1', 'start');
registration.dispose();
assert.ok(args!);
assert.equal(args!.length, 1);
assert.equal(args![0], 'start');
});
test('editorCommand with extra args', function () {
let args: IArguments;
let registration = commands.registerTextEditorCommand('t1', function () {
args = arguments;
});
return workspace.openTextDocument(join(workspace.rootPath || '', './far.js')).then(doc => {
return window.showTextDocument(doc).then(_editor => {
return commands.executeCommand('t1', 12345, commands);
}).then(() => {
assert.ok(args);
assert.equal(args.length, 4);
assert.ok(args[2] === 12345);
assert.ok(args[3] === commands);
registration.dispose();
});
});
});
test('api-command: vscode.diff', function () {
let registration = workspace.registerTextDocumentContentProvider('sc', {
provideTextDocumentContent(uri) {
return `content of URI <b>${uri.toString()}</b>#${Math.random()}`;
}
});
let a = commands.executeCommand('vscode.diff', Uri.parse('sc:a'), Uri.parse('sc:b'), 'DIFF').then(value => {
assert.ok(value === undefined);
registration.dispose();
});
let b = commands.executeCommand('vscode.diff', Uri.parse('sc:a'), Uri.parse('sc:b')).then(value => {
assert.ok(value === undefined);
registration.dispose();
});
let c = commands.executeCommand('vscode.diff', Uri.parse('sc:a'), Uri.parse('sc:b'), 'Title', { selection: new Range(new Position(1, 1), new Position(1, 2)) }).then(value => {
assert.ok(value === undefined);
registration.dispose();
});
let d = commands.executeCommand('vscode.diff').then(() => assert.ok(false), () => assert.ok(true));
let e = commands.executeCommand('vscode.diff', 1, 2, 3).then(() => assert.ok(false), () => assert.ok(true));
return Promise.all([a, b, c, d, e]);
});
test('api-command: vscode.open', function () {
let uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/far.js');
let a = commands.executeCommand('vscode.open', uri).then(() => assert.ok(true), () => assert.ok(false));
let b = commands.executeCommand('vscode.open', uri, ViewColumn.Two).then(() => assert.ok(true), () => assert.ok(false));
let c = commands.executeCommand('vscode.open').then(() => assert.ok(false), () => assert.ok(true));
let d = commands.executeCommand('vscode.open', uri, true).then(() => assert.ok(false), () => assert.ok(true));
return Promise.all([a, b, c, d]);
});
});

View File

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import * as vscode from 'vscode';
suite('vscode API - configuration', () => {
test('configurations, language defaults', function () {
const defaultLanguageSettings = vscode.workspace.getConfiguration().get('[abcLang]');
assert.deepEqual(defaultLanguageSettings, {
'editor.lineNumbers': 'off',
'editor.tabSize': 2
});
});
test('configuration, defaults', () => {
const config = vscode.workspace.getConfiguration('farboo');
assert.ok(config.has('config0'));
assert.equal(config.get('config0'), true);
assert.equal(config.get('config4'), '');
assert.equal(config['config0'], true);
assert.equal(config['config4'], '');
assert.throws(() => (<any>config)['config4'] = 'valuevalue');
assert.ok(config.has('nested.config1'));
assert.equal(config.get('nested.config1'), 42);
assert.ok(config.has('nested.config2'));
assert.equal(config.get('nested.config2'), 'Das Pferd frisst kein Reis.');
});
test('configuration, name vs property', () => {
const config = vscode.workspace.getConfiguration('farboo');
assert.ok(config.has('get'));
assert.equal(config.get('get'), 'get-prop');
assert.deepEqual(config['get'], config.get);
assert.throws(() => config['get'] = <any>'get-prop');
});
});

View File

@ -0,0 +1,130 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { debug, workspace, Disposable, commands, window } from 'vscode';
import { disposeAll } from '../utils';
import { basename } from 'path';
suite('vscode API - debug', function () {
test('breakpoints', async function () {
assert.equal(debug.breakpoints.length, 0);
let onDidChangeBreakpointsCounter = 0;
const toDispose: Disposable[] = [];
toDispose.push(debug.onDidChangeBreakpoints(() => {
onDidChangeBreakpointsCounter++;
}));
debug.addBreakpoints([{ id: '1', enabled: true }, { id: '2', enabled: false, condition: '2 < 5' }]);
assert.equal(onDidChangeBreakpointsCounter, 1);
assert.equal(debug.breakpoints.length, 2);
assert.equal(debug.breakpoints[0].id, '1');
assert.equal(debug.breakpoints[1].id, '2');
assert.equal(debug.breakpoints[1].condition, '2 < 5');
debug.removeBreakpoints([{ id: '1', enabled: true }]);
assert.equal(onDidChangeBreakpointsCounter, 2);
assert.equal(debug.breakpoints.length, 1);
debug.removeBreakpoints([{ id: '2', enabled: false }]);
assert.equal(onDidChangeBreakpointsCounter, 3);
assert.equal(debug.breakpoints.length, 0);
disposeAll(toDispose);
});
test.skip('start debugging', async function () {
let stoppedEvents = 0;
let variablesReceived: () => void;
let initializedReceived: () => void;
let configurationDoneReceived: () => void;
const toDispose: Disposable[] = [];
if (debug.activeDebugSession) {
// We are re-running due to flakyness, make sure to clear out state
let sessionTerminatedRetry: () => void;
toDispose.push(debug.onDidTerminateDebugSession(() => {
sessionTerminatedRetry();
}));
const sessionTerminatedPromise = new Promise<void>(resolve => sessionTerminatedRetry = resolve);
await commands.executeCommand('workbench.action.debug.stop');
await sessionTerminatedPromise;
}
const firstVariablesRetrieved = new Promise<void>(resolve => variablesReceived = resolve);
toDispose.push(debug.registerDebugAdapterTrackerFactory('*', {
createDebugAdapterTracker: () => ({
onDidSendMessage: m => {
if (m.event === 'stopped') {
stoppedEvents++;
}
if (m.type === 'response' && m.command === 'variables') {
variablesReceived();
}
if (m.event === 'initialized') {
initializedReceived();
}
if (m.command === 'configurationDone') {
configurationDoneReceived();
}
}
})
}));
const initializedPromise = new Promise<void>(resolve => initializedReceived = resolve);
const configurationDonePromise = new Promise<void>(resolve => configurationDoneReceived = resolve);
const success = await debug.startDebugging(workspace.workspaceFolders![0], 'Launch debug.js');
assert.equal(success, true);
await initializedPromise;
await configurationDonePromise;
await firstVariablesRetrieved;
assert.notEqual(debug.activeDebugSession, undefined);
assert.equal(stoppedEvents, 1);
const secondVariablesRetrieved = new Promise<void>(resolve => variablesReceived = resolve);
await commands.executeCommand('workbench.action.debug.stepOver');
await secondVariablesRetrieved;
assert.equal(stoppedEvents, 2);
const editor = window.activeTextEditor;
assert.notEqual(editor, undefined);
assert.equal(basename(editor!.document.fileName), 'debug.js');
const thirdVariablesRetrieved = new Promise<void>(resolve => variablesReceived = resolve);
await commands.executeCommand('workbench.action.debug.stepOver');
await thirdVariablesRetrieved;
assert.equal(stoppedEvents, 3);
const fourthVariablesRetrieved = new Promise<void>(resolve => variablesReceived = resolve);
await commands.executeCommand('workbench.action.debug.stepInto');
await fourthVariablesRetrieved;
assert.equal(stoppedEvents, 4);
const fifthVariablesRetrieved = new Promise<void>(resolve => variablesReceived = resolve);
await commands.executeCommand('workbench.action.debug.stepOut');
await fifthVariablesRetrieved;
assert.equal(stoppedEvents, 5);
let sessionTerminated: () => void;
toDispose.push(debug.onDidTerminateDebugSession(() => {
sessionTerminated();
}));
const sessionTerminatedPromise = new Promise<void>(resolve => sessionTerminated = resolve);
await commands.executeCommand('workbench.action.debug.stop');
await sessionTerminatedPromise;
disposeAll(toDispose);
});
test('start debugging failure', async function () {
let errorCount = 0;
try {
await debug.startDebugging(workspace.workspaceFolders![0], 'non existent');
} catch (e) {
errorCount++;
}
assert.equal(errorCount, 1);
});
});

View File

@ -0,0 +1,264 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { workspace, window, Position, Range, commands, TextEditor, TextDocument, TextEditorCursorStyle, TextEditorLineNumbersStyle, SnippetString, Selection, Uri, env } from 'vscode';
import { createRandomFile, deleteFile, closeAllEditors } from '../utils';
suite('vscode API - editors', () => {
teardown(closeAllEditors);
function withRandomFileEditor(initialContents: string, run: (editor: TextEditor, doc: TextDocument) => Thenable<void>): Thenable<boolean> {
return createRandomFile(initialContents).then(file => {
return workspace.openTextDocument(file).then(doc => {
return window.showTextDocument(doc).then((editor) => {
return run(editor, doc).then(_ => {
if (doc.isDirty) {
return doc.save().then(saved => {
assert.ok(saved);
assert.ok(!doc.isDirty);
return deleteFile(file);
});
} else {
return deleteFile(file);
}
});
});
});
});
}
test('insert snippet', () => {
const snippetString = new SnippetString()
.appendText('This is a ')
.appendTabstop()
.appendPlaceholder('placeholder')
.appendText(' snippet');
return withRandomFileEditor('', (editor, doc) => {
return editor.insertSnippet(snippetString).then(inserted => {
assert.ok(inserted);
assert.equal(doc.getText(), 'This is a placeholder snippet');
assert.ok(doc.isDirty);
});
});
});
test('insert snippet with clipboard variables', async function () {
const old = await env.clipboard.readText();
const newValue = 'INTEGRATION-TESTS';
await env.clipboard.writeText(newValue);
const actualValue = await env.clipboard.readText();
if (actualValue !== newValue) {
// clipboard not working?!?
this.skip();
return;
}
const snippetString = new SnippetString('running: $CLIPBOARD');
await withRandomFileEditor('', async (editor, doc) => {
const inserted = await editor.insertSnippet(snippetString);
assert.ok(inserted);
assert.equal(doc.getText(), 'running: INTEGRATION-TESTS');
assert.ok(doc.isDirty);
});
await env.clipboard.writeText(old);
});
test('insert snippet with replacement, editor selection', () => {
const snippetString = new SnippetString()
.appendText('has been');
return withRandomFileEditor('This will be replaced', (editor, doc) => {
editor.selection = new Selection(
new Position(0, 5),
new Position(0, 12)
);
return editor.insertSnippet(snippetString).then(inserted => {
assert.ok(inserted);
assert.equal(doc.getText(), 'This has been replaced');
assert.ok(doc.isDirty);
});
});
});
test('insert snippet with replacement, selection as argument', () => {
const snippetString = new SnippetString()
.appendText('has been');
return withRandomFileEditor('This will be replaced', (editor, doc) => {
const selection = new Selection(
new Position(0, 5),
new Position(0, 12)
);
return editor.insertSnippet(snippetString, selection).then(inserted => {
assert.ok(inserted);
assert.equal(doc.getText(), 'This has been replaced');
assert.ok(doc.isDirty);
});
});
});
test('make edit', () => {
return withRandomFileEditor('', (editor, doc) => {
return editor.edit((builder) => {
builder.insert(new Position(0, 0), 'Hello World');
}).then(applied => {
assert.ok(applied);
assert.equal(doc.getText(), 'Hello World');
assert.ok(doc.isDirty);
});
});
});
test('issue #6281: Edits fail to validate ranges correctly before applying', () => {
return withRandomFileEditor('Hello world!', (editor, doc) => {
return editor.edit((builder) => {
builder.replace(new Range(0, 0, Number.MAX_VALUE, Number.MAX_VALUE), 'new');
}).then(applied => {
assert.ok(applied);
assert.equal(doc.getText(), 'new');
assert.ok(doc.isDirty);
});
});
});
function executeReplace(editor: TextEditor, range: Range, text: string, undoStopBefore: boolean, undoStopAfter: boolean): Thenable<boolean> {
return editor.edit((builder) => {
builder.replace(range, text);
}, { undoStopBefore: undoStopBefore, undoStopAfter: undoStopAfter });
}
test('TextEditor.edit can control undo/redo stack 1', () => {
return withRandomFileEditor('Hello world!', async (editor, doc) => {
const applied1 = await executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false);
assert.ok(applied1);
assert.equal(doc.getText(), 'hello world!');
assert.ok(doc.isDirty);
const applied2 = await executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', false, false);
assert.ok(applied2);
assert.equal(doc.getText(), 'hELLO world!');
assert.ok(doc.isDirty);
await commands.executeCommand('undo');
if (doc.getText() === 'hello world!') {
// see https://github.com/microsoft/vscode/issues/109131
// it looks like an undo stop was inserted in between these two edits
// it is unclear why this happens, but it can happen for a multitude of reasons
await commands.executeCommand('undo');
}
assert.equal(doc.getText(), 'Hello world!');
});
});
test('TextEditor.edit can control undo/redo stack 2', () => {
return withRandomFileEditor('Hello world!', (editor, doc) => {
return executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false).then(applied => {
assert.ok(applied);
assert.equal(doc.getText(), 'hello world!');
assert.ok(doc.isDirty);
return executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', true, false);
}).then(applied => {
assert.ok(applied);
assert.equal(doc.getText(), 'hELLO world!');
assert.ok(doc.isDirty);
return commands.executeCommand('undo');
}).then(_ => {
assert.equal(doc.getText(), 'hello world!');
});
});
});
test('issue #16573: Extension API: insertSpaces and tabSize are undefined', () => {
return withRandomFileEditor('Hello world!\n\tHello world!', (editor, _doc) => {
assert.equal(editor.options.tabSize, 4);
assert.equal(editor.options.insertSpaces, false);
assert.equal(editor.options.cursorStyle, TextEditorCursorStyle.Line);
assert.equal(editor.options.lineNumbers, TextEditorLineNumbersStyle.On);
editor.options = {
tabSize: 2
};
assert.equal(editor.options.tabSize, 2);
assert.equal(editor.options.insertSpaces, false);
assert.equal(editor.options.cursorStyle, TextEditorCursorStyle.Line);
assert.equal(editor.options.lineNumbers, TextEditorLineNumbersStyle.On);
editor.options.tabSize = 'invalid';
assert.equal(editor.options.tabSize, 2);
assert.equal(editor.options.insertSpaces, false);
assert.equal(editor.options.cursorStyle, TextEditorCursorStyle.Line);
assert.equal(editor.options.lineNumbers, TextEditorLineNumbersStyle.On);
return Promise.resolve();
});
});
test('issue #20757: Overlapping ranges are not allowed!', () => {
return withRandomFileEditor('Hello world!\n\tHello world!', (editor, _doc) => {
return editor.edit((builder) => {
// create two edits that overlap (i.e. are illegal)
builder.replace(new Range(0, 0, 0, 2), 'He');
builder.replace(new Range(0, 1, 0, 3), 'el');
}).then(
(_applied) => {
assert.ok(false, 'edit with overlapping ranges should fail');
},
(_err) => {
assert.ok(true, 'edit with overlapping ranges should fail');
}
);
});
});
test('throw when using invalid edit', async function () {
await withRandomFileEditor('foo', editor => {
return new Promise((resolve, reject) => {
editor.edit(edit => {
edit.insert(new Position(0, 0), 'bar');
setTimeout(() => {
try {
edit.insert(new Position(0, 0), 'bar');
reject(new Error('expected error'));
} catch (err) {
assert.ok(true);
resolve();
}
}, 0);
});
});
});
});
test('editor contents are correctly read (small file)', function () {
return testEditorContents('/far.js');
});
test('editor contents are correctly read (large file)', async function () {
return testEditorContents('/lorem.txt');
});
async function testEditorContents(relativePath: string) {
const root = workspace.workspaceFolders![0]!.uri;
const file = Uri.parse(root.toString() + relativePath);
const document = await workspace.openTextDocument(file);
assert.equal(document.getText(), Buffer.from(await workspace.fs.readFile(file)).toString());
}
});

View File

@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { env, extensions, ExtensionKind, UIKind, Uri } from 'vscode';
suite('vscode API - env', () => {
test('env is set', function () {
assert.equal(typeof env.language, 'string');
assert.equal(typeof env.appRoot, 'string');
assert.equal(typeof env.appName, 'string');
assert.equal(typeof env.machineId, 'string');
assert.equal(typeof env.sessionId, 'string');
assert.equal(typeof env.shell, 'string');
});
test('env is readonly', function () {
assert.throws(() => (env as any).language = '234');
assert.throws(() => (env as any).appRoot = '234');
assert.throws(() => (env as any).appName = '234');
assert.throws(() => (env as any).machineId = '234');
assert.throws(() => (env as any).sessionId = '234');
assert.throws(() => (env as any).shell = '234');
});
test('env.remoteName', function () {
const remoteName = env.remoteName;
const knownWorkspaceExtension = extensions.getExtension('vscode.git');
const knownUiExtension = extensions.getExtension('vscode.git-ui');
if (typeof remoteName === 'undefined') {
// not running in remote, so we expect both extensions
assert.ok(knownWorkspaceExtension);
assert.ok(knownUiExtension);
assert.equal(ExtensionKind.UI, knownUiExtension!.extensionKind);
} else if (typeof remoteName === 'string') {
// running in remote, so we only expect workspace extensions
assert.ok(knownWorkspaceExtension);
if (env.uiKind === UIKind.Desktop) {
assert.ok(!knownUiExtension); // we currently can only access extensions that run on same host
}
assert.equal(ExtensionKind.Workspace, knownWorkspaceExtension!.extensionKind);
} else {
assert.fail();
}
});
test('env.uiKind', async function () {
const uri = Uri.parse(`${env.uriScheme}:://vscode.vscode-api-tests/path?key=value&other=false`);
const result = await env.asExternalUri(uri);
const kind = env.uiKind;
if (result.scheme === 'http' || result.scheme === 'https') {
assert.equal(kind, UIKind.Web);
} else {
assert.equal(kind, UIKind.Desktop);
}
});
test('env.asExternalUri - with env.uriScheme', async function () {
const uri = Uri.parse(`${env.uriScheme}:://vscode.vscode-api-tests/path?key=value&other=false`);
const result = await env.asExternalUri(uri);
assert.ok(result);
if (env.uiKind === UIKind.Desktop) {
assert.equal(uri.scheme, result.scheme);
assert.equal(uri.authority, result.authority);
assert.equal(uri.path, result.path);
} else {
assert.ok(result.scheme === 'http' || result.scheme === 'https');
}
});
});

View File

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
const testRunner = require('vscode/lib/testrunner');
const options: any = {
ui: 'tdd',
useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'),
timeout: 60000
};
// These integration tests is being run in multiple environments (electron, web, remote)
// so we need to set the suite name based on the environment as the suite name is used
// for the test results file name
let suite = '';
if (process.env.VSCODE_BROWSER) {
suite = `${process.env.VSCODE_BROWSER} Browser Integration Single Folder Tests`;
} else if (process.env.REMOTE_VSCODE) {
suite = 'Remote Integration Single Folder Tests';
} else {
suite = 'Integration Single Folder Tests';
}
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
options.reporter = 'mocha-multi-reporters';
options.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
testRunner.configure(options);
export = testRunner;

View File

@ -0,0 +1,190 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { join } from 'path';
import * as vscode from 'vscode';
import { createRandomFile, testFs } from '../utils';
suite('vscode API - languages', () => {
const isWindows = process.platform === 'win32';
function positionToString(p: vscode.Position) {
return `[${p.character}/${p.line}]`;
}
function rangeToString(r: vscode.Range) {
return `[${positionToString(r.start)}/${positionToString(r.end)}]`;
}
function assertEqualRange(actual: vscode.Range, expected: vscode.Range, message?: string) {
assert.equal(rangeToString(actual), rangeToString(expected), message);
}
test('setTextDocumentLanguage -> close/open event', async function () {
const file = await createRandomFile('foo\nbar\nbar');
const doc = await vscode.workspace.openTextDocument(file);
const langIdNow = doc.languageId;
let clock = 0;
const disposables: vscode.Disposable[] = [];
let close = new Promise<void>(resolve => {
disposables.push(vscode.workspace.onDidCloseTextDocument(e => {
if (e === doc) {
assert.equal(doc.languageId, langIdNow);
assert.equal(clock, 0);
clock += 1;
resolve();
}
}));
});
let open = new Promise<void>(resolve => {
disposables.push(vscode.workspace.onDidOpenTextDocument(e => {
if (e === doc) { // same instance!
assert.equal(doc.languageId, 'json');
assert.equal(clock, 1);
clock += 1;
resolve();
}
}));
});
let change = vscode.languages.setTextDocumentLanguage(doc, 'json');
await Promise.all([change, close, open]);
assert.equal(clock, 2);
assert.equal(doc.languageId, 'json');
disposables.forEach(disposable => disposable.dispose());
disposables.length = 0;
});
test('setTextDocumentLanguage -> error when language does not exist', async function () {
const file = await createRandomFile('foo\nbar\nbar');
const doc = await vscode.workspace.openTextDocument(file);
try {
await vscode.languages.setTextDocumentLanguage(doc, 'fooLangDoesNotExist');
assert.ok(false);
} catch (err) {
assert.ok(err);
}
});
test('diagnostics, read & event', function () {
let uri = vscode.Uri.file('/foo/bar.txt');
let col1 = vscode.languages.createDiagnosticCollection('foo1');
col1.set(uri, [new vscode.Diagnostic(new vscode.Range(0, 0, 0, 12), 'error1')]);
let col2 = vscode.languages.createDiagnosticCollection('foo2');
col2.set(uri, [new vscode.Diagnostic(new vscode.Range(0, 0, 0, 12), 'error1')]);
let diag = vscode.languages.getDiagnostics(uri);
assert.equal(diag.length, 2);
let tuples = vscode.languages.getDiagnostics();
let found = false;
for (let [thisUri,] of tuples) {
if (thisUri.toString() === uri.toString()) {
found = true;
break;
}
}
assert.ok(tuples.length >= 1);
assert.ok(found);
});
test('link detector', async function () {
const uri = await createRandomFile('class A { // http://a.com }', undefined, '.java');
const doc = await vscode.workspace.openTextDocument(uri);
const target = vscode.Uri.file(isWindows ? 'c:\\foo\\bar' : '/foo/bar');
const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 5));
const linkProvider: vscode.DocumentLinkProvider = {
provideDocumentLinks: _doc => {
return [new vscode.DocumentLink(range, target)];
}
};
vscode.languages.registerDocumentLinkProvider({ language: 'java', scheme: testFs.scheme }, linkProvider);
const links = await vscode.commands.executeCommand<vscode.DocumentLink[]>('vscode.executeLinkProvider', doc.uri);
assert.equal(2, links && links.length);
let [link1, link2] = links!.sort((l1, l2) => l1.range.start.compareTo(l2.range.start));
assert.equal(target.toString(), link1.target && link1.target.toString());
assertEqualRange(range, link1.range);
assert.equal('http://a.com/', link2.target && link2.target.toString());
assertEqualRange(new vscode.Range(new vscode.Position(0, 13), new vscode.Position(0, 25)), link2.range);
});
test('diagnostics & CodeActionProvider', async function () {
class D2 extends vscode.Diagnostic {
customProp = { complex() { } };
constructor() {
super(new vscode.Range(0, 2, 0, 7), 'sonntag');
}
}
let diag1 = new vscode.Diagnostic(new vscode.Range(0, 0, 0, 5), 'montag');
let diag2 = new D2();
let ran = false;
let uri = vscode.Uri.parse('ttt:path.far');
let r1 = vscode.languages.registerCodeActionsProvider({ pattern: '*.far', scheme: 'ttt' }, {
provideCodeActions(_document, _range, ctx): vscode.Command[] {
assert.equal(ctx.diagnostics.length, 2);
let [first, second] = ctx.diagnostics;
assert.ok(first === diag1);
assert.ok(second === diag2);
assert.ok(diag2 instanceof D2);
ran = true;
return [];
}
});
let r2 = vscode.workspace.registerTextDocumentContentProvider('ttt', {
provideTextDocumentContent() {
return 'this is some text';
}
});
let r3 = vscode.languages.createDiagnosticCollection();
r3.set(uri, [diag1]);
let r4 = vscode.languages.createDiagnosticCollection();
r4.set(uri, [diag2]);
await vscode.workspace.openTextDocument(uri);
await vscode.commands.executeCommand('vscode.executeCodeActionProvider', uri, new vscode.Range(0, 0, 0, 10));
assert.ok(ran);
vscode.Disposable.from(r1, r2, r3, r4).dispose();
});
test('completions with document filters', async function () {
let ran = false;
let uri = vscode.Uri.file(join(vscode.workspace.rootPath || '', './bower.json'));
let jsonDocumentFilter = [{ language: 'json', pattern: '**/package.json' }, { language: 'json', pattern: '**/bower.json' }, { language: 'json', pattern: '**/.bower.json' }];
let r1 = vscode.languages.registerCompletionItemProvider(jsonDocumentFilter, {
provideCompletionItems: (_document: vscode.TextDocument, _position: vscode.Position, _token: vscode.CancellationToken): vscode.CompletionItem[] => {
let proposal = new vscode.CompletionItem('foo');
proposal.kind = vscode.CompletionItemKind.Property;
ran = true;
return [proposal];
}
});
await vscode.workspace.openTextDocument(uri);
const result = await vscode.commands.executeCommand<vscode.CompletionList>('vscode.executeCompletionItemProvider', uri, new vscode.Position(1, 0));
r1.dispose();
assert.ok(ran, 'Provider has not been invoked');
assert.ok(result!.items.some(i => i.label === 'foo'), 'Results do not include "foo"');
});
});

View File

@ -0,0 +1,277 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { window, commands } from 'vscode';
import { closeAllEditors } from '../utils';
interface QuickPickExpected {
events: string[];
activeItems: string[][];
selectionItems: string[][];
acceptedItems: {
active: string[][];
selection: string[][];
dispose: boolean[];
};
}
suite('vscode API - quick input', function () {
teardown(closeAllEditors);
test('createQuickPick, select second', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
const quickPick = createQuickPick({
events: ['active', 'active', 'selection', 'accept', 'hide'],
activeItems: [['eins'], ['zwei']],
selectionItems: [['zwei']],
acceptedItems: {
active: [['zwei']],
selection: [['zwei']],
dispose: [true]
},
}, (err?: any) => done(err));
quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label }));
quickPick.show();
(async () => {
await commands.executeCommand('workbench.action.quickOpenSelectNext');
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
})()
.catch(err => done(err));
});
test('createQuickPick, focus second', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
const quickPick = createQuickPick({
events: ['active', 'selection', 'accept', 'hide'],
activeItems: [['zwei']],
selectionItems: [['zwei']],
acceptedItems: {
active: [['zwei']],
selection: [['zwei']],
dispose: [true]
},
}, (err?: any) => done(err));
quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label }));
quickPick.activeItems = [quickPick.items[1]];
quickPick.show();
(async () => {
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
})()
.catch(err => done(err));
});
test('createQuickPick, select first and second', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
const quickPick = createQuickPick({
events: ['active', 'selection', 'active', 'selection', 'accept', 'hide'],
activeItems: [['eins'], ['zwei']],
selectionItems: [['eins'], ['eins', 'zwei']],
acceptedItems: {
active: [['zwei']],
selection: [['eins', 'zwei']],
dispose: [true]
},
}, (err?: any) => done(err));
quickPick.canSelectMany = true;
quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label }));
quickPick.show();
(async () => {
await commands.executeCommand('workbench.action.quickOpenSelectNext');
await commands.executeCommand('workbench.action.quickPickManyToggle');
await commands.executeCommand('workbench.action.quickOpenSelectNext');
await commands.executeCommand('workbench.action.quickPickManyToggle');
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
})()
.catch(err => done(err));
});
test('createQuickPick, selection events', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
const quickPick = createQuickPick({
events: ['active', 'selection', 'accept', 'selection', 'accept', 'hide'],
activeItems: [['eins']],
selectionItems: [['zwei'], ['drei']],
acceptedItems: {
active: [['eins'], ['eins']],
selection: [['zwei'], ['drei']],
dispose: [false, true]
},
}, (err?: any) => done(err));
quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label }));
quickPick.show();
quickPick.selectedItems = [quickPick.items[1]];
setTimeout(() => {
quickPick.selectedItems = [quickPick.items[2]];
}, 0);
});
test('createQuickPick, continue after first accept', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
const quickPick = createQuickPick({
events: ['active', 'selection', 'accept', 'active', 'selection', 'active', 'selection', 'accept', 'hide'],
activeItems: [['eins'], [], ['drei']],
selectionItems: [['eins'], [], ['drei']],
acceptedItems: {
active: [['eins'], ['drei']],
selection: [['eins'], ['drei']],
dispose: [false, true]
},
}, (err?: any) => done(err));
quickPick.items = ['eins', 'zwei'].map(label => ({ label }));
quickPick.show();
(async () => {
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
await timeout(async () => {
quickPick.items = ['drei', 'vier'].map(label => ({ label }));
await timeout(async () => {
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
}, 0);
}, 0);
})()
.catch(err => done(err));
});
test('createQuickPick, dispose in onDidHide', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
let hidden = false;
const quickPick = window.createQuickPick();
quickPick.onDidHide(() => {
if (hidden) {
done(new Error('Already hidden'));
} else {
hidden = true;
quickPick.dispose();
setTimeout(done, 0);
}
});
quickPick.show();
quickPick.hide();
});
test('createQuickPick, hide and dispose', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
let hidden = false;
const quickPick = window.createQuickPick();
quickPick.onDidHide(() => {
if (hidden) {
done(new Error('Already hidden'));
} else {
hidden = true;
setTimeout(done, 0);
}
});
quickPick.show();
quickPick.hide();
quickPick.dispose();
});
});
function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, record = false) {
const quickPick = window.createQuickPick();
let eventIndex = -1;
quickPick.onDidChangeActive(items => {
if (record) {
console.log(`active: [${items.map(item => item.label).join(', ')}]`);
return;
}
try {
eventIndex++;
assert.equal('active', expected.events.shift(), `onDidChangeActive (event ${eventIndex})`);
const expectedItems = expected.activeItems.shift();
assert.deepEqual(items.map(item => item.label), expectedItems, `onDidChangeActive event items (event ${eventIndex})`);
assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedItems, `onDidChangeActive active items (event ${eventIndex})`);
} catch (err) {
done(err);
}
});
quickPick.onDidChangeSelection(items => {
if (record) {
console.log(`selection: [${items.map(item => item.label).join(', ')}]`);
return;
}
try {
eventIndex++;
assert.equal('selection', expected.events.shift(), `onDidChangeSelection (event ${eventIndex})`);
const expectedItems = expected.selectionItems.shift();
assert.deepEqual(items.map(item => item.label), expectedItems, `onDidChangeSelection event items (event ${eventIndex})`);
assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedItems, `onDidChangeSelection selected items (event ${eventIndex})`);
} catch (err) {
done(err);
}
});
quickPick.onDidAccept(() => {
if (record) {
console.log('accept');
return;
}
try {
eventIndex++;
assert.equal('accept', expected.events.shift(), `onDidAccept (event ${eventIndex})`);
const expectedActive = expected.acceptedItems.active.shift();
assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedActive, `onDidAccept active items (event ${eventIndex})`);
const expectedSelection = expected.acceptedItems.selection.shift();
assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedSelection, `onDidAccept selected items (event ${eventIndex})`);
if (expected.acceptedItems.dispose.shift()) {
quickPick.dispose();
}
} catch (err) {
done(err);
}
});
quickPick.onDidHide(() => {
if (record) {
console.log('hide');
done();
return;
}
try {
assert.equal('hide', expected.events.shift());
done();
} catch (err) {
done(err);
}
});
return quickPick;
}
async function timeout<T>(run: () => Promise<T> | T, ms: number): Promise<T> {
return new Promise<T>(resolve => setTimeout(() => resolve(run()), ms));
}

View File

@ -0,0 +1,820 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget, Disposable, UIKind, env, EnvironmentVariableMutatorType, EnvironmentVariableMutator, extensions, ExtensionContext, TerminalOptions, ExtensionTerminalOptions } from 'vscode';
import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
// Disable terminal tests:
// - Web https://github.com/microsoft/vscode/issues/92826
// - Remote https://github.com/microsoft/vscode/issues/96057
((env.uiKind === UIKind.Web || typeof env.remoteName !== 'undefined') ? suite.skip : suite)('vscode API - terminal', () => {
let extensionContext: ExtensionContext;
suiteSetup(async () => {
// Trigger extension activation and grab the context as some tests depend on it
await extensions.getExtension('vscode.vscode-api-tests')?.activate();
extensionContext = (global as any).testExtensionContext;
const config = workspace.getConfiguration('terminal.integrated');
// Disable conpty in integration tests because of https://github.com/microsoft/vscode/issues/76548
await config.update('windowsEnableConpty', false, ConfigurationTarget.Global);
// Disable exit alerts as tests may trigger then and we're not testing the notifications
await config.update('showExitAlert', false, ConfigurationTarget.Global);
// Canvas may cause problems when running in a container
await config.update('rendererType', 'dom', ConfigurationTarget.Global);
});
suite('Terminal', () => {
let disposables: Disposable[] = [];
teardown(() => {
disposables.forEach(d => d.dispose());
disposables.length = 0;
});
test('sendText immediately after createTerminal should not throw', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
terminal.dispose();
disposables.push(window.onDidCloseTerminal(() => done()));
}));
const terminal = window.createTerminal();
doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
});
(process.platform === 'linux' ? test.skip : test)('echo works in the default shell', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
let data = '';
const dataDisposable = window.onDidWriteTerminalData(e => {
try {
equal(terminal, e.terminal);
} catch (e) {
done(e);
return;
}
data += e.data;
if (data.indexOf(expected) !== 0) {
dataDisposable.dispose();
terminal.dispose();
disposables.push(window.onDidCloseTerminal(() => {
done();
}));
}
});
disposables.push(dataDisposable);
}));
// Use a single character to avoid winpty/conpty issues with injected sequences
const expected = '`';
const terminal = window.createTerminal({
env: {
TEST: '`'
}
});
terminal.show();
doesNotThrow(() => {
// Print an environment variable value so the echo statement doesn't get matched
if (process.platform === 'win32') {
terminal.sendText(`$env:TEST`);
} else {
terminal.sendText(`echo $TEST`);
}
});
});
test('onDidCloseTerminal event fires when terminal is disposed', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
terminal.dispose();
disposables.push(window.onDidCloseTerminal(() => done()));
}));
const terminal = window.createTerminal();
});
test('processId immediately after createTerminal should fetch the pid', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
terminal.processId.then(id => {
try {
ok(id && id > 0);
} catch (e) {
done(e);
return;
}
terminal.dispose();
disposables.push(window.onDidCloseTerminal(() => done()));
});
}));
const terminal = window.createTerminal();
});
test('name in constructor should set terminal.name', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
terminal.dispose();
disposables.push(window.onDidCloseTerminal(() => done()));
}));
const terminal = window.createTerminal('a');
try {
equal(terminal.name, 'a');
} catch (e) {
done(e);
return;
}
});
test('creationOptions should be set and readonly for TerminalOptions terminals', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
terminal.dispose();
disposables.push(window.onDidCloseTerminal(() => done()));
}));
const options = {
name: 'foo',
hideFromUser: true
};
const terminal = window.createTerminal(options);
try {
equal(terminal.name, 'foo');
const terminalOptions = terminal.creationOptions as TerminalOptions;
equal(terminalOptions.name, 'foo');
equal(terminalOptions.hideFromUser, true);
throws(() => terminalOptions.name = 'bad', 'creationOptions should be readonly at runtime');
} catch (e) {
done(e);
return;
}
});
test('onDidOpenTerminal should fire when a terminal is created', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(term.name, 'b');
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}));
const terminal = window.createTerminal('b');
});
test('exitStatus.code should be set to undefined after a terminal is disposed', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(term, terminal);
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(t => {
try {
deepEqual(t.exitStatus, { code: undefined });
} catch (e) {
done(e);
return;
}
done();
}));
terminal.dispose();
}));
const terminal = window.createTerminal();
});
// test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
// const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
// equal(active, terminal);
// equal(active, window.activeTerminal);
// reg1.dispose();
// const reg2 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
// equal(active, undefined);
// equal(active, window.activeTerminal);
// reg2.dispose();
// done();
// });
// terminal.dispose();
// });
// const terminal = window.createTerminal();
// terminal.show();
// });
// test('onDidChangeTerminalDimensions should fire when new terminals are created', (done) => {
// const reg1 = window.onDidChangeTerminalDimensions(async (event: TerminalDimensionsChangeEvent) => {
// equal(event.terminal, terminal1);
// equal(typeof event.dimensions.columns, 'number');
// equal(typeof event.dimensions.rows, 'number');
// ok(event.dimensions.columns > 0);
// ok(event.dimensions.rows > 0);
// reg1.dispose();
// let terminal2: Terminal;
// const reg2 = window.onDidOpenTerminal((newTerminal) => {
// // This is guarantees to fire before dimensions change event
// if (newTerminal !== terminal1) {
// terminal2 = newTerminal;
// reg2.dispose();
// }
// });
// let firstCalled = false;
// let secondCalled = false;
// const reg3 = window.onDidChangeTerminalDimensions((event: TerminalDimensionsChangeEvent) => {
// if (event.terminal === terminal1) {
// // The original terminal should fire dimension change after a split
// firstCalled = true;
// } else if (event.terminal !== terminal1) {
// // The new split terminal should fire dimension change
// secondCalled = true;
// }
// if (firstCalled && secondCalled) {
// let firstDisposed = false;
// let secondDisposed = false;
// const reg4 = window.onDidCloseTerminal(term => {
// if (term === terminal1) {
// firstDisposed = true;
// }
// if (term === terminal2) {
// secondDisposed = true;
// }
// if (firstDisposed && secondDisposed) {
// reg4.dispose();
// done();
// }
// });
// terminal1.dispose();
// terminal2.dispose();
// reg3.dispose();
// }
// });
// await timeout(500);
// commands.executeCommand('workbench.action.terminal.split');
// });
// const terminal1 = window.createTerminal({ name: 'test' });
// terminal1.show();
// });
suite('hideFromUser', () => {
test('should be available to terminals API', done => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
disposables.push(window.onDidOpenTerminal(t => {
try {
equal(t, terminal);
equal(t.name, 'bg');
ok(window.terminals.indexOf(terminal) !== -1);
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(() => {
// reg3.dispose();
done();
}));
terminal.dispose();
}));
});
});
suite('window.onDidWriteTerminalData', () => {
test('should listen to all future terminal data events', (done) => {
const openEvents: string[] = [];
const dataEvents: { name: string, data: string }[] = [];
const closeEvents: string[] = [];
disposables.push(window.onDidOpenTerminal(e => openEvents.push(e.name)));
let resolveOnceDataWritten: (() => void) | undefined;
let resolveOnceClosed: (() => void) | undefined;
disposables.push(window.onDidWriteTerminalData(e => {
dataEvents.push({ name: e.terminal.name, data: e.data });
resolveOnceDataWritten!();
}));
disposables.push(window.onDidCloseTerminal(e => {
closeEvents.push(e.name);
try {
if (closeEvents.length === 1) {
deepEqual(openEvents, ['test1']);
deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }]);
deepEqual(closeEvents, ['test1']);
} else if (closeEvents.length === 2) {
deepEqual(openEvents, ['test1', 'test2']);
deepEqual(dataEvents, [{ name: 'test1', data: 'write1' }, { name: 'test2', data: 'write2' }]);
deepEqual(closeEvents, ['test1', 'test2']);
}
resolveOnceClosed!();
} catch (e) {
done(e);
}
}));
const term1Write = new EventEmitter<string>();
const term1Close = new EventEmitter<void>();
window.createTerminal({
name: 'test1', pty: {
onDidWrite: term1Write.event,
onDidClose: term1Close.event,
open: async () => {
term1Write.fire('write1');
// Wait until the data is written
await new Promise<void>(resolve => { resolveOnceDataWritten = resolve; });
term1Close.fire();
// Wait until the terminal is closed
await new Promise<void>(resolve => { resolveOnceClosed = resolve; });
const term2Write = new EventEmitter<string>();
const term2Close = new EventEmitter<void>();
window.createTerminal({
name: 'test2', pty: {
onDidWrite: term2Write.event,
onDidClose: term2Close.event,
open: async () => {
term2Write.fire('write2');
// Wait until the data is written
await new Promise<void>(resolve => { resolveOnceDataWritten = resolve; });
term2Close.fire();
// Wait until the terminal is closed
await new Promise<void>(resolve => { resolveOnceClosed = resolve; });
done();
},
close: () => { }
}
});
},
close: () => { }
}
});
});
});
suite('Extension pty terminals', () => {
test('should fire onDidOpenTerminal and onDidCloseTerminal', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(term.name, 'c');
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(() => done()));
term.dispose();
}));
const pty: Pseudoterminal = {
onDidWrite: new EventEmitter<string>().event,
open: () => { },
close: () => { }
};
window.createTerminal({ name: 'c', pty });
});
// The below tests depend on global UI state and each other
// test('should not provide dimensions on start as the terminal has not been shown yet', (done) => {
// const reg1 = window.onDidOpenTerminal(term => {
// equal(terminal, term);
// reg1.dispose();
// });
// const pty: Pseudoterminal = {
// onDidWrite: new EventEmitter<string>().event,
// open: (dimensions) => {
// equal(dimensions, undefined);
// const reg3 = window.onDidCloseTerminal(() => {
// reg3.dispose();
// done();
// });
// // Show a terminal and wait a brief period before dispose, this will cause
// // the panel to init it's dimenisons and be provided to following terminals.
// // The following test depends on this.
// terminal.show();
// setTimeout(() => terminal.dispose(), 200);
// },
// close: () => {}
// };
// const terminal = window.createTerminal({ name: 'foo', pty });
// });
// test('should provide dimensions on start as the terminal has been shown', (done) => {
// const reg1 = window.onDidOpenTerminal(term => {
// equal(terminal, term);
// reg1.dispose();
// });
// const pty: Pseudoterminal = {
// onDidWrite: new EventEmitter<string>().event,
// open: (dimensions) => {
// // This test depends on Terminal.show being called some time before such
// // that the panel dimensions are initialized and cached.
// ok(dimensions!.columns > 0);
// ok(dimensions!.rows > 0);
// const reg3 = window.onDidCloseTerminal(() => {
// reg3.dispose();
// done();
// });
// terminal.dispose();
// },
// close: () => {}
// };
// const terminal = window.createTerminal({ name: 'foo', pty });
// });
test('should respect dimension overrides', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
term.show();
disposables.push(window.onDidChangeTerminalDimensions(e => {
// The default pty dimensions have a chance to appear here since override
// dimensions happens after the terminal is created. If so just ignore and
// wait for the right dimensions
if (e.dimensions.columns === 10 || e.dimensions.rows === 5) {
try {
equal(e.terminal, terminal);
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}));
}));
const writeEmitter = new EventEmitter<string>();
const overrideDimensionsEmitter = new EventEmitter<TerminalDimensions>();
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
onDidOverrideDimensions: overrideDimensionsEmitter.event,
open: () => overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }),
close: () => { }
};
const terminal = window.createTerminal({ name: 'foo', pty });
});
test('exitStatus.code should be set to the exit code (undefined)', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
equal(terminal.exitStatus, undefined);
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(t => {
try {
equal(terminal, t);
deepEqual(terminal.exitStatus, { code: undefined });
} catch (e) {
done(e);
return;
}
done();
}));
}));
const writeEmitter = new EventEmitter<string>();
const closeEmitter = new EventEmitter<number | undefined>();
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
onDidClose: closeEmitter.event,
open: () => closeEmitter.fire(undefined),
close: () => { }
};
const terminal = window.createTerminal({ name: 'foo', pty });
});
test('exitStatus.code should be set to the exit code (zero)', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
equal(terminal.exitStatus, undefined);
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(t => {
try {
equal(terminal, t);
deepEqual(terminal.exitStatus, { code: 0 });
} catch (e) {
done(e);
return;
}
done();
}));
}));
const writeEmitter = new EventEmitter<string>();
const closeEmitter = new EventEmitter<number | undefined>();
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
onDidClose: closeEmitter.event,
open: () => closeEmitter.fire(0),
close: () => { }
};
const terminal = window.createTerminal({ name: 'foo', pty });
});
test('exitStatus.code should be set to the exit code (non-zero)', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
equal(terminal.exitStatus, undefined);
} catch (e) {
done(e);
return;
}
disposables.push(window.onDidCloseTerminal(t => {
try {
equal(terminal, t);
deepEqual(terminal.exitStatus, { code: 22 });
} catch (e) {
done(e);
return;
}
done();
}));
}));
const writeEmitter = new EventEmitter<string>();
const closeEmitter = new EventEmitter<number | undefined>();
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
onDidClose: closeEmitter.event,
open: () => {
// Wait 500ms as any exits that occur within 500ms of terminal launch are
// are counted as "exiting during launch" which triggers a notification even
// when showExitAlerts is true
setTimeout(() => closeEmitter.fire(22), 500);
},
close: () => { }
};
const terminal = window.createTerminal({ name: 'foo', pty });
});
test('creationOptions should be set and readonly for ExtensionTerminalOptions terminals', (done) => {
disposables.push(window.onDidOpenTerminal(term => {
try {
equal(terminal, term);
} catch (e) {
done(e);
return;
}
terminal.dispose();
disposables.push(window.onDidCloseTerminal(() => done()));
}));
const writeEmitter = new EventEmitter<string>();
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
open: () => { },
close: () => { }
};
const options = { name: 'foo', pty };
const terminal = window.createTerminal(options);
try {
equal(terminal.name, 'foo');
const terminalOptions = terminal.creationOptions as ExtensionTerminalOptions;
equal(terminalOptions.name, 'foo');
equal(terminalOptions.pty, pty);
throws(() => terminalOptions.name = 'bad', 'creationOptions should be readonly at runtime');
} catch (e) {
done(e);
}
});
});
suite('environmentVariableCollection', () => {
test('should have collection variables apply to terminals immediately after setting', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a2~',
'b1~b2~',
'~c2~c1'
];
disposables.push(window.onDidWriteTerminalData(e => {
try {
equal(terminal, e.terminal);
} catch (e) {
done(e);
return;
}
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
collection.append('B', '~b2~');
collection.prepend('C', '~c2~');
const terminal = window.createTerminal({
env: {
A: 'a1',
B: 'b1',
C: 'c1'
}
});
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
terminal.sendText('echo $A');
terminal.sendText('$env:B');
terminal.sendText('echo $B');
terminal.sendText('$env:C');
terminal.sendText('echo $C');
});
test('should have collection variables apply to environment variables that don\'t exist', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a2~',
'~b2~',
'~c2~'
];
disposables.push(window.onDidWriteTerminalData(e => {
try {
equal(terminal, e.terminal);
} catch (e) {
done(e);
return;
}
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
collection.append('B', '~b2~');
collection.prepend('C', '~c2~');
const terminal = window.createTerminal({
env: {
A: null,
B: null,
C: null
}
});
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
terminal.sendText('echo $A');
terminal.sendText('$env:B');
terminal.sendText('echo $B');
terminal.sendText('$env:C');
terminal.sendText('echo $C');
});
test('should respect clearing entries', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a1~',
'~b1~'
];
disposables.push(window.onDidWriteTerminalData(e => {
try {
equal(terminal, e.terminal);
} catch (e) {
done(e);
return;
}
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
collection.replace('B', '~a2~');
collection.clear();
const terminal = window.createTerminal({
env: {
A: '~a1~',
B: '~b1~'
}
});
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
terminal.sendText('echo $A');
terminal.sendText('$env:B');
terminal.sendText('echo $B');
});
test('should respect deleting entries', (done) => {
// Text to match on before passing the test
const expectedText = [
'~a1~',
'~b2~'
];
disposables.push(window.onDidWriteTerminalData(e => {
try {
equal(terminal, e.terminal);
} catch (e) {
done(e);
return;
}
// Multiple expected could show up in the same data event
while (expectedText.length > 0 && e.data.indexOf(expectedText[0]) >= 0) {
expectedText.shift();
// Check if all string are found, if so finish the test
if (expectedText.length === 0) {
disposables.push(window.onDidCloseTerminal(() => done()));
terminal.dispose();
}
}
}));
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
collection.replace('B', '~b2~');
collection.delete('A');
const terminal = window.createTerminal({
env: {
A: '~a1~',
B: '~b2~'
}
});
// Run both PowerShell and sh commands, errors don't matter we're just looking for
// the correct output
terminal.sendText('$env:A');
terminal.sendText('echo $A');
terminal.sendText('$env:B');
terminal.sendText('echo $B');
});
test('get and forEach should work', () => {
const collection = extensionContext.environmentVariableCollection;
disposables.push({ dispose: () => collection.clear() });
collection.replace('A', '~a2~');
collection.append('B', '~b2~');
collection.prepend('C', '~c2~');
// Verify get
deepEqual(collection.get('A'), { value: '~a2~', type: EnvironmentVariableMutatorType.Replace });
deepEqual(collection.get('B'), { value: '~b2~', type: EnvironmentVariableMutatorType.Append });
deepEqual(collection.get('C'), { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend });
// Verify forEach
const entries: [string, EnvironmentVariableMutator][] = [];
collection.forEach((v, m) => entries.push([v, m]));
deepEqual(entries, [
['A', { value: '~a2~', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: '~b2~', type: EnvironmentVariableMutatorType.Append }],
['C', { value: '~c2~', type: EnvironmentVariableMutatorType.Prepend }]
]);
});
});
});
});

View File

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import * as vscode from 'vscode';
suite('vscode API - types', () => {
test('static properties, es5 compat class', function () {
assert.ok(vscode.ThemeIcon.File instanceof vscode.ThemeIcon);
assert.ok(vscode.ThemeIcon.Folder instanceof vscode.ThemeIcon);
assert.ok(vscode.CodeActionKind.Empty instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.QuickFix instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.Refactor instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.RefactorExtract instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.RefactorInline instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.RefactorRewrite instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.Source instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.SourceOrganizeImports instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.SourceFixAll instanceof vscode.CodeActionKind);
// assert.ok(vscode.QuickInputButtons.Back instanceof vscode.QuickInputButtons); never was an instance
});
});

View File

@ -0,0 +1,453 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as os from 'os';
import * as vscode from 'vscode';
import { closeAllEditors, delay, disposeAll } from '../utils';
const webviewId = 'myWebview';
function workspaceFile(...segments: string[]) {
return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments);
}
const testDocument = workspaceFile('bower.json');
suite.skip('vscode API - webview', () => {
const disposables: vscode.Disposable[] = [];
function _register<T extends vscode.Disposable>(disposable: T) {
disposables.push(disposable);
return disposable;
}
teardown(async () => {
await closeAllEditors();
disposeAll(disposables);
});
test('webviews should be able to send and receive messages', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));
const firstResponse = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
window.addEventListener('message', (message) => {
vscode.postMessage({ value: message.data.value + 1 });
});
</script>`);
webview.webview.postMessage({ value: 1 });
assert.strictEqual((await firstResponse).value, 2);
});
test('webviews should not have scripts enabled by default', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {}));
const response = Promise.race<any>([
getMesssage(webview),
new Promise<{}>(resolve => setTimeout(() => resolve({ value: '🎉' }), 1000))
]);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
vscode.postMessage({ value: '💉' });
</script>`);
assert.strictEqual((await response).value, '🎉');
});
test('webviews should update html', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));
{
const response = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
vscode.postMessage({ value: 'first' });
</script>`);
assert.strictEqual((await response).value, 'first');
}
{
const response = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
vscode.postMessage({ value: 'second' });
</script>`);
assert.strictEqual((await response).value, 'second');
}
});
test.skip('webviews should preserve vscode API state when they are hidden', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));
const ready = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
let value = (vscode.getState() || {}).value || 0;
window.addEventListener('message', (message) => {
switch (message.data.type) {
case 'get':
vscode.postMessage({ value });
break;
case 'add':
++value;;
vscode.setState({ value });
vscode.postMessage({ value });
break;
}
});
vscode.postMessage({ type: 'ready' });
</script>`);
await ready;
const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
assert.strictEqual(firstResponse.value, 1);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
await vscode.window.showTextDocument(doc);
// And then back
const ready2 = getMesssage(webview);
webview.reveal(vscode.ViewColumn.One);
await ready2;
// We should still have old state
const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
assert.strictEqual(secondResponse.value, 1);
});
test('webviews should preserve their context when they are moved between view columns', async () => {
const doc = await vscode.workspace.openTextDocument(testDocument);
await vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
// Open webview in same column
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true }));
const ready = getMesssage(webview);
webview.webview.html = statefulWebviewHtml;
await ready;
const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
assert.strictEqual(firstResponse.value, 1);
// Now move webview to new view column
webview.reveal(vscode.ViewColumn.Two);
// We should still have old state
const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
assert.strictEqual(secondResponse.value, 1);
});
test('webviews with retainContextWhenHidden should preserve their context when they are hidden', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
const ready = getMesssage(webview);
webview.webview.html = statefulWebviewHtml;
await ready;
const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
assert.strictEqual((await firstResponse).value, 1);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
await vscode.window.showTextDocument(doc);
// And then back
webview.reveal(vscode.ViewColumn.One);
// We should still have old state
const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
assert.strictEqual(secondResponse.value, 1);
});
test('webviews with retainContextWhenHidden should preserve their page position when hidden', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
const ready = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
${'<h1>Header</h1>'.repeat(200)}
<script>
const vscode = acquireVsCodeApi();
setTimeout(() => {
window.scroll(0, 100);
vscode.postMessage({ value: window.scrollY });
}, 500);
window.addEventListener('message', (message) => {
switch (message.data.type) {
case 'get':
vscode.postMessage({ value: window.scrollY });
break;
}
});
vscode.postMessage({ type: 'ready' });
</script>`);
await ready;
const firstResponse = getMesssage(webview);
assert.strictEqual(Math.round((await firstResponse).value), 100);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
await vscode.window.showTextDocument(doc);
// And then back
webview.reveal(vscode.ViewColumn.One);
// We should still have old scroll pos
const secondResponse = await sendRecieveMessage(webview, { type: 'get' });
assert.strictEqual(Math.round(secondResponse.value), 100);
});
test('webviews with retainContextWhenHidden should be able to recive messages while hidden', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
const ready = getMesssage(webview);
webview.webview.html = statefulWebviewHtml;
await ready;
const firstResponse = await sendRecieveMessage(webview, { type: 'add' });
assert.strictEqual((await firstResponse).value, 1);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
await vscode.window.showTextDocument(doc);
// Try posting a message to our hidden webview
const secondResponse = await sendRecieveMessage(webview, { type: 'add' });
assert.strictEqual((await secondResponse).value, 2);
// Now show webview again
webview.reveal(vscode.ViewColumn.One);
// We should still have old state
const thirdResponse = await sendRecieveMessage(webview, { type: 'get' });
assert.strictEqual(thirdResponse.value, 2);
});
test.skip('webviews should only be able to load resources from workspace by default', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', {
viewColumn: vscode.ViewColumn.One
}, {
enableScripts: true
}));
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
window.addEventListener('message', (message) => {
const img = document.createElement('img');
img.addEventListener('load', () => {
vscode.postMessage({ value: true });
});
img.addEventListener('error', () => {
vscode.postMessage({ value: false });
});
img.src = message.data.src;
document.body.appendChild(img);
});
vscode.postMessage({ type: 'ready' });
</script>`);
const ready = getMesssage(webview);
await ready;
{
const imagePath = webview.webview.asWebviewUri(workspaceFile('image.png'));
const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
assert.strictEqual(response.value, true);
}
// {
// // #102188. Resource filename containing special characters like '%', '#', '?'.
// const imagePath = webview.webview.asWebviewUri(workspaceFile('image%02.png'));
// const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
// assert.strictEqual(response.value, true);
// }
// {
// // #102188. Resource filename containing special characters like '%', '#', '?'.
// const imagePath = webview.webview.asWebviewUri(workspaceFile('image%.png'));
// const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
// assert.strictEqual(response.value, true);
// }
{
const imagePath = webview.webview.asWebviewUri(workspaceFile('no-such-image.png'));
const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
assert.strictEqual(response.value, false);
}
{
const imagePath = webview.webview.asWebviewUri(workspaceFile('..', '..', '..', 'resources', 'linux', 'code.png'));
const response = await sendRecieveMessage(webview, { src: imagePath.toString() });
assert.strictEqual(response.value, false);
}
});
test.skip('webviews should allow overriding allowed resource paths using localResourceRoots', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {
enableScripts: true,
localResourceRoots: [workspaceFile('sub')]
}));
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
window.addEventListener('message', (message) => {
const img = document.createElement('img');
img.addEventListener('load', () => { vscode.postMessage({ value: true }); });
img.addEventListener('error', () => { vscode.postMessage({ value: false }); });
img.src = message.data.src;
document.body.appendChild(img);
});
</script>`);
{
const response = sendRecieveMessage(webview, { src: webview.webview.asWebviewUri(workspaceFile('sub', 'image.png')).toString() });
assert.strictEqual((await response).value, true);
}
{
const response = sendRecieveMessage(webview, { src: webview.webview.asWebviewUri(workspaceFile('image.png')).toString() });
assert.strictEqual((await response).value, false);
}
});
test.skip('webviews using hard-coded old style vscode-resource uri should work', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {
enableScripts: true,
localResourceRoots: [workspaceFile('sub')]
}));
const imagePath = workspaceFile('sub', 'image.png').with({ scheme: 'vscode-resource' }).toString();
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<img src="${imagePath}">
<script>
const vscode = acquireVsCodeApi();
const img = document.getElementsByTagName('img')[0];
img.addEventListener('load', () => { vscode.postMessage({ value: true }); });
img.addEventListener('error', () => { vscode.postMessage({ value: false }); });
</script>`);
const firstResponse = getMesssage(webview);
assert.strictEqual((await firstResponse).value, true);
});
test('webviews should have real view column after they are created, #56097', async () => {
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.Active }, { enableScripts: true }));
// Since we used a symbolic column, we don't know what view column the webview will actually show in at first
assert.strictEqual(webview.viewColumn, undefined);
let changed = false;
const viewStateChanged = new Promise<vscode.WebviewPanelOnDidChangeViewStateEvent>((resolve) => {
webview.onDidChangeViewState(e => {
if (changed) {
throw new Error('Only expected a single view state change');
}
changed = true;
resolve(e);
}, undefined, disposables);
});
assert.strictEqual((await viewStateChanged).webviewPanel.viewColumn, vscode.ViewColumn.One);
const firstResponse = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<script>
const vscode = acquireVsCodeApi();
vscode.postMessage({ });
</script>`);
webview.webview.postMessage({ value: 1 });
await firstResponse;
assert.strictEqual(webview.viewColumn, vscode.ViewColumn.One);
});
if (os.platform() === 'darwin') {
test.skip('webview can copy text from webview', async () => {
const expectedText = `webview text from: ${Date.now()}!`;
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
const ready = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<b>${expectedText}</b>
<script>
const vscode = acquireVsCodeApi();
document.execCommand('selectAll');
vscode.postMessage({ type: 'ready' });
</script>`);
await ready;
await vscode.commands.executeCommand('editor.action.clipboardCopyAction');
await delay(200); // Make sure copy has time to reach webview
assert.strictEqual(await vscode.env.clipboard.readText(), expectedText);
});
}
});
function createHtmlDocumentWithBody(body: string): string {
return /*html*/`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
${body}
</body>
</html>`;
}
const statefulWebviewHtml = createHtmlDocumentWithBody(/*html*/ `
<script>
const vscode = acquireVsCodeApi();
let value = 0;
window.addEventListener('message', (message) => {
switch (message.data.type) {
case 'get':
vscode.postMessage({ value });
break;
case 'add':
++value;;
vscode.setState({ value });
vscode.postMessage({ value });
break;
}
});
vscode.postMessage({ type: 'ready' });
</script>`);
function getMesssage<R = any>(webview: vscode.WebviewPanel): Promise<R> {
return new Promise<R>(resolve => {
const sub = webview.webview.onDidReceiveMessage(message => {
sub.dispose();
resolve(message);
});
});
}
function sendRecieveMessage<T = {}, R = any>(webview: vscode.WebviewPanel, message: T): Promise<R> {
const p = getMesssage<R>(webview);
webview.webview.postMessage(message);
return p;
}

View File

@ -0,0 +1,631 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, QuickPickItem, TextEditor } from 'vscode';
import { join } from 'path';
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';
suite('vscode API - window', () => {
teardown(closeAllEditors);
test('editor, active text editor', async () => {
const doc = await workspace.openTextDocument(join(workspace.rootPath || '', './far.js'));
await window.showTextDocument(doc);
const active = window.activeTextEditor;
assert.ok(active);
assert.ok(pathEquals(active!.document.uri.fsPath, doc.uri.fsPath));
});
test('editor, opened via resource', () => {
const uri = Uri.file(join(workspace.rootPath || '', './far.js'));
return window.showTextDocument(uri).then((_editor) => {
const active = window.activeTextEditor;
assert.ok(active);
assert.ok(pathEquals(active!.document.uri.fsPath, uri.fsPath));
});
});
// test('editor, UN-active text editor', () => {
// assert.equal(window.visibleTextEditors.length, 0);
// assert.ok(window.activeTextEditor === undefined);
// });
test('editor, assign and check view columns', async () => {
const doc = await workspace.openTextDocument(join(workspace.rootPath || '', './far.js'));
let p1 = window.showTextDocument(doc, ViewColumn.One).then(editor => {
assert.equal(editor.viewColumn, ViewColumn.One);
});
let p2 = window.showTextDocument(doc, ViewColumn.Two).then(editor_1 => {
assert.equal(editor_1.viewColumn, ViewColumn.Two);
});
let p3 = window.showTextDocument(doc, ViewColumn.Three).then(editor_2 => {
assert.equal(editor_2.viewColumn, ViewColumn.Three);
});
return Promise.all([p1, p2, p3]);
});
test('editor, onDidChangeVisibleTextEditors', async () => {
let eventCounter = 0;
let reg = window.onDidChangeVisibleTextEditors(_editor => {
eventCounter += 1;
});
const doc = await workspace.openTextDocument(join(workspace.rootPath || '', './far.js'));
await window.showTextDocument(doc, ViewColumn.One);
assert.equal(eventCounter, 1);
await window.showTextDocument(doc, ViewColumn.Two);
assert.equal(eventCounter, 2);
await window.showTextDocument(doc, ViewColumn.Three);
assert.equal(eventCounter, 3);
reg.dispose();
});
test('editor, onDidChangeTextEditorViewColumn (close editor)', () => {
let actualEvent: TextEditorViewColumnChangeEvent;
let registration1 = workspace.registerTextDocumentContentProvider('bikes', {
provideTextDocumentContent() {
return 'mountainbiking,roadcycling';
}
});
return Promise.all([
workspace.openTextDocument(Uri.parse('bikes://testing/one')).then(doc => window.showTextDocument(doc, ViewColumn.One)),
workspace.openTextDocument(Uri.parse('bikes://testing/two')).then(doc => window.showTextDocument(doc, ViewColumn.Two))
]).then(async editors => {
let [one, two] = editors;
await new Promise<void>(resolve => {
let registration2 = window.onDidChangeTextEditorViewColumn(event => {
actualEvent = event;
registration2.dispose();
resolve();
});
// close editor 1, wait a little for the event to bubble
one.hide();
});
assert.ok(actualEvent);
assert.ok(actualEvent.textEditor === two);
assert.ok(actualEvent.viewColumn === two.viewColumn);
registration1.dispose();
});
});
test('editor, onDidChangeTextEditorViewColumn (move editor group)', () => {
let actualEvents: TextEditorViewColumnChangeEvent[] = [];
let registration1 = workspace.registerTextDocumentContentProvider('bikes', {
provideTextDocumentContent() {
return 'mountainbiking,roadcycling';
}
});
return Promise.all([
workspace.openTextDocument(Uri.parse('bikes://testing/one')).then(doc => window.showTextDocument(doc, ViewColumn.One)),
workspace.openTextDocument(Uri.parse('bikes://testing/two')).then(doc => window.showTextDocument(doc, ViewColumn.Two))
]).then(editors => {
let [, two] = editors;
two.show();
return new Promise<void>(resolve => {
let registration2 = window.onDidChangeTextEditorViewColumn(event => {
actualEvents.push(event);
if (actualEvents.length === 2) {
registration2.dispose();
resolve();
}
});
// move active editor group left
return commands.executeCommand('workbench.action.moveActiveEditorGroupLeft');
}).then(() => {
assert.equal(actualEvents.length, 2);
for (const event of actualEvents) {
assert.equal(event.viewColumn, event.textEditor.viewColumn);
}
registration1.dispose();
});
});
});
test('active editor not always correct... #49125', async function () {
if (process.env['BUILD_SOURCEVERSION']) {
this.skip();
return;
}
function assertActiveEditor(editor: TextEditor) {
if (window.activeTextEditor === editor) {
assert.ok(true);
return;
}
function printEditor(editor: TextEditor): string {
return `doc: ${editor.document.uri.toString()}, column: ${editor.viewColumn}, active: ${editor === window.activeTextEditor}`;
}
const visible = window.visibleTextEditors.map(editor => printEditor(editor));
assert.ok(false, `ACTIVE editor should be ${printEditor(editor)}, BUT HAVING ${visible.join(', ')}`);
}
const randomFile1 = await createRandomFile();
const randomFile2 = await createRandomFile();
const [docA, docB] = await Promise.all([
workspace.openTextDocument(randomFile1),
workspace.openTextDocument(randomFile2)
]);
for (let c = 0; c < 4; c++) {
let editorA = await window.showTextDocument(docA, ViewColumn.One);
assertActiveEditor(editorA);
let editorB = await window.showTextDocument(docB, ViewColumn.Two);
assertActiveEditor(editorB);
}
});
test('default column when opening a file', async () => {
const [docA, docB, docC] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile())
]);
await window.showTextDocument(docA, ViewColumn.One);
await window.showTextDocument(docB, ViewColumn.Two);
assert.ok(window.activeTextEditor);
assert.ok(window.activeTextEditor!.document === docB);
assert.equal(window.activeTextEditor!.viewColumn, ViewColumn.Two);
const editor = await window.showTextDocument(docC);
assert.ok(
window.activeTextEditor === editor,
`wanted fileName:${editor.document.fileName}/viewColumn:${editor.viewColumn} but got fileName:${window.activeTextEditor!.document.fileName}/viewColumn:${window.activeTextEditor!.viewColumn}. a:${docA.fileName}, b:${docB.fileName}, c:${docC.fileName}`
);
assert.ok(window.activeTextEditor!.document === docC);
assert.equal(window.activeTextEditor!.viewColumn, ViewColumn.Two);
});
test('showTextDocument ViewColumn.BESIDE', async () => {
const [docA, docB, docC] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile())
]);
await window.showTextDocument(docA, ViewColumn.One);
await window.showTextDocument(docB, ViewColumn.Beside);
assert.ok(window.activeTextEditor);
assert.ok(window.activeTextEditor!.document === docB);
assert.equal(window.activeTextEditor!.viewColumn, ViewColumn.Two);
await window.showTextDocument(docC, ViewColumn.Beside);
assert.ok(window.activeTextEditor!.document === docC);
assert.equal(window.activeTextEditor!.viewColumn, ViewColumn.Three);
});
test('showTextDocument ViewColumn is always defined (even when opening > ViewColumn.Nine)', async () => {
const [doc1, doc2, doc3, doc4, doc5, doc6, doc7, doc8, doc9, doc10] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile())
]);
await window.showTextDocument(doc1, ViewColumn.One);
await window.showTextDocument(doc2, ViewColumn.Two);
await window.showTextDocument(doc3, ViewColumn.Three);
await window.showTextDocument(doc4, ViewColumn.Four);
await window.showTextDocument(doc5, ViewColumn.Five);
await window.showTextDocument(doc6, ViewColumn.Six);
await window.showTextDocument(doc7, ViewColumn.Seven);
await window.showTextDocument(doc8, ViewColumn.Eight);
await window.showTextDocument(doc9, ViewColumn.Nine);
await window.showTextDocument(doc10, ViewColumn.Beside);
assert.ok(window.activeTextEditor);
assert.ok(window.activeTextEditor!.document === doc10);
assert.equal(window.activeTextEditor!.viewColumn, 10);
});
test('issue #27408 - showTextDocument & vscode.diff always default to ViewColumn.One', async () => {
const [docA, docB, docC] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile())
]);
await window.showTextDocument(docA, ViewColumn.One);
await window.showTextDocument(docB, ViewColumn.Two);
assert.ok(window.activeTextEditor);
assert.ok(window.activeTextEditor!.document === docB);
assert.equal(window.activeTextEditor!.viewColumn, ViewColumn.Two);
await window.showTextDocument(docC, ViewColumn.Active);
assert.ok(window.activeTextEditor!.document === docC);
assert.equal(window.activeTextEditor!.viewColumn, ViewColumn.Two);
});
test('issue #5362 - Incorrect TextEditor passed by onDidChangeTextEditorSelection', (done) => {
const file10Path = join(workspace.rootPath || '', './10linefile.ts');
const file30Path = join(workspace.rootPath || '', './30linefile.ts');
let finished = false;
let failOncePlease = (err: Error) => {
if (finished) {
return;
}
finished = true;
done(err);
};
let passOncePlease = () => {
if (finished) {
return;
}
finished = true;
done(null);
};
let subscription = window.onDidChangeTextEditorSelection((e) => {
let lineCount = e.textEditor.document.lineCount;
let pos1 = e.textEditor.selections[0].active.line;
let pos2 = e.selections[0].active.line;
if (pos1 !== pos2) {
failOncePlease(new Error('received invalid selection changed event!'));
return;
}
if (pos1 >= lineCount) {
failOncePlease(new Error(`Cursor position (${pos1}) is not valid in the document ${e.textEditor.document.fileName} that has ${lineCount} lines.`));
return;
}
});
// Open 10 line file, show it in slot 1, set cursor to line 10
// Open 30 line file, show it in slot 1, set cursor to line 30
// Open 10 line file, show it in slot 1
// Open 30 line file, show it in slot 1
workspace.openTextDocument(file10Path).then((doc) => {
return window.showTextDocument(doc, ViewColumn.One);
}).then((editor10line) => {
editor10line.selection = new Selection(new Position(9, 0), new Position(9, 0));
}).then(() => {
return workspace.openTextDocument(file30Path);
}).then((doc) => {
return window.showTextDocument(doc, ViewColumn.One);
}).then((editor30line) => {
editor30line.selection = new Selection(new Position(29, 0), new Position(29, 0));
}).then(() => {
return workspace.openTextDocument(file10Path);
}).then((doc) => {
return window.showTextDocument(doc, ViewColumn.One);
}).then(() => {
return workspace.openTextDocument(file30Path);
}).then((doc) => {
return window.showTextDocument(doc, ViewColumn.One);
}).then(() => {
subscription.dispose();
}).then(passOncePlease, failOncePlease);
});
test('#7013 - input without options', function () {
const source = new CancellationTokenSource();
let p = window.showInputBox(undefined, source.token);
assert.ok(typeof p === 'object');
source.dispose();
});
test('showInputBox - undefined on cancel', async function () {
const source = new CancellationTokenSource();
const p = window.showInputBox(undefined, source.token);
source.cancel();
const value = await p;
assert.equal(value, undefined);
});
test('showInputBox - cancel early', async function () {
const source = new CancellationTokenSource();
source.cancel();
const p = window.showInputBox(undefined, source.token);
const value = await p;
assert.equal(value, undefined);
});
test('showInputBox - \'\' on Enter', function () {
const p = window.showInputBox();
return Promise.all<any>([
commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'),
p.then(value => assert.equal(value, ''))
]);
});
test('showInputBox - default value on Enter', function () {
const p = window.showInputBox({ value: 'farboo' });
return Promise.all<any>([
p.then(value => assert.equal(value, 'farboo')),
commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'),
]);
});
test('showInputBox - `undefined` on Esc', function () {
const p = window.showInputBox();
return Promise.all<any>([
commands.executeCommand('workbench.action.closeQuickOpen'),
p.then(value => assert.equal(value, undefined))
]);
});
test('showInputBox - `undefined` on Esc (despite default)', function () {
const p = window.showInputBox({ value: 'farboo' });
return Promise.all<any>([
commands.executeCommand('workbench.action.closeQuickOpen'),
p.then(value => assert.equal(value, undefined))
]);
});
test('showInputBox - value not empty on second try', async function () {
const one = window.showInputBox({ value: 'notempty' });
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
assert.equal(await one, 'notempty');
const two = window.showInputBox({ value: 'notempty' });
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
assert.equal(await two, 'notempty');
});
test('showQuickPick, accept first', async function () {
const tracker = createQuickPickTracker<string>();
const first = tracker.nextItem();
const pick = window.showQuickPick(['eins', 'zwei', 'drei'], {
onDidSelectItem: tracker.onDidSelectItem
});
assert.equal(await first, 'eins');
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
assert.equal(await pick, 'eins');
return tracker.done();
});
test('showQuickPick, accept second', async function () {
const tracker = createQuickPickTracker<string>();
const first = tracker.nextItem();
const pick = window.showQuickPick(['eins', 'zwei', 'drei'], {
onDidSelectItem: tracker.onDidSelectItem
});
assert.equal(await first, 'eins');
const second = tracker.nextItem();
await commands.executeCommand('workbench.action.quickOpenSelectNext');
assert.equal(await second, 'zwei');
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
assert.equal(await pick, 'zwei');
return tracker.done();
});
test('showQuickPick, select first two', async function () {
const label = 'showQuickPick, select first two';
let i = 0;
const resolves: ((value: string) => void)[] = [];
let done: () => void;
const unexpected = new Promise<void>((resolve, reject) => {
done = () => resolve();
resolves.push(reject);
});
const picks = window.showQuickPick(['eins', 'zwei', 'drei'], {
onDidSelectItem: item => resolves.pop()!(item as string),
canPickMany: true
});
const first = new Promise(resolve => resolves.push(resolve));
console.log(`${label}: ${++i}`);
await new Promise(resolve => setTimeout(resolve, 100)); // Allow UI to update.
console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.quickOpenSelectNext');
console.log(`${label}: ${++i}`);
assert.equal(await first, 'eins');
console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.quickPickManyToggle');
console.log(`${label}: ${++i}`);
const second = new Promise(resolve => resolves.push(resolve));
await commands.executeCommand('workbench.action.quickOpenSelectNext');
console.log(`${label}: ${++i}`);
assert.equal(await second, 'zwei');
console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.quickPickManyToggle');
console.log(`${label}: ${++i}`);
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
console.log(`${label}: ${++i}`);
assert.deepStrictEqual(await picks, ['eins', 'zwei']);
console.log(`${label}: ${++i}`);
done!();
return unexpected;
});
test('showQuickPick, keep selection (microsoft/vscode-azure-account#67)', async function () {
const picks = window.showQuickPick([
{ label: 'eins' },
{ label: 'zwei', picked: true },
{ label: 'drei', picked: true }
], {
canPickMany: true
});
await new Promise<void>(resolve => setTimeout(() => resolve(), 100));
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
if (await Promise.race([picks, new Promise<boolean>(resolve => setTimeout(() => resolve(false), 100))]) === false) {
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
if (await Promise.race([picks, new Promise<boolean>(resolve => setTimeout(() => resolve(false), 1000))]) === false) {
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
if (await Promise.race([picks, new Promise<boolean>(resolve => setTimeout(() => resolve(false), 1000))]) === false) {
assert.ok(false, 'Picks not resolved!');
}
}
}
assert.deepStrictEqual((await picks)!.map(pick => pick.label), ['zwei', 'drei']);
});
test('showQuickPick, undefined on cancel', function () {
const source = new CancellationTokenSource();
const p = window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token);
source.cancel();
return p.then(value => {
assert.equal(value, undefined);
});
});
test('showQuickPick, cancel early', function () {
const source = new CancellationTokenSource();
source.cancel();
const p = window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token);
return p.then(value => {
assert.equal(value, undefined);
});
});
test('showQuickPick, canceled by another picker', function () {
const source = new CancellationTokenSource();
const result = window.showQuickPick(['eins', 'zwei', 'drei'], { ignoreFocusOut: true }).then(result => {
source.cancel();
assert.equal(result, undefined);
});
window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token);
return result;
});
test('showQuickPick, canceled by input', function () {
const result = window.showQuickPick(['eins', 'zwei', 'drei'], { ignoreFocusOut: true }).then(result => {
assert.equal(result, undefined);
});
const source = new CancellationTokenSource();
window.showInputBox(undefined, source.token);
source.cancel();
return result;
});
test('showQuickPick, native promise - #11754', async function () {
const data = new Promise<string[]>(resolve => {
resolve(['a', 'b', 'c']);
});
const source = new CancellationTokenSource();
const result = window.showQuickPick(data, undefined, source.token);
source.cancel();
const value_1 = await result;
assert.equal(value_1, undefined);
});
test('showQuickPick, never resolve promise and cancel - #22453', function () {
const result = window.showQuickPick(new Promise<string[]>(_resolve => { }));
const a = result.then(value => {
assert.equal(value, undefined);
});
const b = commands.executeCommand('workbench.action.closeQuickOpen');
return Promise.all([a, b]);
});
test('showWorkspaceFolderPick', async function () {
const p = window.showWorkspaceFolderPick(undefined);
await new Promise(resolve => setTimeout(resolve, 10));
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
const r1 = await Promise.race([p, new Promise<boolean>(resolve => setTimeout(() => resolve(false), 100))]);
if (r1 !== false) {
return;
}
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
const r2 = await Promise.race([p, new Promise<boolean>(resolve => setTimeout(() => resolve(false), 1000))]);
if (r2 !== false) {
return;
}
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
const r3 = await Promise.race([p, new Promise<boolean>(resolve => setTimeout(() => resolve(false), 1000))]);
assert.ok(r3 !== false);
});
test('Default value for showInput Box not accepted when it fails validateInput, reversing #33691', async function () {
const result = window.showInputBox({
validateInput: (value: string) => {
if (!value || value.trim().length === 0) {
return 'Cannot set empty description';
}
return null;
}
});
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
await commands.executeCommand('workbench.action.closeQuickOpen');
assert.equal(await result, undefined);
});
function createQuickPickTracker<T extends string | QuickPickItem>() {
const resolves: ((value: T) => void)[] = [];
let done: () => void;
const unexpected = new Promise<void>((resolve, reject) => {
done = () => resolve();
resolves.push(reject);
});
return {
onDidSelectItem: (item: T) => resolves.pop()!(item),
nextItem: () => new Promise<T>(resolve => resolves.push(resolve)),
done: () => {
done!();
return unexpected;
},
};
}
test('editor, selection change kind', () => {
return workspace.openTextDocument(join(workspace.rootPath || '', './far.js')).then(doc => window.showTextDocument(doc)).then(editor => {
return new Promise<void>((resolve, _reject) => {
let subscription = window.onDidChangeTextEditorSelection(e => {
assert.ok(e.textEditor === editor);
assert.equal(e.kind, TextEditorSelectionChangeKind.Command);
subscription.dispose();
resolve();
});
editor.selection = new Selection(editor.selection.anchor, editor.selection.active.translate(2));
});
});
});
});

View File

@ -0,0 +1,261 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as vscode from 'vscode';
import { createRandomFile, withLogDisabled } from '../utils';
suite('vscode API - workspace events', () => {
const disposables: vscode.Disposable[] = [];
teardown(() => {
for (const dispo of disposables) {
dispo.dispose();
}
disposables.length = 0;
});
test('onWillCreate/onDidCreate', async function () {
const base = await createRandomFile();
const newUri = base.with({ path: base.path + '-foo' });
let onWillCreate: vscode.FileWillCreateEvent | undefined;
let onDidCreate: vscode.FileCreateEvent | undefined;
disposables.push(vscode.workspace.onWillCreateFiles(e => onWillCreate = e));
disposables.push(vscode.workspace.onDidCreateFiles(e => onDidCreate = e));
const edit = new vscode.WorkspaceEdit();
edit.createFile(newUri);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
assert.ok(onWillCreate);
assert.equal(onWillCreate?.files.length, 1);
assert.equal(onWillCreate?.files[0].toString(), newUri.toString());
assert.ok(onDidCreate);
assert.equal(onDidCreate?.files.length, 1);
assert.equal(onDidCreate?.files[0].toString(), newUri.toString());
});
test('onWillCreate/onDidCreate, make changes, edit another file', async function () {
const base = await createRandomFile();
const baseDoc = await vscode.workspace.openTextDocument(base);
const newUri = base.with({ path: base.path + '-foo' });
disposables.push(vscode.workspace.onWillCreateFiles(e => {
const ws = new vscode.WorkspaceEdit();
ws.insert(base, new vscode.Position(0, 0), 'HALLO_NEW');
e.waitUntil(Promise.resolve(ws));
}));
const edit = new vscode.WorkspaceEdit();
edit.createFile(newUri);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
assert.equal(baseDoc.getText(), 'HALLO_NEW');
});
test('onWillCreate/onDidCreate, make changes, edit new file fails', withLogDisabled(async function () {
const base = await createRandomFile();
const newUri = base.with({ path: base.path + '-foo' });
disposables.push(vscode.workspace.onWillCreateFiles(e => {
const ws = new vscode.WorkspaceEdit();
ws.insert(e.files[0], new vscode.Position(0, 0), 'nope');
e.waitUntil(Promise.resolve(ws));
}));
const edit = new vscode.WorkspaceEdit();
edit.createFile(newUri);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
assert.equal((await vscode.workspace.fs.readFile(newUri)).toString(), '');
assert.equal((await vscode.workspace.openTextDocument(newUri)).getText(), '');
}));
test('onWillDelete/onDidDelete', async function () {
const base = await createRandomFile();
let onWilldelete: vscode.FileWillDeleteEvent | undefined;
let onDiddelete: vscode.FileDeleteEvent | undefined;
disposables.push(vscode.workspace.onWillDeleteFiles(e => onWilldelete = e));
disposables.push(vscode.workspace.onDidDeleteFiles(e => onDiddelete = e));
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(base);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
assert.ok(onWilldelete);
assert.equal(onWilldelete?.files.length, 1);
assert.equal(onWilldelete?.files[0].toString(), base.toString());
assert.ok(onDiddelete);
assert.equal(onDiddelete?.files.length, 1);
assert.equal(onDiddelete?.files[0].toString(), base.toString());
});
test('onWillDelete/onDidDelete, make changes', async function () {
const base = await createRandomFile();
const newUri = base.with({ path: base.path + '-NEW' });
disposables.push(vscode.workspace.onWillDeleteFiles(e => {
const edit = new vscode.WorkspaceEdit();
edit.createFile(newUri);
edit.insert(newUri, new vscode.Position(0, 0), 'hahah');
e.waitUntil(Promise.resolve(edit));
}));
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(base);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
});
test('onWillDelete/onDidDelete, make changes, del another file', async function () {
const base = await createRandomFile();
const base2 = await createRandomFile();
disposables.push(vscode.workspace.onWillDeleteFiles(e => {
if (e.files[0].toString() === base.toString()) {
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(base2);
e.waitUntil(Promise.resolve(edit));
}
}));
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(base);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
});
test('onWillDelete/onDidDelete, make changes, double delete', async function () {
const base = await createRandomFile();
let cnt = 0;
disposables.push(vscode.workspace.onWillDeleteFiles(e => {
if (++cnt === 0) {
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(e.files[0]);
e.waitUntil(Promise.resolve(edit));
}
}));
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(base);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
});
test('onWillRename/onDidRename', async function () {
const oldUri = await createRandomFile();
const newUri = oldUri.with({ path: oldUri.path + '-NEW' });
let onWillRename: vscode.FileWillRenameEvent | undefined;
let onDidRename: vscode.FileRenameEvent | undefined;
disposables.push(vscode.workspace.onWillRenameFiles(e => onWillRename = e));
disposables.push(vscode.workspace.onDidRenameFiles(e => onDidRename = e));
const edit = new vscode.WorkspaceEdit();
edit.renameFile(oldUri, newUri);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
assert.ok(onWillRename);
assert.equal(onWillRename?.files.length, 1);
assert.equal(onWillRename?.files[0].oldUri.toString(), oldUri.toString());
assert.equal(onWillRename?.files[0].newUri.toString(), newUri.toString());
assert.ok(onDidRename);
assert.equal(onDidRename?.files.length, 1);
assert.equal(onDidRename?.files[0].oldUri.toString(), oldUri.toString());
assert.equal(onDidRename?.files[0].newUri.toString(), newUri.toString());
});
test('onWillRename - make changes (saved file)', function () {
return testOnWillRename(false);
});
test('onWillRename - make changes (dirty file)', function () {
return testOnWillRename(true);
});
async function testOnWillRename(withDirtyFile: boolean): Promise<void> {
const oldUri = await createRandomFile('BAR');
if (withDirtyFile) {
const edit = new vscode.WorkspaceEdit();
edit.insert(oldUri, new vscode.Position(0, 0), 'BAR');
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
const oldDocument = await vscode.workspace.openTextDocument(oldUri);
assert.ok(oldDocument.isDirty);
}
const newUri = oldUri.with({ path: oldUri.path + '-NEW' });
const anotherFile = await createRandomFile('BAR');
let onWillRename: vscode.FileWillRenameEvent | undefined;
disposables.push(vscode.workspace.onWillRenameFiles(e => {
onWillRename = e;
const edit = new vscode.WorkspaceEdit();
edit.insert(e.files[0].oldUri, new vscode.Position(0, 0), 'FOO');
edit.replace(anotherFile, new vscode.Range(0, 0, 0, 3), 'FARBOO');
e.waitUntil(Promise.resolve(edit));
}));
const edit = new vscode.WorkspaceEdit();
edit.renameFile(oldUri, newUri);
const success = await vscode.workspace.applyEdit(edit);
assert.ok(success);
assert.ok(onWillRename);
assert.equal(onWillRename?.files.length, 1);
assert.equal(onWillRename?.files[0].oldUri.toString(), oldUri.toString());
assert.equal(onWillRename?.files[0].newUri.toString(), newUri.toString());
const newDocument = await vscode.workspace.openTextDocument(newUri);
const anotherDocument = await vscode.workspace.openTextDocument(anotherFile);
assert.equal(newDocument.getText(), withDirtyFile ? 'FOOBARBAR' : 'FOOBAR');
assert.equal(anotherDocument.getText(), 'FARBOO');
assert.ok(newDocument.isDirty);
assert.ok(anotherDocument.isDirty);
}
});

View File

@ -0,0 +1,181 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as vscode from 'vscode';
import { posix } from 'path';
suite('vscode API - workspace-fs', () => {
let root: vscode.Uri;
suiteSetup(function () {
root = vscode.workspace.workspaceFolders![0]!.uri;
});
test('fs.stat', async function () {
const stat = await vscode.workspace.fs.stat(root);
assert.equal(stat.type, vscode.FileType.Directory);
assert.equal(typeof stat.size, 'number');
assert.equal(typeof stat.mtime, 'number');
assert.equal(typeof stat.ctime, 'number');
assert.ok(stat.mtime > 0);
assert.ok(stat.ctime > 0);
const entries = await vscode.workspace.fs.readDirectory(root);
assert.ok(entries.length > 0);
// find far.js
const tuple = entries.find(tuple => tuple[0] === 'far.js')!;
assert.ok(tuple);
assert.equal(tuple[0], 'far.js');
assert.equal(tuple[1], vscode.FileType.File);
});
test('fs.stat - bad scheme', async function () {
try {
await vscode.workspace.fs.stat(vscode.Uri.parse('foo:/bar/baz/test.txt'));
assert.ok(false);
} catch {
assert.ok(true);
}
});
test('fs.stat - missing file', async function () {
try {
await vscode.workspace.fs.stat(root.with({ path: root.path + '.bad' }));
assert.ok(false);
} catch (e) {
assert.ok(true);
}
});
test('fs.write/stat/delete', async function () {
const uri = root.with({ path: posix.join(root.path, 'new.file') });
await vscode.workspace.fs.writeFile(uri, Buffer.from('HELLO'));
const stat = await vscode.workspace.fs.stat(uri);
assert.equal(stat.type, vscode.FileType.File);
await vscode.workspace.fs.delete(uri);
try {
await vscode.workspace.fs.stat(uri);
assert.ok(false);
} catch {
assert.ok(true);
}
});
test('fs.delete folder', async function () {
const folder = root.with({ path: posix.join(root.path, 'folder') });
const file = root.with({ path: posix.join(root.path, 'folder/file') });
await vscode.workspace.fs.createDirectory(folder);
await vscode.workspace.fs.writeFile(file, Buffer.from('FOO'));
await vscode.workspace.fs.stat(folder);
await vscode.workspace.fs.stat(file);
// ensure non empty folder cannot be deleted
try {
await vscode.workspace.fs.delete(folder, { recursive: false, useTrash: false });
assert.ok(false);
} catch {
await vscode.workspace.fs.stat(folder);
await vscode.workspace.fs.stat(file);
}
// ensure non empty folder cannot be deleted is DEFAULT
try {
await vscode.workspace.fs.delete(folder); // recursive: false as default
assert.ok(false);
} catch {
await vscode.workspace.fs.stat(folder);
await vscode.workspace.fs.stat(file);
}
// delete non empty folder with recursive-flag
await vscode.workspace.fs.delete(folder, { recursive: true, useTrash: false });
// esnure folder/file are gone
try {
await vscode.workspace.fs.stat(folder);
assert.ok(false);
} catch {
assert.ok(true);
}
try {
await vscode.workspace.fs.stat(file);
assert.ok(false);
} catch {
assert.ok(true);
}
});
test('throws FileSystemError', async function () {
try {
await vscode.workspace.fs.stat(vscode.Uri.file(`/c468bf16-acfd-4591-825e-2bcebba508a3/71b1f274-91cb-4c19-af00-8495eaab4b73/4b60cb48-a6f2-40ea-9085-0936f4a8f59a.tx6`));
assert.ok(false);
} catch (e) {
assert.ok(e instanceof vscode.FileSystemError);
assert.equal(e.name, vscode.FileSystemError.FileNotFound().name);
}
});
test('throws FileSystemError', async function () {
try {
await vscode.workspace.fs.stat(vscode.Uri.parse('foo:/bar'));
assert.ok(false);
} catch (e) {
assert.ok(e instanceof vscode.FileSystemError);
assert.equal(e.name, vscode.FileSystemError.Unavailable().name);
}
});
test('vscode.workspace.fs.remove() (and copy()) succeed unexpectedly. #84177', async function () {
const entries = await vscode.workspace.fs.readDirectory(root);
assert.ok(entries.length > 0);
const someFolder = root.with({ path: posix.join(root.path, '6b1f9d664a92') });
try {
await vscode.workspace.fs.delete(someFolder, { recursive: true });
assert.ok(false);
} catch (err) {
assert.ok(true);
}
});
test('vscode.workspace.fs.remove() (and copy()) succeed unexpectedly. #84177', async function () {
const entries = await vscode.workspace.fs.readDirectory(root);
assert.ok(entries.length > 0);
const folder = root.with({ path: posix.join(root.path, 'folder') });
const file = root.with({ path: posix.join(root.path, 'folder/file') });
await vscode.workspace.fs.createDirectory(folder);
await vscode.workspace.fs.writeFile(file, Buffer.from('FOO'));
const someFolder = root.with({ path: posix.join(root.path, '6b1f9d664a92/a564c52da70a') });
try {
await vscode.workspace.fs.copy(folder, someFolder, { overwrite: true });
assert.ok(true);
} catch (err) {
assert.ok(false, err);
} finally {
await vscode.workspace.fs.delete(folder, { recursive: true, useTrash: false });
await vscode.workspace.fs.delete(someFolder, { recursive: true, useTrash: false });
}
});
});

View File

@ -0,0 +1,265 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event } from 'vscode';
// Disable tasks tests:
// - Web https://github.com/microsoft/vscode/issues/90528
((env.uiKind === UIKind.Web) ? suite.skip : suite)('vscode API - tasks', () => {
suite('Tasks', () => {
let disposables: Disposable[] = [];
teardown(() => {
disposables.forEach(d => d.dispose());
disposables.length = 0;
});
test('CustomExecution task should start and shutdown successfully', (done) => {
interface CustomTestingTaskDefinition extends TaskDefinition {
/**
* One of the task properties. This can be used to customize the task in the tasks.json
*/
customProp1: string;
}
const taskType: string = 'customTesting';
const taskName = 'First custom task';
let isPseudoterminalClosed = false;
let terminal: Terminal | undefined;
// There's a strict order that should be observed here:
// 1. The terminal opens
// 2. The terminal is written to.
// 3. The terminal is closed.
enum TestOrder {
Start,
TerminalOpened,
TerminalWritten,
TerminalClosed
}
let testOrder = TestOrder.Start;
disposables.push(window.onDidOpenTerminal(term => {
try {
assert.equal(testOrder, TestOrder.Start);
} catch (e) {
done(e);
}
testOrder = TestOrder.TerminalOpened;
terminal = term;
}));
disposables.push(window.onDidWriteTerminalData(e => {
try {
assert.equal(testOrder, TestOrder.TerminalOpened);
testOrder = TestOrder.TerminalWritten;
assert.notEqual(terminal, undefined);
assert.equal(e.data, 'testing\r\n');
} catch (e) {
done(e);
}
if (terminal) {
terminal.dispose();
}
}));
disposables.push(window.onDidCloseTerminal(() => {
try {
assert.equal(testOrder, TestOrder.TerminalWritten);
testOrder = TestOrder.TerminalClosed;
// Pseudoterminal.close should have fired by now, additionally we want
// to make sure all events are flushed before continuing with more tests
assert.ok(isPseudoterminalClosed);
} catch (e) {
done(e);
return;
}
done();
}));
disposables.push(tasks.registerTaskProvider(taskType, {
provideTasks: () => {
const result: Task[] = [];
const kind: CustomTestingTaskDefinition = {
type: taskType,
customProp1: 'testing task one'
};
const writeEmitter = new EventEmitter<string>();
const execution = new CustomExecution((): Thenable<Pseudoterminal> => {
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
open: () => writeEmitter.fire('testing\r\n'),
close: () => isPseudoterminalClosed = true
};
return Promise.resolve(pty);
});
const task = new Task(kind, TaskScope.Workspace, taskName, taskType, execution);
result.push(task);
return result;
},
resolveTask(_task: Task): Task | undefined {
try {
assert.fail('resolveTask should not trigger during the test');
} catch (e) {
done(e);
}
return undefined;
}
}));
commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`);
});
test('sync CustomExecution task should flush all data on close', (done) => {
interface CustomTestingTaskDefinition extends TaskDefinition {
/**
* One of the task properties. This can be used to customize the task in the tasks.json
*/
customProp1: string;
}
const taskType: string = 'customTesting';
const taskName = 'First custom task';
disposables.push(window.onDidOpenTerminal(term => {
disposables.push(window.onDidWriteTerminalData(e => {
try {
assert.equal(e.data, 'exiting');
} catch (e) {
done(e);
}
disposables.push(window.onDidCloseTerminal(() => done()));
term.dispose();
}));
}));
disposables.push(tasks.registerTaskProvider(taskType, {
provideTasks: () => {
const result: Task[] = [];
const kind: CustomTestingTaskDefinition = {
type: taskType,
customProp1: 'testing task one'
};
const writeEmitter = new EventEmitter<string>();
const closeEmitter = new EventEmitter<void>();
const execution = new CustomExecution((): Thenable<Pseudoterminal> => {
const pty: Pseudoterminal = {
onDidWrite: writeEmitter.event,
onDidClose: closeEmitter.event,
open: () => {
writeEmitter.fire('exiting');
closeEmitter.fire();
},
close: () => { }
};
return Promise.resolve(pty);
});
const task = new Task(kind, TaskScope.Workspace, taskName, taskType, execution);
result.push(task);
return result;
},
resolveTask(_task: Task): Task | undefined {
try {
assert.fail('resolveTask should not trigger during the test');
} catch (e) {
done(e);
}
return undefined;
}
}));
commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`);
});
test('Execution from onDidEndTaskProcess and onDidStartTaskProcess are equal to original', () => {
return new Promise<void>(async (resolve) => {
const task = new Task({ type: 'testTask' }, TaskScope.Workspace, 'echo', 'testTask', new ShellExecution('echo', ['hello test']));
let taskExecution: TaskExecution | undefined;
const executeDoneEvent: EventEmitter<void> = new EventEmitter();
const taskExecutionShouldBeSet: Promise<void> = new Promise(resolve => {
const disposable = executeDoneEvent.event(() => {
resolve();
disposable.dispose();
});
});
let count = 2;
const progressMade: EventEmitter<void> = new EventEmitter();
let startSucceeded = false;
let endSucceeded = false;
disposables.push(progressMade.event(() => {
count--;
if ((count === 0) && startSucceeded && endSucceeded) {
resolve();
}
}));
disposables.push(tasks.onDidStartTaskProcess(async (e) => {
await taskExecutionShouldBeSet;
if (e.execution === taskExecution) {
startSucceeded = true;
progressMade.fire();
}
}));
disposables.push(tasks.onDidEndTaskProcess(async (e) => {
await taskExecutionShouldBeSet;
if (e.execution === taskExecution) {
endSucceeded = true;
progressMade.fire();
}
}));
taskExecution = await tasks.executeTask(task);
executeDoneEvent.fire();
});
});
// https://github.com/microsoft/vscode/issues/100577
test('A CustomExecution task can be fetched and executed', () => {
return new Promise<void>(async (resolve, reject) => {
class CustomTerminal implements Pseudoterminal {
private readonly writeEmitter = new EventEmitter<string>();
public readonly onDidWrite: Event<string> = this.writeEmitter.event;
public async close(): Promise<void> { }
private closeEmitter = new EventEmitter<void>();
onDidClose: Event<void> = this.closeEmitter.event;
public open(): void {
this.closeEmitter.fire();
resolve();
}
}
function buildTask(): Task {
const task = new Task(
{
type: 'customTesting',
},
TaskScope.Workspace,
'Test Task',
'customTesting',
new CustomExecution(
async (): Promise<Pseudoterminal> => {
return new CustomTerminal();
}
)
);
return task;
}
disposables.push(tasks.registerTaskProvider('customTesting', {
provideTasks: () => {
return [buildTask()];
},
resolveTask(_task: Task): undefined {
return undefined;
}
}));
const task = await tasks.fetchTasks({ type: 'customTesting' });
if (task && task.length > 0) {
await tasks.executeTask(task[0]);
} else {
reject('fetched task can\'t be undefined');
}
});
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="../../../../src/vs/vscode.d.ts" />
/// <reference path="../../../../src/vs/vscode.proposed.d.ts" />
/// <reference types='@types/node'/>

View File

@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { TestFS } from './memfs';
import * as assert from 'assert';
export function rndName() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
}
export const testFs = new TestFS('fake-fs', true);
vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs, { isCaseSensitive: testFs.isCaseSensitive });
export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, ext = ''): Promise<vscode.Uri> {
let fakeFile: vscode.Uri;
if (dir) {
assert.equal(dir.scheme, testFs.scheme);
fakeFile = dir.with({ path: dir.path + '/' + rndName() + ext });
} else {
fakeFile = vscode.Uri.parse(`${testFs.scheme}:/${rndName() + ext}`);
}
testFs.writeFile(fakeFile, Buffer.from(contents), { create: true, overwrite: true });
return fakeFile;
}
export async function deleteFile(file: vscode.Uri): Promise<boolean> {
try {
testFs.delete(file);
return true;
} catch {
return false;
}
}
export function pathEquals(path1: string, path2: string): boolean {
if (process.platform !== 'linux') {
path1 = path1.toLowerCase();
path2 = path2.toLowerCase();
}
return path1 === path2;
}
export function closeAllEditors(): Thenable<any> {
return vscode.commands.executeCommand('workbench.action.closeAllEditors');
}
export async function revertAllDirty(): Promise<void> {
return vscode.commands.executeCommand('_workbench.revertAllDirty');
}
export function disposeAll(disposables: vscode.Disposable[]) {
vscode.Disposable.from(...disposables).dispose();
}
export function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export function withLogDisabled(runnable: () => Promise<any>): () => Promise<void> {
return async (): Promise<void> => {
const logLevel = await vscode.commands.executeCommand('_extensionTests.getLogLevel');
await vscode.commands.executeCommand('_extensionTests.setLogLevel', 6 /* critical */);
try {
await runnable();
} finally {
await vscode.commands.executeCommand('_extensionTests.setLogLevel', logLevel);
}
};
}

View File

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
const testRunner = require('vscode/lib/testrunner');
const options: any = {
ui: 'tdd',
useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'),
timeout: 60000
};
// These integration tests is being run in multiple environments (electron, web, remote)
// so we need to set the suite name based on the environment as the suite name is used
// for the test results file name
let suite = '';
if (process.env.VSCODE_BROWSER) {
suite = `${process.env.VSCODE_BROWSER} Browser Integration Workspace Tests`;
} else if (process.env.REMOTE_VSCODE) {
suite = 'Remote Integration Workspace Tests';
} else {
suite = 'Integration Workspace Tests';
}
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
options.reporter = 'mocha-multi-reporters';
options.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
testRunner.configure(options);
export = testRunner;

View File

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as vscode from 'vscode';
import { closeAllEditors, pathEquals } from '../utils';
import { join } from 'path';
suite('vscode API - workspace', () => {
teardown(closeAllEditors);
test('rootPath', () => {
assert.ok(pathEquals(vscode.workspace.rootPath!, join(__dirname, '../../testWorkspace')));
});
test('workspaceFile', () => {
assert.ok(pathEquals(vscode.workspace.workspaceFile!.fsPath, join(__dirname, '../../testworkspace.code-workspace')));
});
test('workspaceFolders', () => {
assert.equal(vscode.workspace.workspaceFolders!.length, 2);
assert.ok(pathEquals(vscode.workspace.workspaceFolders![0].uri.fsPath, join(__dirname, '../../testWorkspace')));
assert.ok(pathEquals(vscode.workspace.workspaceFolders![1].uri.fsPath, join(__dirname, '../../testWorkspace2')));
assert.ok(pathEquals(vscode.workspace.workspaceFolders![1].name, 'Test Workspace 2'));
});
test('getWorkspaceFolder', () => {
const folder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(join(__dirname, '../../testWorkspace2/far.js')));
assert.ok(!!folder);
if (folder) {
assert.ok(pathEquals(folder.uri.fsPath, join(__dirname, '../../testWorkspace2')));
}
});
});