chore: use codemirror in the on-hover locator editor (#28090)
This commit is contained in:
Родитель
fae5dd898a
Коммит
1b3349d091
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче