mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
Add terminal profile smoke tests (#137079)
This commit is contained in:
parent
bdc6162b1d
commit
00fa317c43
|
@ -271,6 +271,13 @@ export class ViewsService extends Disposable implements IViewsService {
|
|||
} else if (location === ViewContainerLocation.Panel) {
|
||||
this.paneCompositeService.hideActivePaneComposite(location);
|
||||
}
|
||||
|
||||
// The blur event doesn't fire on WebKit when the focused element is hidden,
|
||||
// so the context key needs to be forced here too otherwise a view may still
|
||||
// think it's showing, breaking toggle commands.
|
||||
if (this.focusedViewContextKey.get() === id) {
|
||||
this.focusedViewContextKey.reset();
|
||||
}
|
||||
} else {
|
||||
view.setExpanded(false);
|
||||
}
|
||||
|
|
|
@ -46,6 +46,9 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr
|
|||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isAbsolute } from 'vs/base/common/path';
|
||||
import { ITerminalQuickPickItem } from 'vs/workbench/contrib/terminal/browser/terminalProfileQuickpick';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { getIconId, getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
|
||||
|
||||
// allow-any-unicode-next-line
|
||||
export const switchTerminalActionViewItemSeparator = '─────────';
|
||||
|
@ -1582,6 +1585,52 @@ export function registerTerminalActions() {
|
|||
}
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TerminalCommandId.Join,
|
||||
title: { value: localize('workbench.action.terminal.join', "Join Terminals"), original: 'Join Terminals' },
|
||||
category,
|
||||
f1: true,
|
||||
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated))
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const themeService = accessor.get(IThemeService);
|
||||
const groupService = accessor.get(ITerminalGroupService);
|
||||
const picks: ITerminalQuickPickItem[] = [];
|
||||
if (!groupService.activeInstance || groupService.instances.length === 1) {
|
||||
return;
|
||||
}
|
||||
const otherInstances = groupService.instances.filter(i => i.instanceId !== groupService.activeInstance?.instanceId);
|
||||
for (const terminal of otherInstances) {
|
||||
const group = groupService.getGroupForInstance(terminal);
|
||||
if (group?.terminalInstances.length === 1) {
|
||||
const iconId = getIconId(terminal);
|
||||
const label = `$(${iconId}): ${terminal.title}`;
|
||||
const iconClasses: string[] = [];
|
||||
const colorClass = getColorClass(terminal);
|
||||
if (colorClass) {
|
||||
iconClasses.push(colorClass);
|
||||
}
|
||||
const uriClasses = getUriClasses(terminal, themeService.getColorTheme().type);
|
||||
if (uriClasses) {
|
||||
iconClasses.push(...uriClasses);
|
||||
}
|
||||
picks.push({
|
||||
terminal,
|
||||
label,
|
||||
iconClasses
|
||||
});
|
||||
}
|
||||
}
|
||||
const result = await accessor.get(IQuickInputService).pick(picks, {});
|
||||
if (result) {
|
||||
groupService.joinInstances([result.terminal, groupService.activeInstance!]);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -1736,6 +1785,24 @@ export function registerTerminalActions() {
|
|||
}
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TerminalCommandId.KillAll,
|
||||
title: { value: localize('workbench.action.terminal.killAll', "Kill All Terminals"), original: 'Kill All Terminals' },
|
||||
f1: true,
|
||||
category,
|
||||
precondition: ContextKeyExpr.or(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen),
|
||||
icon: Codicon.trash
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const terminalService = accessor.get(ITerminalService);
|
||||
for (const instance of terminalService.instances) {
|
||||
await terminalService.safeDisposeTerminal(instance);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
@ -1818,7 +1885,7 @@ export function registerTerminalActions() {
|
|||
constructor() {
|
||||
super({
|
||||
id: TerminalCommandId.SelectDefaultProfile,
|
||||
title: { value: localize('workbench.action.terminal.selectDefaultProfile', "Select Default Profile"), original: 'Select Default Profile' },
|
||||
title: { value: localize('workbench.action.terminal.selectDefaultShell', "Select Default Profile"), original: 'Select Default Profile' },
|
||||
f1: true,
|
||||
category,
|
||||
precondition: TerminalContextKeys.processSupported
|
||||
|
|
|
@ -20,7 +20,6 @@ import { getInstanceFromResource } from 'vs/workbench/contrib/terminal/browser/t
|
|||
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
|
||||
import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
||||
export class TerminalGroupService extends Disposable implements ITerminalGroupService, ITerminalFindHost {
|
||||
declare _serviceBrand: undefined;
|
||||
|
@ -60,7 +59,6 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe
|
|||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IViewsService private readonly _viewsService: IViewsService,
|
||||
@IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService,
|
||||
@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
) {
|
||||
|
@ -81,7 +79,7 @@ export class TerminalGroupService extends Disposable implements ITerminalGroupSe
|
|||
if (location === ViewContainerLocation.Panel) {
|
||||
const panel = this._viewDescriptorService.getViewContainerByViewId(TERMINAL_VIEW_ID);
|
||||
if (panel && this._viewDescriptorService.getViewContainerModel(panel).activeViewDescriptors.length === 1) {
|
||||
this._layoutService.setPartHidden(true, Parts.PANEL_PART);
|
||||
this._viewsService.closeView(TERMINAL_VIEW_ID);
|
||||
TerminalContextKeys.tabsMouse.bindTo(this._contextKeyService).set(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/brow
|
|||
import * as nls from 'vs/nls';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IQuickPickTerminalObject } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
|
||||
|
||||
|
||||
type DefaultProfileName = string;
|
||||
|
@ -238,3 +239,7 @@ export interface IProfileQuickPickItem extends IQuickPickItem {
|
|||
profileName: string;
|
||||
keyMods?: IKeyMods | undefined;
|
||||
}
|
||||
|
||||
export interface ITerminalQuickPickItem extends IPickerQuickAccessItem {
|
||||
terminal: ITerminalInstance
|
||||
}
|
||||
|
|
|
@ -76,9 +76,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
|
|||
ariaLabel: createWithProfileLabel,
|
||||
accept: () => this._commandService.executeCommand(TerminalCommandId.NewWithProfile)
|
||||
});
|
||||
|
||||
return terminalPicks;
|
||||
|
||||
}
|
||||
|
||||
private _createPick(terminal: ITerminalInstance, terminalIndex: number, filter: string, groupIndex?: number): IPickerQuickAccessItem | undefined {
|
||||
|
|
|
@ -889,7 +889,7 @@ export class TerminalService implements ITerminalService {
|
|||
// Launch the contributed profile
|
||||
if (contributedProfile) {
|
||||
const resolvedLocation = this.resolveLocation(options?.location);
|
||||
const splitActiveTerminal = typeof options?.location === 'object' && 'splitActiveTerminal' in options.location ? options.location.splitActiveTerminal : false;
|
||||
const splitActiveTerminal = typeof options?.location === 'object' && 'splitActiveTerminal' in options.location ? options.location.splitActiveTerminal : typeof options?.location === 'object' ? 'parentTerminal' in options.location : false;
|
||||
let location: TerminalLocation | { viewColumn: number, preserveState?: boolean } | { splitActiveTerminal: boolean } | undefined;
|
||||
if (splitActiveTerminal) {
|
||||
location = resolvedLocation === TerminalLocation.Editor ? { viewColumn: SIDE_GROUP } : { splitActiveTerminal: true };
|
||||
|
|
|
@ -430,6 +430,7 @@ export const enum TerminalCommandId {
|
|||
Kill = 'workbench.action.terminal.kill',
|
||||
KillEditor = 'workbench.action.terminal.killEditor',
|
||||
KillInstance = 'workbench.action.terminal.killInstance',
|
||||
KillAll = 'workbench.action.terminal.killAll',
|
||||
QuickKill = 'workbench.action.terminal.quickKill',
|
||||
ConfigureTerminalSettings = 'workbench.action.terminal.openSettings',
|
||||
CopySelection = 'workbench.action.terminal.copySelection',
|
||||
|
@ -450,6 +451,7 @@ export const enum TerminalCommandId {
|
|||
Unsplit = 'workbench.action.terminal.unsplit',
|
||||
UnsplitInstance = 'workbench.action.terminal.unsplitInstance',
|
||||
JoinInstance = 'workbench.action.terminal.joinInstance',
|
||||
Join = 'workbench.action.terminal.join',
|
||||
Relaunch = 'workbench.action.terminal.relaunch',
|
||||
FocusPreviousPane = 'workbench.action.terminal.focusPreviousPane',
|
||||
ShowTabs = 'workbench.action.terminal.showTabs',
|
||||
|
|
|
@ -225,14 +225,14 @@ async function poll<T>(
|
|||
if (trial > retryCount) {
|
||||
console.error('** Timeout!');
|
||||
console.error(lastError);
|
||||
|
||||
console.error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.`);
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await fn();
|
||||
|
||||
console.log('DEBUG: poll result', result);
|
||||
if (acceptFn(result)) {
|
||||
return result;
|
||||
} else {
|
||||
|
@ -250,7 +250,7 @@ async function poll<T>(
|
|||
export class Code {
|
||||
|
||||
private _activeWindowId: number | undefined = undefined;
|
||||
private driver: IDriver;
|
||||
driver: IDriver;
|
||||
|
||||
constructor(
|
||||
private client: IDisposable,
|
||||
|
|
|
@ -41,12 +41,13 @@ function buildDriver(browser: playwright.Browser, context: playwright.BrowserCon
|
|||
|
||||
class PlaywrightDriver implements IDriver {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
page: playwright.Page;
|
||||
constructor(
|
||||
private readonly _browser: playwright.Browser,
|
||||
private readonly _context: playwright.BrowserContext,
|
||||
private readonly _page: playwright.Page
|
||||
) {
|
||||
this.page = _page;
|
||||
}
|
||||
|
||||
async getWindowIds() { return [1]; }
|
||||
|
@ -70,6 +71,12 @@ class PlaywrightDriver implements IDriver {
|
|||
if (i > 0) {
|
||||
await timeout(100);
|
||||
}
|
||||
|
||||
if (keybinding.startsWith('Alt') || keybinding.startsWith('Control')) {
|
||||
await this._page.keyboard.press(keybinding);
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = chord.split('+');
|
||||
const keysDown: string[] = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
|
|
|
@ -79,14 +79,14 @@ export class QuickAccess {
|
|||
await this.editors.waitForEditorFocus(fileName);
|
||||
}
|
||||
|
||||
async runCommand(commandId: string): Promise<void> {
|
||||
async runCommand(commandId: string, keepOpen?: boolean): Promise<void> {
|
||||
await this.openQuickAccess(`>${commandId}`);
|
||||
|
||||
// wait for best choice to be focused
|
||||
await this.code.waitForTextContent(QuickInput.QUICK_INPUT_FOCUSED_ELEMENT);
|
||||
|
||||
// wait and click on best choice
|
||||
await this.quickInput.selectQuickInputElement(0);
|
||||
await this.quickInput.selectQuickInputElement(0, keepOpen);
|
||||
}
|
||||
|
||||
async openQuickOutline(): Promise<void> {
|
||||
|
|
|
@ -39,12 +39,14 @@ export class QuickInput {
|
|||
await this.code.waitForElement(QuickInput.QUICK_INPUT, r => !!r && r.attributes.style.indexOf('display: none;') !== -1);
|
||||
}
|
||||
|
||||
async selectQuickInputElement(index: number): Promise<void> {
|
||||
async selectQuickInputElement(index: number, keepOpen?: boolean): Promise<void> {
|
||||
await this.waitForQuickInputOpened();
|
||||
for (let from = 0; from < index; from++) {
|
||||
await this.code.dispatchKeybinding('down');
|
||||
}
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.waitForQuickInputClosed();
|
||||
if (!keepOpen) {
|
||||
await this.waitForQuickInputClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,30 +3,84 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IElement, QuickInput } from '.';
|
||||
import { Code } from './code';
|
||||
import { QuickAccess } from './quickaccess';
|
||||
|
||||
const TERMINAL_VIEW_SELECTOR = `#terminal`;
|
||||
const XTERM_SELECTOR = `${TERMINAL_VIEW_SELECTOR} .terminal-wrapper`;
|
||||
const XTERM_TEXTAREA = `${XTERM_SELECTOR} textarea.xterm-helper-textarea`;
|
||||
const CONTRIBUTED_PROFILE_NAME = `JavaScript Debug Terminal`;
|
||||
const TABS = '.tabs-list .terminal-tabs-entry';
|
||||
const XTERM_FOCUSED_SELECTOR = '.terminal.xterm.focus';
|
||||
|
||||
const enum TerminalCommandId {
|
||||
Rename = 'workbench.action.terminal.rename',
|
||||
ChangeColor = 'workbench.action.terminal.changeColor',
|
||||
ChangeIcon = 'workbench.action.terminal.changeIcon',
|
||||
Split = 'workbench.action.terminal.split',
|
||||
KillAll = 'workbench.action.terminal.killAll',
|
||||
Unsplit = 'workbench.action.terminal.unsplit',
|
||||
Join = 'workbench.action.terminal.join',
|
||||
Show = 'workbench.action.terminal.toggleTerminal',
|
||||
CreateNew = 'workbench.action.terminal.new',
|
||||
NewWithProfile = 'workbench.action.terminal.newWithProfile',
|
||||
SelectDefaultProfile = 'workbench.action.terminal.selectDefaultShell'
|
||||
}
|
||||
|
||||
export class Terminal {
|
||||
|
||||
constructor(private code: Code, private quickaccess: QuickAccess) { }
|
||||
constructor(private code: Code, private quickaccess: QuickAccess, private quickinput: QuickInput) { }
|
||||
|
||||
async showTerminal(): Promise<void> {
|
||||
await this.quickaccess.runCommand('workbench.action.terminal.toggleTerminal');
|
||||
await this.code.waitForActiveElement(XTERM_TEXTAREA);
|
||||
await this.code.waitForTerminalBuffer(XTERM_SELECTOR, lines => lines.some(line => line.length > 0));
|
||||
// TODO: Strongly type using non-const enum TerminalCommandId
|
||||
async runCommand(commandId: string, value?: string): Promise<void> {
|
||||
await this.quickaccess.runCommand(commandId, !!value || commandId === TerminalCommandId.Join);
|
||||
if (commandId === TerminalCommandId.Show || commandId === TerminalCommandId.CreateNew) {
|
||||
return await this._waitForTerminal();
|
||||
}
|
||||
if (value) {
|
||||
await this.code.waitForSetValue(QuickInput.QUICK_INPUT_INPUT, value);
|
||||
}
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
await this.quickinput.waitForQuickInputClosed();
|
||||
}
|
||||
|
||||
async runCommand(commandText: string): Promise<void> {
|
||||
async runCommandInTerminal(commandText: string): Promise<void> {
|
||||
await this.code.writeInTerminal(XTERM_SELECTOR, commandText);
|
||||
// hold your horses
|
||||
await new Promise(c => setTimeout(c, 500));
|
||||
await this.code.dispatchKeybinding('enter');
|
||||
}
|
||||
|
||||
// TODO: Return something more robust:
|
||||
// export interface ITerminalInstance {
|
||||
// name: string;
|
||||
// icon: string;
|
||||
// }
|
||||
// export type TerminalGroup = ITerminalInstance[];
|
||||
// export type TerminalLayout = TerminalGroup[];
|
||||
async getTabLabels(expectedCount: number, splits?: boolean, accept?: (result: IElement[]) => boolean): Promise<string[]> {
|
||||
const result: string[] = [];
|
||||
const tabs = await this.code.waitForElements(TABS, true, e => accept ? accept(e) : e.length === expectedCount && (!splits || e.some(e => e.textContent.startsWith('┌'))) && e.every(element => element.textContent.trim().length > 1));
|
||||
for (const t of tabs) {
|
||||
result.push(t.textContent);
|
||||
}
|
||||
if (!result[0].startsWith('┌')) {
|
||||
const first = result[1];
|
||||
const second = result[0];
|
||||
return [first, second];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async runProfileCommand(command: string, contributed?: boolean, altKey?: boolean): Promise<void> {
|
||||
await this.quickaccess.runCommand(command, true);
|
||||
if (contributed) {
|
||||
await this.code.waitForSetValue(QuickInput.QUICK_INPUT_INPUT, CONTRIBUTED_PROFILE_NAME);
|
||||
}
|
||||
await this.code.dispatchKeybinding(altKey ? 'Alt+Enter' : 'enter');
|
||||
await this.quickinput.waitForQuickInputClosed();
|
||||
}
|
||||
|
||||
async waitForTerminalText(accept: (buffer: string[]) => boolean, message?: string): Promise<void> {
|
||||
try {
|
||||
await this.code.waitForTerminalBuffer(XTERM_SELECTOR, accept);
|
||||
|
@ -37,4 +91,13 @@ export class Terminal {
|
|||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async getPage(): Promise<any> {
|
||||
return (this.code.driver as any).page;
|
||||
}
|
||||
|
||||
private async _waitForTerminal(): Promise<void> {
|
||||
await this.code.waitForElement(XTERM_FOCUSED_SELECTOR);
|
||||
await this.code.waitForTerminalBuffer(XTERM_SELECTOR, lines => lines.some(line => line.length > 0));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ export class Workbench {
|
|||
this.problems = new Problems(code, this.quickaccess);
|
||||
this.settingsEditor = new SettingsEditor(code, userDataPath, this.editors, this.editor, this.quickaccess);
|
||||
this.keybindingsEditor = new KeybindingsEditor(code);
|
||||
this.terminal = new Terminal(code, this.quickaccess);
|
||||
this.terminal = new Terminal(code, this.quickaccess, this.quickinput);
|
||||
this.notebook = new Notebook(this.quickaccess, code);
|
||||
this.localization = new Localization(code);
|
||||
}
|
||||
|
|
|
@ -5,31 +5,105 @@
|
|||
|
||||
import { ok } from 'assert';
|
||||
import { ParsedArgs } from 'minimist';
|
||||
import { Application } from '../../../../automation';
|
||||
import { Code, Terminal } from '../../../../automation';
|
||||
import { afterSuite, beforeSuite } from '../../utils';
|
||||
|
||||
export function setup(opts: ParsedArgs) {
|
||||
describe('Terminal Profiles', () => {
|
||||
let app: Application;
|
||||
const ContributedProfileName = `JavaScript Debug Terminal`;
|
||||
|
||||
export function setup(opts: ParsedArgs) {
|
||||
|
||||
describe('Terminal Profiles', () => {
|
||||
let code: Code;
|
||||
let terminal: Terminal;
|
||||
const enum TerminalCommandId {
|
||||
Split = 'workbench.action.terminal.split',
|
||||
KillAll = 'workbench.action.terminal.killAll',
|
||||
Show = 'workbench.action.terminal.toggleTerminal',
|
||||
CreateNew = 'workbench.action.terminal.new',
|
||||
NewWithProfile = 'workbench.action.terminal.newWithProfile',
|
||||
SelectDefaultProfile = 'workbench.action.terminal.selectDefaultShell'
|
||||
}
|
||||
beforeSuite(opts);
|
||||
afterSuite(opts);
|
||||
|
||||
before(function () {
|
||||
app = this.app;
|
||||
code = this.app.code;
|
||||
terminal = this.app.workbench.terminal;
|
||||
});
|
||||
|
||||
it('should launch the default profile', async function () {
|
||||
await app.workbench.terminal.showTerminal();
|
||||
afterEach(async () => {
|
||||
await terminal.runCommand(TerminalCommandId.KillAll);
|
||||
});
|
||||
|
||||
// Verify the terminal buffer has some content
|
||||
await app.workbench.terminal.waitForTerminalText(buffer => {
|
||||
return buffer.some(e => e.length > 0);
|
||||
}, 'The terminal buffer should have some content');
|
||||
it('should launch the default profile', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
// TODO: Use getSingleTabLabel? Share logic with getTabLabel?
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? !e.textContent.endsWith(ContributedProfileName) : false);
|
||||
});
|
||||
|
||||
// Verify the terminal single tab shows up and has a title
|
||||
const terminalTab = await app.code.waitForElement('.single-terminal-tab');
|
||||
ok(terminalTab.textContent.trim().length > 0);
|
||||
it.skip('should set the default profile to a contributed one', async () => {
|
||||
await terminal.runProfileCommand(TerminalCommandId.SelectDefaultProfile, true);
|
||||
await terminal.runCommand(TerminalCommandId.CreateNew);
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? e.textContent.endsWith(ContributedProfileName) : false);
|
||||
});
|
||||
|
||||
it.skip('should use the default contributed profile on panel open and for splitting', async () => {
|
||||
await terminal.runProfileCommand(TerminalCommandId.SelectDefaultProfile, true);
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.Split);
|
||||
const tabs = await terminal.getTabLabels(2);
|
||||
console.log('DEBUG: tabs', tabs);
|
||||
ok(tabs[0].startsWith('┌') && tabs[0].endsWith(ContributedProfileName));
|
||||
ok(tabs[1].startsWith('└') && tabs[1].endsWith(ContributedProfileName));
|
||||
});
|
||||
|
||||
it('should set the default profile', async () => {
|
||||
await terminal.runProfileCommand(TerminalCommandId.SelectDefaultProfile, undefined);
|
||||
await terminal.runCommand(TerminalCommandId.CreateNew);
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? !e.textContent.endsWith(ContributedProfileName) : false);
|
||||
});
|
||||
|
||||
it('should use the default profile on panel open and for splitting', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? !e.textContent.endsWith(ContributedProfileName) : false);
|
||||
await terminal.runCommand(TerminalCommandId.Split);
|
||||
const tabs = await terminal.getTabLabels(2, true);
|
||||
ok(tabs[0].startsWith('┌') && !tabs[0].endsWith(ContributedProfileName));
|
||||
ok(tabs[1].startsWith('└') && !tabs[1].endsWith(ContributedProfileName));
|
||||
});
|
||||
|
||||
it('clicking the plus button should create a terminal and display the tabs view showing no split decorations', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await code.waitAndClick('li.action-item.monaco-dropdown-with-primary > div.action-container.menu-entry > a');
|
||||
const tabLabels = await terminal.getTabLabels(2);
|
||||
ok(!tabLabels[0].startsWith('┌') && !tabLabels[1].startsWith('└'));
|
||||
});
|
||||
|
||||
it('createWithProfile command should create a terminal with a profile', async () => {
|
||||
await terminal.runProfileCommand(TerminalCommandId.NewWithProfile);
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? !e.textContent.endsWith(ContributedProfileName) : false);
|
||||
});
|
||||
|
||||
it.skip('createWithProfile command should create a terminal with a contributed profile', async () => {
|
||||
await terminal.runProfileCommand(TerminalCommandId.NewWithProfile, true);
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? e.textContent.endsWith(ContributedProfileName) : false);
|
||||
});
|
||||
|
||||
it('createWithProfile command should create a split terminal with a profile', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runProfileCommand(TerminalCommandId.NewWithProfile, undefined, true);
|
||||
const tabs = await terminal.getTabLabels(2, true);
|
||||
ok(tabs[0].startsWith('┌') && !tabs[0].endsWith(ContributedProfileName));
|
||||
ok(tabs[1].startsWith('└') && !tabs[1].endsWith(ContributedProfileName));
|
||||
});
|
||||
|
||||
it.skip('createWithProfile command should create a split terminal with a contributed profile', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? !e.textContent.endsWith(ContributedProfileName) : false);
|
||||
await terminal.runProfileCommand(TerminalCommandId.NewWithProfile, true, true);
|
||||
const tabs = await terminal.getTabLabels(2, true);
|
||||
ok(tabs[0].startsWith('┌') && !tabs[0].endsWith(ContributedProfileName));
|
||||
ok(tabs[1].startsWith('└') && tabs[1].endsWith(ContributedProfileName));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
139
test/smoke/src/areas/terminal/terminal-tabs.test.ts
Normal file
139
test/smoke/src/areas/terminal/terminal-tabs.test.ts
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ok } from 'assert';
|
||||
import { ParsedArgs } from 'minimist';
|
||||
import { Code, Terminal } from '../../../../automation/out';
|
||||
import { afterSuite, beforeSuite } from '../../utils';
|
||||
|
||||
export function setup(opts: ParsedArgs) {
|
||||
// TODO: Re-enable when stable
|
||||
describe.skip('Terminal Tabs', () => {
|
||||
let code: Code;
|
||||
let terminal: Terminal;
|
||||
|
||||
// TODO: Move into automation/terminal
|
||||
const enum TerminalCommandId {
|
||||
Rename = 'workbench.action.terminal.rename',
|
||||
ChangeColor = 'workbench.action.terminal.changeColor',
|
||||
ChangeIcon = 'workbench.action.terminal.changeIcon',
|
||||
Split = 'workbench.action.terminal.split',
|
||||
KillAll = 'workbench.action.terminal.killAll',
|
||||
Unsplit = 'workbench.action.terminal.unsplit',
|
||||
Join = 'workbench.action.terminal.join',
|
||||
Show = 'workbench.action.terminal.toggleTerminal',
|
||||
CreateNew = 'workbench.action.terminal.new'
|
||||
}
|
||||
|
||||
beforeSuite(opts);
|
||||
afterSuite(opts);
|
||||
|
||||
before(function () {
|
||||
code = this.app.code;
|
||||
terminal = this.app.workbench.terminal;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await terminal.runCommand(TerminalCommandId.KillAll);
|
||||
});
|
||||
|
||||
it('clicking the plus button should create a terminal and display the tabs view showing no split decorations', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await code.waitAndClick('li.action-item.monaco-dropdown-with-primary > div.action-container.menu-entry > a');
|
||||
const tabLabels = await terminal.getTabLabels(2);
|
||||
ok(!tabLabels[0].startsWith('┌') && !tabLabels[1].startsWith('└'));
|
||||
});
|
||||
|
||||
it('should update color of the single tab', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
const color = 'Cyan';
|
||||
await terminal.runCommand(TerminalCommandId.ChangeColor, color);
|
||||
const singleTab = await code.waitForElement('.single-terminal-tab');
|
||||
ok(singleTab.className.includes(`terminal-icon-terminal_ansi${color}`));
|
||||
});
|
||||
|
||||
it('should update color of the tab in the tabs list', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.Split);
|
||||
const tabs = await terminal.getTabLabels(2);
|
||||
ok(tabs[0].startsWith('┌'));
|
||||
ok(tabs[1].startsWith('└'));
|
||||
const color = 'Cyan';
|
||||
await terminal.runCommand(TerminalCommandId.ChangeColor, color);
|
||||
await code.waitForElement(`.terminal-tabs-entry .terminal-icon-terminal_ansi${color}`);
|
||||
});
|
||||
|
||||
it('should update icon of the single tab', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
const icon = 'symbol-method';
|
||||
await terminal.runCommand(TerminalCommandId.ChangeIcon, icon);
|
||||
await code.waitForElement(`.single-terminal-tab .codicon-${icon}`);
|
||||
});
|
||||
|
||||
it('should update icon of the tab in the tabs list', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.Split);
|
||||
const tabs = await terminal.getTabLabels(2);
|
||||
ok(tabs[0].startsWith('┌'));
|
||||
ok(tabs[1].startsWith('└'));
|
||||
const icon = 'symbol-method';
|
||||
await terminal.runCommand(TerminalCommandId.ChangeIcon, icon);
|
||||
await code.waitForElement(`.terminal-tabs-entry .codicon-${icon}`);
|
||||
});
|
||||
|
||||
it('should rename the single tab', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
const name = 'my terminal name';
|
||||
await terminal.runCommand(TerminalCommandId.Rename, name);
|
||||
await code.waitForElement('.single-terminal-tab', e => e ? e?.textContent.includes(name) : false);
|
||||
});
|
||||
|
||||
it('should rename the tab in the tabs list', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.Split);
|
||||
const name = 'my terminal name';
|
||||
await terminal.runCommand(TerminalCommandId.Rename, name);
|
||||
await terminal.getTabLabels(2, true, t => t.some(element => element.textContent.includes(name)));
|
||||
});
|
||||
|
||||
it('should create a split terminal when single tab is alt clicked', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
const page = await terminal.getPage();
|
||||
page.keyboard.down('Alt');
|
||||
await code.waitAndClick('.single-terminal-tab');
|
||||
page.keyboard.up('Alt');
|
||||
await terminal.getTabLabels(2, true);
|
||||
});
|
||||
|
||||
it('should do nothing when join tabs is run with only one terminal', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.Join);
|
||||
await code.waitForElement('.single-terminal-tab');
|
||||
});
|
||||
|
||||
it('should join tabs when more than one terminal', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.CreateNew);
|
||||
await terminal.runCommand(TerminalCommandId.Join);
|
||||
await terminal.getTabLabels(2, true);
|
||||
});
|
||||
|
||||
it('should do nothing when unsplit tabs called with no splits', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.CreateNew);
|
||||
await terminal.getTabLabels(2, false);
|
||||
await terminal.runCommand(TerminalCommandId.Unsplit);
|
||||
await terminal.getTabLabels(2, false);
|
||||
});
|
||||
|
||||
it('should unsplit tabs', async () => {
|
||||
await terminal.runCommand(TerminalCommandId.Show);
|
||||
await terminal.runCommand(TerminalCommandId.Split);
|
||||
await terminal.getTabLabels(2, true);
|
||||
await terminal.runCommand(TerminalCommandId.Unsplit);
|
||||
await terminal.getTabLabels(2, false, t => t.every(label => !label.textContent.startsWith('┌') && !label.textContent.startsWith('└')));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -28,6 +28,7 @@ import { setup as setupMultirootTests } from './areas/multiroot/multiroot.test';
|
|||
import { setup as setupLocalizationTests } from './areas/workbench/localization.test';
|
||||
import { setup as setupLaunchTests } from './areas/workbench/launch.test';
|
||||
import { setup as setupTerminalProfileTests } from './areas/terminal/terminal-profiles.test';
|
||||
import { setup as setupTerminalTabsTests } from './areas/terminal/terminal-tabs.test';
|
||||
|
||||
const testDataPath = path.join(os.tmpdir(), 'vscsmoke');
|
||||
if (fs.existsSync(testDataPath)) {
|
||||
|
@ -360,4 +361,5 @@ describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
|
|||
|
||||
// TODO: Enable terminal tests for non-web
|
||||
if (opts.web) { setupTerminalProfileTests(opts); }
|
||||
if (opts.web) { setupTerminalTabsTests(opts); }
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue