diff --git a/demos/_unit-test/dropdown-html.php b/demos/_unit-test/dropdown-html.php
new file mode 100644
index 0000000000..667c7e2b81
--- /dev/null
+++ b/demos/_unit-test/dropdown-html.php
@@ -0,0 +1,112 @@
+ $v . ' "\' <';
+$htmlValues = [
+ $makeTestStringFx('d') => $makeTestStringFx('dTitle'),
+ $makeTestStringFx('u') => $makeTestStringFx('uTitle'),
+];
+
+$form = Form::addTo($app);
+
+$form->addControl('dropdown_single', [
+ Form\Control\Dropdown::class,
+ 'caption' => 'Dropdown single',
+ 'values' => $htmlValues,
+]);
+
+$form->addControl('dropdown_single2', [
+ Form\Control\Dropdown::class,
+ 'caption' => 'Dropdown single allow addition',
+ 'values' => $htmlValues,
+ 'dropdownOptions' => ['allowAdditions' => true],
+]);
+
+$form->addControl('dropdown_multi', [
+ Form\Control\Dropdown::class,
+ 'caption' => 'Dropdown multiple',
+ 'multiple' => true,
+ 'values' => $htmlValues,
+]);
+
+$form->addControl('dropdown_multi2', [
+ Form\Control\Dropdown::class,
+ 'caption' => 'Dropdown multiple allow addition',
+ 'multiple' => true,
+ 'values' => $htmlValues,
+ 'dropdownOptions' => ['allowAdditions' => true],
+]);
+
+$lookupModel = new Model();
+$lookupModel->addField('id', ['type' => 'string']);
+$lookupModel->addField('name', ['type' => 'string']);
+$lookupModel->setPersistence(new Persistence\Array_(array_combine(
+ array_keys($htmlValues),
+ array_map(static fn ($v) => ['name' => $v], $htmlValues)
+)));
+
+/* $form->addControl('lookup_single', [
+ Form\Control\Lookup::class,
+ 'caption' => 'Lookup single',
+ 'model' => $lookupModel,
+]);
+
+$form->addControl('lookup_single2', [
+ Form\Control\Lookup::class,
+ 'caption' => 'Lookup single allow addition',
+ 'model' => $lookupModel,
+ 'settings' => ['allowAdditions' => true],
+]);
+
+$form->addControl('lookup_multi', [
+ Form\Control\Lookup::class,
+ 'caption' => 'Lookup multiple',
+ 'multiple' => true,
+ 'model' => $lookupModel,
+]);
+
+$form->addControl('lookup_multi2', [
+ Form\Control\Lookup::class,
+ 'caption' => 'Lookup multiple allow addition',
+ 'multiple' => true,
+ 'model' => $lookupModel,
+ 'settings' => ['allowAdditions' => true],
+]); */
+
+foreach (array_keys($form->entity->getFields()) as $k) {
+ $form->entity->set($k, $makeTestStringFx('d'));
+}
+
+$initData = $form->entity->get();
+
+$form->onSubmit(static function (Form $form) use ($app, $initData, $makeTestStringFx) {
+ $message = $app->encodeJson($form->entity->get());
+
+ // TODO remove once https://github.com/fomantic/Fomantic-UI/pull/3205 is merged
+ foreach ($form->entity->get() as $k => $v) {
+ $form->entity->set($k, str_replace('"', '"', $v));
+ }
+
+ $view = new Message('Values:');
+ $view->setApp($form->getApp());
+ $view->invokeInit();
+ $view->text->addParagraph($message);
+ $view->text->addParagraph('match init: ' . ($form->entity->get() === $initData));
+ $view->text->addParagraph('match u add: ' . ($form->entity->get() === array_map(static fn ($k) => (str_contains($k, 'multi') ? $initData[$k] . ',' : '') . $makeTestStringFx('u'), array_combine(array_keys($initData), array_keys($initData)))));
+ $view->text->addParagraph('match empty: ' . ($form->entity->get() === array_map(static fn () => '', $initData)));
+ $view->text->addParagraph('match u only: ' . ($form->entity->get() === array_map(static fn () => $makeTestStringFx('u'), $initData)));
+
+ return $view;
+});
diff --git a/src/Behat/Context.php b/src/Behat/Context.php
index 1cfad1ab6d..0c5932e2d4 100644
--- a/src/Behat/Context.php
+++ b/src/Behat/Context.php
@@ -216,6 +216,13 @@ protected function assertNoDuplicateId(): void
}
}
+ private function quoteXpath(string $value): string
+ {
+ return str_contains($value, '\'')
+ ? 'concat(\'' . str_replace('\'', '\', "\'", \'', $value) . '\')'
+ : '\'' . $value . '\'';
+ }
+
/**
* @return array{ 'css'|'xpath', string }
*/
@@ -271,27 +278,35 @@ protected function findElement(?NodeElement $context, string $selector): NodeEle
return $elements[0];
}
- protected function unquoteStepArgument(string $argument): string
+ protected function unquoteStepArgument(string $value): string
{
- // copied from https://github.com/Behat/MinkExtension/blob/v2.2/src/Behat/MinkExtension/Context/MinkContext.php#L567
- return str_replace('\"', '"', $argument);
+ assert(str_starts_with($value, '"') && str_ends_with($value, '"'));
+ $res = substr($value, 1, -1);
+
+ // based on https://github.com/Behat/MinkExtension/blob/v2.2/src/Behat/MinkExtension/Context/MinkContext.php#L567
+ return str_replace(['\\\\', '\"'], ['\\', '"'], $res);
}
/**
* Sleep for a certain time in ms.
*
- * @When I wait :arg1 ms
+ * @When ~^I wait ("(?:\\[\\"]|[^"])*+") ms$~
*/
- public function iWait(int $ms): void
+ public function iWait(string $ms): void
{
+ $ms = (int) $this->unquoteStepArgument($ms);
+
$this->getSession()->wait($ms);
}
/**
- * @When I write :arg1 into selector :selector
+ * @When ~^I write ("(?:\\[\\"]|[^"])*+") into selector ("(?:\\[\\"]|[^"])*+")$~
*/
public function iPressWrite(string $text, string $selector): void
{
+ $text = $this->unquoteStepArgument($text);
+ $selector = $this->unquoteStepArgument($selector);
+
if ($selector === 'document' && $text === '[escape]') {
$this->getSession()->executeScript('document.dispatchEvent(new KeyboardEvent(\'keydown\', {keyCode: 27, which: 27}))');
@@ -303,10 +318,13 @@ public function iPressWrite(string $text, string $selector): void
}
/**
- * @When I drag selector :selector onto selector :selectorTarget
+ * @When ~^I drag selector ("(?:\\[\\"]|[^"])*+") onto selector ("(?:\\[\\"]|[^"])*+")$~
*/
public function iDragElementOnto(string $selector, string $selectorTarget): void
{
+ $selector = $this->unquoteStepArgument($selector);
+ $selectorTarget = $this->unquoteStepArgument($selectorTarget);
+
$elem = $this->findElement(null, $selector);
$elemTarget = $this->findElement(null, $selectorTarget);
$this->getSession()->getDriver()->dragTo($elem->getXpath(), $elemTarget->getXpath());
@@ -315,28 +333,34 @@ public function iDragElementOnto(string $selector, string $selectorTarget): void
// {{{ button
/**
- * @When I press button :arg1
+ * @When ~^I press button ("(?:\\[\\"]|[^"])*+")$~
*/
public function iPressButton(string $buttonLabel): void
{
- $button = $this->findElement(null, '//div[text()="' . $buttonLabel . '"]');
+ $buttonLabel = $this->unquoteStepArgument($buttonLabel);
+
+ $button = $this->findElement(null, '//div[text()=' . $this->quoteXpath($buttonLabel) . ']');
$button->click();
}
/**
- * @Then I see button :arg1
+ * @Then ~^I see button ("(?:\\[\\"]|[^"])*+")$~
*/
public function iSeeButton(string $buttonLabel): void
{
- $this->findElement(null, '//div[text()="' . $buttonLabel . '"]');
+ $buttonLabel = $this->unquoteStepArgument($buttonLabel);
+
+ $this->findElement(null, '//div[text()=' . $this->quoteXpath($buttonLabel) . ']');
}
/**
- * @Then I don't see button :arg1
+ * @Then ~^I don't see button ("(?:\\[\\"]|[^"])*+")$~
*/
public function idontSeeButton(string $text): void
{
- $element = $this->findElement(null, '//div[text()="' . $text . '"]');
+ $text = $this->unquoteStepArgument($text);
+
+ $element = $this->findElement(null, '//div[text()=' . $this->quoteXpath($text) . ']');
if (!str_contains($element->getAttribute('style'), 'display: none')) {
throw new \Exception('Element with text "' . $text . '" must be invisible');
}
@@ -347,18 +371,22 @@ public function idontSeeButton(string $text): void
// {{{ link
/**
- * @Given I click link :arg1
+ * @Given ~^I click link ("(?:\\[\\"]|[^"])*+")$~
*/
public function iClickLink(string $label): void
{
- $this->findElement(null, '//a[text()="' . $label . '"]')->click();
+ $label = $this->unquoteStepArgument($label);
+
+ $this->findElement(null, '//a[text()=' . $this->quoteXpath($label) . ']')->click();
}
/**
- * @When I click using selector :selector
+ * @When ~^I click using selector ("(?:\\[\\"]|[^"])*+")$~
*/
public function iClickUsingSelector(string $selector): void
{
+ $selector = $this->unquoteStepArgument($selector);
+
$element = $this->findElement(null, $selector);
$element->click();
}
@@ -368,10 +396,12 @@ public function iClickUsingSelector(string $selector): void
*
* One solution can be waiting for AJAX after each \WebDriver\AbstractWebDriver::curl() call.
*
- * @When PATCH DRIVER I click using selector :selector
+ * @When ~^PATCH DRIVER I click using selector ("(?:\\[\\"]|[^"])*+")$~
*/
public function iClickPatchedUsingSelector(string $selector): void
{
+ $selector = $this->unquoteStepArgument($selector);
+
$element = $this->findElement(null, $selector);
$driver = $this->getSession()->getDriver();
@@ -384,19 +414,24 @@ public function iClickPatchedUsingSelector(string $selector): void
}
/**
- * @When I click paginator page :arg1
+ * @When ~^I click paginator page ("(?:\\[\\"]|[^"])*+")$~
*/
public function iClickPaginatorPage(string $pageNumber): void
{
+ $pageNumber = $this->unquoteStepArgument($pageNumber);
+
$element = $this->findElement(null, 'a.item[data-page="' . $pageNumber . '"]');
$element->click();
}
/**
- * @When I fill field using :selector with :value
+ * @When ~^I fill field using ("(?:\\[\\"]|[^"])*+") with ("(?:\\[\\"]|[^"])*+")$~
*/
public function iFillField(string $selector, string $value): void
{
+ $selector = $this->unquoteStepArgument($selector);
+ $value = $this->unquoteStepArgument($value);
+
$element = $this->findElement(null, $selector);
$element->setValue($value);
}
@@ -406,43 +441,47 @@ public function iFillField(string $selector, string $value): void
// {{{ modal
/**
- * @When I press Modal button :arg
+ * @When ~^I press Modal button ("(?:\\[\\"]|[^"])*+")$~
*/
public function iPressModalButton(string $buttonLabel): void
{
+ $buttonLabel = $this->unquoteStepArgument($buttonLabel);
+
$modal = $this->findElement(null, '.modal.visible.active.front');
- $button = $this->findElement($modal, '//div[text()="' . $buttonLabel . '"]');
+ $button = $this->findElement($modal, '//div[text()=' . $this->quoteXpath($buttonLabel) . ']');
$button->click();
}
/**
- * @Then Modal is open with text :arg1
- * @Then Modal is open with text :arg1 in selector :arg2
+ * @Then ~^Modal is open with text ("(?:\\[\\"]|[^"])*+")$~
+ * @Then ~^Modal is open with text ("(?:\\[\\"]|[^"])*+") in selector ("(?:\\[\\"]|[^"])*+")$~
*
* Check if text is present in modal or dynamic modal.
*/
- public function modalIsOpenWithText(string $text, string $selector = '*'): void
+ public function modalIsOpenWithText(string $text, string $selector = '"*"'): void
{
- $textEncoded = str_contains($text, '"')
- ? 'concat("' . str_replace('"', '", \'"\', "', $text) . '")'
- : '"' . $text . '"';
+ $text = $this->unquoteStepArgument($text);
+ $selector = $this->unquoteStepArgument($selector);
$modal = $this->findElement(null, '.modal.visible.active.front');
- $this->findElement($modal, '//' . $selector . '[text()[normalize-space()=' . $textEncoded . ']]');
+ $this->findElement($modal, '//' . $selector . '[text()[normalize-space()=' . $this->quoteXpath($text) . ']]');
}
/**
- * @When I fill Modal field :arg1 with :arg2
+ * @When ~^I fill Modal field ("(?:\\[\\"]|[^"])*+") with ("(?:\\[\\"]|[^"])*+")$~
*/
public function iFillModalField(string $fieldName, string $value): void
{
+ $fieldName = $this->unquoteStepArgument($fieldName);
+ $value = $this->unquoteStepArgument($value);
+
$modal = $this->findElement(null, '.modal.visible.active.front');
$field = $modal->find('named', ['field', $fieldName]);
$field->setValue($value);
}
/**
- * @When I click close modal
+ * @When ~^I click close modal$~
*/
public function iClickCloseModal(): void
{
@@ -452,7 +491,7 @@ public function iClickCloseModal(): void
}
/**
- * @When I hide js modal
+ * @When ~^I hide js modal$~
*/
public function iHideJsModal(): void
{
@@ -465,7 +504,7 @@ public function iHideJsModal(): void
// {{{ panel
/**
- * @Then Panel is open
+ * @Then ~^Panel is open$~
*/
public function panelIsOpen(): void
{
@@ -473,32 +512,40 @@ public function panelIsOpen(): void
}
/**
- * @Then Panel is open with text :arg1
- * @Then Panel is open with text :arg1 in selector :arg2
+ * @Then ~^Panel is open with text ("(?:\\[\\"]|[^"])*+")$~
+ * @Then ~^Panel is open with text ("(?:\\[\\"]|[^"])*+") in selector ("(?:\\[\\"]|[^"])*+")$~
*/
- public function panelIsOpenWithText(string $text, string $selector = '*'): void
+ public function panelIsOpenWithText(string $text, string $selector = '"*"'): void
{
+ $text = $this->unquoteStepArgument($text);
+ $selector = $this->unquoteStepArgument($selector);
+
$panel = $this->findElement(null, '.atk-right-panel.atk-visible');
- $this->findElement($panel, '//' . $selector . '[text()[normalize-space()="' . $text . '"]]');
+ $this->findElement($panel, '//' . $selector . '[text()[normalize-space()=' . $this->quoteXpath($text) . ']]');
}
/**
- * @When I fill Panel field :arg1 with :arg2
+ * @When ~^I fill Panel field ("(?:\\[\\"]|[^"])*+") with ("(?:\\[\\"]|[^"])*+")$~
*/
public function iFillPanelField(string $fieldName, string $value): void
{
+ $fieldName = $this->unquoteStepArgument($fieldName);
+ $value = $this->unquoteStepArgument($value);
+
$panel = $this->findElement(null, '.atk-right-panel.atk-visible');
$field = $panel->find('named', ['field', $fieldName]);
$field->setValue($value);
}
/**
- * @When I press Panel button :arg
+ * @When ~^I press Panel button ("(?:\\[\\"]|[^"])*+")$~
*/
public function iPressPanelButton(string $buttonLabel): void
{
+ $buttonLabel = $this->unquoteStepArgument($buttonLabel);
+
$panel = $this->findElement(null, '.atk-right-panel.atk-visible');
- $button = $this->findElement($panel, '//div[text()="' . $buttonLabel . '"]');
+ $button = $this->findElement($panel, '//div[text()=' . $this->quoteXpath($buttonLabel) . ']');
$button->click();
}
@@ -507,20 +554,24 @@ public function iPressPanelButton(string $buttonLabel): void
// {{{ tab
/**
- * @When I click tab with title :arg1
+ * @When ~^I click tab with title ("(?:\\[\\"]|[^"])*+")$~
*/
public function iClickTabWithTitle(string $tabTitle): void
{
+ $tabTitle = $this->unquoteStepArgument($tabTitle);
+
$tabMenu = $this->findElement(null, '.ui.tabular.menu');
- $link = $this->findElement($tabMenu, '//div[text()="' . $tabTitle . '"]');
+ $link = $this->findElement($tabMenu, '//div[text()=' . $this->quoteXpath($tabTitle) . ']');
$link->click();
}
/**
- * @Then Active tab should be :arg1
+ * @Then ~^Active tab should be ("(?:\\[\\"]|[^"])*+")$~
*/
public function activeTabShouldBe(string $title): void
{
+ $title = $this->unquoteStepArgument($title);
+
$tab = $this->findElement(null, '.ui.tabular.menu > .item.active');
if ($tab->getText() !== $title) {
throw new \Exception('Active tab is not ' . $title);
@@ -532,7 +583,7 @@ public function activeTabShouldBe(string $title): void
// {{{ input
/**
- * @Then ~^input "([^"]*)" value should start with "([^"]*)"$~
+ * @Then ~^input ("(?:\\[\\"]|[^"])*+") value should start with ("(?:\\[\\"]|[^"])*+")$~
*/
public function inputValueShouldStartWith(string $inputName, string $text): void
{
@@ -547,22 +598,29 @@ public function inputValueShouldStartWith(string $inputName, string $text): void
}
/**
- * @When I search grid for :arg1
+ * @When ~^I search grid for ("(?:\\[\\"]|[^"])*+")$~
*/
public function iSearchGridFor(string $text): void
{
+ $text = $this->unquoteStepArgument($text);
+
$search = $this->findElement(null, 'input.atk-grid-search');
$search->setValue($text);
}
/**
- * @When I select value :arg1 in lookup :arg2
+ * TODO better method name, it selects name/title, not value.
+ *
+ * @When ~^I select value ("(?:\\[\\"]|[^"])*+") in lookup ("(?:\\[\\"]|[^"])*+")$~
*/
public function iSelectValueInLookup(string $value, string $inputName): void
{
+ $value = $this->unquoteStepArgument($value);
+ $inputName = $this->unquoteStepArgument($inputName);
+
// get dropdown item from Fomantic-UI which is direct parent of input HTML element
$isSelectorXpath = $this->parseSelector($inputName)[0] === 'xpath';
- $lookupElem = $this->findElement(null, ($isSelectorXpath ? $inputName : '//input[@name="' . $inputName . '"]') . '/parent::div');
+ $lookupElem = $this->findElement(null, ($isSelectorXpath ? $inputName : '//input[@name=' . $this->quoteXpath($inputName) . ']') . '/parent::div');
if ($value === '') {
$this->findElement($lookupElem, 'i.remove.icon')->click();
@@ -570,12 +628,13 @@ public function iSelectValueInLookup(string $value, string $inputName): void
return;
}
- // open dropdown and wait till fully opened (just a click is not triggering it)
+ // open dropdown and wait till fully opened
+ $this->findElement($lookupElem, 'i.dropdown.icon')->click(); // TODO remove once https://github.com/fomantic/Fomantic-UI/issues/3204 is fixed
$this->getSession()->executeScript('$(arguments[0]).dropdown(\'show\')', [$lookupElem]);
$this->jqueryWait('$(arguments[0]).hasClass(\'visible\')', [$lookupElem]);
// select value
- $valueElem = $this->findElement($lookupElem, '//div[text()="' . $value . '"]');
+ $valueElem = $this->findElement($lookupElem, '//div.menu//div.item[text()=' . $this->quoteXpath($value) . ']');
$this->getSession()->executeScript('$(arguments[0]).dropdown(\'set selected\', arguments[1]);', [$lookupElem, $valueElem->getAttribute('data-value')]);
$this->jqueryWait();
@@ -585,11 +644,15 @@ public function iSelectValueInLookup(string $value, string $inputName): void
}
/**
- * @When I select file input :arg1 with :arg2 as :arg3
+ * @When ~^I select file input ("(?:\\[\\"]|[^"])*+") with ("(?:\\[\\"]|[^"])*+") as ("(?:\\[\\"]|[^"])*+")$~
*/
public function iSelectFile(string $inputName, string $fileContent, string $fileName): void
{
- $element = $this->findElement(null, '//input[@name="' . $inputName . '" and @type="hidden"]/following-sibling::input[@type="file"]');
+ $inputName = $this->unquoteStepArgument($inputName);
+ $fileContent = $this->unquoteStepArgument($fileContent);
+ $fileName = $this->unquoteStepArgument($fileName);
+
+ $element = $this->findElement(null, '//input[@name=' . $this->quoteXpath($inputName) . ' and @type="hidden"]/following-sibling::input[@type="file"]');
$this->getSession()->executeScript(<<<'EOF'
const dataTransfer = new DataTransfer();
dataTransfer.items.add(new File([new Uint8Array(arguments[1])], arguments[2]));
@@ -606,7 +669,7 @@ private function getScopeBuilderRuleElem(string $ruleName): NodeElement
/**
* Generic ScopeBuilder rule with select operator and input value.
*
- * @Then ~^rule "([^"]*)" operator is "([^"]*)" and value is "([^"]*)"$~
+ * @Then ~^rule ("(?:\\[\\"]|[^"])*+") operator is ("(?:\\[\\"]|[^"])*+") and value is ("(?:\\[\\"]|[^"])*+")$~
*/
public function scopeBuilderRule(string $name, string $operator, string $value): void
{
@@ -622,7 +685,7 @@ public function scopeBuilderRule(string $name, string $operator, string $value):
/**
* HasOne reference or enum type rule for ScopeBuilder.
*
- * @Then ~^reference rule "([^"]*)" operator is "([^"]*)" and value is "([^"]*)"$~
+ * @Then ~^reference rule ("(?:\\[\\"]|[^"])*+") operator is ("(?:\\[\\"]|[^"])*+") and value is ("(?:\\[\\"]|[^"])*+")$~
*/
public function scopeBuilderReferenceRule(string $name, string $operator, string $value): void
{
@@ -638,7 +701,7 @@ public function scopeBuilderReferenceRule(string $name, string $operator, string
/**
* HasOne select or enum type rule for ScopeBuilder.
*
- * @Then ~^select rule "([^"]*)" operator is "([^"]*)" and value is "([^"]*)"$~
+ * @Then ~^select rule ("(?:\\[\\"]|[^"])*+") operator is ("(?:\\[\\"]|[^"])*+") and value is ("(?:\\[\\"]|[^"])*+")$~
*/
public function scopeBuilderSelectRule(string $name, string $operator, string $value): void
{
@@ -654,7 +717,7 @@ public function scopeBuilderSelectRule(string $name, string $operator, string $v
/**
* Date, Time or Datetime rule for ScopeBuilder.
*
- * @Then ~^date rule "([^"]*)" operator is "([^"]*)" and value is "([^"]*)"$~
+ * @Then ~^date rule ("(?:\\[\\"]|[^"])*+") operator is ("(?:\\[\\"]|[^"])*+") and value is ("(?:\\[\\"]|[^"])*+")$~
*/
public function scopeBuilderDateRule(string $name, string $operator, string $value): void
{
@@ -670,7 +733,7 @@ public function scopeBuilderDateRule(string $name, string $operator, string $val
/**
* Boolean type rule for ScopeBuilder.
*
- * @Then ~^bool rule "([^"]*)" has value "([^"]*)"$~
+ * @Then ~^bool rule ("(?:\\[\\"]|[^"])*+") has value ("(?:\\[\\"]|[^"])*+")$~
*/
public function scopeBuilderBoolRule(string $name, string $value): void
{
@@ -686,7 +749,7 @@ public function scopeBuilderBoolRule(string $name, string $value): void
}
/**
- * @Then ~^I check if input value for "([^"]*)" match text in "([^"]*)"$~
+ * @Then ~^I check if input value for ("(?:\\[\\"]|[^"])*+") match text in ("(?:\\[\\"]|[^"])*+")$~
*/
public function compareInputValueText(string $compareSelector, string $compareToSelector): void
{
@@ -699,7 +762,7 @@ public function compareInputValueText(string $compareSelector, string $compareTo
}
/**
- * @Then ~^I check if input value for "([^"]*)" match text "([^"]*)"$~
+ * @Then ~^I check if input value for ("(?:\\[\\"]|[^"])*+") match text ("(?:\\[\\"]|[^"])*+")$~
*/
public function compareInputValueToText(string $selector, string $text): void
{
@@ -717,30 +780,35 @@ public function compareInputValueToText(string $selector, string $text): void
// {{{ misc
/**
- * @Then dump :arg1
+ * @Then ~^dump ("(?:\\[\\"]|[^"])*+")$~
*/
public function dump(string $arg1): void
{
- $element = $this->getSession()->getPage()->find('xpath', '//div[text()="' . $arg1 . '"]');
+ $arg1 = $this->unquoteStepArgument($arg1);
+
+ $element = $this->getSession()->getPage()->find('xpath', '//div[text()=' . $this->quoteXpath($arg1) . ']');
var_dump($element->getOuterHtml());
}
/**
- * @When I click filter column name :arg1
+ * @When ~^I click filter column name ("(?:\\[\\"]|[^"])*+")$~
*/
public function iClickFilterColumnName(string $columnName): void
{
+ $columnName = $this->unquoteStepArgument($columnName);
+
$column = $this->findElement(null, "th[data-column='" . $columnName . "']");
$icon = $this->findElement($column, 'i');
$icon->click();
}
/**
- * @Then ~^container "([^"]*)" should display "([^"]*)" item\(s\)$~
+ * @Then ~^container ("(?:\\[\\"]|[^"])*+") should display ("(?:\\[\\"]|[^"])*+") item\(s\)$~
*/
- public function containerShouldHaveNumberOfItem(string $selector, int $numberOfitems): void
+ public function containerShouldHaveNumberOfItem(string $selector, string $numberOfitems): void
{
$selector = $this->unquoteStepArgument($selector);
+ $numberOfitems = (int) $this->unquoteStepArgument($numberOfitems);
$items = $this->getSession()->getPage()->findAll('css', $selector);
$count = 0;
@@ -753,7 +821,7 @@ public function containerShouldHaveNumberOfItem(string $selector, int $numberOfi
}
/**
- * @When I scroll to top
+ * @When ~^I scroll to top$~
*/
public function iScrollToTop(): void
{
@@ -761,7 +829,7 @@ public function iScrollToTop(): void
}
/**
- * @When I scroll to bottom
+ * @When ~^I scroll to bottom$~
*/
public function iScrollToBottom(): void
{
@@ -769,7 +837,7 @@ public function iScrollToBottom(): void
}
/**
- * @Then Toast display should contain text :arg1
+ * @Then ~^Toast display should contain text ("(?:\\[\\"]|[^"])*+")$~
*/
public function toastDisplayShouldContainText(string $text): void
{
@@ -783,7 +851,7 @@ public function toastDisplayShouldContainText(string $text): void
}
/**
- * @Then No toast should be displayed
+ * @Then ~^No toast should be displayed$~
*/
public function noToastShouldBeDisplayed(): void
{
@@ -797,7 +865,7 @@ public function noToastShouldBeDisplayed(): void
* Remove once https://github.com/Behat/MinkExtension/pull/386 and
* https://github.com/minkphp/Mink/issues/656 are fixed and released.
*
- * @Then ~^PATCH MINK the (?i)url(?-i) should match "((?:[^"]|\\")*)"$~
+ * @Then ~^PATCH MINK the URL should match ("(?:\\[\\"]|[^"])*+")$~
*/
public function assertUrlRegExp(string $pattern): void
{
@@ -807,7 +875,7 @@ public function assertUrlRegExp(string $pattern): void
}
/**
- * @Then ~^I check if text in "([^"]*)" match text in "([^"]*)"$~
+ * @Then ~^I check if text in ("(?:\\[\\"]|[^"])*+") match text in ("(?:\\[\\"]|[^"])*+")$~
*/
public function compareElementText(string $compareSelector, string $compareToSelector): void
{
@@ -820,7 +888,7 @@ public function compareElementText(string $compareSelector, string $compareToSel
}
/**
- * @Then ~^I check if text in "([^"]*)" match text "([^"]*)"$~
+ * @Then ~^I check if text in ("(?:\\[\\"]|[^"])*+") match text ("(?:\\[\\"]|[^"])*+")$~
*/
public function textInContainerShouldMatch(string $selector, string $text): void
{
@@ -833,7 +901,7 @@ public function textInContainerShouldMatch(string $selector, string $text): void
}
/**
- * @Then ~^I check if text in "([^"]*)" match regex "([^"]*)"$~
+ * @Then ~^I check if text in ("(?:\\[\\"]|[^"])*+") match regex ("(?:\\[\\"]|[^"])*+")$~
*/
public function textInContainerShouldMatchRegex(string $selector, string $regex): void
{
@@ -846,10 +914,14 @@ public function textInContainerShouldMatchRegex(string $selector, string $regex)
}
/**
- * @Then Element :arg1 attribute :arg2 should contain text :arg3
+ * @Then ~^Element ("(?:\\[\\"]|[^"])*+") attribute ("(?:\\[\\"]|[^"])*+") should contain text ("(?:\\[\\"]|[^"])*+")$~
*/
public function elementAttributeShouldContainText(string $selector, string $attribute, string $text): void
{
+ $selector = $this->unquoteStepArgument($selector);
+ $attribute = $this->unquoteStepArgument($attribute);
+ $text = $this->unquoteStepArgument($text);
+
$element = $this->findElement(null, $selector);
$attr = $element->getAttribute($attribute);
if (!str_contains($attr, $text)) {
@@ -858,10 +930,13 @@ public function elementAttributeShouldContainText(string $selector, string $attr
}
/**
- * @Then Element :arg1 should contain class :arg2
+ * @Then ~^Element ("(?:\\[\\"]|[^"])*+") should contain class ("(?:\\[\\"]|[^"])*+")$~
*/
public function elementShouldContainClass(string $selector, string $class): void
{
+ $selector = $this->unquoteStepArgument($selector);
+ $class = $this->unquoteStepArgument($class);
+
$element = $this->findElement(null, $selector);
$classes = explode(' ', $element->getAttribute('class'));
if (!in_array($class, $classes, true)) {
@@ -870,10 +945,13 @@ public function elementShouldContainClass(string $selector, string $class): void
}
/**
- * @Then Element :arg1 should not contain class :arg2
+ * @Then ~^Element ("(?:\\[\\"]|[^"])*+") should not contain class ("(?:\\[\\"]|[^"])*+")$~
*/
public function elementShouldNotContainClass(string $selector, string $class): void
{
+ $selector = $this->unquoteStepArgument($selector);
+ $class = $this->unquoteStepArgument($class);
+
$element = $this->findElement(null, $selector);
$classes = explode(' ', $element->getAttribute('class'));
if (in_array($class, $classes, true)) {
diff --git a/src/Form/Control/Lookup.php b/src/Form/Control/Lookup.php
index ae13d44795..4664807381 100644
--- a/src/Form/Control/Lookup.php
+++ b/src/Form/Control/Lookup.php
@@ -369,6 +369,15 @@ protected function initDropdown($jsChain): void
$settings['clearable'] = true;
}
+ if ($this->entityField !== null && $this->entityField->get() !== null) {
+ $idField = $this->idField
+ ?? $this->model->idField;
+
+ $entity = $this->model->loadBy($idField, $this->entityField->get());
+
+ $settings['values'] = [array_merge($this->renderRow($entity), ['selected' => true])];
+ }
+
$jsChain->dropdown($settings);
}
@@ -401,16 +410,6 @@ protected function renderView(): void
$this->initDropdown($jsChain);
- if ($this->entityField !== null && $this->entityField->get() !== null) {
- $idField = $this->idField
- ?? $this->model->idField;
-
- $entity = $this->model->loadBy($idField, $this->entityField->get());
-
- $row = $this->renderRow($entity);
- $jsChain->dropdown('set text', $row['title'], true);
- }
-
$this->js(true, $jsChain);
parent::renderView();
diff --git a/tests-behat/callback.feature b/tests-behat/callback.feature
index 80cdb307ec..01ca3b5dc3 100644
--- a/tests-behat/callback.feature
+++ b/tests-behat/callback.feature
@@ -19,4 +19,4 @@ Feature: Callback
When I click using selector "(//div.ui.atk-test.button)[1]"
Then Modal is open with text "Edit Country"
When I press Modal button "Save"
- Then Toast display should contain text 'Country action "edit" with "Andorra" entity was executed.'
+ Then Toast display should contain text "Country action \"edit\" with \"Andorra\" entity was executed."
diff --git a/tests-behat/card-deck.feature b/tests-behat/card-deck.feature
index c34e0cebe0..275a94cd6c 100644
--- a/tests-behat/card-deck.feature
+++ b/tests-behat/card-deck.feature
@@ -11,7 +11,7 @@ Feature: CardDeck
When I fill in "atk_fp_country__numcode" with "123"
When I fill in "atk_fp_country__phonecode" with "1"
When I press Modal button "Save"
- Then Toast display should contain text 'Country action "add" with "Test" entity was executed.'
+ Then Toast display should contain text "Country action \"add\" with \"Test\" entity was executed."
Scenario: search
When I fill in "atk-vue-search" with "united kingdom"
@@ -21,18 +21,18 @@ Feature: CardDeck
When I press button "Edit"
Then Modal is open with text "Edit Country"
When I press Modal button "Save"
- Then Toast display should contain text 'Country action "edit" with "United Kingdom" entity was executed.'
+ Then Toast display should contain text "Country action \"edit\" with \"United Kingdom\" entity was executed."
# make sure search query stick
Then I should see "United Kingdom"
Scenario: delete
When I press button "Delete"
When I press Modal button "Ok"
- Then Toast display should contain text 'Country action "delete" with "United Kingdom" entity was executed.'
+ Then Toast display should contain text "Country action \"delete\" with \"United Kingdom\" entity was executed."
Scenario: delete - with unlocked DB
When I persist DB changes across requests
When I press button "Delete"
When I press Modal button "Ok"
- Then Toast display should contain text 'Record has been deleted!'
+ Then Toast display should contain text "Record has been deleted!"
Then I should not see "United Kingdom"
diff --git a/tests-behat/checkbox.feature b/tests-behat/checkbox.feature
index 0a77618015..fd2424389b 100644
--- a/tests-behat/checkbox.feature
+++ b/tests-behat/checkbox.feature
@@ -3,10 +3,10 @@ Feature: Checkbox
Scenario:
Given I am on "form-control/checkbox.php"
When I press button "Save"
- Then Toast display should contain text '{ "test": false, "test_checked": true, "also_checked": true }'
+ Then Toast display should contain text "{ \"test\": false, \"test_checked\": true, \"also_checked\": true }"
When I click using selector "//div.ui.checkbox[not(self::*.checked)][input[@name='test']]"
When I click using selector "//div.ui.checkbox.checked[input[@name='test_checked']]"
When I click using selector "//div.ui.checkbox.checked[input[@name='also_checked']]"
When I press button "Save"
- Then Toast display should contain text '{ "test": true, "test_checked": false, "also_checked": false }'
+ Then Toast display should contain text "{ \"test\": true, \"test_checked\": false, \"also_checked\": false }"
diff --git a/tests-behat/crud.feature b/tests-behat/crud.feature
index 1c0dd19128..bc1009f6ac 100644
--- a/tests-behat/crud.feature
+++ b/tests-behat/crud.feature
@@ -11,7 +11,7 @@ Feature: Crud
When I fill in "atk_fp_country__numcode" with "123"
When I fill in "atk_fp_country__phonecode" with "1"
When I press Modal button "Save"
- Then Toast display should contain text 'Country action "add" with "Test" entity was executed.'
+ Then Toast display should contain text "Country action \"add\" with \"Test\" entity was executed."
Scenario: search
When I search grid for "united kingdom"
@@ -30,7 +30,7 @@ Feature: Crud
When I fill in "atk_fp_country__numcode" with "123"
When I fill in "atk_fp_country__phonecode" with "1"
When I press Modal button "Save"
- Then Toast display should contain text 'Country action "add" with "Test 2" entity was executed.'
+ Then Toast display should contain text "Country action \"add\" with \"Test 2\" entity was executed."
# TODO add should keep search
# related with https://github.com/atk4/ui/issues/526 (list newly added record first)
When I search grid for "united kingdo"
@@ -39,7 +39,7 @@ Feature: Crud
When I press button "Edit"
Then Modal is open with text "Edit Country"
When I press Modal button "Save"
- Then Toast display should contain text 'Country action "edit" with "United Kingdom" entity was executed.'
+ Then Toast display should contain text "Country action \"edit\" with \"United Kingdom\" entity was executed."
# make sure search query stick
Then I should see "United Kingdom"
@@ -56,13 +56,13 @@ Feature: Crud
Then Modal is open with text "Edit Country"
When I fill in "atk_fp_country__name" with "My United Kingdom"
When I press Modal button "Save"
- Then Toast display should contain text 'Record has been saved!'
+ Then Toast display should contain text "Record has been saved!"
Then I should see "My United Kingdom"
Scenario: delete
When I press button "Delete"
When I press Modal button "Ok"
- Then Toast display should contain text 'Country action "delete" with "United Kingdom" entity was executed.'
+ Then Toast display should contain text "Country action \"delete\" with \"United Kingdom\" entity was executed."
Then I should not see "United Kingdom"
Scenario: search across multiple columns
diff --git a/tests-behat/dropdown.feature b/tests-behat/dropdown.feature
index 37f7e484c7..de3ded351c 100644
--- a/tests-behat/dropdown.feature
+++ b/tests-behat/dropdown.feature
@@ -6,7 +6,7 @@ Feature: Dropdown
When I select value "Sugar/Sweetened" in lookup "sub_category_id"
When I select value "Soda" in lookup "product_id"
When I click using selector "(//div[text()='Save'])[2]"
- Then Modal is open with text '{ "category_id": "2", "sub_category_id": "9", "product_id": "4" }' in selector "p"
+ Then Modal is open with text "{ \"category_id\": \"2\", \"sub_category_id\": \"9\", \"product_id\": \"4\" }" in selector "p"
When I click close modal
Then I should see "Soda"
When I select value "Coffee and Tea" in lookup "sub_category_id"
@@ -25,6 +25,32 @@ Feature: Dropdown
When I select value "" in lookup "multi"
Then I check if input value for "input[name='multi']" match text ""
+ Scenario: dropdown with escaped HTML
+ Given I am on "_unit-test/dropdown-html.php"
+ When I press button "Save"
+ Then Modal is open with text "match init: 1"
+ When I click close modal
+ When I select value "uTitle \"' <" in lookup "dropdown_single"
+ When I select value "uTitle \"' <" in lookup "dropdown_single2"
+ When I select value "uTitle \"' <" in lookup "dropdown_multi"
+ When I select value "uTitle \"' <" in lookup "dropdown_multi2"
+ When I press button "Save"
+ Then Modal is open with text "match u add: 1"
+ When I click close modal
+ When I select value "" in lookup "dropdown_single"
+ When I select value "" in lookup "dropdown_single2"
+ When I select value "" in lookup "dropdown_multi"
+ When I select value "" in lookup "dropdown_multi2"
+ When I press button "Save"
+ Then Modal is open with text "match empty: 1"
+ When I click close modal
+ When I select value "uTitle \"' <" in lookup "dropdown_single"
+ When I select value "uTitle \"' <" in lookup "dropdown_single2"
+ When I select value "uTitle \"' <" in lookup "dropdown_multi"
+ When I select value "uTitle \"' <" in lookup "dropdown_multi2"
+ When I press button "Save"
+ Then Modal is open with text "match u only: 1"
+
Scenario: dropdown menu
Given I am on "basic/menu.php"
When I click using selector "//div.ui.dropdown[div[text()='With Callback']]"
diff --git a/tests-behat/grid.feature b/tests-behat/grid.feature
index e0e6699521..bb70cb2136 100644
--- a/tests-behat/grid.feature
+++ b/tests-behat/grid.feature
@@ -16,7 +16,7 @@ Feature: Grid
Scenario: search no ajax
Given I am on "collection/grid.php?no-ajax=1"
When I search grid for "kingdom"
- Then PATCH MINK the url should match "~_q=kingdom~"
+ Then PATCH MINK the URL should match "~_q=kingdom~"
Then I should see "United Kingdom"
Scenario: Checkbox click event must not bubble to row click
@@ -35,10 +35,10 @@ Feature: Grid
Then No toast should be displayed
When I click using selector "//div[@id='grid']//tr[2]//div.ui.dropdown[div[text()='Actions...']]//div.menu/div[text()='Action MenuItem']"
Then Toast display should contain text "Clicked Action MenuItem"
- Then PATCH MINK the url should match "~_unit-test/grid-rowclick.php$~"
+ Then PATCH MINK the URL should match "~_unit-test/grid-rowclick.php$~"
When I click using selector "//div[@id='grid']//tr[2]//a"
Then No toast should be displayed
- Then PATCH MINK the url should match "~_unit-test/grid-rowclick.php#test~"
+ Then PATCH MINK the URL should match "~_unit-test/grid-rowclick.php#test~"
Scenario: master checkbox
Given I am on "_unit-test/grid-master-checkbox.php"
@@ -116,7 +116,7 @@ Feature: Grid
When I click paginator page "2"
Then I should see "Bahamas"
When I click using selector "//tr[td[text()='Bahamas']]//div.ui.button[text()='Say HI']"
- Then Toast display should contain text 'Loaded "Bahamas" from ID=16'
+ Then Toast display should contain text "Loaded \"Bahamas\" from ID=16"
Scenario: Row remote action - change row CSS
Given I am on "interactive/scroll-grid-container.php"
diff --git a/tests-behat/lookup.feature b/tests-behat/lookup.feature
index e490f783eb..11e817f1b9 100644
--- a/tests-behat/lookup.feature
+++ b/tests-behat/lookup.feature
@@ -6,8 +6,8 @@ Feature: Lookup
When I select value "Dairy" in lookup "atk_fp_product__product_category_id"
# '6f3c91cf51e02fd5' = substr(md5('product_sub_category'), 0, 16)
When I select value "Yogourt" in lookup "atk_fp_product__6f3c91cf51e02fd5_id"
- When I press modal button "Save"
- Then Toast display should contain text 'Dairy - Yogourt'
+ When I press Modal button "Save"
+ Then Toast display should contain text "Dairy - Yogourt"
Scenario: Testing lookup in VirtualPage
Given I am on "_unit-test/lookup-virtual-page.php"
@@ -33,5 +33,5 @@ Feature: Lookup
When I fill in "atk_fp_country__numcode" with "88"
When I fill in "atk_fp_country__phonecode" with "8"
When I press Modal button "Save"
- Then Toast display should contain text 'Country action "add" with "Plusia" entity was executed.'
+ Then Toast display should contain text "Country action \"add\" with \"Plusia\" entity was executed."
Then I check if text in "//div.text[../input[@name='country2']]" match text "Plusia"
diff --git a/tests-behat/modal-error.feature b/tests-behat/modal-error.feature
index 78bf81d089..014895df64 100644
--- a/tests-behat/modal-error.feature
+++ b/tests-behat/modal-error.feature
@@ -13,10 +13,10 @@ Feature: Nested modals /w error handling
Scenario: Modal with JS error
When I press button "Test Modal load JS error"
Then Modal is open with text "API JavaScript Error"
- Then Modal is open with text 'Fomantic-UI "modal.onShow" setting cannot be customized outside atk'
+ Then Modal is open with text "Fomantic-UI \"modal.onShow\" setting cannot be customized outside atk"
When I hide js modal
When I press button "Test Modal load JS error"
- Then Modal is open with text 'Fomantic-UI "modal.onShow" setting cannot be customized outside atk'
+ Then Modal is open with text "Fomantic-UI \"modal.onShow\" setting cannot be customized outside atk"
When I hide js modal
Scenario: ModalExecutor with PHP error exception is displayed
diff --git a/tests-behat/multiline.feature b/tests-behat/multiline.feature
index 7a77aa558a..22fbdd1c02 100644
--- a/tests-behat/multiline.feature
+++ b/tests-behat/multiline.feature
@@ -10,7 +10,7 @@ Feature: Multiline
Then the "div[name=-atk_fp_multiline_item__total_sql]" element should contain "134"
Then the "div[name=-atk_fp_multiline_item__total_php]" element should contain "134"
When I press button "Save"
- Then Toast display should contain text '"atk_fp_multiline_item__box": "67", "atk_fp_multiline_item__total_sql": "134" }'
+ Then Toast display should contain text "\"atk_fp_multiline_item__box\": \"67\", \"atk_fp_multiline_item__total_sql\": \"134\" }"
Scenario: add row
When I click using selector "//tfoot//button[i.plus.icon]"
@@ -23,17 +23,17 @@ Feature: Multiline
Then I check if text in "//tr[3]//div[@name='-atk_fp_multiline_item__total_sql']" match text "15"
Then I check if text in "//tr[3]//div[@name='-atk_fp_multiline_item__total_php']" match text "15"
When I press button "Save"
- Then Toast display should contain text '"atk_fp_multiline_item__box": "5", "atk_fp_multiline_item__total_sql": "15" } ]'
+ Then Toast display should contain text "\"atk_fp_multiline_item__box\": \"5\", \"atk_fp_multiline_item__total_sql\": \"15\" } ]"
Then I should not see "Must not be empty"
Scenario: delete row
When I click using selector "//tr[3]//input[@type='checkbox']"
When I click using selector "//tfoot//button[i.trash.icon]"
When I press button "Save"
- Then Toast display should contain text '"atk_fp_multiline_item__box": "100", "atk_fp_multiline_item__total_sql": "200" } ]'
+ Then Toast display should contain text "\"atk_fp_multiline_item__box\": \"100\", \"atk_fp_multiline_item__total_sql\": \"200\" } ]"
Scenario: delete all rows
When I click using selector "//thead//input[@type='checkbox']"
When I click using selector "//tfoot//button[i.trash.icon]"
When I press button "Save"
- Then Toast display should contain text '[]'
+ Then Toast display should contain text "[]"
diff --git a/tests-behat/radio.feature b/tests-behat/radio.feature
index 21059c0335..4909f80d8c 100644
--- a/tests-behat/radio.feature
+++ b/tests-behat/radio.feature
@@ -3,10 +3,10 @@ Feature: Radio
Scenario:
Given I am on "form-control/form6.php"
When I press button "Save"
- Then Toast display should contain text '"enum_d": "male", "enum_r": "male"'
- Then Toast display should contain text '"list_d": "1", "list_r": "1"'
- Then Toast display should contain text '"int_d": "7 000", "int_r": "7 000"'
- Then Toast display should contain text '"string_d": "M", "string_r": "M"'
+ Then Toast display should contain text "\"enum_d\": \"male\", \"enum_r\": \"male\""
+ Then Toast display should contain text "\"list_d\": \"1\", \"list_r\": \"1\""
+ Then Toast display should contain text "\"int_d\": \"7 000\", \"int_r\": \"7 000\""
+ Then Toast display should contain text "\"string_d\": \"M\", \"string_r\": \"M\""
Then Element "//input[@name='int_r' and @checked='checked']" attribute "value" should contain text "7000"
When I select value "female" in lookup "enum_d"
@@ -14,35 +14,35 @@ Feature: Radio
When I select value "female" in lookup "list_d"
When I click using selector "//div.ui.radio[not(self::*.checked)][input[@name='list_r'] and label[text()='female']]"
When I press button "Save"
- Then Toast display should contain text '"enum_d": "female", "enum_r": "female"'
- Then Toast display should contain text '"list_d": "0", "list_r": "0"'
- Then Toast display should contain text '"int_d": "7 000", "int_r": "7 000"'
- Then Toast display should contain text '"string_d": "M", "string_r": "M"'
+ Then Toast display should contain text "\"enum_d\": \"female\", \"enum_r\": \"female\""
+ Then Toast display should contain text "\"list_d\": \"0\", \"list_r\": \"0\""
+ Then Toast display should contain text "\"int_d\": \"7 000\", \"int_r\": \"7 000\""
+ Then Toast display should contain text "\"string_d\": \"M\", \"string_r\": \"M\""
When I select value "female" in lookup "int_d"
When I click using selector "//div.ui.radio[not(self::*.checked)][input[@name='int_r'] and label[text()='female']]"
When I select value "female" in lookup "string_d"
When I click using selector "//div.ui.radio[not(self::*.checked)][input[@name='string_r'] and label[text()='female']]"
When I press button "Save"
- Then Toast display should contain text '"enum_d": "female", "enum_r": "female"'
- Then Toast display should contain text '"list_d": "0", "list_r": "0"'
- Then Toast display should contain text '"int_d": "5", "int_r": "5"'
- Then Toast display should contain text '"string_d": "F", "string_r": "F"'
+ Then Toast display should contain text "\"enum_d\": \"female\", \"enum_r\": \"female\""
+ Then Toast display should contain text "\"list_d\": \"0\", \"list_r\": \"0\""
+ Then Toast display should contain text "\"int_d\": \"5\", \"int_r\": \"5\""
+ Then Toast display should contain text "\"string_d\": \"F\", \"string_r\": \"F\""
When I select value "" in lookup "enum_d"
When I click using selector "//div.ui.radio.checked[input[@name='enum_r']]"
When I select value "" in lookup "list_d"
When I click using selector "//div.ui.radio.checked[input[@name='list_r']]"
When I press button "Save"
- Then Toast display should contain text '"enum_d": null, "enum_r": null'
- Then Toast display should contain text '"list_d": null, "list_r": null'
- Then Toast display should contain text '"int_d": "5", "int_r": "5"'
- Then Toast display should contain text '"string_d": "F", "string_r": "F"'
+ Then Toast display should contain text "\"enum_d\": null, \"enum_r\": null"
+ Then Toast display should contain text "\"list_d\": null, \"list_r\": null"
+ Then Toast display should contain text "\"int_d\": \"5\", \"int_r\": \"5\""
+ Then Toast display should contain text "\"string_d\": \"F\", \"string_r\": \"F\""
When I select value "" in lookup "int_d"
When I click using selector "//div.ui.radio.checked[input[@name='int_r']]"
When I select value "" in lookup "string_d"
When I click using selector "//div.ui.radio.checked[input[@name='string_r']]"
When I press button "Save"
- Then Toast display should contain text '"enum_d": null, "enum_r": null'
- Then Toast display should contain text '"list_d": null, "list_r": null'
- Then Toast display should contain text '"int_d": null, "int_r": null'
- Then Toast display should contain text '"string_d": null, "string_r": null'
+ Then Toast display should contain text "\"enum_d\": null, \"enum_r\": null"
+ Then Toast display should contain text "\"list_d\": null, \"list_r\": null"
+ Then Toast display should contain text "\"int_d\": null, \"int_r\": null"
+ Then Toast display should contain text "\"string_d\": null, \"string_r\": null"
diff --git a/tests-behat/remove-observer.feature b/tests-behat/remove-observer.feature
index 36a2b887e1..0f94e993a5 100644
--- a/tests-behat/remove-observer.feature
+++ b/tests-behat/remove-observer.feature
@@ -85,9 +85,9 @@ Feature: Remove observer
Scenario: abort SSE when owner is reloaded
Given I am on "_unit-test/remove-observer.php"
When I press button "Run slow SSE"
- When I wait 2000 ms
+ When I wait "2000" ms
Then I should see "Abort failed"
Given I am on "_unit-test/remove-observer.php"
When I press button "Run slow SSE & remove"
- When I wait 2000 ms
+ When I wait "2000" ms
Then I should not see "Abort failed"
diff --git a/tests-behat/sortable.feature b/tests-behat/sortable.feature
index 59a2f32b2e..0891023328 100644
--- a/tests-behat/sortable.feature
+++ b/tests-behat/sortable.feature
@@ -16,4 +16,4 @@ Feature: Sortable / Draggable
Scenario: drag column resize
Given I am on "collection/table2.php"
When I drag selector "(//div.grip-resizable)[2]" onto selector "(//div.grip-resizable)[1]"
- Then Toast display should contain text 'New widths: { "action": "wide", "amount": "narrow", "amount_copy": "wide" }'
+ Then Toast display should contain text "New widths: { \"action\": \"wide\", \"amount\": \"narrow\", \"amount_copy\": \"wide\" }"
diff --git a/tests-behat/virtual-page.feature b/tests-behat/virtual-page.feature
index 68254e3ae1..dcb853f935 100644
--- a/tests-behat/virtual-page.feature
+++ b/tests-behat/virtual-page.feature
@@ -2,34 +2,34 @@ Feature: VirtualPage
Scenario:
Given I am on "interactive/virtual.php"
- When I click link 'More info on Car'
+ When I click link "More info on Car"
Then I check if text in ".__atk-behat-test-car" match text "Car"
When I press button "Open Lorem Ipsum"
- Then Modal is open with text 'This is yet another modal'
+ Then Modal is open with text "This is yet another modal"
Scenario:
Given I am on "interactive/virtual.php"
- When I press button 'Load in Modal'
- Then Modal is open with text 'Contents of your pop-up here'
+ When I press button "Load in Modal"
+ Then Modal is open with text "Contents of your pop-up here"
When I click close modal
Scenario:
- When I click link 'Inside current layout'
+ When I click link "Inside current layout"
Then I check if text in ".__atk-behat-test-content" match text "Contents of your pop-up here"
Scenario:
Given I am on "interactive/virtual.php"
- When I click link 'On a blank page'
+ When I click link "On a blank page"
Then I check if text in ".__atk-behat-test-content" match text "Contents of your pop-up here"
Scenario:
Given I am on "_unit-test/virtual-page.php"
- When I click link 'Open First'
+ When I click link "Open First"
Then I check if text in ".__atk-behat-test-first" match text "First Level Page"
- When I click link 'Open Second'
+ When I click link "Open Second"
Then I check if text in ".__atk-behat-test-second" match text "Second Level Page"
- When I click link 'Open Third'
+ When I click link "Open Third"
Then I check if text in ".__atk-behat-test-third" match text "Third Level Page"
When I select value "Beverages" in lookup "category"
When I press button "Save"
- Then Toast display should contain text 'Beverages'
+ Then Toast display should contain text "Beverages"
diff --git a/tests/Behat/ContextTest.php b/tests/Behat/ContextTest.php
new file mode 100644
index 0000000000..832d231f90
--- /dev/null
+++ b/tests/Behat/ContextTest.php
@@ -0,0 +1,75 @@
+
+ */
+ protected function extractPhpdocRegexes(string $file): array
+ {
+ $content = file_get_contents($file);
+
+ $res = [];
+ preg_match_all('~@(Given|Then|When) (.*)~', $content, $matchesAll, \PREG_SET_ORDER);
+ foreach ($matchesAll as $matches) {
+ $res[] = $matches[2];
+ }
+
+ return $res;
+ }
+
+ /**
+ * @dataProvider provideFiles
+ */
+ #[DataProvider('provideFiles')]
+ public function testFileHasRegexes(string $file): void
+ {
+ self::assertGreaterThan(0, count($this->extractPhpdocRegexes($file)));
+ }
+
+ /**
+ * @dataProvider provideFiles
+ */
+ #[DataProvider('provideFiles')]
+ public function testRegexStartAndEnd(string $file): void
+ {
+ foreach ($this->extractPhpdocRegexes($file) as $regex) {
+ self::assertStringStartsWith('~^', $regex);
+ self::assertStringEndsWith('$~', $regex);
+ self::assertStringEndsNotWith('.$~', $regex);
+ }
+ }
+
+ /**
+ * @dataProvider provideFiles
+ */
+ #[DataProvider('provideFiles')]
+ public function testRegexArgumentFormat(string $file): void
+ {
+ foreach ($this->extractPhpdocRegexes($file) as $regex) {
+ preg_match_all('~\((?:\(.*?\)|.)+?\)~', $regex, $matchesAll, \PREG_SET_ORDER);
+ foreach ($matchesAll as $matches) {
+ if (!str_contains($matches[0], '"')) {
+ continue;
+ }
+
+ self::assertSame('("(?:\\\[\\\"]|[^"])*+")', $matches[0]);
+ }
+ }
+ }
+
+ /**
+ * @return iterable>
+ */
+ public static function provideFiles(): iterable
+ {
+ yield [dirname(__DIR__, 2) . '/src/Behat/Context.php'];
+ }
+}