Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
11
lib/vscode/extensions/vscode-api-tests/src/extension.ts
Normal file
11
lib/vscode/extensions/vscode-api-tests/src/extension.ts
Normal 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;
|
||||
}
|
242
lib/vscode/extensions/vscode-api-tests/src/memfs.ts
Normal file
242
lib/vscode/extensions/vscode-api-tests/src/memfs.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -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]);
|
||||
});
|
||||
});
|
@ -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');
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
@ -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());
|
||||
}
|
||||
});
|
@ -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');
|
||||
}
|
||||
});
|
||||
});
|
@ -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;
|
@ -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"');
|
||||
});
|
||||
|
||||
});
|
@ -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));
|
||||
}
|
@ -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 }]
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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
|
||||
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
@ -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));
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
});
|
@ -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 });
|
||||
}
|
||||
});
|
||||
});
|
@ -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
8
lib/vscode/extensions/vscode-api-tests/src/typings/ref.d.ts
vendored
Normal file
8
lib/vscode/extensions/vscode-api-tests/src/typings/ref.d.ts
vendored
Normal 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'/>
|
74
lib/vscode/extensions/vscode-api-tests/src/utils.ts
Normal file
74
lib/vscode/extensions/vscode-api-tests/src/utils.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
@ -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;
|
@ -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')));
|
||||
}
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user