diff --git a/front_end/panels/console/BUILD.gn b/front_end/panels/console/BUILD.gn index 6bffba2d32..3a166b23b8 100644 --- a/front_end/panels/console/BUILD.gn +++ b/front_end/panels/console/BUILD.gn @@ -48,7 +48,6 @@ devtools_module("console") { "../../ui/legacy:bundle", "../../ui/legacy/components/data_grid:bundle", "../../ui/legacy/components/object_ui:bundle", - "../../ui/legacy/components/text_editor:bundle", "../../ui/legacy/components/utils:bundle", "../../ui/legacy/theme_support:bundle", "../console_counters:bundle", diff --git a/front_end/panels/console/ConsolePinPane.ts b/front_end/panels/console/ConsolePinPane.ts index a10df37bcf..9c43b88082 100644 --- a/front_end/panels/console/ConsolePinPane.ts +++ b/front_end/panels/console/ConsolePinPane.ts @@ -255,6 +255,7 @@ export class ConsolePin { ]), CodeMirror.EditorView.domEventHandlers({blur: (_e, view) => this.onBlur(view)}), TextEditor.Config.baseConfiguration(expression), + TextEditor.Config.closeBrackets, TextEditor.Config.autocompletion, ], })); diff --git a/front_end/panels/console/ConsolePrompt.ts b/front_end/panels/console/ConsolePrompt.ts index b5106199ea..ca4e2290b6 100644 --- a/front_end/panels/console/ConsolePrompt.ts +++ b/front_end/panels/console/ConsolePrompt.ts @@ -6,9 +6,9 @@ import * as Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as SDK from '../../core/sdk/sdk.js'; -import * as TextUtils from '../../models/text_utils/text_utils.js'; +import * as CodeMirror from '../../third_party/codemirror.next/codemirror.next.js'; +import * as TextEditor from '../../ui/components/text_editor/text_editor.js'; import * as ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js'; -import * as TextEditor from '../../ui/legacy/components/text_editor/text_editor.js'; import * as UI from '../../ui/legacy/legacy.js'; import {ConsolePanel} from './ConsolePanel.js'; @@ -27,17 +27,17 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin Promise; + private requestPreviewCurrent = 0; private readonly innerPreviewElement: HTMLElement; private readonly promptIcon: UI.Icon.Icon; private readonly iconThrottler: Common.Throttler.Throttler; private readonly eagerEvalSetting: Common.Settings.Setting; private previewRequestForTest: Promise|null; - private defaultAutocompleteConfig: UI.TextEditor.AutocompleteConfig|null; private highlightingNode: boolean; constructor() { @@ -46,7 +46,6 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin this.editorUpdate(update)), + TextEditor.JavaScript.argumentHints(), + TextEditor.JavaScript.completion(), + TextEditor.Config.showCompletionHint, + CodeMirror.javascript.javascript(), + TextEditor.Config.baseConfiguration(this.initialText), + TextEditor.Config.autocompletion, + CodeMirror.javascript.javascriptLanguage.data.of({ + autocomplete: (context: CodeMirror.CompletionContext): CodeMirror.CompletionResult | null => + this.historyCompletions(context), + }), + CodeMirror.EditorView.contentAttributes.of({'aria-label': i18nString(UIStrings.consolePrompt)}), + CodeMirror.EditorView.lineWrapping, + CodeMirror.autocompletion({aboveCursor: true}), + ], + }); - const options = { - devtoolsAccessibleName: (i18nString(UIStrings.consolePrompt) as string), - lineNumbers: false, - lineWrapping: true, - mimeType: 'javascript', - autoHeight: true, - }; - this.editor = factory.createEditor((options as UI.TextEditor.Options)); + this.editor = new TextEditor.TextEditor.TextEditor(editorState); + this.editor.addEventListener('keydown', (event): void => { + if (event.defaultPrevented) { + event.stopPropagation(); + } + }); + editorContainerElement.appendChild(this.editor); - this.defaultAutocompleteConfig = - ObjectUI.JavaScriptAutocomplete.JavaScriptAutocompleteConfig.createConfigForEditor(this.editor); - this.editor.configureAutocomplete(Object.assign({}, this.defaultAutocompleteConfig, { - suggestionsCallback: this.wordsWithQuery.bind(this), - anchorBehavior: UI.GlassPane.AnchorBehavior.PreferTop, - })); - this.editor.widget().element.addEventListener('keydown', this.editorKeyDown.bind(this), true); - this.editor.widget().show(editorContainerElement); - this.editor.addEventListener(UI.TextEditor.Events.CursorChanged, this.updatePromptIcon, this); - this.editor.addEventListener(UI.TextEditor.Events.TextChanged, this.onTextChanged, this); - this.editor.addEventListener(UI.TextEditor.Events.SuggestionChanged, this.onTextChanged, this); - - this.setText(this.initialText); - this.initialText = ''; if (this.hasFocus()) { this.focus(); } this.element.removeAttribute('tabindex'); - this.editor.widget().element.tabIndex = -1; this.editorSetForTest(); @@ -126,7 +125,7 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin { - if (!this.editor) { - return; - } - const text = this.editor.textWithCurrentSuggestion().trim(); + const id = ++this.requestPreviewCurrent; + const text = TextEditor.Config.contentIncludingHint(this.editor.editor).trim(); const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext); const {preview, result} = await ObjectUI.JavaScriptREPL.JavaScriptREPL.evaluateAndBuildPreview( text, true /* throwOnSideEffect */, true /* replMode */, 500 /* timeout */); + if (this.requestPreviewCurrent !== id) { + return; + } this.innerPreviewElement.removeChildren(); - if (preview.deepTextContent() !== this.editor.textWithCurrentSuggestion().trim()) { + if (preview.deepTextContent() !== TextEditor.Config.contentIncludingHint(this.editor.editor).trim()) { this.innerPreviewElement.appendChild(preview); } if (result && 'object' in result && result.object && result.object.subtype === 'node') { @@ -174,149 +174,102 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin this.moveHistory(-1)}, + {key: 'ArrowDown', run: (): boolean => this.moveHistory(1)}, + {mac: 'Ctrl-p', run: (): boolean => this.moveHistory(-1, true)}, + {mac: 'Ctrl-n', run: (): boolean => this.moveHistory(1, true)}, + {key: 'Enter', run: (): boolean => this.evaluate(), shift: CodeMirror.insertNewlineAndIndent}, + ]; } - private async enterWillEvaluate(): Promise { - if (!this.isCaretAtEndOfPrompt()) { - return true; + private moveHistory(dir: -1|1, force = false): boolean { + const {editor} = this.editor, {main} = editor.state.selection; + if (!force) { + if (!main.empty) { + return false; + } + const cursorCoords = editor.coordsAtPos(main.head); + const endCoords = editor.coordsAtPos(dir < 0 ? 0 : editor.state.doc.length); + // Check if there are wrapped lines in this direction, and let + // the cursor move normally if there are. + if (cursorCoords && endCoords && + (dir < 0 ? cursorCoords.top > endCoords.top + 5 : cursorCoords.bottom < endCoords.bottom - 5)) { + return false; + } } - return await ObjectUI.JavaScriptAutocomplete.JavaScriptAutocomplete.isExpressionComplete(this.text()); + + const history = this.historyInternal; + const newText = dir < 0 ? history.previous(this.text()) : history.next(); + if (newText === undefined) { + return false; + } + + const cursorPos = dir < 0 ? newText.search(/\n|$/) : newText.length; + this.editor.dispatch({ + changes: {from: 0, to: this.editor.state.doc.length, insert: newText}, + selection: CodeMirror.EditorSelection.cursor(cursorPos), + scrollIntoView: true, + }); + return true; + } + + private enterWillEvaluate(): boolean { + const {state} = this.editor; + return state.doc.length > 0 && TextEditor.JavaScript.isExpressionComplete(state); + } + + private evaluate(): boolean { + if (this.enterWillEvaluate()) { + this.appendCommand(this.text(), true); + this.editor.dispatch({ + changes: {from: 0, to: this.editor.state.doc.length}, + scrollIntoView: true, + }); + } else if (this.editor.state.doc.length) { + return CodeMirror.insertNewlineAndIndent(this.editor.editor); + } else { + this.editor.dispatch({scrollIntoView: true}); + } + this.enterProcessedForTest(); + return true; } private updatePromptIcon(): void { this.iconThrottler.schedule(async () => { - const canComplete = await this.enterWillEvaluate(); - this.promptIcon.classList.toggle('console-prompt-incomplete', !canComplete); + this.promptIcon.classList.toggle('console-prompt-incomplete', !this.enterWillEvaluate()); }); } - private async enterKeyPressed(event: KeyboardEvent): Promise { - if (event.altKey || event.ctrlKey || event.shiftKey) { - return; - } - - event.consume(true); - - // Since we prevent default, manually emulate the native "scroll on key input" behavior. - this.element.scrollIntoView(); - this.clearAutocomplete(); - - const str = this.text(); - if (!str.length) { - return; - } - - if (await this.enterWillEvaluate()) { - await this.appendCommand(str, true); - } else if (this.editor) { - this.editor.newlineAndIndent(); - } - this.enterProcessedForTest(); - } - - private async appendCommand(text: string, useCommandLineAPI: boolean): Promise { - this.setText(''); + private appendCommand(text: string, useCommandLineAPI: boolean): void { const currentExecutionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext); if (currentExecutionContext) { const executionContext = currentExecutionContext; @@ -333,10 +286,19 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin(); @@ -350,30 +312,18 @@ export class ConsolePrompt extends Common.ObjectWrapper.eventMixin { - if (!this.editor || !this.defaultAutocompleteConfig || !this.defaultAutocompleteConfig.suggestionsCallback) { - return []; - } - const query = this.editor.text(queryRange); - const words = await this.defaultAutocompleteConfig.suggestionsCallback(queryRange, substituteRange, force); - const historyWords = this.historyCompletions(query, force); - return words ? words.concat(historyWords) : historyWords; + this.editor.focus(); } private editorSetForTest(): void { diff --git a/front_end/panels/console/ConsoleView.ts b/front_end/panels/console/ConsoleView.ts index eaeb659f7b..240d80eff0 100644 --- a/front_end/panels/console/ConsoleView.ts +++ b/front_end/panels/console/ConsoleView.ts @@ -1218,7 +1218,7 @@ export class ConsoleView extends UI.Widget.VBox implements UI.SearchableView.Sea } private clearPromptBackwards(): void { - this.prompt.setText(''); + this.prompt.clear(); } private promptKeyDown(event: Event): void { diff --git a/front_end/panels/elements/ElementsTreeElement.ts b/front_end/panels/elements/ElementsTreeElement.ts index 642e9932d6..25f08ee416 100644 --- a/front_end/panels/elements/ElementsTreeElement.ts +++ b/front_end/panels/elements/ElementsTreeElement.ts @@ -1044,6 +1044,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement { }, ]), TextEditor.Config.baseConfiguration(initialValue), + TextEditor.Config.closeBrackets, TextEditor.Config.autocompletion, CodeMirror.html.html(), TextEditor.Config.domWordWrap.instance(), diff --git a/front_end/panels/sources/BreakpointEditDialog.ts b/front_end/panels/sources/BreakpointEditDialog.ts index 62056ebe80..4153e94dc0 100644 --- a/front_end/panels/sources/BreakpointEditDialog.ts +++ b/front_end/panels/sources/BreakpointEditDialog.ts @@ -71,6 +71,7 @@ export class BreakpointEditDialog extends UI.Widget.Widget { const editorConfig = [ CodeMirror.javascript.javascriptLanguage, TextEditor.Config.baseConfiguration(oldCondition || ''), + TextEditor.Config.closeBrackets, TextEditor.Config.autocompletion, CodeMirror.EditorView.lineWrapping, TextEditor.Config.showCompletionHint, diff --git a/front_end/ui/components/text_editor/config.ts b/front_end/ui/components/text_editor/config.ts index a9cb97e0a4..450422e8a3 100644 --- a/front_end/ui/components/text_editor/config.ts +++ b/front_end/ui/components/text_editor/config.ts @@ -68,7 +68,10 @@ export const tabMovesFocus = DynamicSetting.bool('textEditorTabMovesFocus', CM.k shift: (view: CM.EditorView): boolean => view.state.doc.length ? CM.indentLess(view) : false, }])); -export const autocompletion = CM.autocompletion({icons: false}); +export const autocompletion = CM.autocompletion({ + icons: false, + optionClass: (option: CM.Completion): string => option.type === 'secondary' ? 'cm-secondaryCompletion' : '', +}); export const sourcesAutocompletion = DynamicSetting.bool('textEditorAutocompletion', autocompletion); @@ -185,7 +188,6 @@ const baseKeymap = CM.keymap.of([ {key: 'Mod-d', run: CM.selectNextOccurrence}, {key: 'Alt-ArrowLeft', mac: 'Ctrl-ArrowLeft', run: CM.cursorSubwordBackward, shift: CM.selectSubwordBackward}, {key: 'Alt-ArrowRight', mac: 'Ctrl-ArrowRight', run: CM.cursorSubwordForward, shift: CM.selectSubwordForward}, - ...CM.closeBracketsKeymap, ...CM.standardKeymap, ...CM.historyKeymap, ]); @@ -219,7 +221,6 @@ export function baseConfiguration(text: string): CM.Extension { CM.EditorState.allowMultipleSelections.of(true), CM.indentOnInput(), CodeHighlighter.CodeHighlighter.highlightStyle, - CM.closeBrackets(), baseKeymap, tabMovesFocus.instance(), bracketMatching.instance(), @@ -234,6 +235,11 @@ export function baseConfiguration(text: string): CM.Extension { ]; } +export const closeBrackets: CM.Extension = [ + CM.closeBrackets(), + CM.keymap.of(CM.closeBracketsKeymap), +]; + // Root editor tooltips at the top of the document, creating a special // element with the editor styles mounted in it for them. This is // annoying, but necessary because a scrollable parent node clips them @@ -299,12 +305,16 @@ export const showCompletionHint = CM.ViewPlugin.fromClass(class { } topCompletion(state: CM.EditorState): string|null { - const completions = CM.currentCompletions(state); - if (!completions.length) { + const completion = CM.selectedCompletion(state); + if (!completion) { return null; } - const {label} = completions[0]; - if (label.length > 100 || label.indexOf('\n') > -1) { + let {label, apply} = completion; + if (typeof apply === 'string') { + label = apply; + apply = undefined; + } + if (apply || label.length > 100 || label.indexOf('\n') > -1 || completion.type === 'secondary') { return null; } const pos = state.selection.main.head; @@ -312,13 +322,11 @@ export const showCompletionHint = CM.ViewPlugin.fromClass(class { if (pos !== lineBefore.to) { return null; } - const textBefore = lineBefore.text.slice(0, pos - lineBefore.from); - for (let i = label.length - 1; i > 0; i--) { - if (textBefore.endsWith(label.slice(0, i)) && !/\w/.test(textBefore.charAt(textBefore.length - i - 1))) { - return label.slice(i); - } + const wordBefore = /[\w$]+$/.exec(lineBefore.text); + if (wordBefore && !label.startsWith(wordBefore[0])) { + return null; } - return null; + return label.slice(wordBefore ? wordBefore[0].length : 0); } }, {decorations: p => p.decorations}); diff --git a/front_end/ui/components/text_editor/javascript.ts b/front_end/ui/components/text_editor/javascript.ts index d06ca9daa5..46d8f003d8 100644 --- a/front_end/ui/components/text_editor/javascript.ts +++ b/front_end/ui/components/text_editor/javascript.ts @@ -90,7 +90,7 @@ export const enum QueryType { PropertyExpression = 2, } -export function getQueryType(tree: CodeMirror.Tree, pos: number): { +export function getQueryType(tree: CodeMirror.Tree, pos: number, doc: CodeMirror.Text): { type: QueryType, from?: number, relatedNode?: CodeMirror.SyntaxNode, @@ -101,13 +101,15 @@ export function getQueryType(tree: CodeMirror.Tree, pos: number): { return null; } - if (node.name === 'VariableName') { - return {type: QueryType.Expression, from: node.from}; - } if (node.name === 'PropertyName') { return parent?.name !== 'MemberExpression' ? null : {type: QueryType.PropertyName, from: node.from, relatedNode: parent}; } + if (node.name === 'VariableName' || + // Treat alphabetic keywords as variables + !node.firstChild && node.to - node.from < 20 && !/[^a-z]/.test(doc.sliceString(node.from, node.to))) { + return {type: QueryType.Expression, from: node.from}; + } if (node.name === 'String') { const parent = node.parent; return parent?.name === 'MemberExpression' && parent.childBefore(node.from)?.name === '[' ? @@ -134,8 +136,8 @@ export function getQueryType(tree: CodeMirror.Tree, pos: number): { export async function javascriptCompletionSource(cx: CodeMirror.CompletionContext): Promise { - const query = getQueryType(CodeMirror.syntaxTree(cx.state), cx.pos); - if (!query || query.from === undefined && !cx.explicit) { + const query = getQueryType(CodeMirror.syntaxTree(cx.state), cx.pos, cx.state.doc); + if (!query || query.from === undefined && !cx.explicit && query.type === QueryType.Expression) { return null; } @@ -162,7 +164,8 @@ export async function javascriptCompletionSource(cx: CodeMirror.CompletionContex if (!objectExpr) { return null; } - result = await completeProperties(cx.state.sliceDoc(objectExpr.from, objectExpr.to), quote); + result = await completeProperties( + cx.state.sliceDoc(objectExpr.from, objectExpr.to), quote, cx.state.sliceDoc(cx.pos, cx.pos + 1) === ']'); } else { return null; } @@ -250,6 +253,7 @@ class PropertyCache { async function completeProperties( expression: string, quoted?: string, + hasBracket: boolean = false, ): Promise { const cache = PropertyCache.instance(); if (!quoted) { @@ -262,7 +266,7 @@ async function completeProperties( if (!context) { return new CompletionSet(); } - const result = completePropertiesInner(expression, context, quoted); + const result = completePropertiesInner(expression, context, quoted, hasBracket); if (!quoted) { cache.set(expression, result); } @@ -275,6 +279,7 @@ async function completePropertiesInner( expression: string, context: SDK.RuntimeModel.ExecutionContext, quoted?: string, + hasBracket: boolean = false, ): Promise { const result = new CompletionSet(); if (!context) { @@ -313,7 +318,7 @@ async function completePropertiesInner( label, type: prop.value?.type === 'function' ? functionType : otherType, }; - if (quoted) { + if (quoted && !hasBracket) { completion.apply = label + ']'; } if (!prop.isOwn) { diff --git a/front_end/ui/components/text_editor/theme.ts b/front_end/ui/components/text_editor/theme.ts index b4e60d7d5d..a5ab45d4c7 100644 --- a/front_end/ui/components/text_editor/theme.ts +++ b/front_end/ui/components/text_editor/theme.ts @@ -18,7 +18,7 @@ export const editorTheme = CM.EditorView.theme({ fontSize: 'var(--source-code-font-size)', }, - '.cm-panels, .cm-tooltip': { + '.cm-panels': { backgroundColor: 'var(--color-background-elevation-1)', }, @@ -110,25 +110,50 @@ export const editorTheme = CM.EditorView.theme({ color: 'var(--color-text-secondary)', }, + '.cm-tooltip': { + boxShadow: 'var(--drop-shadow)', + backgroundColor: 'var(--color-background-elevation-1)', + }, + '.cm-argumentHints': { pointerEvents: 'none', padding: '0 4px', whiteSpace: 'nowrap', lineHeight: '20px', marginBottom: '4px', - boxShadow: 'var(--drop-shadow)', - backgroundColor: 'var(--color-background)', width: 'fit-content', }, '.cm-tooltip.cm-tooltip-autocomplete > ul': { + backgroundColor: 'var(--color-background)', minWidth: '16em', + '& > li.cm-secondaryCompletion': { + display: 'flex', + backgroundColor: 'var(--color-background-elevation-1)', + justifyContent: 'space-between', + '&::before': { + content: '">"', + fontWeight: 'bold', + color: 'var(--color-primary-variant)', + marginRight: '5px', + }, + }, + '& > li:hover': { + backgroundColor: 'var(--item-hover-color)', + }, '& > li[aria-selected]': { backgroundColor: 'var(--color-selected-option-background)', - color: 'var(--color-selected-option)', + '&, &.cm-secondaryCompletion::before': { + color: 'var(--color-selected-option)', + }, }, }, + '.cm-completionMatchedText': { + textDecoration: 'none', + fontWeight: 'bold', + }, + '.cm-highlightedLine': { animation: 'cm-fading-highlight 2s 0s', }, diff --git a/front_end/ui/legacy/components/source_frame/SourceFrame.ts b/front_end/ui/legacy/components/source_frame/SourceFrame.ts index 62a3aa679b..49075f0cbd 100644 --- a/front_end/ui/legacy/components/source_frame/SourceFrame.ts +++ b/front_end/ui/legacy/components/source_frame/SourceFrame.ts @@ -206,6 +206,7 @@ export class SourceFrameImpl extends Common.ObjectWrapper.eventMixin this.dispatchEventToListeners(Events.EditorUpdate, update)), TextEditor.Config.baseConfiguration(doc), + TextEditor.Config.closeBrackets, TextEditor.Config.sourcesAutocompletion.instance(), TextEditor.Config.showWhitespace.instance(), TextEditor.Config.allowScrollPastEof.instance(), diff --git a/front_end/ui/legacy/themeColors.css b/front_end/ui/legacy/themeColors.css index 2b2fd95c93..44ae6f9f8f 100644 --- a/front_end/ui/legacy/themeColors.css +++ b/front_end/ui/legacy/themeColors.css @@ -167,6 +167,7 @@ --color-selected-option: #fff; --color-selected-option-background: #1a73e8; --color-highlighted-line: rgb(255 255 0 / 50%); + --color-completion-hover: rgb(56 121 217 / 10%); --color-search-match-border: rgb(128 128 128); --color-selected-search-match: var(--color-text-primary); --color-selected-search-match-background: rgb(241 234 0); @@ -307,6 +308,7 @@ --color-selected-option: #fff; --color-selected-option-background: #0e639c; --color-highlighted-line: hsl(133deg 100% 30% / 50%); + --color-completion-hover: rgb(56 121 217 / 10%); --color-search-match-border: rgb(128 128 128); --color-selected-search-match: #eee; --color-selected-search-match-background: hsl(133deg 100% 30%); diff --git a/test/e2e/console/console-autocomplete_test.ts b/test/e2e/console/console-autocomplete_test.ts index e86beca9d7..5523397bf6 100644 --- a/test/e2e/console/console-autocomplete_test.ts +++ b/test/e2e/console/console-autocomplete_test.ts @@ -4,7 +4,7 @@ import {click, getBrowserAndPages, typeText, waitFor, waitForNone} from '../../shared/helper.js'; import {beforeEach, describe, it} from '../../shared/mocha-extensions.js'; -import {CONSOLE_TAB_SELECTOR, focusConsolePrompt} from '../helpers/console-helpers.js'; +import {CONSOLE_TAB_SELECTOR, CONSOLE_TOOLTIP_SELECTOR, focusConsolePrompt} from '../helpers/console-helpers.js'; import {openSourcesPanel} from '../helpers/sources-helpers.js'; describe('The Console Tab', async () => { @@ -33,15 +33,15 @@ describe('The Console Tab', async () => { async function objectAutocompleteTest(textAfterObject: string) { const {frontend} = getBrowserAndPages(); - const appearPromise = waitFor('.suggest-box'); + const appearPromise = waitFor(CONSOLE_TOOLTIP_SELECTOR); await typeText('object'); await appearPromise; - const disappearPromise = waitForNone('.suggest-box'); + const disappearPromise = waitForNone(CONSOLE_TOOLTIP_SELECTOR); await frontend.keyboard.press('Escape'); await disappearPromise; - const appearPromise2 = waitFor('.suggest-box'); + const appearPromise2 = waitFor(CONSOLE_TOOLTIP_SELECTOR); await typeText(textAfterObject); await appearPromise2; diff --git a/test/e2e/console/console-repl-mode_test.ts b/test/e2e/console/console-repl-mode_test.ts index 06c4f56d9e..f3563f3c0b 100644 --- a/test/e2e/console/console-repl-mode_test.ts +++ b/test/e2e/console/console-repl-mode_test.ts @@ -6,7 +6,7 @@ import {assert} from 'chai'; import {click, getBrowserAndPages, typeText, waitFor, waitForNone} from '../../shared/helper.js'; import {describe, it} from '../../shared/mocha-extensions.js'; -import {CONSOLE_TAB_SELECTOR, focusConsolePrompt} from '../helpers/console-helpers.js'; +import {CONSOLE_TAB_SELECTOR, CONSOLE_TOOLTIP_SELECTOR, focusConsolePrompt} from '../helpers/console-helpers.js'; describe('The Console Tab', async function() { // The tests in this suite are particularly slow, as they perform a lot of actions @@ -32,11 +32,11 @@ describe('The Console Tab', async function() { // 4. Hit enter // 5. Wait for the results to show up and verify them. - const appearPromise = waitFor('.suggest-box'); + const appearPromise = waitFor(CONSOLE_TOOLTIP_SELECTOR); await typeText('let'); await appearPromise; - const disappearPromise = waitForNone('.suggest-box'); + const disappearPromise = waitForNone(CONSOLE_TOOLTIP_SELECTOR); await frontend.keyboard.press('Escape'); await disappearPromise; @@ -46,11 +46,11 @@ describe('The Console Tab', async function() { return document.querySelectorAll('.console-user-command-result').length === 1; }); - const appearPromise2 = waitFor('.suggest-box'); + const appearPromise2 = waitFor(CONSOLE_TOOLTIP_SELECTOR); await typeText('let'); await appearPromise2; - const disappearPromise2 = waitForNone('.suggest-box'); + const disappearPromise2 = waitForNone(CONSOLE_TOOLTIP_SELECTOR); await frontend.keyboard.press('Escape'); await disappearPromise2; @@ -60,11 +60,11 @@ describe('The Console Tab', async function() { return document.querySelectorAll('.console-user-command-result').length === 2; }); - const appearPromise3 = waitFor('.suggest-box'); + const appearPromise3 = waitFor(CONSOLE_TOOLTIP_SELECTOR); await typeText('x'); await appearPromise3; - const disappearPromise3 = waitForNone('.suggest-box'); + const disappearPromise3 = waitForNone(CONSOLE_TOOLTIP_SELECTOR); await frontend.keyboard.press('Escape'); await disappearPromise3; diff --git a/test/e2e/helpers/console-helpers.ts b/test/e2e/helpers/console-helpers.ts index 03364e6374..db46372603 100644 --- a/test/e2e/helpers/console-helpers.ts +++ b/test/e2e/helpers/console-helpers.ts @@ -15,6 +15,7 @@ export const LOG_LEVELS_SELECTOR = '[aria-label^="Log level: "]'; export const LOG_LEVELS_VERBOSE_OPTION_SELECTOR = '[aria-label^="Verbose"]'; export const CONSOLE_PROMPT_SELECTOR = '.console-prompt-editor-container'; export const CONSOLE_VIEW_SELECTOR = '.console-view'; +export const CONSOLE_TOOLTIP_SELECTOR = '.cm-tooltip'; export const STACK_PREVIEW_CONTAINER = '.stack-preview-container'; export const CONSOLE_MESSAGE_WRAPPER_SELECTOR = '.console-group-messages .console-message-wrapper'; export const CONSOLE_SELECTOR = '.console-user-command-result'; @@ -160,8 +161,8 @@ export async function typeIntoConsole(frontend: puppeteer.Page, message: string) const consoleElement = await waitFor(CONSOLE_PROMPT_SELECTOR, undefined, asyncScope); await consoleElement.type(message); // Wait for autocomplete text to catch up. - const line = await waitFor('.CodeMirror-activeline', consoleElement, asyncScope); - const autocomplete = await line.$('.auto-complete-text'); + const line = await waitFor('[aria-label="Console prompt"]', consoleElement, asyncScope); + const autocomplete = await $(CONSOLE_TOOLTIP_SELECTOR); // The autocomplete element doesn't exist until the first autocomplete suggestion // is actually given. diff --git a/test/unittests/front_end/ui/components/text_editor/TextEditor_test.ts b/test/unittests/front_end/ui/components/text_editor/TextEditor_test.ts index f1f438511e..4102a18a13 100644 --- a/test/unittests/front_end/ui/components/text_editor/TextEditor_test.ts +++ b/test/unittests/front_end/ui/components/text_editor/TextEditor_test.ts @@ -101,7 +101,7 @@ describeWithEnvironment('TextEditor', () => { related?: string, ): Promise { const state = makeState(code, CodeMirror.javascript.javascriptLanguage); - const query = TextEditor.JavaScript.getQueryType(CodeMirror.syntaxTree(state), pos); + const query = TextEditor.JavaScript.getQueryType(CodeMirror.syntaxTree(state), pos, state.doc); if (type === undefined) { assert.isNull(query); } else { @@ -117,6 +117,7 @@ describeWithEnvironment('TextEditor', () => { it('recognizes expression queries', async () => { await testQueryType('foo', 3, TextEditor.JavaScript.QueryType.Expression, 'foo'); await testQueryType('foo ', 4, TextEditor.JavaScript.QueryType.Expression, ''); + await testQueryType('let', 3, TextEditor.JavaScript.QueryType.Expression, 'let'); }); it('recognizes propery name queries', async () => { diff --git a/test/webtests/http/tests/devtools/a11y-axe-core/console-a11y-test-expected.txt b/test/webtests/http/tests/devtools/a11y-axe-core/console-a11y-test-expected.txt index 41fdf3b4c8..0347a5f644 100644 --- a/test/webtests/http/tests/devtools/a11y-axe-core/console-a11y-test-expected.txt +++ b/test/webtests/http/tests/devtools/a11y-axe-core/console-a11y-test-expected.txt @@ -1,4 +1,23 @@ Tests accessibility in the console using the axe-core linter. -aXe violations: [] +aXe violations: [ + { + "ruleDescription": "Ensures ARIA attributes are allowed for an element's role", + "helpUrl": "https://dequeuniversity.com/rules/axe/3.3/aria-allowed-attr?application=axeAPI", + "ruleId": "aria-allowed-attr", + "impact": "critical", + "failedNodes": [ + { + "target": [ + [ + "devtools-text-editor", + ".cm-content" + ] + ], + "html": "
", + "failureSummary": "Fix any of the following:\n ARIA attribute is not allowed: aria-expanded=\"false\"" + } + ] + } +]