Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

459 regex #505

Merged
merged 44 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
47611d9
zwischenstand mit div und innerHtml
NessArt Jun 30, 2023
7e5f77d
zwischenstand mit div und ohne innerHtml aber position fehler
NessArt Jul 6, 2023
c8982b4
WIP cursor pos working
NessArt Aug 9, 2023
cd430ce
WIP div as input
NessArt Aug 9, 2023
3de82fc
doku
NessArt Aug 9, 2023
d1fa822
toast, cursor improvement, second input
NessArt Aug 10, 2023
601ebfe
drittes Inputfeld
NessArt Aug 10, 2023
bf82edc
moved initial highlight call
NessArt Aug 10, 2023
6587d84
toast only when regex new in story
NessArt Aug 11, 2023
e555474
WIP
NessArt Aug 11, 2023
534dead
WIP lifecycle call and keystroke call interfere with each other so th…
NessArt Aug 11, 2023
866a887
lifecycle and keystroke dont interfere
NessArt Aug 11, 2023
a4c93e1
Merge branch 'dev' into 459-regex-frontend
NessArt Sep 22, 2023
e7a375a
style of new divs like old text input
NessArt Sep 22, 2023
0c13cea
regex styling in background
NessArt Oct 4, 2023
87706ef
regex highlight only for selected steps
NessArt Oct 5, 2023
5a6db28
WIP highlighting in then steps and /^m/
NessArt Oct 12, 2023
043bc91
@@ highlight for regex
NessArt Oct 12, 2023
2fe7135
Merge
NessArt Oct 13, 2023
6baef64
merge fix ts file
NessArt Oct 13, 2023
8c59fc6
css merge fix and html merge
NessArt Oct 24, 2023
86c1708
Merge remote-tracking branch 'origin/dev' into 459-regex-frontend
NessArt Oct 24, 2023
e6a96af
wip
NessArt Nov 9, 2023
8685bb0
working multiple scenario highlight
NessArt Nov 16, 2023
d861ee2
highlight regex when insert with copy
NessArt Nov 16, 2023
e56a77e
remove console log
NessArt Nov 16, 2023
2b1edc4
darkmode style for regexHightlight
NessArt Nov 16, 2023
0d128c5
Merge branch 'dev' into 459-regex-frontend
NessArt Nov 16, 2023
f572c4a
resolve merge conflict
NessArt Nov 16, 2023
9b8e28e
doku and saveBool change in ms
NessArt Nov 17, 2023
7e1724e
highlight for /^__$/ and [Regex:__]
NessArt Nov 17, 2023
be3be85
highlight for ms
NessArt Nov 17, 2023
b067163
toastr information
NessArt Nov 17, 2023
41282a7
wip new highlighting and tooltip
NessArt Nov 23, 2023
7bf6990
wip tooltip
NessArt Nov 23, 2023
fcff698
less redundancy, tooltip for regex, mouse position fix
NessArt Nov 29, 2023
28f4651
shortend highlight logic, ms with tooltip
NessArt Nov 30, 2023
433b3f0
fix no highlight bug when story or scenario change
NessArt Dec 1, 2023
5f5cf22
Merge branch 'dev' into 459-regex-frontend
NessArt Dec 1, 2023
44a2baa
allow inline regex execution for then text
jonycoo Dec 6, 2023
296b1d1
include nested Brackets: Regex
jonycoo Dec 7, 2023
76f7084
expand regex to all then text
jonycoo Dec 12, 2023
b2ce4bf
Fix: Resolve bug causing undefined in non regex steps
jonycoo Dec 14, 2023
2af5539
remove duplicated code with new service
NessArt Dec 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions backend/features/step_definitions/stepdefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,17 @@ Then('So I will be navigated to the website: {string}', async function checkUrl(
await driver.sleep(100 + currentParameters.waitTime);
});

const resolveRegex = (rawString) => {
// undefined to empty string
rawString = !rawString ? '' : rawString;
const regex = /\{Regex:([^}]*(?:\{[^}]*\}[^}]*)*)(\})(?=\s|$)/g;
return rawString.replace(regex, '($1)');
};

// Search a textfield in the html code and assert it with a Text
Then('So I can see the text {string} in the textbox: {string}', async function checkForTextInField(expectedText, label) {
const resultString = resolveRegex(expectedText);

const world = this;

const identifiers = [`//*[@id='${label}']`, `//*[@*='${label}']`, `//*[contains(@*, '${label}')]`,
Expand All @@ -677,7 +686,7 @@ Then('So I can see the text {string} in the textbox: {string}', async function c
let resp = await elem.getText();
resp = resp == '' ? await elem.getAttribute('value') : resp;
resp = resp == '' ? await elem.getAttribute('outerHTML') : resp;
match(resp, RegExp(expectedText.toString()), `Textfield does not contain the string/regex: ${expectedText} , actual: ${resp}`);
match(resp, RegExp(resultString), `Textfield does not contain the string/regex: ${resultString} , actual: ${resp}`);
})
.catch(async (e) => {
await driver.takeScreenshot().then(async (buffer) => {
Expand All @@ -689,7 +698,8 @@ Then('So I can see the text {string} in the textbox: {string}', async function c
});

// Search if a is text in html code
Then('So I can see the text: {string}', async function (expectedText) { // text is present
Then('So I can see the text: {string}', async function textPresent(expectedText) { // text is present
const resultString = resolveRegex(expectedText);
const world = this;
try {
await driver.wait(async () => driver.executeScript('return document.readyState').then(async (readyState) => readyState === 'complete'));
Expand All @@ -699,7 +709,7 @@ Then('So I can see the text: {string}', async function (expectedText) { // text
const innerHtmlBody = await driver.executeScript('return document.documentElement.innerHTML');
const outerHtmlBody = await driver.executeScript('return document.documentElement.outerHTML');
const bodyAll = cssBody + innerHtmlBody + outerHtmlBody;
match(bodyAll, RegExp(expectedText.toString()), `Page HTML does not contain the string/regex: ${expectedText}`);
match(bodyAll, RegExp(resultString), `Page HTML does not contain the string/regex: ${resultString}`);
});
} catch (e) {
await driver.takeScreenshot().then(async (buffer) => {
Expand All @@ -711,7 +721,7 @@ Then('So I can see the text: {string}', async function (expectedText) { // text
});

// Search a textfield in the html code and assert if it's empty
Then('So I can\'t see text in the textbox: {string}', async function (label) {
Then('So I can\'t see text in the textbox: {string}', async function textAbsent(label) {
const world = this;
const identifiers = [`//*[@id='${label}']`, `//*[@*='${label}']`, `//*[contains(@id, '${label}')]`, `${label}`];
const promises = [];
Expand Down Expand Up @@ -800,6 +810,7 @@ Then('So the picture {string} has the name {string}', async function checkPictur

// Search if a text isn't in html code
Then('So I can\'t see the text: {string}', async function checkIfTextIsMissing(expectedText) {
const resultString = resolveRegex(expectedText.toString());
const world = this;
try {
await driver.wait(async () => driver.executeScript('return document.readyState').then(async (readyState) => readyState === 'complete'));
Expand All @@ -808,7 +819,7 @@ Then('So I can\'t see the text: {string}', async function checkIfTextIsMissing(e
const innerHtmlBody = await driver.executeScript('return document.documentElement.innerHTML');
const outerHtmlBody = await driver.executeScript('return document.documentElement.outerHTML');
const bodyAll = cssBody + innerHtmlBody + outerHtmlBody;
doesNotMatch(bodyAll, RegExp(expectedText.toString()), `Page HTML does contain the string/regex: ${expectedText}`);
doesNotMatch(bodyAll, RegExp(resultString), `Page HTML does contain the string/regex: ${resultString}`);
});
} catch (e) {
await driver.takeScreenshot().then(async (buffer) => {
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/app/Services/highlight-input.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { HighlightInputService } from './highlight-input.service';

describe('HighlightInputService', () => {
let service: HighlightInputService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(HighlightInputService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
154 changes: 154 additions & 0 deletions frontend/src/app/Services/highlight-input.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Injectable } from '@angular/core';
import { ToastrService } from 'ngx-toastr';

@Injectable({
providedIn: 'root'
})
export class HighlightInputService {

constructor(public toastr: ToastrService) { }
targetOffset: number = 0;

/**
* Add value and highlight regex, Style regex in value and give value to addToValue() function
* Value is in textContent and style is in innerHTML
* If initialCall only check if a regex is already there and hightlight it
* If valueIndex only hightlights regex in first field of regexSteps, only then steps for now. Gets checked with stepPre
* Get cursor position with getCaretCharacterOffsetWithin Helper and set position again, else it is lost because of overwriting and changing the HTML Element in case of hightlighting
* @param element HTML element of contentedible div
* @param initialCall if call is from ngAfterView
* @param isDark theming Service
* @param regexInStory if first regex in Story
* @param valueIndex index of input field
* @param stepPre pre text of step
* @returns if a regex was detected
*/
highlightRegex(element, initialCall?:boolean, isDark?:boolean, regexInStory?:boolean, valueIndex?: number, stepPre?: string) {
const regexPattern =/(\{Regex:)(.*?)(\})(?=\s|$)/g;// Regex pattern to recognize and highlight regex expressions -> start with {Regex: and end with }

const textField = element
const textContent = textField.textContent;
//Get current cursor position
const offset = this.getCaretCharacterOffsetWithin(textField)
const regexSteps = ['So I can see the text', 'So I can see the text:', 'So I can\'t see the text:', 'So I can\'t see text in the textbox:']
var regexDetected = false;

let highlightedText;
if(!valueIndex || (0==valueIndex && regexSteps.includes(stepPre))){
if(isDark){
highlightedText = textContent.replace(regexPattern, (match, match1, match2, match3) => {
regexDetected = true;
return `<span uk-tooltip="title:Regular Expression detected!;pos:right">`+
`<span style="color: var(--light-grey); font-weight: bold">${match1}</span>`+
`<span style="color: var(--light-blue); font-weight: bold">${match2}</span>`+
`<span style="color: var(--light-grey); font-weight: bold">${match3}</span></span>`;
});
} else{
highlightedText = textContent.replace(regexPattern, (match, match1, match2, match3) => {
regexDetected = true;
return `<span uk-tooltip="title:Regular Expression detected!;pos:right">`+
`<span style="color: var(--brown-grey); font-weight: bold">${match1}</span>`+
`<span style="color: var(--ocean-blue); font-weight: bold">${match2}</span>`+
`<span style="color: var(--brown-grey); font-weight: bold">${match3}</span></span>`;
});
}
}
textField.innerHTML = highlightedText ? highlightedText : textContent;

// Toastr logic
if(initialCall && regexDetected) {
regexInStory = true
}
if(regexDetected && !regexInStory){
this.toastr.info('View our Documentation for more Info','Regular Expression detected!');
}

// Set cursor to correct position
if(!initialCall){
if (regexDetected) { //maybe not needed
const selection = window.getSelection();
selection.removeAllRanges()

// Call the function to find the correct node and offset
this.targetOffset = offset
const result = this.findNodeAndOffset(textField);

if (result !== null) {
const [node, offsetIndex] = result;
requestAnimationFrame(() => {
if (node.nodeType === 3) {
// Text node
selection.setBaseAndExtent(node, offsetIndex, node, offsetIndex);
} else if (node.nodeType === 1 && node.childNodes.length > 0) {
// Element node with child nodes (e.g., <span>)
selection.setBaseAndExtent(node.childNodes[0], offsetIndex, node.childNodes[0], offsetIndex);
}
});
}
} else {
requestAnimationFrame(() => {
const selection = window.getSelection();
selection.removeAllRanges();
selection.setBaseAndExtent(textField.firstChild, offset, textField.firstChild, offset)
})
}
}
return regexDetected;
}

/**
* Helper for Regex Highlighter, find right node and index for current cursor position
* @param element HTMLElement
* @returns node: node with cursor, number: offest of cursor in node
*/
findNodeAndOffset(element: Node): [Node, number] | null {
if (element.nodeType === 3) {
// Text node
const textLength = (element.nodeValue || "").length;
if (this.targetOffset <= textLength) {
return [element, this.targetOffset];
} else {
this.targetOffset -= textLength;
}
} else if (element.nodeType === 1){
// Element node
for (let i = 0; i < element.childNodes.length; i++) {
const child = element.childNodes[i];
const result = this.findNodeAndOffset(child);
if (result !== null) {
return result;
}
}
}
return null;
}

/**
* Helper for Regex Highlighter, extract current cursor position
* @param element HTMLElement
* @returns num, offset of cursor position
*/
getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
}
24 changes: 24 additions & 0 deletions frontend/src/app/base-editor/base-editor.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,27 @@ input.background {
padding-top: 0;
}

.contentEditableElement { /*needed for trailing whitespaces*/
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}

.inputEditableDiv{
margin: 1px;
padding: 1px;
border: none;
border-bottom: 1px solid #333;
position: relative;
top: -2px;
width: auto;
min-width: 5px;

margin-left: 10px;
margin-right: 10px;
padding-left: 5px;
padding-right: 5px;
}

Loading
Loading