mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
499 lines
16 KiB
PHP
499 lines
16 KiB
PHP
<?php
|
|
|
|
namespace SilverStripe\Framework\Tests\Behaviour;
|
|
|
|
use BadMethodCallException;
|
|
use Behat\Behat\Context\Context;
|
|
use Behat\Mink\Exception\ElementHtmlException;
|
|
use Behat\Gherkin\Node\TableNode;
|
|
use Behat\Mink\Session;
|
|
use PHPUnit\Framework\Assert;
|
|
use SilverStripe\BehatExtension\Context\MainContextAwareTrait;
|
|
use SilverStripe\BehatExtension\Utility\StepHelper;
|
|
use Symfony\Component\DomCrawler\Crawler;
|
|
use Behat\Mink\Element\NodeElement;
|
|
use SilverStripe\SiteConfig\SiteConfig;
|
|
|
|
/**
|
|
* CmsFormsContext
|
|
*
|
|
* Context used to define steps related to forms inside CMS.
|
|
*/
|
|
class CmsFormsContext implements Context
|
|
{
|
|
use MainContextAwareTrait;
|
|
use StepHelper;
|
|
|
|
/**
|
|
* Get Mink session from MinkContext
|
|
*
|
|
* @param string $name
|
|
* @return Session
|
|
*/
|
|
public function getSession($name = null)
|
|
{
|
|
return $this->getMainContext()->getSession($name);
|
|
}
|
|
|
|
/**
|
|
* Returns fixed step argument (with \\" replaced back to ").
|
|
* Copied from {@see MinkContext}
|
|
*
|
|
* @param string $argument
|
|
* @return string
|
|
*/
|
|
protected function fixStepArgument($argument)
|
|
{
|
|
return str_replace('\\"', '"', $argument ?? '');
|
|
}
|
|
|
|
/**
|
|
* @Then /^I should( not? |\s*)see an edit page form$/
|
|
*/
|
|
public function stepIShouldSeeAnEditPageForm($negative)
|
|
{
|
|
$page = $this->getSession()->getPage();
|
|
|
|
$form = $page->find('css', '#Form_EditForm');
|
|
if (trim($negative ?? '')) {
|
|
Assert::assertNull($form, 'I should not see an edit page form');
|
|
} else {
|
|
Assert::assertNotNull($form, 'I should see an edit page form');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^I fill in the "(?P<field>(?:[^"]|\\")*)" HTML field with "(?P<value>(?:[^"]|\\")*)"$/
|
|
* @When /^I fill in "(?P<value>(?:[^"]|\\")*)" for the "(?P<field>(?:[^"]|\\")*)" HTML field$/
|
|
*/
|
|
public function stepIFillInTheHtmlFieldWith($field, $value)
|
|
{
|
|
$inputField = $this->getHtmlField($field);
|
|
$value = $this->fixStepArgument($value);
|
|
|
|
$this->getSession()->evaluateScript(sprintf(
|
|
"jQuery('#%s').entwine('ss').getEditor().setContent('%s')",
|
|
$inputField->getAttribute('id'),
|
|
addcslashes($value ?? '', "'")
|
|
));
|
|
$this->getSession()->evaluateScript(sprintf(
|
|
"jQuery('#%s').entwine('ss').getEditor().save()",
|
|
$inputField->getAttribute('id')
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @When /^I append "(?P<value>(?:[^"]|\\")*)" to the "(?P<field>(?:[^"]|\\")*)" HTML field$/
|
|
*/
|
|
public function stepIAppendTotheHtmlField($field, $value)
|
|
{
|
|
$inputField = $this->getHtmlField($field);
|
|
$value = $this->fixStepArgument($value);
|
|
|
|
$this->getSession()->evaluateScript(sprintf(
|
|
"jQuery('#%s').entwine('ss').getEditor().insertContent('%s')",
|
|
$inputField->getAttribute('id'),
|
|
addcslashes($value ?? '', "'")
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @Then /^the "(?P<locator>(?:[^"]|\\")*)" HTML field should(?P<negative> not? |\s*)contain "(?P<html>.*)"$/
|
|
*/
|
|
public function theHtmlFieldShouldContain($locator, $negative, $html)
|
|
{
|
|
$element = $this->getHtmlField($locator);
|
|
$actual = $element->getValue();
|
|
$regex = '/' . preg_quote($html ?? '', '/') . '/ui';
|
|
$failed = false;
|
|
|
|
if (trim($negative ?? '')) {
|
|
if (preg_match($regex ?? '', $actual ?? '')) {
|
|
$failed = true;
|
|
}
|
|
} else {
|
|
if (!preg_match($regex ?? '', $actual ?? '')) {
|
|
$failed = true;
|
|
}
|
|
}
|
|
|
|
if ($failed) {
|
|
$message = sprintf(
|
|
'The string "%s" should%sbe found in the HTML of the element matching name "%s". Actual content: "%s"',
|
|
$html,
|
|
$negative,
|
|
$locator,
|
|
$actual
|
|
);
|
|
throw new ElementHtmlException($message, $this->getSession(), $element);
|
|
}
|
|
}
|
|
|
|
// @codingStandardsIgnoreStart
|
|
/**
|
|
* Checks formatting in the HTML field, by analyzing the HTML node surrounding
|
|
* the text for certain properties.
|
|
*
|
|
* Example: Given "my text" in the "Content" HTML field should be right aligned
|
|
* Example: Given "my text" in the "Content" HTML field should not be bold
|
|
*
|
|
* @todo Use an actual DOM parser for more accurate assertions
|
|
*
|
|
* @Given /^"(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field should(?P<negate>(?: not)?) be (?P<formatting>(.*))$/
|
|
*/
|
|
public function stepContentInHtmlFieldShouldHaveFormatting($text, $field, $negate, $formatting) {
|
|
$inputField = $this->getHtmlField($field);
|
|
|
|
$crawler = new Crawler($inputField->getValue());
|
|
$matchedNode = null;
|
|
foreach($crawler->filterXPath('//*') as $node) {
|
|
if(
|
|
$node->firstChild
|
|
&& $node->firstChild->nodeType == XML_TEXT_NODE
|
|
&& stripos($node->firstChild->nodeValue ?? '', $text ?? '') !== FALSE
|
|
) {
|
|
$matchedNode = $node;
|
|
}
|
|
}
|
|
Assert::assertNotNull($matchedNode);
|
|
|
|
if ($formatting == 'bold') {
|
|
if ($negate) {
|
|
Assert::assertNotEquals('strong', $matchedNode->nodeName);
|
|
} else {
|
|
Assert::assertEquals('strong', $matchedNode->nodeName);
|
|
}
|
|
} else if ($formatting == 'left aligned') {
|
|
if ($matchedNode->getAttribute('class')) {
|
|
if ($negate) {
|
|
Assert::assertNotEquals('text-left', $matchedNode->getAttribute('class'));
|
|
} else {
|
|
Assert::assertEquals('text-left', $matchedNode->getAttribute('class'));
|
|
}
|
|
}
|
|
} else if ($formatting == 'right aligned') {
|
|
if ($negate) {
|
|
Assert::assertNotEquals('text-right', $matchedNode->getAttribute('class'));
|
|
} else {
|
|
Assert::assertEquals('text-right', $matchedNode->getAttribute('class'));
|
|
}
|
|
}
|
|
}
|
|
// @codingStandardsIgnoreEnd
|
|
|
|
/**
|
|
* Selects the first textual match in the HTML editor. Does not support
|
|
* selection across DOM node boundaries.
|
|
*
|
|
* @When /^I select "(?P<text>([^"]*))" in the "(?P<field>(?:[^"]|\\")*)" HTML field$/
|
|
*/
|
|
public function stepIHighlightTextInHtmlField($text, $field)
|
|
{
|
|
$inputField = $this->getHtmlField($field);
|
|
$inputFieldId = $inputField->getAttribute('id');
|
|
$text = addcslashes($text ?? '', "'");
|
|
|
|
$js = <<<JS
|
|
// TODO <IE9 support
|
|
// TODO Allow text matches across nodes
|
|
var editor = jQuery('#$inputFieldId').entwine('ss').getEditor(),
|
|
doc = editor.getInstance().getDoc(),
|
|
sel = editor.getInstance().selection,
|
|
rng = document.createRange(),
|
|
matched = false;
|
|
|
|
editor.getInstance().focus();
|
|
jQuery(doc).find('body *').each(function() {
|
|
if(!matched) {
|
|
for(var i=0;i<this.childNodes.length;i++) {
|
|
if(!matched && this.childNodes[i].nodeValue && this.childNodes[i].nodeValue.match('$text')) {
|
|
rng.setStart(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text'));
|
|
rng.setEnd(this.childNodes[i], this.childNodes[i].nodeValue.indexOf('$text') + '$text'.length);
|
|
sel.setRng(rng);
|
|
editor.getInstance().nodeChanged();
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
JS;
|
|
|
|
$this->getSession()->executeScript($js);
|
|
}
|
|
|
|
/**
|
|
* @Given /^I should( not? |\s*)see a "([^"]*)" field$/
|
|
*/
|
|
public function iShouldSeeAField($negative, $text)
|
|
{
|
|
$page = $this->getSession()->getPage();
|
|
$els = $page->findAll('named', ['field', "'$text'"]);
|
|
$matchedEl = null;
|
|
/** @var NodeElement $el */
|
|
foreach ($els as $el) {
|
|
if ($el->isVisible()) {
|
|
$matchedEl = $el;
|
|
}
|
|
}
|
|
|
|
if (trim($negative ?? '')) {
|
|
Assert::assertNull($matchedEl);
|
|
} else {
|
|
Assert::assertNotNull($matchedEl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Click on the element with the provided CSS Selector
|
|
*
|
|
* @When /^I press the "([^"]*)" HTML field button$/
|
|
*/
|
|
public function iClickOnTheHtmlFieldButton($button)
|
|
{
|
|
$xpath = "//*[@aria-label='" . $button . "']";
|
|
$session = $this->getSession();
|
|
$element = $session->getPage()->find('xpath', $xpath);
|
|
if (null === $element) {
|
|
// If it can't find the exact name, find one that starts with the phrase
|
|
// Helpful for "Insert link" which has a conditional label for keyboard shortcut
|
|
$xpath = "//*[starts-with(@aria-label, '" . $button . "')]";
|
|
$element = $session->getPage()->find('xpath', $xpath);
|
|
|
|
if (null === $element) {
|
|
throw new \InvalidArgumentException(sprintf('Could not find element with xpath %s', $xpath));
|
|
};
|
|
}
|
|
|
|
$element->click();
|
|
}
|
|
|
|
/*
|
|
* @example Given the CMS settings has the following data
|
|
* | Title | My site title |
|
|
* | Theme | My site theme |
|
|
* @Given /^the CMS settings have the following data$/
|
|
*/
|
|
public function theCmsSettingsHasData(TableNode $fieldsTable)
|
|
{
|
|
$fields = $fieldsTable->getRowsHash();
|
|
$siteConfig = SiteConfig::get()->first();
|
|
foreach ($fields as $field => $value) {
|
|
$siteConfig->$field = $value;
|
|
}
|
|
$siteConfig->write();
|
|
$siteConfig->flushCache();
|
|
}
|
|
|
|
/**
|
|
* Select a value in the tree dropdown field
|
|
*
|
|
* NOTE: This is react specific, may need to move to its own react section later
|
|
*
|
|
* @When /^I select "([^"]*)" in the "([^"]*)" tree dropdown$/
|
|
*/
|
|
public function iSelectValueInTreeDropdown($text, $selector)
|
|
{
|
|
$page = $this->getSession()->getPage();
|
|
/** @var NodeElement $parentElement */
|
|
$parentElement = null;
|
|
$this->retryThrowable(function () use (&$parentElement, &$page, $selector) {
|
|
$parentElement = $page->find('css', $selector);
|
|
Assert::assertNotNull($parentElement, sprintf('"%s" element not found', $selector));
|
|
$page = $this->getSession()->getPage();
|
|
});
|
|
|
|
$this->retryThrowable(function () use ($parentElement, $selector) {
|
|
$dropdown = $parentElement->find('css', '.Select-arrow');
|
|
Assert::assertNotNull($dropdown, sprintf('Unable to find the dropdown in "%s"', $selector));
|
|
$dropdown->click();
|
|
});
|
|
|
|
$this->retryThrowable(function () use ($text, $parentElement, $selector) {
|
|
$element = $parentElement->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
|
|
Assert::assertNotNull($element, sprintf('"%s" not found in "%s"', $text, $selector));
|
|
$element->click();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Locate an HTML editor field
|
|
*
|
|
* @param string $locator Raw html field identifier as passed from
|
|
* @return NodeElement
|
|
*/
|
|
protected function getHtmlField($locator)
|
|
{
|
|
$locator = $this->fixStepArgument($locator);
|
|
$page = $this->getSession()->getPage();
|
|
|
|
// Searching by name is usually good...
|
|
$element = $page->find('css', 'textarea.htmleditor[name=\'' . $locator . '\']');
|
|
|
|
if ($element === null) {
|
|
$element = $this->findInputByLabelContent($locator);
|
|
}
|
|
|
|
Assert::assertNotNull($element, sprintf('HTML field "%s" not found', $locator));
|
|
return $element;
|
|
}
|
|
|
|
protected function findInputByLabelContent($locator)
|
|
{
|
|
$page = $this->getSession()->getPage();
|
|
$label = $page->findAll('xpath', sprintf('//label[contains(text(), \'%s\')]', $locator));
|
|
|
|
if (empty($label)) {
|
|
return null;
|
|
}
|
|
|
|
Assert::assertCount(1, $label, sprintf(
|
|
'Found more than one element containing the phrase "%s".',
|
|
$locator
|
|
));
|
|
|
|
$label = array_shift($label);
|
|
|
|
$fieldId = $label->getAttribute('for');
|
|
return $page->find('css', '#' . $fieldId);
|
|
}
|
|
|
|
/**
|
|
* @Given /^the "([^"]*)" field ((?:does not have)|(?:has)) property "([^"]*)"$/
|
|
*/
|
|
public function assertTheFieldHasProperty($name, $cond, $property)
|
|
{
|
|
$name = $this->fixStepArgument($name);
|
|
$property = $this->fixStepArgument($property);
|
|
|
|
$context = $this->getMainContext();
|
|
$fieldObj = $context->assertSession()->fieldExists($name);
|
|
|
|
// Check property
|
|
$hasProperty = $fieldObj->hasAttribute($property);
|
|
switch ($cond) {
|
|
case 'has':
|
|
assert($hasProperty, "Field $name does not have property $property");
|
|
break;
|
|
case 'does not have':
|
|
assert(!$hasProperty, "Field $name should not have property $property");
|
|
break;
|
|
default:
|
|
throw new BadMethodCallException("Invalid condition");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^I switch to the "([^"]*)" iframe$/
|
|
* @param string $id iframe id property
|
|
*/
|
|
public function stepSwitchToTheFrame($id)
|
|
{
|
|
$this->getMainContext()->getSession()->getDriver()->switchToIFrame($id);
|
|
}
|
|
|
|
/**
|
|
* @When /^I am not in an iframe$/
|
|
*/
|
|
public function stepSwitchToParentFrame()
|
|
{
|
|
$this->getMainContext()->getSession()->getDriver()->switchToIFrame(null);
|
|
}
|
|
|
|
/**
|
|
* @When /^my session expires$/
|
|
*/
|
|
public function stepMySessionExpires()
|
|
{
|
|
// Destroy cookie to detach session
|
|
$this->getMainContext()->getSession()->setCookie('PHPSESSID', null);
|
|
}
|
|
|
|
/**
|
|
* @When /^I should see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
|
|
* @param string $buttonLabel
|
|
* @param string $gridFieldName
|
|
* @param string $rowName
|
|
*/
|
|
public function assertIShouldSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
|
|
{
|
|
$button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
|
|
Assert::assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel));
|
|
}
|
|
|
|
/**
|
|
* @When /^I should not see the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
|
|
* @param string $buttonLabel
|
|
* @param string $gridFieldName
|
|
* @param string $rowName
|
|
*/
|
|
public function assertIShouldNotSeeTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
|
|
{
|
|
$button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
|
|
Assert::assertNull($button, sprintf('Button "%s" found', $buttonLabel));
|
|
}
|
|
|
|
/**
|
|
* @When /^I click the "([^"]*)" button in the "([^"]*)" gridfield for the "([^"]*)" row$/
|
|
* @param string $buttonLabel
|
|
* @param string $gridFieldName
|
|
* @param string $rowName
|
|
*/
|
|
public function stepIClickTheGridFieldButtonForRow($buttonLabel, $gridFieldName, $rowName)
|
|
{
|
|
$button = $this->getGridFieldButton($gridFieldName, $rowName, $buttonLabel);
|
|
Assert::assertNotNull($button, sprintf('Button "%s" not found', $buttonLabel));
|
|
|
|
$button->click();
|
|
}
|
|
|
|
/**
|
|
* Finds a button in the gridfield row
|
|
*
|
|
* @param $gridFieldName
|
|
* @param $rowName
|
|
* @param $buttonLabel
|
|
* @return $button
|
|
*/
|
|
protected function getGridFieldButton($gridFieldName, $rowName, $buttonLabel)
|
|
{
|
|
$page = $this->getSession()->getPage();
|
|
$gridField = $page->find('xpath', sprintf('//*[@data-name="%s"]', $gridFieldName));
|
|
Assert::assertNotNull($gridField, sprintf('Gridfield "%s" not found', $gridFieldName));
|
|
|
|
$name = $gridField->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $rowName));
|
|
if (!$name) {
|
|
return null;
|
|
}
|
|
|
|
if ($dropdownButton = $name->getParent()->find('css', '.action-menu__toggle')) {
|
|
$dropdownButton->click();
|
|
}
|
|
|
|
$button = $name->getParent()->find('named', ['link_or_button', $buttonLabel]);
|
|
|
|
return $button;
|
|
}
|
|
|
|
/**
|
|
* @When /^I click the "([^"]*)" option in the "([^"]*)" listbox$/
|
|
* @param $optionLabel
|
|
* @param $fieldName
|
|
*/
|
|
public function stepIClickTheListBoxOption($optionLabel, $fieldName)
|
|
{
|
|
$page = $this->getSession()->getPage();
|
|
$listBox = $page->find('xpath', sprintf('//*[@name="%s[]"]', $fieldName));
|
|
Assert::assertNotNull($listBox, sprintf('The listbox %s is not found', $fieldName));
|
|
|
|
$option = $listBox->getParent()
|
|
->find('css', '.chosen-choices')
|
|
->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $optionLabel));
|
|
Assert::assertNotNull($option, sprintf('Option %s is not found', $optionLabel));
|
|
|
|
$button = $option->getParent()->find('css', 'a');
|
|
|
|
$button->click();
|
|
}
|
|
}
|