Add terminal profile smoke tests (#137079)

This commit is contained in:
Megan Rogge 2021-11-17 14:00:01 -08:00 committed by GitHub
parent bdc6162b1d
commit 00fa317c43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 402 additions and 38 deletions

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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 };

View file

@ -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',

View file

@ -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,

View file

@ -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++) {

View file

@ -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> {

View file

@ -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();
}
}
}

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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));
});
});
}

View 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('└')));
});
});
}

View file

@ -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); }
});