mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Keyboard navigation smoke test (#18796)
This commit is contained in:
@@ -31,3 +31,4 @@ export * from './driver';
|
|||||||
export * from './sql/connectionDialog';
|
export * from './sql/connectionDialog';
|
||||||
export * from './sql/profiler';
|
export * from './sql/profiler';
|
||||||
export * from './sql/queryEditors';
|
export * from './sql/queryEditors';
|
||||||
|
export * from './sql/constants';
|
||||||
|
|||||||
7
test/automation/src/sql/constants.ts
Normal file
7
test/automation/src/sql/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
export const ctrlOrCmd = process.platform === 'darwin' ? 'cmd' : 'ctrl';
|
||||||
|
export const winOrCtrl = process.platform === 'darwin' ? 'ctrl' : 'win';
|
||||||
@@ -8,20 +8,22 @@ import { QuickAccess } from '../quickaccess';
|
|||||||
import { QuickInput } from '../quickinput';
|
import { QuickInput } from '../quickinput';
|
||||||
import { Editors } from '../editors';
|
import { Editors } from '../editors';
|
||||||
import { IElement } from '..';
|
import { IElement } from '..';
|
||||||
|
import * as constants from '../sql/constants';
|
||||||
|
|
||||||
|
const activeCellSelector = '.notebook-cell.active';
|
||||||
|
|
||||||
export class Notebook {
|
export class Notebook {
|
||||||
|
|
||||||
public readonly notebookToolbar: NotebookToolbar;
|
public readonly notebookToolbar: NotebookToolbar;
|
||||||
public readonly textCellToolbar: TextCellToolbar;
|
public readonly textCellToolbar: TextCellToolbar;
|
||||||
|
public readonly notebookFind: NotebookFind;
|
||||||
public readonly view: NotebookTreeView;
|
public readonly view: NotebookTreeView;
|
||||||
|
|
||||||
public readonly winOrCtrl = process.platform === 'darwin' ? 'ctrl' : 'win';
|
|
||||||
public readonly ctrlOrCmd = process.platform === 'darwin' ? 'cmd' : 'ctrl';
|
|
||||||
|
|
||||||
constructor(private code: Code, private quickAccess: QuickAccess, private quickInput: QuickInput, private editors: Editors) {
|
constructor(private code: Code, private quickAccess: QuickAccess, private quickInput: QuickInput, private editors: Editors) {
|
||||||
this.notebookToolbar = new NotebookToolbar(code);
|
this.notebookToolbar = new NotebookToolbar(code);
|
||||||
this.textCellToolbar = new TextCellToolbar(code);
|
this.textCellToolbar = new TextCellToolbar(code);
|
||||||
this.view = new NotebookTreeView(code, quickAccess);
|
this.view = new NotebookTreeView(code, quickAccess);
|
||||||
|
this.notebookFind = new NotebookFind(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
async openFile(fileName: string): Promise<void> {
|
async openFile(fileName: string): Promise<void> {
|
||||||
@@ -33,7 +35,7 @@ export class Notebook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async newUntitledNotebook(): Promise<void> {
|
async newUntitledNotebook(): Promise<void> {
|
||||||
await this.code.dispatchKeybinding(this.winOrCtrl + '+Alt+n');
|
await this.code.dispatchKeybinding(`${constants.winOrCtrl}+Alt+n`);
|
||||||
await this.editors.waitForActiveTab(`Notebook-0`);
|
await this.editors.waitForActiveTab(`Notebook-0`);
|
||||||
await this.code.waitForElement('.notebookEditor');
|
await this.code.waitForElement('.notebookEditor');
|
||||||
}
|
}
|
||||||
@@ -47,21 +49,35 @@ export class Notebook {
|
|||||||
await this.code.dispatchKeybinding('ctrl+shift+c');
|
await this.code.dispatchKeybinding('ctrl+shift+c');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.code.waitForElement('.notebook-cell.active');
|
await this.code.waitForElement(activeCellSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitForActiveCellGone(): Promise<void> {
|
||||||
|
await this.code.waitForElementGone(activeCellSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async runActiveCell(): Promise<void> {
|
async runActiveCell(): Promise<void> {
|
||||||
await this.code.dispatchKeybinding('F5');
|
await this.code.dispatchKeybinding('F5');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exitActiveCell(): Promise<void> {
|
||||||
|
await this.code.dispatchKeybinding('escape'); // first escape to exit edit mode
|
||||||
|
await this.code.dispatchKeybinding('escape'); // second escape to deselect cell
|
||||||
|
}
|
||||||
|
|
||||||
async runAllCells(): Promise<void> {
|
async runAllCells(): Promise<void> {
|
||||||
await this.code.dispatchKeybinding('ctrl+shift+F5');
|
await this.code.dispatchKeybinding('ctrl+shift+F5');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cell Actions
|
// Cell Actions
|
||||||
|
|
||||||
async waitForTypeInEditor(text: string) {
|
async getActiveCell(id?: string): Promise<IElement> {
|
||||||
const editor = '.notebook-cell.active .monaco-editor';
|
const activeCell = id ? `${activeCellSelector}[id="${id}"]` : activeCellSelector;
|
||||||
|
return this.code.waitForElement(activeCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitForTypeInEditor(text: string, cellId?: string) {
|
||||||
|
const editor = cellId ? `${activeCellSelector}[id="${cellId}"] .monaco-editor` : `${activeCellSelector} .monaco-editor`;
|
||||||
await this.code.waitAndClick(editor);
|
await this.code.waitAndClick(editor);
|
||||||
|
|
||||||
const textarea = `${editor} textarea`;
|
const textarea = `${editor} textarea`;
|
||||||
@@ -72,7 +88,7 @@ export class Notebook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForActiveCellEditorContents(accept: (contents: string) => boolean): Promise<any> {
|
async waitForActiveCellEditorContents(accept: (contents: string) => boolean): Promise<any> {
|
||||||
const selector = '.notebook-cell.active .monaco-editor .view-lines';
|
const selector = `${activeCellSelector} .monaco-editor .view-lines`;
|
||||||
return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
|
return this.code.waitForTextContent(selector, undefined, c => accept(c.replace(/\u00a0/g, ' ')));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,24 +98,24 @@ export class Notebook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async selectAllTextInRichTextEditor(): Promise<void> {
|
public async selectAllTextInRichTextEditor(): Promise<void> {
|
||||||
const editor = '.notebook-cell.active .notebook-preview[contenteditable="true"]';
|
const editor = `${activeCellSelector} .notebook-preview[contenteditable="true"]`;
|
||||||
await this.selectAllText(editor);
|
await this.selectAllText(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async selectAllTextInEditor(): Promise<void> {
|
public async selectAllTextInEditor(): Promise<void> {
|
||||||
const editor = '.notebook-cell.active .monaco-editor';
|
const editor = `${activeCellSelector} .monaco-editor`;
|
||||||
await this.selectAllText(editor);
|
await this.selectAllText(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async selectAllText(selector: string): Promise<void> {
|
private async selectAllText(selector: string): Promise<void> {
|
||||||
await this.code.waitAndClick(selector);
|
await this.code.waitAndClick(selector);
|
||||||
await this.code.dispatchKeybinding(this.ctrlOrCmd + '+a');
|
await this.code.dispatchKeybinding(`${constants.ctrlOrCmd}+a`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly placeholderSelector = 'div.placeholder-cell-component';
|
private static readonly placeholderSelector = 'div.placeholder-cell-component';
|
||||||
async addCellFromPlaceholder(cellType: 'Markdown' | 'Code'): Promise<void> {
|
async addCellFromPlaceholder(cellType: 'Markdown' | 'Code'): Promise<void> {
|
||||||
await this.code.waitAndClick(`${Notebook.placeholderSelector} p a[id="add${cellType}"]`);
|
await this.code.waitAndClick(`${Notebook.placeholderSelector} p a[id="add${cellType}"]`);
|
||||||
await this.code.waitForElement('.notebook-cell.active');
|
await this.code.waitForElement(activeCellSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForPlaceholderGone(): Promise<void> {
|
async waitForPlaceholderGone(): Promise<void> {
|
||||||
@@ -172,12 +188,12 @@ export class Notebook {
|
|||||||
// Cell Output Actions
|
// Cell Output Actions
|
||||||
|
|
||||||
async waitForJupyterErrorOutput(): Promise<void> {
|
async waitForJupyterErrorOutput(): Promise<void> {
|
||||||
const jupyterErrorOutput = `.notebook-cell.active .notebook-output mime-output[data-mime-type="application/vnd.jupyter.stderr"]`;
|
const jupyterErrorOutput = `${activeCellSelector} .notebook-output mime-output[data-mime-type="application/vnd.jupyter.stderr"]`;
|
||||||
await this.code.waitForElement(jupyterErrorOutput);
|
await this.code.waitForElement(jupyterErrorOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForActiveCellResults(): Promise<void> {
|
async waitForActiveCellResults(): Promise<void> {
|
||||||
const outputComponent = '.notebook-cell.active .notebook-output';
|
const outputComponent = `${activeCellSelector} .notebook-output`;
|
||||||
await this.code.waitForElement(outputComponent);
|
await this.code.waitForElement(outputComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +208,7 @@ export class Notebook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForActiveCellResultsGone(): Promise<void> {
|
async waitForActiveCellResultsGone(): Promise<void> {
|
||||||
const outputComponent = '.notebook-cell.active .notebook-output';
|
const outputComponent = `${activeCellSelector} .notebook-output`;
|
||||||
await this.code.waitForElementGone(outputComponent);
|
await this.code.waitForElementGone(outputComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,3 +454,14 @@ export class NotebookTreeView {
|
|||||||
await this.code.waitForElementGone(NotebookTreeView.pinnedNotebooksSelector);
|
await this.code.waitForElementGone(NotebookTreeView.pinnedNotebooksSelector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class NotebookFind {
|
||||||
|
|
||||||
|
constructor(private code: Code) { }
|
||||||
|
|
||||||
|
async openFindWidget(): Promise<void> {
|
||||||
|
const findWidgetCmd = `${constants.ctrlOrCmd}+f`;
|
||||||
|
await this.code.dispatchKeybinding(findWidgetCmd);
|
||||||
|
await this.code.waitForElement('.editor-widget.find-widget.visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { Application } from '../../../../../automation';
|
import { Application, ctrlOrCmd } from '../../../../../automation';
|
||||||
import * as minimist from 'minimist';
|
import * as minimist from 'minimist';
|
||||||
import { afterSuite, beforeSuite } from '../../../utils';
|
import { afterSuite, beforeSuite } from '../../../utils';
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
|
|
||||||
export function setup(opts: minimist.ParsedArgs) {
|
export function setup(opts: minimist.ParsedArgs) {
|
||||||
describe('Notebook', () => {
|
describe('Notebook', () => {
|
||||||
beforeSuite(opts);
|
beforeSuite(opts);
|
||||||
@@ -59,6 +58,7 @@ export function setup(opts: minimist.ParsedArgs) {
|
|||||||
await app.workbench.sqlNotebook.waitForColorization('6', 'mtk1'); // employees
|
await app.workbench.sqlNotebook.waitForColorization('6', 'mtk1'); // employees
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Python Notebooks
|
// Python Notebooks
|
||||||
|
|
||||||
it('can open new notebook, configure Python, and execute one cell', async function () {
|
it('can open new notebook, configure Python, and execute one cell', async function () {
|
||||||
@@ -132,6 +132,50 @@ export function setup(opts: minimist.ParsedArgs) {
|
|||||||
await app.code.dispatchKeybinding('escape');
|
await app.code.dispatchKeybinding('escape');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Notebook keyboard navigation', async () => {
|
||||||
|
it('can enter and exit edit mode and navigate using keyboard nav', async function () {
|
||||||
|
const app = this.app as Application;
|
||||||
|
await app.workbench.sqlNotebook.newUntitledNotebook();
|
||||||
|
await app.workbench.sqlNotebook.addCellFromPlaceholder('Code'); // add new code cell
|
||||||
|
await app.workbench.sqlNotebook.waitForPlaceholderGone();
|
||||||
|
const activeCodeCellId = (await app.workbench.sqlNotebook.getActiveCell()).attributes['id'];
|
||||||
|
await app.workbench.sqlNotebook.waitForTypeInEditor('code cell', activeCodeCellId); // the new cell should be in edit mode
|
||||||
|
await app.workbench.sqlNotebook.exitActiveCell();
|
||||||
|
await app.workbench.sqlNotebook.waitForActiveCellGone();
|
||||||
|
|
||||||
|
await app.workbench.sqlNotebook.addCell('markdown'); // add markdown cell
|
||||||
|
await app.workbench.sqlNotebook.textCellToolbar.changeTextCellView('Split View');
|
||||||
|
const activeTextCellId = (await app.workbench.sqlNotebook.getActiveCell()).attributes['id'];
|
||||||
|
await app.workbench.sqlNotebook.waitForTypeInEditor('text cell', activeTextCellId); // Text cell should be in edit mode
|
||||||
|
|
||||||
|
await app.code.dispatchKeybinding('escape'); // exit edit mode and stay in browse mode
|
||||||
|
await app.code.dispatchKeybinding('up'); // select code cell
|
||||||
|
await app.workbench.sqlNotebook.getActiveCell(activeCodeCellId); // check that the code cell is now active
|
||||||
|
await app.code.dispatchKeybinding('enter');
|
||||||
|
await app.workbench.sqlNotebook.waitForTypeInEditor('test', activeCodeCellId); // code cell should be in edit mode after hitting enter
|
||||||
|
await app.code.dispatchKeybinding('escape'); // exit edit mode and stay in browse mode
|
||||||
|
await app.code.dispatchKeybinding('down'); // select text cell
|
||||||
|
await app.code.dispatchKeybinding('enter');
|
||||||
|
await app.workbench.sqlNotebook.textCellToolbar.changeTextCellView('Split View');
|
||||||
|
await app.workbench.sqlNotebook.waitForTypeInEditor('test', activeTextCellId); // text cell should be in edit mode after hitting enter
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot move through cells when find widget is invoked', async function () {
|
||||||
|
const app = this.app as Application;
|
||||||
|
await app.workbench.sqlNotebook.newUntitledNotebook();
|
||||||
|
await app.workbench.sqlNotebook.addCell('markdown');
|
||||||
|
await app.workbench.sqlNotebook.exitActiveCell();
|
||||||
|
await app.workbench.sqlNotebook.addCell('markdown');
|
||||||
|
await app.workbench.sqlNotebook.exitActiveCell();
|
||||||
|
await app.workbench.sqlNotebook.addCell('markdown');
|
||||||
|
await app.code.dispatchKeybinding('escape');
|
||||||
|
const activeCellId = (await app.workbench.sqlNotebook.getActiveCell()).attributes['id'];
|
||||||
|
await app.workbench.sqlNotebook.notebookFind.openFindWidget();
|
||||||
|
await app.code.dispatchKeybinding('down');
|
||||||
|
await app.workbench.sqlNotebook.getActiveCell(activeCellId); // verify that the active cell is the same
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Notebook Toolbar Actions', async () => {
|
describe('Notebook Toolbar Actions', async () => {
|
||||||
|
|
||||||
it('Collapse and Expand Cell', async function () {
|
it('Collapse and Expand Cell', async function () {
|
||||||
@@ -333,27 +377,27 @@ export function setup(opts: minimist.ParsedArgs) {
|
|||||||
|
|
||||||
it('can bold text with keyboard shortcut', async function () {
|
it('can bold text with keyboard shortcut', async function () {
|
||||||
const app = this.app as Application;
|
const app = this.app as Application;
|
||||||
await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+b', 'p strong');
|
await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+b`, 'p strong');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can italicize text with keyboard shortcut', async function () {
|
it('can italicize text with keyboard shortcut', async function () {
|
||||||
const app = this.app as Application;
|
const app = this.app as Application;
|
||||||
await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+i', 'p em');
|
await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+i`, 'p em');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can underline text with keyboard shortcut', async function () {
|
it('can underline text with keyboard shortcut', async function () {
|
||||||
const app = this.app as Application;
|
const app = this.app as Application;
|
||||||
await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+u', 'p u');
|
await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+u`, 'p u');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can highlight text with keyboard shortcut', async function () {
|
it('can highlight text with keyboard shortcut', async function () {
|
||||||
const app = this.app as Application;
|
const app = this.app as Application;
|
||||||
await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+shift+h', 'p mark');
|
await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+shift+h`, 'p mark');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can codify text with keyboard shortcut', async function () {
|
it('can codify text with keyboard shortcut', async function () {
|
||||||
const app = this.app as Application;
|
const app = this.app as Application;
|
||||||
await verifyToolbarKeyboardShortcut(app, app.workbench.sqlNotebook.ctrlOrCmd + '+shift+k', 'pre code');
|
await verifyToolbarKeyboardShortcut(app, `${ctrlOrCmd}+shift+k`, 'pre code');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user