chore: use codemirror in the on-hover locator editor (#28090)

This commit is contained in:
Pavel Feldman 2023-11-10 22:00:28 -08:00 коммит произвёл GitHub
Родитель fae5dd898a
Коммит 1b3349d091
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 149 добавлений и 68 удалений

20
package-lock.json сгенерированный
Просмотреть файл

@ -2283,10 +2283,10 @@
"mimic-response": "^1.0.0"
}
},
"node_modules/codemirror": {
"version": "5.65.9",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz",
"integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw=="
"node_modules/codemirror-shadow-1": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/codemirror-shadow-1/-/codemirror-shadow-1-0.0.1.tgz",
"integrity": "sha512-kD3OZpCCHr3LHRKfbGx5IogHTWq4Uo9jH2bXPVa7/n6ppkgI66rx4tniQY1BpqWp/JNhQmQsXhQoaZ1TH6t0xQ=="
},
"node_modules/color-convert": {
"version": "1.9.3",
@ -7310,7 +7310,7 @@
"packages/web": {
"version": "0.0.0",
"dependencies": {
"codemirror": "^5.65.9",
"codemirror-shadow-1": "0.0.1",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"
}
@ -8962,10 +8962,10 @@
"mimic-response": "^1.0.0"
}
},
"codemirror": {
"version": "5.65.9",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz",
"integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw=="
"codemirror-shadow-1": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/codemirror-shadow-1/-/codemirror-shadow-1-0.0.1.tgz",
"integrity": "sha512-kD3OZpCCHr3LHRKfbGx5IogHTWq4Uo9jH2bXPVa7/n6ppkgI66rx4tniQY1BpqWp/JNhQmQsXhQoaZ1TH6t0xQ=="
},
"color-convert": {
"version": "1.9.3",
@ -11862,7 +11862,7 @@
"web": {
"version": "file:packages/web",
"requires": {
"codemirror": "^5.65.9",
"codemirror-shadow-1": "0.0.1",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"
}

Просмотреть файл

@ -10,7 +10,7 @@ This project incorporates components from the projects listed below. The origina
- balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match)
- brace-expansion@1.1.11 (https://github.com/juliangruber/brace-expansion)
- buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32)
- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror)
- codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror)
- colors@1.4.0 (https://github.com/Marak/colors.js)
- commander@8.3.0 (https://github.com/tj/commander.js)
- concat-map@0.0.1 (https://github.com/substack/node-concat-map)
@ -326,11 +326,11 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL
=========================================
END OF buffer-crc32@0.2.13 AND INFORMATION
%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE
%% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
Copyright (C) 2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -350,7 +350,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
END OF codemirror@5.65.9 AND INFORMATION
END OF codemirror-shadow-1@0.0.1 AND INFORMATION
%% colors@1.4.0 NOTICES AND INFORMATION BEGIN HERE
=========================================

Просмотреть файл

@ -44,8 +44,9 @@ x-pw-dialog {
display: flex;
flex-direction: column;
position: absolute;
min-width: 500px;
min-height: 200px;
width: 500px;
height: 200px;
z-index: 10;
}
x-pw-dialog-body {
@ -54,6 +55,17 @@ x-pw-dialog-body {
flex: auto;
}
x-pw-dialog-body label {
margin: 10px;
display: flex;
align-items: center;
cursor: pointer;
}
x-pw-dialog-body input {
cursor: pointer;
}
x-pw-highlight {
position: absolute;
top: 0;
@ -205,27 +217,17 @@ x-pw-overlay x-pw-tool-item {
margin: 2px;
}
input.locator-editor {
display: flex;
padding: 10px;
flex: none;
border: none;
border-bottom: 1px solid #dddddd;
}
input.locator-editor:focus,
textarea.text-editor:focus {
outline: none;
}
textarea.text-editor {
font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif;
flex: auto;
border: none;
padding: 10px;
margin: 10px;
color: #333;
}
textarea.text-editor:focus {
outline: none;
}
x-div {
display: block;
@ -242,3 +244,16 @@ x-spacer {
*[hidden] {
display: none !important;
}
x-locator-editor {
flex: none;
width: 100%;
height: 60px;
padding: 4px;
border-bottom: 1px solid #dddddd;
}
.CodeMirror {
width: 100% !important;
height: 100% !important;
}

Просмотреть файл

@ -60,7 +60,7 @@ export class Highlight {
this._glassPaneElement.style.pointerEvents = 'none';
this._glassPaneElement.style.display = 'flex';
this._glassPaneElement.style.backgroundColor = 'transparent';
for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mousemove', 'mouseleave', 'focus', 'scroll']) {
for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mouseleave', 'focus', 'scroll']) {
this._glassPaneElement.addEventListener(eventName, e => {
e.stopPropagation();
e.stopImmediatePropagation();

Просмотреть файл

@ -23,10 +23,28 @@ import { Highlight, type HighlightOptions } from '../injected/highlight';
import { isInsideScope } from './domUtils';
import { elementText } from './selectorUtils';
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
import type { Language } from '../../utils/isomorphic/locatorGenerators';
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
import { parseSelector } from '@isomorphic/selectorParser';
import { normalizeWhiteSpace } from '@isomorphic/stringUtils';
// @ts-ignore @no-check-deps
import CodeMirrorImpl from 'codemirror-shadow-1';
import type CodeMirrorType from 'codemirror';
// @no-check-deps
import codemirrorCSS from 'codemirror-shadow-1/lib/codemirror.css?inline';
// @no-check-deps
import 'codemirror-shadow-1/mode/css/css';
// @no-check-deps
import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed';
// @no-check-deps
import 'codemirror-shadow-1/mode/javascript/javascript';
// @no-check-deps
import 'codemirror-shadow-1/mode/python/python';
// @no-check-deps
import 'codemirror-shadow-1/mode/clike/clike';
const CodeMirror = CodeMirrorImpl as typeof CodeMirrorType;
interface RecorderDelegate {
performAction?(action: actions.Action): Promise<void>;
recordAction?(action: actions.Action): Promise<void>;
@ -507,7 +525,7 @@ class TextAssertionTool implements RecorderTool {
selector,
signals: [],
// Interestingly, inputElement.checked is reversed inside this event handler.
checked: (target as HTMLInputElement).checked,
checked: !(target as HTMLInputElement).checked,
};
} else {
return {
@ -576,12 +594,21 @@ class TextAssertionTool implements RecorderTool {
this._dialogElement.appendChild(toolbarElement);
const bodyElement = this._recorder.document.createElement('x-pw-dialog-body');
const locatorElement = this._recorder.document.createElement('input');
locatorElement.classList.add('locator-editor');
locatorElement.value = asLocator(this._recorder.state.language, this._action.selector);
locatorElement.addEventListener('input', () => {
const cmStyle = this._recorder.document.createElement('style');
const cmElement = this._recorder.document.createElement('x-locator-editor');
cmStyle.textContent = codemirrorCSS;
bodyElement.appendChild(cmStyle);
bodyElement.appendChild(cmElement);
const cm = CodeMirror(cmElement, {
value: asLocator(this._recorder.state.language, this._action.selector),
mode: cmModeForLanguage(this._recorder.state.language),
readOnly: false,
lineNumbers: false,
lineWrapping: true,
});
cm.on('change', () => {
if (this._action) {
const selector = locatorOrSelectorAsSelector(this._recorder.state.language, locatorElement.value, this._recorder.state.testIdAttributeName);
const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName);
const model: HighlightModel = {
selector,
elements: this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document),
@ -590,27 +617,46 @@ class TextAssertionTool implements RecorderTool {
this._recorder.updateHighlight(model, true);
}
});
const textElement = this._recorder.document.createElement('textarea');
textElement.value = this._renderValue(this._action);
textElement.classList.add('text-editor');
textElement.addEventListener('input', () => {
if (this._action?.name === 'assertText')
this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full);
if (this._action?.name === 'assertChecked')
this._action.checked = textElement.value === 'true';
if (this._action?.name === 'assertValue')
this._action.value = textElement.value;
});
let elementToFocus: HTMLElement | null = null;
if (this._action.name !== 'assertChecked') {
const textElement = this._recorder.document.createElement('textarea');
textElement.setAttribute('spellcheck', 'false');
textElement.value = this._renderValue(this._action);
textElement.classList.add('text-editor');
textElement.addEventListener('input', () => {
if (this._action?.name === 'assertText')
this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full);
if (this._action?.name === 'assertChecked')
this._action.checked = textElement.value === 'true';
if (this._action?.name === 'assertValue')
this._action.value = textElement.value;
});
bodyElement.appendChild(textElement);
elementToFocus = textElement;
} else {
const labelElement = this._recorder.document.createElement('label');
labelElement.textContent = 'Value:';
const checkboxElement = this._recorder.document.createElement('input');
labelElement.appendChild(checkboxElement);
checkboxElement.type = 'checkbox';
checkboxElement.checked = this._action.checked;
checkboxElement.addEventListener('change', () => {
if (this._action?.name === 'assertChecked')
this._action.checked = checkboxElement.checked;
});
bodyElement.appendChild(labelElement);
elementToFocus = labelElement;
}
bodyElement.appendChild(locatorElement);
bodyElement.appendChild(textElement);
this._dialogElement.appendChild(bodyElement);
this._recorder.highlight.appendChild(this._dialogElement);
const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement);
this._dialogElement.style.top = position.anchorTop + 'px';
this._dialogElement.style.left = position.anchorLeft + 'px';
textElement.focus();
elementToFocus?.focus();
cm.refresh();
}
private _createLabel(action: actions.AssertAction) {
@ -1131,4 +1177,14 @@ export class PollingRecorder implements RecorderDelegate {
}
}
function cmModeForLanguage(language: Language): string {
if (language === 'python')
return 'python';
if (language === 'java')
return 'text/x-java';
if (language === 'csharp')
return 'text/x-csharp';
return 'javascript';
}
export default PollingRecorder;

Просмотреть файл

@ -99,7 +99,7 @@ This project incorporates components from the projects listed below. The origina
- chalk@4.1.2 (https://github.com/chalk/chalk)
- chokidar@3.5.3 (https://github.com/paulmillr/chokidar)
- ci-info@3.8.0 (https://github.com/watson/ci-info)
- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror)
- codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror)
- color-convert@1.9.3 (https://github.com/Qix-/color-convert)
- color-convert@2.0.1 (https://github.com/Qix-/color-convert)
- color-name@1.1.3 (https://github.com/dfcreative/color-name)
@ -3156,11 +3156,11 @@ SOFTWARE.
=========================================
END OF ci-info@3.8.0 AND INFORMATION
%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE
%% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
Copyright (C) 2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -3180,7 +3180,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
END OF codemirror@5.65.9 AND INFORMATION
END OF codemirror-shadow-1@0.0.1 AND INFORMATION
%% color-convert@1.9.3 NOTICES AND INFORMATION BEGIN HERE
=========================================

Просмотреть файл

@ -4,7 +4,7 @@
"version": "0.0.0",
"scripts": {},
"dependencies": {
"codemirror": "^5.65.9",
"codemirror-shadow-1": "0.0.1",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0"
}

Просмотреть файл

@ -14,13 +14,15 @@
limitations under the License.
*/
import codemirror from 'codemirror';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/python/python';
import 'codemirror/mode/clike/clike';
// @ts-ignore
import codemirror from 'codemirror-shadow-1';
import type codemirrorType from 'codemirror';
import 'codemirror-shadow-1/lib/codemirror.css';
import 'codemirror-shadow-1/mode/css/css';
import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed';
import 'codemirror-shadow-1/mode/javascript/javascript';
import 'codemirror-shadow-1/mode/python/python';
import 'codemirror-shadow-1/mode/clike/clike';
export type CodeMirror = typeof codemirror;
export type CodeMirror = typeof codemirrorType;
export default codemirror;

Просмотреть файл

@ -79,7 +79,7 @@ async function innerCheckDeps(root) {
});
const sourceFiles = program.getSourceFiles();
const errors = [];
sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x, x.fileName));
sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x, x.fileName, x.getFullText()));
if (errors.length) {
for (const error of errors)
@ -112,7 +112,7 @@ async function innerCheckDeps(root) {
return packageJSON;
function visit(node, fileName) {
function visit(node, fileName, text) {
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
if (node.importClause) {
if (node.importClause.isTypeOnly)
@ -151,6 +151,14 @@ async function innerCheckDeps(root) {
return;
}
const fullStart = node.getFullStart();
const commentRanges = ts.getLeadingCommentRanges(text, fullStart);
for (const range of commentRanges || []) {
const comment = text.substring(range.pos, range.end);
if (comment.includes('@no-check-deps'))
return;
}
if (importName.startsWith('@'))
deps.add(importName.split('/').slice(0, 2).join('/'));
else
@ -159,7 +167,7 @@ async function innerCheckDeps(root) {
if (!allowExternalImport(importName, packageJSON))
errors.push(`Disallowed external dependency ${importName} from ${path.relative(root, fileName)}`);
}
ts.forEachChild(node, x => visit(x, fileName));
ts.forEachChild(node, x => visit(x, fileName, text));
}
function calculateDeps(from) {

Просмотреть файл

@ -59,7 +59,7 @@ This project incorporates components from the projects listed below. The origina
}
}
const packages = await checkDir('node_modules/codemirror');
const packages = await checkDir('node_modules/codemirror-shadow-1');
for (const [key, value] of Object.entries(packages)) {
if (value.licenseText)
allPackages[key] = value;