diff --git a/.travis.yml b/.travis.yml index ab9621e..b779753 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,24 @@ language: php sudo: false php: - - 5.5 - 5.6 -script: - - vendor/bin/phpunit tests - +env: + matrix: + - PHPUNIT_TEST=1 + - PHPCS_TEST=1 + +matrix: + include: + - php: 5.5 + env: PHPUNIT_TEST=1 + before_script: - - composer install --dev --prefer-dist + - composer install --dev --prefer-dist + - pyrus install pear/PHP_CodeSniffer + - phpenv rehash + + +script: + - "if [ \"$PHPUNIT_TEST\" = \"1\" ]; then vendor/bin/phpunit tests; fi" + - "if [ \"$PHPCS_TEST\" = \"1\" ]; then phpcs --standard=PSR2 -n src/ tests/; fi" diff --git a/build.php b/build.php index 692c589..8bd98ff 100644 --- a/build.php +++ b/build.php @@ -58,4 +58,4 @@ function findFiles($dir) } return $files; -} \ No newline at end of file +} diff --git a/init.php b/init.php index 2dc3ff5..f3021e5 100644 --- a/init.php +++ b/init.php @@ -9,12 +9,11 @@ * with this source code in the file LICENSE. */ -spl_autoload_register(function($class) -{ +spl_autoload_register(function ($class) { if (false !== strpos($class, 'SilverStripe\\BehatExtension')) { require_once(__DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php'); return true; } }, true, false); -return new SilverStripe\BehatExtension\Extension; \ No newline at end of file +return new SilverStripe\BehatExtension\Extension; diff --git a/src/SilverStripe/BehatExtension/Compiler/CoreInitializationPass.php b/src/SilverStripe/BehatExtension/Compiler/CoreInitializationPass.php index 7db3c67..397abc7 100644 --- a/src/SilverStripe/BehatExtension/Compiler/CoreInitializationPass.php +++ b/src/SilverStripe/BehatExtension/Compiler/CoreInitializationPass.php @@ -2,8 +2,8 @@ namespace SilverStripe\BehatExtension\Compiler; -use Symfony\Component\DependencyInjection\ContainerBuilder, - Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; /** * Loads SilverStripe core. Required to initialize autoloading. @@ -22,7 +22,7 @@ class CoreInitializationPass implements CompilerPassInterface $_GET['flush'] = 1; require_once $frameworkPath . '/core/Core.php'; - if(class_exists('TestRunner')) { + if (class_exists('TestRunner')) { // 3.x compat \TestRunner::use_test_manifest(); } else { @@ -34,4 +34,4 @@ class CoreInitializationPass implements CompilerPassInterface // Remove the error handler so that PHPUnit can add its own restore_error_handler(); } -} \ No newline at end of file +} diff --git a/src/SilverStripe/BehatExtension/Compiler/MinkExtensionBaseUrlPass.php b/src/SilverStripe/BehatExtension/Compiler/MinkExtensionBaseUrlPass.php index 0a5bf33..f86cbe4 100644 --- a/src/SilverStripe/BehatExtension/Compiler/MinkExtensionBaseUrlPass.php +++ b/src/SilverStripe/BehatExtension/Compiler/MinkExtensionBaseUrlPass.php @@ -2,8 +2,8 @@ namespace SilverStripe\BehatExtension\Compiler; -use Symfony\Component\DependencyInjection\ContainerBuilder, - Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; /** * Behat\SilverStripe container compilation pass. @@ -24,22 +24,23 @@ class MinkExtensionBaseUrlPass implements CompilerPassInterface $frameworkPath = $container->getParameter('behat.silverstripe_extension.framework_path'); global $_FILE_TO_URL_MAPPING; - if($container->getParameter('behat.mink.base_url')) { + if ($container->getParameter('behat.mink.base_url')) { // If base_url is already defined, also set it in the SilverStripe mapping $_FILE_TO_URL_MAPPING[dirname($frameworkPath)] = $container->getParameter('behat.mink.base_url'); - } else if($envPath = $this->findEnvironmentConfigFile($frameworkPath)) { + } elseif ($envPath = $this->findEnvironmentConfigFile($frameworkPath)) { // Otherwise try to retrieve it from _ss_environment include_once $envPath; - if( - isset($_FILE_TO_URL_MAPPING) + if (isset($_FILE_TO_URL_MAPPING) && !($container->hasParameter('behat.mink.base_url') && $container->getParameter('behat.mink.base_url')) ) { $baseUrl = $this->findBaseUrlFromMapping(dirname($frameworkPath), $_FILE_TO_URL_MAPPING); - if($baseUrl) $container->setParameter('behat.mink.base_url', $baseUrl); + if ($baseUrl) { + $container->setParameter('behat.mink.base_url', $baseUrl); + } } } - if(!$container->getParameter('behat.mink.base_url')) { + if (!$container->getParameter('behat.mink.base_url')) { throw new \InvalidArgumentException( '"base_url" not configured. Please specify it in your behat.yml configuration, ' . 'or in your _ss_environment.php configuration through $_FILE_TO_URL_MAPPING' @@ -60,11 +61,12 @@ class MinkExtensionBaseUrlPass implements CompilerPassInterface * @param String Absolute start path to search upwards from * @return Boolean Absolute path to environment file */ - protected function findEnvironmentConfigFile($path) { + protected function findEnvironmentConfigFile($path) + { $envPath = null; $envFile = '_ss_environment.php'; //define the name of the environment file $path = '.'; //define the dir to start scanning from (have to add the trailing slash) - + //check this dir and every parent dir (until we hit the base of the drive) do { $path = realpath($path) . '/'; @@ -87,12 +89,13 @@ class MinkExtensionBaseUrlPass implements CompilerPassInterface * @param Array Map of paths to host names * @return String URL */ - protected function findBaseUrlFromMapping($path, $mapping) { + protected function findBaseUrlFromMapping($path, $mapping) + { $fullPath = $path; $url = null; - while($path && $path != "/" && !preg_match('/^[A-Z]:\\\\$/', $path)) { - if(isset($mapping[$path])) { - $url = $mapping[$path] . str_replace(DIRECTORY_SEPARATOR, '/', substr($fullPath,strlen($path))); + while ($path && $path != "/" && !preg_match('/^[A-Z]:\\\\$/', $path)) { + if (isset($mapping[$path])) { + $url = $mapping[$path] . str_replace(DIRECTORY_SEPARATOR, '/', substr($fullPath, strlen($path))); break; } else { $path = dirname($path); // traverse up diff --git a/src/SilverStripe/BehatExtension/Console/Processor/InitProcessor.php b/src/SilverStripe/BehatExtension/Console/Processor/InitProcessor.php index c273a14..bf13fc2 100644 --- a/src/SilverStripe/BehatExtension/Console/Processor/InitProcessor.php +++ b/src/SilverStripe/BehatExtension/Console/Processor/InitProcessor.php @@ -2,12 +2,12 @@ namespace SilverStripe\BehatExtension\Console\Processor; -use Symfony\Component\DependencyInjection\ContainerInterface, - Symfony\Component\Console\Command\Command, - Symfony\Component\Console\Input\InputArgument, - Symfony\Component\Console\Input\InputInterface, - Symfony\Component\Console\Output\OutputInterface, - Symfony\Component\Console\Input\InputOption; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; use Behat\Behat\Console\Processor\InitProcessor as BaseProcessor; @@ -33,7 +33,10 @@ class InitProcessor extends BaseProcessor { parent::configure($command); - $command->addOption('--namespace', null, InputOption::VALUE_OPTIONAL, + $command->addOption( + '--namespace', + null, + InputOption::VALUE_OPTIONAL, "Optional namespace for FeatureContext, defaults to \\Test\\Behaviour.\n" ); } @@ -68,7 +71,7 @@ class InitProcessor extends BaseProcessor unset($_GET['flush']); $featuresPath = $input->getArgument('features'); - if(!$featuresPath) { + if (!$featuresPath) { throw new \InvalidArgumentException('Please specify a module name (e.g. "@mymodule")'); } @@ -86,21 +89,21 @@ class InitProcessor extends BaseProcessor throw new \InvalidArgumentException(sprintf('Module "%s" not found', $currentModuleName)); } $currentModulePath = $modules[$currentModuleName]; - } + } if (!$currentModuleName) { throw new \InvalidArgumentException('Can not find module to initialize suite.'); } // TODO Retrieve from module definition once that's implemented - if($input->getOption('namespace')) { + if ($input->getOption('namespace')) { $namespace = $input->getOption('namespace'); } else { $namespace = ucfirst($currentModuleName); } $namespace .= '\\' . $this->container->getParameter('behat.silverstripe_extension.context.namespace_suffix'); - $featuresPath = rtrim($currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix,DIRECTORY_SEPARATOR); + $featuresPath = rtrim($currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix, DIRECTORY_SEPARATOR); $basePath = $this->container->getParameter('behat.paths.base').DIRECTORY_SEPARATOR; $bootstrapPath = $featuresPath.DIRECTORY_SEPARATOR.'bootstrap'; $contextPath = $bootstrapPath.DIRECTORY_SEPARATOR.'Context'; @@ -140,7 +143,7 @@ class InitProcessor extends BaseProcessor */ protected function getFeatureContextSkelet() { -return <<<'PHP' + return <<<'PHP' addArgument('features', InputArgument::OPTIONAL, + $command->addArgument( + 'features', + InputArgument::OPTIONAL, "Feature(s) to run. Could be:". "\n- a dir (src/to/module/Features/), " . "\n- a feature (src/to/module/Features/*.feature), " . @@ -91,7 +93,7 @@ class LocatorProcessor extends BaseProcessor $featuresPath = $currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix.DIRECTORY_SEPARATOR.$featuresPath; } - if($input->getOption('namespace')) { + if ($input->getOption('namespace')) { $namespace = $input->getOption('namespace'); } else { $namespace = ucfirst($currentModuleName); diff --git a/src/SilverStripe/BehatExtension/Context/BasicContext.php b/src/SilverStripe/BehatExtension/Context/BasicContext.php index 2af042c..543f356 100644 --- a/src/SilverStripe/BehatExtension/Context/BasicContext.php +++ b/src/SilverStripe/BehatExtension/Context/BasicContext.php @@ -2,15 +2,15 @@ namespace SilverStripe\BehatExtension\Context; -use Behat\Behat\Context\BehatContext, - Behat\Behat\Context\Step, - Behat\Behat\Event\StepEvent, - Behat\Behat\Event\ScenarioEvent; +use Behat\Behat\Context\BehatContext; +use Behat\Behat\Context\Step; +use Behat\Behat\Event\StepEvent; +use Behat\Behat\Event\ScenarioEvent; use Behat\Mink\Driver\Selenium2Driver; -use Behat\Gherkin\Node\PyStringNode, - Behat\Gherkin\Node\TableNode; +use Behat\Gherkin\Node\PyStringNode; +use Behat\Gherkin\Node\TableNode; // PHPUnit require_once BASE_PATH . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php'; @@ -28,22 +28,22 @@ class BasicContext extends BehatContext protected $context; /** - * Date format in date() syntax - * @var String - */ - protected $dateFormat = 'Y-m-d'; + * Date format in date() syntax + * @var String + */ + protected $dateFormat = 'Y-m-d'; - /** - * Time format in date() syntax - * @var String - */ - protected $timeFormat = 'H:i:s'; + /** + * Time format in date() syntax + * @var String + */ + protected $timeFormat = 'H:i:s'; - /** - * Date/time format in date() syntax - * @var String - */ - protected $datetimeFormat = 'Y-m-d H:i:s'; + /** + * Date/time format in date() syntax + * @var String + */ + protected $datetimeFormat = 'Y-m-d H:i:s'; /** * Initializes context. @@ -51,19 +51,21 @@ class BasicContext extends BehatContext * * @param array $parameters context parameters (set them up through behat.yml) */ - public function __construct(array $parameters) { + public function __construct(array $parameters) + { // Initialize your context here $this->context = $parameters; } - /** - * Get Mink session from MinkContext - * - * @return \Behat\Mink\Session - */ - public function getSession($name = null) { - return $this->getMainContext()->getSession($name); - } + /** + * Get Mink session from MinkContext + * + * @return \Behat\Mink\Session + */ + public function getSession($name = null) + { + return $this->getMainContext()->getSession($name); + } /** * @AfterStep ~@modal @@ -71,9 +73,10 @@ class BasicContext extends BehatContext * Excluding scenarios with @modal tag is required, * because modal dialogs stop any JS interaction */ - public function appendErrorHandlerBeforeStep(StepEvent $event) { - try{ - $javascript = <<getSession()->executeScript($javascript); - }catch(\WebDriver\Exception $e){ - $this->logException($e); + $this->getSession()->executeScript($javascript); + } catch (\WebDriver\Exception $e) { + $this->logException($e); } } @@ -102,17 +105,18 @@ JS; * Excluding scenarios with @modal tag is required, * because modal dialogs stop any JS interaction */ - public function readErrorHandlerAfterStep(StepEvent $event) { - try{ - $page = $this->getSession()->getPage(); + public function readErrorHandlerAfterStep(StepEvent $event) + { + try { + $page = $this->getSession()->getPage(); - $jserrors = $page->find('xpath', '//body[@data-jserrors]'); - if (null !== $jserrors) { - $this->takeScreenshot($event); - file_put_contents('php://stderr', $jserrors->getAttribute('data-jserrors') . PHP_EOL); - } + $jserrors = $page->find('xpath', '//body[@data-jserrors]'); + if (null !== $jserrors) { + $this->takeScreenshot($event); + file_put_contents('php://stderr', $jserrors->getAttribute('data-jserrors') . PHP_EOL); + } - $javascript = <<getSession()->executeScript($javascript); - }catch(\WebDriver\Exception $e){ - $this->logException($e); + $this->getSession()->executeScript($javascript); + } catch (\WebDriver\Exception $e) { + $this->logException($e); } } @@ -133,16 +137,17 @@ JS; * * @BeforeStep */ - public function handleAjaxBeforeStep(StepEvent $event) { - try{ - $ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps(); - $ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps)); + public function handleAjaxBeforeStep(StepEvent $event) + { + try { + $ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps(); + $ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps)); - if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) { - return; - } + if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) { + return; + } - $javascript = <<getSession()->wait(500); // give browser a chance to process and render response - $this->getSession()->executeScript($javascript); - }catch(\WebDriver\Exception $e){ - $this->logException($e); + $this->getSession()->wait(500); // give browser a chance to process and render response + $this->getSession()->executeScript($javascript); + } catch (\WebDriver\Exception $e) { + $this->logException($e); } } @@ -180,35 +185,38 @@ JS; * * @AfterStep ~@modal */ - public function handleAjaxAfterStep(StepEvent $event) { - try{ - $ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps(); - $ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps)); + public function handleAjaxAfterStep(StepEvent $event) + { + try { + $ajaxEnabledSteps = $this->getMainContext()->getAjaxSteps(); + $ajaxEnabledSteps = implode('|', array_filter($ajaxEnabledSteps)); - if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) { - return; - } + if (empty($ajaxEnabledSteps) || !preg_match('/(' . $ajaxEnabledSteps . ')/i', $event->getStep()->getText())) { + return; + } - $this->handleAjaxTimeout(); + $this->handleAjaxTimeout(); - $javascript = <<getSession()->executeScript($javascript); - }catch(\WebDriver\Exception $e){ - $this->logException($e); + $this->getSession()->executeScript($javascript); + } catch (\WebDriver\Exception $e) { + $this->logException($e); } } - public function handleAjaxTimeout() { + public function handleAjaxTimeout() + { $timeoutMs = $this->getMainContext()->getAjaxTimeout(); // Wait for an ajax request to complete, but only for a maximum of 5 seconds to avoid deadlocks - $this->getSession()->wait($timeoutMs, + $this->getSession()->wait( + $timeoutMs, "(typeof window.__ajaxStatus !== 'undefined' ? window.__ajaxStatus() : 'no ajax') !== 'waiting'" ); @@ -222,54 +230,58 @@ JS; * * @AfterStep */ - public function takeScreenshotAfterFailedStep(StepEvent $event) { - if (4 === $event->getResult()) { - try{ - $this->takeScreenshot($event); - }catch(\WebDriver\Exception $e){ - $this->logException($e); - } - } - } + public function takeScreenshotAfterFailedStep(StepEvent $event) + { + if (4 === $event->getResult()) { + try { + $this->takeScreenshot($event); + } catch (\WebDriver\Exception $e) { + $this->logException($e); + } + } + } - /** - * Close modal dialog if test scenario fails on CMS page - * - * @AfterScenario - */ - public function closeModalDialog(ScenarioEvent $event) { - try{ - // Only for failed tests on CMS page - if (4 === $event->getResult()) { - $cmsElement = $this->getSession()->getPage()->find('css', '.cms'); - if($cmsElement) { - try { - // Navigate away triggered by reloading the page - $this->getSession()->reload(); - $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); - } catch(\WebDriver\Exception $e) { - // no-op, alert might not be present - } - } - } - }catch(\WebDriver\Exception $e){ - $this->logException($e); - } - } + /** + * Close modal dialog if test scenario fails on CMS page + * + * @AfterScenario + */ + public function closeModalDialog(ScenarioEvent $event) + { + try { + // Only for failed tests on CMS page + if (4 === $event->getResult()) { + $cmsElement = $this->getSession()->getPage()->find('css', '.cms'); + if ($cmsElement) { + try { + // Navigate away triggered by reloading the page + $this->getSession()->reload(); + $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); + } catch (\WebDriver\Exception $e) { + // no-op, alert might not be present + } + } + } + } catch (\WebDriver\Exception $e) { + $this->logException($e); + } + } /** * Delete any created files and folders from assets directory * * @AfterScenario @assets */ - public function cleanAssetsAfterScenario(ScenarioEvent $event) { - foreach(\File::get() as $file) { + public function cleanAssetsAfterScenario(ScenarioEvent $event) + { + foreach (\File::get() as $file) { $file->delete(); } - \Filesystem::removeFolder(ASSETS_PATH, true); + \Filesystem::removeFolder(ASSETS_PATH, true); } - public function takeScreenshot(StepEvent $event) { + public function takeScreenshot(StepEvent $event) + { $driver = $this->getSession()->getDriver(); // quit silently when unsupported if (!($driver instanceof Selenium2Driver)) { @@ -282,7 +294,9 @@ JS; $screenshotPath = null; $path = $this->getMainContext()->getScreenshotPath(); - if(!$path) return; // quit silently when path is not set + if (!$path) { + return; + } // quit silently when path is not set \Filesystem::makeFolder($path); $path = realpath($path); @@ -310,7 +324,8 @@ JS; /** * @Then /^I should be redirected to "([^"]+)"/ */ - public function stepIShouldBeRedirectedTo($url) { + public function stepIShouldBeRedirectedTo($url) + { if ($this->getMainContext()->canIntercept()) { $client = $this->getSession()->getDriver()->getClient(); $client->followRedirects(true); @@ -325,7 +340,8 @@ JS; /** * @Given /^the page can't be found/ */ - public function stepPageCantBeFound() { + public function stepPageCantBeFound() + { $page = $this->getSession()->getPage(); assertTrue( // Content from ErrorPage default record @@ -338,19 +354,23 @@ JS; /** * @Given /^I wait (?:for )?([\d\.]+) second(?:s?)$/ */ - public function stepIWaitFor($secs) { + public function stepIWaitFor($secs) + { $this->getSession()->wait((float)$secs*1000); } /** * @Given /^I press the "([^"]*)" button$/ */ - public function stepIPressTheButton($button) { + public function stepIPressTheButton($button) + { $page = $this->getSession()->getPage(); $els = $page->findAll('named', array('link_or_button', "'$button'")); $matchedEl = null; - foreach($els as $el) { - if($el->isVisible()) $matchedEl = $el; + foreach ($els as $el) { + if ($el->isVisible()) { + $matchedEl = $el; + } } assertNotNull($matchedEl, sprintf('%s button not found', $button)); $matchedEl->click(); @@ -363,7 +383,8 @@ JS; * * @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), confirming the dialog$/ */ - public function stepIPressTheButtonConfirmingTheDialog($button) { + public function stepIPressTheButtonConfirmingTheDialog($button) + { $this->stepIPressTheButton($button); $this->iConfirmTheDialog(); } @@ -374,15 +395,17 @@ JS; * * @Given /^I (?:press|follow) the "([^"]*)" (?:button|link), dismissing the dialog$/ */ - public function stepIPressTheButtonDismissingTheDialog($button) { - $this->stepIPressTheButton($button); - $this->iDismissTheDialog(); + public function stepIPressTheButtonDismissingTheDialog($button) + { + $this->stepIPressTheButton($button); + $this->iDismissTheDialog(); } /** * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element$/ */ - public function iClickInTheElement($clickType, $text, $selector) { + public function iClickInTheElement($clickType, $text, $selector) + { $clickTypeMap = array( "double click" => "doubleclick", "click" => "click" @@ -402,25 +425,28 @@ JS; * * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, confirming the dialog$/ */ - public function iClickInTheElementConfirmingTheDialog($clickType, $text, $selector) { - $this->iClickInTheElement($clickType, $text, $selector); - $this->iConfirmTheDialog(); - } + public function iClickInTheElementConfirmingTheDialog($clickType, $text, $selector) + { + $this->iClickInTheElement($clickType, $text, $selector); + $this->iConfirmTheDialog(); + } /** * Needs to be in single command to avoid "unexpected alert open" errors in Selenium. * Example: I click "Delete" in the ".actions" element, dismissing the dialog * * @Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, dismissing the dialog$/ */ - public function iClickInTheElementDismissingTheDialog($clickType, $text, $selector) { - $this->iClickInTheElement($clickType, $text, $selector); - $this->iDismissTheDialog(); - } + public function iClickInTheElementDismissingTheDialog($clickType, $text, $selector) + { + $this->iClickInTheElement($clickType, $text, $selector); + $this->iDismissTheDialog(); + } /** * @Given /^I type "([^"]*)" into the dialog$/ */ - public function iTypeIntoTheDialog($data) { + public function iTypeIntoTheDialog($data) + { $data = array( 'text' => $data, ); @@ -430,7 +456,8 @@ JS; /** * @Given /^I confirm the dialog$/ */ - public function iConfirmTheDialog() { + public function iConfirmTheDialog() + { $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); $this->handleAjaxTimeout(); } @@ -438,7 +465,8 @@ JS; /** * @Given /^I dismiss the dialog$/ */ - public function iDismissTheDialog() { + public function iDismissTheDialog() + { $this->getSession()->getDriver()->getWebDriverSession()->dismiss_alert(); $this->handleAjaxTimeout(); } @@ -446,7 +474,8 @@ JS; /** * @Given /^(?:|I )attach the file "(?P[^"]*)" to "(?P(?:[^"]|\\")*)" with HTML5$/ */ - public function iAttachTheFileTo($field, $path) { + public function iAttachTheFileTo($field, $path) + { // Remove wrapped button styling to make input field accessible to Selenium $js = <<getSession()->getPage(); - $parent = null; + /** + * Select an individual input from within a group, matched by the top-most label. + * + * @Given /^I select "([^"]*)" from "([^"]*)" input group$/ + */ + public function iSelectFromInputGroup($value, $labelText) + { + $page = $this->getSession()->getPage(); + $parent = null; - foreach($page->findAll('css', 'label') as $label) { - if($label->getText() == $labelText) { - $parent = $label->getParent(); - } - } + foreach ($page->findAll('css', 'label') as $label) { + if ($label->getText() == $labelText) { + $parent = $label->getParent(); + } + } - if(!$parent) throw new \InvalidArgumentException(sprintf('Input group with label "%s" cannot be found', $labelText)); + if (!$parent) { + throw new \InvalidArgumentException(sprintf('Input group with label "%s" cannot be found', $labelText)); + } - foreach($parent->findAll('css', 'label') as $option) { - if($option->getText() == $value) { - $input = null; + foreach ($parent->findAll('css', 'label') as $option) { + if ($option->getText() == $value) { + $input = null; - // First, look for inputs referenced by the "for" element on this label - $for = $option->getAttribute('for'); - if ($for) $input = $parent->findById($for); + // First, look for inputs referenced by the "for" element on this label + $for = $option->getAttribute('for'); + if ($for) { + $input = $parent->findById($for); + } - // Otherwise look for inputs _inside_ the label - if (!$input) $input = $option->find('css', 'input'); + // Otherwise look for inputs _inside_ the label + if (!$input) { + $input = $option->find('css', 'input'); + } - if(!$input) throw new \InvalidArgumentException(sprintf('Input "%s" cannot be found', $value)); + if (!$input) { + throw new \InvalidArgumentException(sprintf('Input "%s" cannot be found', $value)); + } - $this->getSession()->getDriver()->click($input->getXPath()); - } - } - } + $this->getSession()->getDriver()->click($input->getXPath()); + } + } + } /** * Pauses the scenario until the user presses a key. Useful when debugging a scenario. * * @Then /^(?:|I )put a breakpoint$/ */ - public function iPutABreakpoint() { + public function iPutABreakpoint() + { fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m"); - while (fgets(STDIN, 1024) == '') {} + while (fgets(STDIN, 1024) == '') { + } fwrite(STDOUT, "\033[u"); return; } - /** - * Transforms relative time statements compatible with strtotime(). - * Example: "time of 1 hour ago" might return "22:00:00" if its currently "23:00:00". - * Customize through {@link setTimeFormat()}. - * - * @Transform /^(?:(the|a)) time of (?.*)$/ - */ - public function castRelativeToAbsoluteTime($prefix, $val) { - $timestamp = strtotime($val); - if(!$timestamp) { - throw new \InvalidArgumentException(sprintf( - "Can't resolve '%s' into a valid datetime value", - $val - )); - } - return date($this->timeFormat, $timestamp); - } + /** + * Transforms relative time statements compatible with strtotime(). + * Example: "time of 1 hour ago" might return "22:00:00" if its currently "23:00:00". + * Customize through {@link setTimeFormat()}. + * + * @Transform /^(?:(the|a)) time of (?.*)$/ + */ + public function castRelativeToAbsoluteTime($prefix, $val) + { + $timestamp = strtotime($val); + if (!$timestamp) { + throw new \InvalidArgumentException(sprintf( + "Can't resolve '%s' into a valid datetime value", + $val + )); + } + return date($this->timeFormat, $timestamp); + } - /** - * Transforms relative date and time statements compatible with strtotime(). - * Example: "datetime of 2 days ago" might return "2013-10-10 22:00:00" if its currently - * the 12th of October 2013. Customize through {@link setDatetimeFormat()}. - * - * @Transform /^(?:(the|a)) datetime of (?.*)$/ - */ - public function castRelativeToAbsoluteDatetime($prefix, $val) { - $timestamp = strtotime($val); - if(!$timestamp) { - throw new \InvalidArgumentException(sprintf( - "Can't resolve '%s' into a valid datetime value", - $val - )); - } - return date($this->datetimeFormat, $timestamp); - } + /** + * Transforms relative date and time statements compatible with strtotime(). + * Example: "datetime of 2 days ago" might return "2013-10-10 22:00:00" if its currently + * the 12th of October 2013. Customize through {@link setDatetimeFormat()}. + * + * @Transform /^(?:(the|a)) datetime of (?.*)$/ + */ + public function castRelativeToAbsoluteDatetime($prefix, $val) + { + $timestamp = strtotime($val); + if (!$timestamp) { + throw new \InvalidArgumentException(sprintf( + "Can't resolve '%s' into a valid datetime value", + $val + )); + } + return date($this->datetimeFormat, $timestamp); + } - /** - * Transforms relative date statements compatible with strtotime(). - * Example: "date 2 days ago" might return "2013-10-10" if its currently - * the 12th of October 2013. Customize through {@link setDateFormat()}. - * - * @Transform /^(?:(the|a)) date of (?.*)$/ - */ - public function castRelativeToAbsoluteDate($prefix, $val) { - $timestamp = strtotime($val); - if(!$timestamp) { - throw new \InvalidArgumentException(sprintf( - "Can't resolve '%s' into a valid datetime value", - $val - )); - } - return date($this->dateFormat, $timestamp); - } + /** + * Transforms relative date statements compatible with strtotime(). + * Example: "date 2 days ago" might return "2013-10-10" if its currently + * the 12th of October 2013. Customize through {@link setDateFormat()}. + * + * @Transform /^(?:(the|a)) date of (?.*)$/ + */ + public function castRelativeToAbsoluteDate($prefix, $val) + { + $timestamp = strtotime($val); + if (!$timestamp) { + throw new \InvalidArgumentException(sprintf( + "Can't resolve '%s' into a valid datetime value", + $val + )); + } + return date($this->dateFormat, $timestamp); + } - public function getDateFormat() { - return $this->dateFormat; - } + public function getDateFormat() + { + return $this->dateFormat; + } - public function setDateFormat($format) { - $this->dateFormat = $format; - } + public function setDateFormat($format) + { + $this->dateFormat = $format; + } - public function getTimeFormat() { - return $this->timeFormat; - } + public function getTimeFormat() + { + return $this->timeFormat; + } - public function setTimeFormat($format) { - $this->timeFormat = $format; - } + public function setTimeFormat($format) + { + $this->timeFormat = $format; + } - public function getDatetimeFormat() { - return $this->datetimeFormat; - } + public function getDatetimeFormat() + { + return $this->datetimeFormat; + } - public function setDatetimeFormat($format) { - $this->datetimeFormat = $format; - } + public function setDatetimeFormat($format) + { + $this->datetimeFormat = $format; + } /** * Checks that field with specified in|name|label|value is disabled. @@ -595,9 +644,10 @@ JS; * @Then /^the "(?P(?:[^"]|\\")*)" (?P(?:(field|button))) should (?P(?:(not |)))be disabled/ * @Then /^the (?P(?:(field|button))) "(?P(?:[^"]|\\")*)" should (?P(?:(not |)))be disabled/ */ - public function stepFieldShouldBeDisabled($name, $type, $negate) { + public function stepFieldShouldBeDisabled($name, $type, $negate) + { $page = $this->getSession()->getPage(); - if($type == 'field') { + if ($type == 'field') { $element = $page->findField($name); } else { $element = $page->find('named', array( @@ -608,30 +658,31 @@ JS; assertNotNull($element, sprintf("Element '%s' not found", $name)); $disabledAttribute = $element->getAttribute('disabled'); - if(trim($negate)) { + if (trim($negate)) { assertNull($disabledAttribute, sprintf("Failed asserting element '%s' is not disabled", $name)); } else { assertNotNull($disabledAttribute, sprintf("Failed asserting element '%s' is disabled", $name)); } } - /** - * Checks that checkbox with specified in|name|label|value is enabled. - * Example: Then the field "Email" should be enabled - * Example: Then the "Email" field should be enabled - * - * @Then /^the "(?P(?:[^"]|\\")*)" field should be enabled/ - * @Then /^the field "(?P(?:[^"]|\\")*)" should be enabled/ - */ - public function stepFieldShouldBeEnabled($field) { - $page = $this->getSession()->getPage(); - $fieldElement = $page->findField($field); - assertNotNull($fieldElement, sprintf("Field '%s' not found", $field)); + /** + * Checks that checkbox with specified in|name|label|value is enabled. + * Example: Then the field "Email" should be enabled + * Example: Then the "Email" field should be enabled + * + * @Then /^the "(?P(?:[^"]|\\")*)" field should be enabled/ + * @Then /^the field "(?P(?:[^"]|\\")*)" should be enabled/ + */ + public function stepFieldShouldBeEnabled($field) + { + $page = $this->getSession()->getPage(); + $fieldElement = $page->findField($field); + assertNotNull($fieldElement, sprintf("Field '%s' not found", $field)); - $disabledAttribute = $fieldElement->getAttribute('disabled'); + $disabledAttribute = $fieldElement->getAttribute('disabled'); - assertNull($disabledAttribute, sprintf("Failed asserting field '%s' is enabled", $field)); - } + assertNull($disabledAttribute, sprintf("Failed asserting field '%s' is enabled", $field)); + } /** * Clicks a link in a specific region (an element identified by a CSS selector, a "data-title" attribute, @@ -642,14 +693,15 @@ JS; * * @Given /^I (?:follow|click) "(?P[^"]*)" in the "(?P[^"]*)" region$/ */ - public function iFollowInTheRegion($link, $region) { + public function iFollowInTheRegion($link, $region) + { $context = $this->getMainContext(); $regionObj = $context->getRegionObj($region); assertNotNull($regionObj); $linkObj = $regionObj->findLink($link); if (empty($linkObj)) { - throw new \Exception(sprintf('The link "%s" was not found in the region "%s" + throw new \Exception(sprintf('The link "%s" was not found in the region "%s" on the page %s', $link, $region, $this->getSession()->getCurrentUrl())); } @@ -663,14 +715,15 @@ JS; * * @Given /^I fill in "(?P[^"]*)" with "(?P[^"]*)" in the "(?P[^"]*)" region$/ */ - public function iFillinTheRegion($field, $value, $region){ + public function iFillinTheRegion($field, $value, $region) + { $context = $this->getMainContext(); $regionObj = $context->getRegionObj($region); assertNotNull($regionObj, "Region Object is null"); $fieldObj = $regionObj->findField($field); if (empty($fieldObj)) { - throw new \Exception(sprintf('The field "%s" was not found in the region "%s" + throw new \Exception(sprintf('The field "%s" was not found in the region "%s" on the page %s', $field, $region, $this->getSession()->getCurrentUrl())); } @@ -688,7 +741,8 @@ JS; * * @Given /^I should (?P(?:(not |)))see "(?P[^"]*)" in the "(?P[^"]*)" region$/ */ - public function iSeeTextInRegion($negate, $text, $region) { + public function iSeeTextInRegion($negate, $text, $region) + { $context = $this->getMainContext(); $regionObj = $context->getRegionObj($region); assertNotNull($regionObj); @@ -697,7 +751,7 @@ JS; $actual = preg_replace('/\s+/u', ' ', $actual); $regex = '/'.preg_quote($text, '/').'/ui'; - if(trim($negate)) { + if (trim($negate)) { if (preg_match($regex, $actual)) { $message = sprintf( 'The text "%s" was found in the text of the "%s" region on the page %s.', @@ -720,27 +774,28 @@ JS; throw new \Exception($message); } } - } - /** - * Selects the specified radio button - * - * @Given /^I select the "([^"]*)" radio button$/ - */ - public function iSelectTheRadioButton($radioLabel) { - $session = $this->getSession(); - $radioButton = $session->getPage()->find('named', array( + /** + * Selects the specified radio button + * + * @Given /^I select the "([^"]*)" radio button$/ + */ + public function iSelectTheRadioButton($radioLabel) + { + $session = $this->getSession(); + $radioButton = $session->getPage()->find('named', array( 'radio', $this->getSession()->getSelectorsHandler()->xpathLiteral($radioLabel) )); - assertNotNull($radioButton); - $session->getDriver()->click($radioButton->getXPath()); - } + assertNotNull($radioButton); + $session->getDriver()->click($radioButton->getXPath()); + } /** * @Then /^the "([^"]*)" table should contain "([^"]*)"$/ */ - public function theTableShouldContain($selector, $text) { + public function theTableShouldContain($selector, $text) + { $table = $this->getTable($selector); $element = $table->find('named', array('content', "'$text'")); @@ -750,7 +805,8 @@ JS; /** * @Then /^the "([^"]*)" table should not contain "([^"]*)"$/ */ - public function theTableShouldNotContain($selector, $text) { + public function theTableShouldNotContain($selector, $text) + { $table = $this->getTable($selector); $element = $table->find('named', array('content', "'$text'")); @@ -760,7 +816,8 @@ JS; /** * @Given /^I click on "([^"]*)" in the "([^"]*)" table$/ */ - public function iClickOnInTheTable($text, $selector) { + public function iClickOnInTheTable($text, $selector) + { $table = $this->getTable($selector); $element = $table->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text)); @@ -778,22 +835,24 @@ JS; * * @return Behat\Mink\Element\NodeElement */ - protected function getTable($selector) { + protected function getTable($selector) + { $selector = $this->getSession()->getSelectorsHandler()->xpathLiteral($selector); $page = $this->getSession()->getPage(); $candidates = $page->findAll( 'xpath', $this->getSession()->getSelectorsHandler()->selectorToXpath( - "xpath", ".//table[(./@id = $selector or contains(./@title, $selector))]" + "xpath", + ".//table[(./@id = $selector or contains(./@title, $selector))]" ) ); // Find tables by a field - $candidates += $page->findAll('xpath', "//table//caption[contains(normalize-space(string(.)), + $candidates += $page->findAll('xpath', "//table//caption[contains(normalize-space(string(.)), $selector)]/ancestor-or-self::table[1]"); // Find tables by a .title node - $candidates += $page->findAll('xpath', "//table//*[contains(concat(' ',normalize-space(@class),' '), ' title ') and contains(normalize-space(string(.)), + $candidates += $page->findAll('xpath', "//table//*[contains(concat(' ',normalize-space(@class),' '), ' title ') and contains(normalize-space(string(.)), $selector)]/ancestor-or-self::table[1]"); // Some tables don't have a visible title, so look for a fieldset with data-name instead @@ -802,8 +861,8 @@ JS; assertTrue((bool)$candidates, 'Could not find any table elements'); $table = null; - foreach($candidates as $candidate) { - if(!$table && $candidate->isVisible()) { + foreach ($candidates as $candidate) { + if (!$table && $candidate->isVisible()) { $table = $candidate; } } @@ -813,49 +872,51 @@ JS; return $table; } - /** - * Checks the order of two texts. - * Assumptions: the two texts appear in their conjunct parent element once - * @Then /^I should see the text "(?P(?:[^"]|\\")*)" (before|after) the text "(?P(?:[^"]|\\")*)" in the "(?P[^"]*)" element$/ - */ - public function theTextBeforeAfter($textBefore, $order, $textAfter, $element) { - $ele = $this->getSession()->getPage()->find('css', $element); - assertNotNull($ele, sprintf('%s not found', $element)); + /** + * Checks the order of two texts. + * Assumptions: the two texts appear in their conjunct parent element once + * @Then /^I should see the text "(?P(?:[^"]|\\")*)" (before|after) the text "(?P(?:[^"]|\\")*)" in the "(?P[^"]*)" element$/ + */ + public function theTextBeforeAfter($textBefore, $order, $textAfter, $element) + { + $ele = $this->getSession()->getPage()->find('css', $element); + assertNotNull($ele, sprintf('%s not found', $element)); - // Check both of the texts exist in the element - $text = $ele->getText(); - assertTrue(strpos($text, $textBefore) !== 'FALSE', sprintf('%s not found in the element %s', $textBefore, $element)); - assertTrue(strpos($text, $textAfter) !== 'FALSE', sprintf('%s not found in the element %s', $textAfter, $element)); + // Check both of the texts exist in the element + $text = $ele->getText(); + assertTrue(strpos($text, $textBefore) !== 'FALSE', sprintf('%s not found in the element %s', $textBefore, $element)); + assertTrue(strpos($text, $textAfter) !== 'FALSE', sprintf('%s not found in the element %s', $textAfter, $element)); - /// Use strpos to get the position of the first occurrence of the two texts (case-sensitive) - // and compare them with the given order (before or after) - if($order === 'before') { - assertTrue(strpos($text, $textBefore) < strpos($text, $textAfter)); - } else { - assertTrue(strpos($text, $textBefore) > strpos($text, $textAfter)); - } - } + /// Use strpos to get the position of the first occurrence of the two texts (case-sensitive) + // and compare them with the given order (before or after) + if ($order === 'before') { + assertTrue(strpos($text, $textBefore) < strpos($text, $textAfter)); + } else { + assertTrue(strpos($text, $textBefore) > strpos($text, $textAfter)); + } + } - /** - * Wait until a certain amount of seconds till I see an element identified by a CSS selector. - * - * Example: Given I wait for 10 seconds until I see the ".css_element" element - * - * @Given /^I wait for (\d+) seconds until I see the "([^"]*)" element$/ - **/ - public function iWaitXUntilISee($wait, $selector) { - $page = $this->getSession()->getPage(); + /** + * Wait until a certain amount of seconds till I see an element identified by a CSS selector. + * + * Example: Given I wait for 10 seconds until I see the ".css_element" element + * + * @Given /^I wait for (\d+) seconds until I see the "([^"]*)" element$/ + **/ + public function iWaitXUntilISee($wait, $selector) + { + $page = $this->getSession()->getPage(); - $this->spin(function($page) use ($page, $selector){ - $element = $page->find('css', $selector); + $this->spin(function ($page) use ($page, $selector) { + $element = $page->find('css', $selector); - if(empty($element)) { - return false; - } else { - return $element->isVisible(); - } - }); - } + if (empty($element)) { + return false; + } else { + return $element->isVisible(); + } + }); + } /** * Wait until a particular element is visible, using a CSS selector. Useful for content loaded via AJAX, or only @@ -865,13 +926,14 @@ JS; * * @Given /^I wait until I see the "([^"]*)" element$/ */ - public function iWaitUntilISee($selector) { + public function iWaitUntilISee($selector) + { $page = $this->getSession()->getPage(); - $this->spin(function($page) use ($page, $selector){ + $this->spin(function ($page) use ($page, $selector) { $element = $page->find('css', $selector); - if(empty($element)){ + if (empty($element)) { return false; - } else{ + } else { return ($element->isVisible()); } }); @@ -885,16 +947,17 @@ JS; * * @Given /^I wait until I see the text "([^"]*)"$/ */ - public function iWaitUntilISeeText($text){ + public function iWaitUntilISeeText($text) + { $page = $this->getSession()->getPage(); $session = $this->getSession(); - $this->spin(function($page) use ($page, $session, $text) { + $this->spin(function ($page) use ($page, $session, $text) { $element = $page->find( 'xpath', $session->getSelectorsHandler()->selectorToXpath("xpath", ".//*[contains(text(), '$text')]") ); - if(empty($element)) { + if (empty($element)) { return false; } else { return ($element->isVisible()); @@ -902,64 +965,68 @@ JS; }); } - /** - * @Given /^I scroll to the bottom$/ - */ - public function iScrollToBottom() { - $javascript = 'window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight));'; - $this->getSession()->executeScript($javascript); - } + /** + * @Given /^I scroll to the bottom$/ + */ + public function iScrollToBottom() + { + $javascript = 'window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight));'; + $this->getSession()->executeScript($javascript); + } - /** - * @Given /^I scroll to the top$/ - */ - public function iScrollToTop() { - $this->getSession()->executeScript('window.scrollTo(0,0);'); - } + /** + * @Given /^I scroll to the top$/ + */ + public function iScrollToTop() + { + $this->getSession()->executeScript('window.scrollTo(0,0);'); + } - /** - * Scroll to a certain element by label. - * Requires an "id" attribute to uniquely identify the element in the document. - * - * Example: Given I scroll to the "Submit" button - * Example: Given I scroll to the "My Date" field - * - * @Given /^I scroll to the "([^"]*)" (field|link|button)$/ - */ - public function iScrollToField($locator, $type) { - $page = $this->getSession()->getPage(); + /** + * Scroll to a certain element by label. + * Requires an "id" attribute to uniquely identify the element in the document. + * + * Example: Given I scroll to the "Submit" button + * Example: Given I scroll to the "My Date" field + * + * @Given /^I scroll to the "([^"]*)" (field|link|button)$/ + */ + public function iScrollToField($locator, $type) + { + $page = $this->getSession()->getPage(); $el = $page->find('named', array($type, "'$locator'")); assertNotNull($el, sprintf('%s element not found', $locator)); $id = $el->getAttribute('id'); - if(empty($id)) { - throw new \InvalidArgumentException('Element requires an "id" attribute'); - } + if (empty($id)) { + throw new \InvalidArgumentException('Element requires an "id" attribute'); + } - $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id); - $this->getSession()->executeScript($js); - } + $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id); + $this->getSession()->executeScript($js); + } - /** - * Scroll to a certain element by CSS selector. - * Requires an "id" attribute to uniquely identify the element in the document. - * - * Example: Given I scroll to the ".css_element" element - * - * @Given /^I scroll to the "(?P(?:[^"]|\\")*)" element$/ - */ - public function iScrollToElement($locator) { - $el = $this->getSession()->getPage()->find('css', $locator); - assertNotNull($el, sprintf('The element "%s" is not found', $locator)); + /** + * Scroll to a certain element by CSS selector. + * Requires an "id" attribute to uniquely identify the element in the document. + * + * Example: Given I scroll to the ".css_element" element + * + * @Given /^I scroll to the "(?P(?:[^"]|\\")*)" element$/ + */ + public function iScrollToElement($locator) + { + $el = $this->getSession()->getPage()->find('css', $locator); + assertNotNull($el, sprintf('The element "%s" is not found', $locator)); - $id = $el->getAttribute('id'); - if(empty($id)) { - throw new \InvalidArgumentException('Element requires an "id" attribute'); - } + $id = $el->getAttribute('id'); + if (empty($id)) { + throw new \InvalidArgumentException('Element requires an "id" attribute'); + } - $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id); - $this->getSession()->executeScript($js); - } + $js = sprintf("document.getElementById('%s').scrollIntoView(true);", $id); + $this->getSession()->executeScript($js); + } /** * Continuously poll the dom until callback returns true, code copied from @@ -971,10 +1038,11 @@ JS; * @return bool Returns true if the lambda returns successfully * @throws \Exception Thrown if the wait threshold is exceeded without the lambda successfully returning */ - public function spin($lambda, $wait = 60) { + public function spin($lambda, $wait = 60) + { for ($i = 0; $i < $wait; $i++) { try { - if($lambda($this)) { + if ($lambda($this)) { return true; } } catch (\Exception $e) { @@ -995,11 +1063,11 @@ JS; - /** - * We have to catch exceptions and log somehow else otherwise behat falls over - */ - protected function logException($e){ - file_put_contents('php://stderr', 'Exception caught: '.$e); - } - + /** + * We have to catch exceptions and log somehow else otherwise behat falls over + */ + protected function logException($e) + { + file_put_contents('php://stderr', 'Exception caught: '.$e); + } } diff --git a/src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php b/src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php index 9880ef4..b5a159c 100644 --- a/src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php +++ b/src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php @@ -46,7 +46,7 @@ class ModuleContextClassGuesser implements ClassGuesserInterface // Try fully qualified namespace if (class_exists($class = $this->namespaceBase.'\\'.$this->namespaceSuffix.'\\'.$this->contextClass)) { return $class; - } + } // Fall back to namespace with SilverStripe prefix // TODO Remove once core has namespace capabilities for modules if (class_exists($class = 'SilverStripe\\'.$this->namespaceBase.'\\'.$this->namespaceSuffix.'\\'.$this->contextClass)) { diff --git a/src/SilverStripe/BehatExtension/Context/EmailContext.php b/src/SilverStripe/BehatExtension/Context/EmailContext.php index a23f6ae..f3c2341 100644 --- a/src/SilverStripe/BehatExtension/Context/EmailContext.php +++ b/src/SilverStripe/BehatExtension/Context/EmailContext.php @@ -2,15 +2,15 @@ namespace SilverStripe\BehatExtension\Context; -use Behat\Behat\Context\ClosuredContextInterface, -Behat\Behat\Context\TranslatedContextInterface, -Behat\Behat\Context\BehatContext, -Behat\Behat\Context\Step, -Behat\Behat\Event\FeatureEvent, -Behat\Behat\Event\ScenarioEvent, -Behat\Behat\Exception\PendingException; -use Behat\Gherkin\Node\PyStringNode, -Behat\Gherkin\Node\TableNode; +use Behat\Behat\Context\ClosuredContextInterface; +use Behat\Behat\Context\TranslatedContextInterface; +use Behat\Behat\Context\BehatContext; +use Behat\Behat\Context\Step; +use Behat\Behat\Event\FeatureEvent; +use Behat\Behat\Event\ScenarioEvent; +use Behat\Behat\Exception\PendingException; +use Behat\Gherkin\Node\PyStringNode; +use Behat\Gherkin\Node\TableNode; use Symfony\Component\DomCrawler\Crawler; // PHPUnit @@ -59,7 +59,7 @@ class EmailContext extends BehatContext // to ensure its available both in CLI execution and the tested browser session $this->mailer = new \SilverStripe\BehatExtension\Utility\TestMailer(); \Email::set_mailer($this->mailer); - \Config::inst()->update("Email","send_all_emails_to", null); + \Config::inst()->update("Email", "send_all_emails_to", null); } /** @@ -70,7 +70,7 @@ class EmailContext extends BehatContext $to = ($direction == 'to') ? $email : null; $from = ($direction == 'from') ? $email : null; $match = $this->mailer->findEmail($to, $from); - if(trim($negate)) { + if (trim($negate)) { assertNull($match); } else { assertNotNull($match); @@ -87,8 +87,10 @@ class EmailContext extends BehatContext $from = ($direction == 'from') ? $email : null; $match = $this->mailer->findEmail($to, $from, $subject); $allMails = $this->mailer->findEmails($to, $from); - $allTitles = $allMails ? '"' . implode('","', array_map(function($email) {return $email->Subject;}, $allMails)) . '"' : null; - if(trim($negate)) { + $allTitles = $allMails ? '"' . implode('","', array_map(function ($email) { + return $email->Subject; + }, $allMails)) . '"' : null; + if (trim($negate)) { assertNull($match); } else { $msg = sprintf( @@ -97,10 +99,10 @@ class EmailContext extends BehatContext $email, $subject ); - if($allTitles) { + if ($allTitles) { $msg .= ' Existing emails: ' . $allTitles; } - assertNotNull($match,$msg); + assertNotNull($match, $msg); } $this->lastMatchedEmail = $match; } @@ -109,51 +111,51 @@ class EmailContext extends BehatContext * Example: Given the email should contain "Thank you for registering!". * Assumes an email has been identified by a previous step, * e.g. through 'Given there should be an email to "test@test.com"'. - * - * @Given /^the email should (not |)contain "([^"]*)"$/ - */ - public function thereTheEmailContains($negate, $content) - { - if(!$this->lastMatchedEmail) { - throw new \LogicException('No matched email found from previous step'); - } + * + * @Given /^the email should (not |)contain "([^"]*)"$/ + */ + public function thereTheEmailContains($negate, $content) + { + if (!$this->lastMatchedEmail) { + throw new \LogicException('No matched email found from previous step'); + } - $email = $this->lastMatchedEmail; - $emailContent = null; - if($email->Content) { - $emailContent = $email->Content; - } else { - $emailContent = $email->PlainContent; - } + $email = $this->lastMatchedEmail; + $emailContent = null; + if ($email->Content) { + $emailContent = $email->Content; + } else { + $emailContent = $email->PlainContent; + } - if(trim($negate)) { - assertNotContains($content, $emailContent); - } else { - assertContains($content, $emailContent); - } - } + if (trim($negate)) { + assertNotContains($content, $emailContent); + } else { + assertContains($content, $emailContent); + } + } - /** - * Example: Given the email contains "Thank you for registering!". - * Then the email should contain plain text "Thank you for registering!" - * Assumes an email has been identified by a previous step, - * e.g. through 'Given there should be an email to "test@test.com"'. - * - * @Given /^the email should contain plain text "([^"]*)"$/ - */ - public function thereTheEmailContainsPlainText($content) - { - if(!$this->lastMatchedEmail) { - throw new \LogicException('No matched email found from previous step'); - } + /** + * Example: Given the email contains "Thank you for registering!". + * Then the email should contain plain text "Thank you for registering!" + * Assumes an email has been identified by a previous step, + * e.g. through 'Given there should be an email to "test@test.com"'. + * + * @Given /^the email should contain plain text "([^"]*)"$/ + */ + public function thereTheEmailContainsPlainText($content) + { + if (!$this->lastMatchedEmail) { + throw new \LogicException('No matched email found from previous step'); + } - $email = $this->lastMatchedEmail; - $emailContent = ($email->Content) ? ($email->Content) : ($email->PlainContent); - $emailPlainText = strip_tags($emailContent); - $emailPlainText = preg_replace("/\h+/", " ", $emailPlainText); + $email = $this->lastMatchedEmail; + $emailContent = ($email->Content) ? ($email->Content) : ($email->PlainContent); + $emailPlainText = strip_tags($emailContent); + $emailPlainText = preg_replace("/\h+/", " ", $emailPlainText); - assertContains($content, $emailPlainText); - } + assertContains($content, $emailPlainText); + } /** * @When /^I click on the "([^"]*)" link in the email (to|from) "([^"]*)"$/ @@ -195,12 +197,12 @@ class EmailContext extends BehatContext /** * Assumes an email has been identified by a previous step, * e.g. through 'Given there should be an email to "test@test.com"'. - * + * * @When /^I click on the "([^"]*)" link in the email"$/ */ public function iGoToInTheEmail($linkSelector) { - if(!$this->lastMatchedEmail) { + if (!$this->lastMatchedEmail) { throw new \LogicException('No matched email found from previous step'); } @@ -223,91 +225,92 @@ class EmailContext extends BehatContext return $this->mailer->clearEmails(); } - /** - * Example: Then the email should contain the following data: - * | row1 | - * | row2 | - * Assumes an email has been identified by a previous step. - * @Then /^the email should (not |)contain the following data:$/ - */ - public function theEmailContainFollowingData($negate, TableNode $table) { - if(!$this->lastMatchedEmail) { - throw new \LogicException('No matched email found from previous step'); - } + /** + * Example: Then the email should contain the following data: + * | row1 | + * | row2 | + * Assumes an email has been identified by a previous step. + * @Then /^the email should (not |)contain the following data:$/ + */ + public function theEmailContainFollowingData($negate, TableNode $table) + { + if (!$this->lastMatchedEmail) { + throw new \LogicException('No matched email found from previous step'); + } - $email = $this->lastMatchedEmail; - $emailContent = null; - if($email->Content) { - $emailContent = $email->Content; - } else { - $emailContent = $email->PlainContent; - } - // Convert html content to plain text - $emailContent = strip_tags($emailContent); - $emailContent = preg_replace("/\h+/", " ", $emailContent); - $rows = $table->getRows(); - - // For "should not contain" - if(trim($negate)) { - foreach($rows as $row) { - assertNotContains($row[0], $emailContent); - } - } else { - foreach($rows as $row) { - assertContains($row[0], $emailContent); - } - } - } + $email = $this->lastMatchedEmail; + $emailContent = null; + if ($email->Content) { + $emailContent = $email->Content; + } else { + $emailContent = $email->PlainContent; + } + // Convert html content to plain text + $emailContent = strip_tags($emailContent); + $emailContent = preg_replace("/\h+/", " ", $emailContent); + $rows = $table->getRows(); + + // For "should not contain" + if (trim($negate)) { + foreach ($rows as $row) { + assertNotContains($row[0], $emailContent); + } + } else { + foreach ($rows as $row) { + assertContains($row[0], $emailContent); + } + } + } - /** - * @Then /^there should (not |)be an email titled "([^"]*)"$/ - */ - public function thereIsAnEmailTitled($negate, $subject) - { - $match = $this->mailer->findEmail(null, null, $subject); - if(trim($negate)) { - assertNull($match); - } else { - $msg = sprintf( - 'Could not find email titled "%s".', - $subject - ); - assertNotNull($match,$msg); - } - $this->lastMatchedEmail = $match; - } + /** + * @Then /^there should (not |)be an email titled "([^"]*)"$/ + */ + public function thereIsAnEmailTitled($negate, $subject) + { + $match = $this->mailer->findEmail(null, null, $subject); + if (trim($negate)) { + assertNull($match); + } else { + $msg = sprintf( + 'Could not find email titled "%s".', + $subject + ); + assertNotNull($match, $msg); + } + $this->lastMatchedEmail = $match; + } - /** - * @Then /^the email should (not |)be sent from "([^"]*)"$/ - */ - public function theEmailSentFrom($negate, $from) - { - if(!$this->lastMatchedEmail) { - throw new \LogicException('No matched email found from previous step'); - } + /** + * @Then /^the email should (not |)be sent from "([^"]*)"$/ + */ + public function theEmailSentFrom($negate, $from) + { + if (!$this->lastMatchedEmail) { + throw new \LogicException('No matched email found from previous step'); + } - $match = $this->lastMatchedEmail; - if(trim($negate)) { - assertNotContains($from, $match->From); - } else { - assertContains($from, $match->From); - } - } + $match = $this->lastMatchedEmail; + if (trim($negate)) { + assertNotContains($from, $match->From); + } else { + assertContains($from, $match->From); + } + } - /** - * @Then /^the email should (not |)be sent to "([^"]*)"$/ - */ - public function theEmailSentTo($negate, $to) - { - if(!$this->lastMatchedEmail) { - throw new \LogicException('No matched email found from previous step'); - } + /** + * @Then /^the email should (not |)be sent to "([^"]*)"$/ + */ + public function theEmailSentTo($negate, $to) + { + if (!$this->lastMatchedEmail) { + throw new \LogicException('No matched email found from previous step'); + } - $match = $this->lastMatchedEmail; - if(trim($negate)) { - assertNotContains($to, $match->To); - } else { - assertContains($to, $match->To); - } - } + $match = $this->lastMatchedEmail; + if (trim($negate)) { + assertNotContains($to, $match->To); + } else { + assertContains($to, $match->To); + } + } } diff --git a/src/SilverStripe/BehatExtension/Context/FixtureContext.php b/src/SilverStripe/BehatExtension/Context/FixtureContext.php index 84e6f8b..07b553c 100644 --- a/src/SilverStripe/BehatExtension/Context/FixtureContext.php +++ b/src/SilverStripe/BehatExtension/Context/FixtureContext.php @@ -2,18 +2,16 @@ namespace SilverStripe\BehatExtension\Context; -use Behat\Behat\Context\BehatContext, - Behat\Behat\Event\ScenarioEvent, - Behat\Gherkin\Node\PyStringNode, - Behat\Gherkin\Node\TableNode, - SilverStripe\Filesystem\Storage\AssetStore; +use Behat\Behat\Context\BehatContext; +use Behat\Behat\Event\ScenarioEvent; +use Behat\Gherkin\Node\PyStringNode; +use Behat\Gherkin\Node\TableNode; +use SilverStripe\Filesystem\Storage\AssetStore; use SilverStripe\ORM\DB; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\Versioning\Versioned; use SilverStripe\Security\Permission; - - // PHPUnit require_once BASE_PATH . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php'; @@ -22,649 +20,711 @@ require_once BASE_PATH . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions */ class FixtureContext extends BehatContext { - protected $context; + protected $context; - /** - * @var \FixtureFactory - */ - protected $fixtureFactory; + /** + * @var \FixtureFactory + */ + protected $fixtureFactory; - /** - * @var String Absolute path where file fixtures are located. - * These will automatically get copied to their location - * declare through the 'Given a file "..."' step defition. - */ - protected $filesPath; + /** + * @var String Absolute path where file fixtures are located. + * These will automatically get copied to their location + * declare through the 'Given a file "..."' step defition. + */ + protected $filesPath; - /** - * @var String Tracks all files and folders created from fixtures, for later cleanup. - */ - protected $createdFilesPaths = array(); + /** + * @var String Tracks all files and folders created from fixtures, for later cleanup. + */ + protected $createdFilesPaths = array(); - /** - * @var array Stores the asset tuples. - */ - protected $createdAssets = array(); + /** + * @var array Stores the asset tuples. + */ + protected $createdAssets = array(); - public function __construct(array $parameters) { - $this->context = $parameters; - } + public function __construct(array $parameters) + { + $this->context = $parameters; + } - public function getSession($name = null) { - return $this->getMainContext()->getSession($name); - } + public function getSession($name = null) + { + return $this->getMainContext()->getSession($name); + } - /** - * @return \FixtureFactory - */ - public function getFixtureFactory() { - if(!$this->fixtureFactory) { - $this->fixtureFactory = \Injector::inst()->create('FixtureFactory', 'FixtureContextFactory'); - } - return $this->fixtureFactory; - } + /** + * @return \FixtureFactory + */ + public function getFixtureFactory() + { + if (!$this->fixtureFactory) { + $this->fixtureFactory = \Injector::inst()->create('FixtureFactory', 'FixtureContextFactory'); + } + return $this->fixtureFactory; + } - /** - * @param \FixtureFactory $factory - */ - public function setFixtureFactory(\FixtureFactory $factory) { - $this->fixtureFactory = $factory; - } + /** + * @param \FixtureFactory $factory + */ + public function setFixtureFactory(\FixtureFactory $factory) + { + $this->fixtureFactory = $factory; + } - /** - * @param String - */ - public function setFilesPath($path) { - $this->filesPath = $path; - } + /** + * @param String + */ + public function setFilesPath($path) + { + $this->filesPath = $path; + } - /** - * @return String - */ - public function getFilesPath() { - return $this->filesPath; - } + /** + * @return String + */ + public function getFilesPath() + { + return $this->filesPath; + } - /** - * @BeforeScenario @database-defaults - */ - public function beforeDatabaseDefaults(ScenarioEvent $event) { - \SapphireTest::empty_temp_db(); - DB::get_conn()->quiet(); - $dataClasses = \ClassInfo::subclassesFor('SilverStripe\\ORM\\DataObject'); - array_shift($dataClasses); - foreach ($dataClasses as $dataClass) { - \singleton($dataClass)->requireDefaultRecords(); - } - } + /** + * @BeforeScenario @database-defaults + */ + public function beforeDatabaseDefaults(ScenarioEvent $event) + { + \SapphireTest::empty_temp_db(); + DB::get_conn()->quiet(); + $dataClasses = \ClassInfo::subclassesFor('SilverStripe\\ORM\\DataObject'); + array_shift($dataClasses); + foreach ($dataClasses as $dataClass) { + \singleton($dataClass)->requireDefaultRecords(); + } + } /** * @AfterScenario */ - public function afterResetDatabase(ScenarioEvent $event) { - \SapphireTest::empty_temp_db(); - } + public function afterResetDatabase(ScenarioEvent $event) + { + \SapphireTest::empty_temp_db(); + } - /** - * @AfterScenario - */ - public function afterResetAssets(ScenarioEvent $event) { - $store = $this->getAssetStore(); - if (is_array($this->createdAssets)) { - foreach ($this->createdAssets as $asset) { - $store->delete($asset['FileFilename'], $asset['FileHash']); - } - } - } + /** + * @AfterScenario + */ + public function afterResetAssets(ScenarioEvent $event) + { + $store = $this->getAssetStore(); + if (is_array($this->createdAssets)) { + foreach ($this->createdAssets as $asset) { + $store->delete($asset['FileFilename'], $asset['FileHash']); + } + } + } - /** - * Example: Given a "page" "Page 1" - * - * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"$/ - */ - public function stepCreateRecord($type, $id) { - $class = $this->convertTypeToClass($type); - $fields = $this->prepareFixture($class, $id); - $this->fixtureFactory->createObject($class, $id, $fields); - } + /** + * Example: Given a "page" "Page 1" + * + * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"$/ + */ + public function stepCreateRecord($type, $id) + { + $class = $this->convertTypeToClass($type); + $fields = $this->prepareFixture($class, $id); + $this->fixtureFactory->createObject($class, $id, $fields); + } - /** - * Example: Given a "page" "Page 1" has the "content" "My content" - * - * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" has (?:(an|a|the) )"(?.*)" "(?.*)"$/ - */ - public function stepCreateRecordHasField($type, $id, $field, $value) { - $class = $this->convertTypeToClass($type); - $fields = $this->convertFields( - $class, - array($field => $value) - ); - // We should check if this fixture object already exists - if it does, we update it. If not, we create it - if($existingFixture = $this->fixtureFactory->get($class, $id)) { - // Merge existing data with new data, and create new object to replace existing object - foreach($fields as $k => $v) { - $existingFixture->$k = $v; - } - $existingFixture->write(); - } else { - $this->fixtureFactory->createObject($class, $id, $fields); - } - } + /** + * Example: Given a "page" "Page 1" has the "content" "My content" + * + * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" has (?:(an|a|the) )"(?.*)" "(?.*)"$/ + */ + public function stepCreateRecordHasField($type, $id, $field, $value) + { + $class = $this->convertTypeToClass($type); + $fields = $this->convertFields( + $class, + array($field => $value) + ); + // We should check if this fixture object already exists - if it does, we update it. If not, we create it + if ($existingFixture = $this->fixtureFactory->get($class, $id)) { + // Merge existing data with new data, and create new object to replace existing object + foreach ($fields as $k => $v) { + $existingFixture->$k = $v; + } + $existingFixture->write(); + } else { + $this->fixtureFactory->createObject($class, $id, $fields); + } + } - /** - * Example: Given a "page" "Page 1" with "URL"="page-1" and "Content"="my page 1" - * Example: Given the "page" "Page 1" has "URL"="page-1" and "Content"="my page 1" - * - * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" (?:(with|has)) (?".*)$/ - */ - public function stepCreateRecordWithData($type, $id, $data) { - $class = $this->convertTypeToClass($type); - preg_match_all( - '/"(?[^"]+)"\s*=\s*"(?[^"]+)"/', - $data, - $matches - ); - $fields = $this->convertFields( - $class, - array_combine($matches['key'], $matches['value']) - ); - $fields = $this->prepareFixture($class, $id, $fields); - // We should check if this fixture object already exists - if it does, we update it. If not, we create it - if($existingFixture = $this->fixtureFactory->get($class, $id)) { - // Merge existing data with new data, and create new object to replace existing object - foreach($fields as $k => $v) { - $existingFixture->$k = $v; - } - $existingFixture->write(); - } else { - $this->fixtureFactory->createObject($class, $id, $fields); - } - } + /** + * Example: Given a "page" "Page 1" with "URL"="page-1" and "Content"="my page 1" + * Example: Given the "page" "Page 1" has "URL"="page-1" and "Content"="my page 1" + * + * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" (?:(with|has)) (?".*)$/ + */ + public function stepCreateRecordWithData($type, $id, $data) + { + $class = $this->convertTypeToClass($type); + preg_match_all( + '/"(?[^"]+)"\s*=\s*"(?[^"]+)"/', + $data, + $matches + ); + $fields = $this->convertFields( + $class, + array_combine($matches['key'], $matches['value']) + ); + $fields = $this->prepareFixture($class, $id, $fields); + // We should check if this fixture object already exists - if it does, we update it. If not, we create it + if ($existingFixture = $this->fixtureFactory->get($class, $id)) { + // Merge existing data with new data, and create new object to replace existing object + foreach ($fields as $k => $v) { + $existingFixture->$k = $v; + } + $existingFixture->write(); + } else { + $this->fixtureFactory->createObject($class, $id, $fields); + } + } - /** - * Example: And the "page" "Page 2" has the following data - * | Content | | - * | My Property | foo | - * | My Boolean | bar | - * - * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" has the following data$/ - */ - public function stepCreateRecordWithTable($type, $id, $null, TableNode $fieldsTable) { - $class = $this->convertTypeToClass($type); - // TODO Support more than one record - $fields = $this->convertFields($class, $fieldsTable->getRowsHash()); - $fields = $this->prepareFixture($class, $id, $fields); + /** + * Example: And the "page" "Page 2" has the following data + * | Content | | + * | My Property | foo | + * | My Boolean | bar | + * + * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" has the following data$/ + */ + public function stepCreateRecordWithTable($type, $id, $null, TableNode $fieldsTable) + { + $class = $this->convertTypeToClass($type); + // TODO Support more than one record + $fields = $this->convertFields($class, $fieldsTable->getRowsHash()); + $fields = $this->prepareFixture($class, $id, $fields); - // We should check if this fixture object already exists - if it does, we update it. If not, we create it - if($existingFixture = $this->fixtureFactory->get($class, $id)) { - // Merge existing data with new data, and create new object to replace existing object - foreach($fields as $k => $v) { - $existingFixture->$k = $v; - } - $existingFixture->write(); - } else { - $this->fixtureFactory->createObject($class, $id, $fields); - } - } + // We should check if this fixture object already exists - if it does, we update it. If not, we create it + if ($existingFixture = $this->fixtureFactory->get($class, $id)) { + // Merge existing data with new data, and create new object to replace existing object + foreach ($fields as $k => $v) { + $existingFixture->$k = $v; + } + $existingFixture->write(); + } else { + $this->fixtureFactory->createObject($class, $id, $fields); + } + } - /** - * Example: Given the "page" "Page 1.1" is a child of the "page" "Page1". - * Note that this change is not published by default - * - * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" is a (?[^\s]*) of (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"/ - */ - public function stepUpdateRecordRelation($type, $id, $relation, $relationType, $relationId) { - $class = $this->convertTypeToClass($type); + /** + * Example: Given the "page" "Page 1.1" is a child of the "page" "Page1". + * Note that this change is not published by default + * + * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" is a (?[^\s]*) of (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"/ + */ + public function stepUpdateRecordRelation($type, $id, $relation, $relationType, $relationId) + { + $class = $this->convertTypeToClass($type); - $relationClass = $this->convertTypeToClass($relationType); - $relationObj = $this->fixtureFactory->get($relationClass, $relationId); - if(!$relationObj) $relationObj = $this->fixtureFactory->createObject($relationClass, $relationId); + $relationClass = $this->convertTypeToClass($relationType); + $relationObj = $this->fixtureFactory->get($relationClass, $relationId); + if (!$relationObj) { + $relationObj = $this->fixtureFactory->createObject($relationClass, $relationId); + } - $data = array(); - if($relation == 'child') { - $data['ParentID'] = $relationObj->ID; - } + $data = array(); + if ($relation == 'child') { + $data['ParentID'] = $relationObj->ID; + } - $obj = $this->fixtureFactory->get($class, $id); - if($obj) { - $obj->update($data); - $obj->write(); - } else { - $obj = $this->fixtureFactory->createObject($class, $id, $data); - } + $obj = $this->fixtureFactory->get($class, $id); + if ($obj) { + $obj->update($data); + $obj->write(); + } else { + $obj = $this->fixtureFactory->createObject($class, $id, $data); + } - switch($relation) { - case 'parent': - $relationObj->ParentID = $obj->ID; - $relationObj->write(); - break; - case 'child': - // already written through $data above - break; - default: - throw new \InvalidArgumentException(sprintf( - 'Invalid relation "%s"', $relation - )); - } - } + switch ($relation) { + case 'parent': + $relationObj->ParentID = $obj->ID; + $relationObj->write(); + break; + case 'child': + // already written through $data above + break; + default: + throw new \InvalidArgumentException(sprintf( + 'Invalid relation "%s"', + $relation + )); + } + } - /** - * Assign a type of object to another type of object. The base object will be created if it does not exist already. - * If the last part of the string (in the "X" relation) is omitted, then the first matching relation will be used. - * - * @example I assign the "TaxonomyTerm" "For customers" to the "Page" "Page1" - * @Given /^I assign (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" to (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"$/ - */ - public function stepIAssignObjToObj($type, $value, $relationType, $relationId) { - self::stepIAssignObjToObjInTheRelation($type, $value, $relationType, $relationId, null); - } + /** + * Assign a type of object to another type of object. The base object will be created if it does not exist already. + * If the last part of the string (in the "X" relation) is omitted, then the first matching relation will be used. + * + * @example I assign the "TaxonomyTerm" "For customers" to the "Page" "Page1" + * @Given /^I assign (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" to (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"$/ + */ + public function stepIAssignObjToObj($type, $value, $relationType, $relationId) + { + self::stepIAssignObjToObjInTheRelation($type, $value, $relationType, $relationId, null); + } - /** - * Assign a type of object to another type of object. The base object will be created if it does not exist already. - * If the last part of the string (in the "X" relation) is omitted, then the first matching relation will be used. - * Assumption: one object has relationship (has_one, has_many or many_many ) with the other object - * - * @example I assign the "TaxonomyTerm" "For customers" to the "Page" "Page1" in the "Terms" relation - * @Given /^I assign (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" to (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" in the "(?[^"]+)" relation$/ - */ - public function stepIAssignObjToObjInTheRelation($type, $value, $relationType, $relationId, $relationName) { - $class = $this->convertTypeToClass($type); - $relationClass = $this->convertTypeToClass($relationType); + /** + * Assign a type of object to another type of object. The base object will be created if it does not exist already. + * If the last part of the string (in the "X" relation) is omitted, then the first matching relation will be used. + * Assumption: one object has relationship (has_one, has_many or many_many ) with the other object + * + * @example I assign the "TaxonomyTerm" "For customers" to the "Page" "Page1" in the "Terms" relation + * @Given /^I assign (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" to (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" in the "(?[^"]+)" relation$/ + */ + public function stepIAssignObjToObjInTheRelation($type, $value, $relationType, $relationId, $relationName) + { + $class = $this->convertTypeToClass($type); + $relationClass = $this->convertTypeToClass($relationType); - // Check if this fixture object already exists - if not, we create it - $relationObj = $this->fixtureFactory->get($relationClass, $relationId); - if(!$relationObj) $relationObj = $this->fixtureFactory->createObject($relationClass, $relationId); + // Check if this fixture object already exists - if not, we create it + $relationObj = $this->fixtureFactory->get($relationClass, $relationId); + if (!$relationObj) { + $relationObj = $this->fixtureFactory->createObject($relationClass, $relationId); + } - // Check if there is relationship defined in many_many (includes belongs_many_many) - $manyField = null; - $oneField = null; - if ($relationObj->many_many()) { - $manyField = array_search($class, $relationObj->many_many()); - if($manyField && strlen($relationName) > 0) $manyField = $relationName; - } - if(empty($manyField) && $relationObj->has_many()) { - $manyField = array_search($class, $relationObj->has_many()); - if($manyField && strlen($relationName) > 0) $manyField = $relationName; - } - if(empty($manyField) && $relationObj->has_one()) { - $oneField = array_search($class, $relationObj->has_one()); - if($oneField && strlen($relationName) > 0) $oneField = $relationName; - } - if(empty($manyField) && empty($oneField)) { - throw new \Exception("'$relationClass' has no relationship (has_one, has_many and many_many) with '$class'!"); - } + // Check if there is relationship defined in many_many (includes belongs_many_many) + $manyField = null; + $oneField = null; + if ($relationObj->many_many()) { + $manyField = array_search($class, $relationObj->many_many()); + if ($manyField && strlen($relationName) > 0) { + $manyField = $relationName; + } + } + if (empty($manyField) && $relationObj->has_many()) { + $manyField = array_search($class, $relationObj->has_many()); + if ($manyField && strlen($relationName) > 0) { + $manyField = $relationName; + } + } + if (empty($manyField) && $relationObj->has_one()) { + $oneField = array_search($class, $relationObj->has_one()); + if ($oneField && strlen($relationName) > 0) { + $oneField = $relationName; + } + } + if (empty($manyField) && empty($oneField)) { + throw new \Exception("'$relationClass' has no relationship (has_one, has_many and many_many) with '$class'!"); + } - // Get the searchable field to check if the fixture object already exists - $temObj = new $class; - if(isset($temObj->Name)) $field = "Name"; - else if(isset($temObj->Title)) $field = "Title"; - else $field = "ID"; + // Get the searchable field to check if the fixture object already exists + $temObj = new $class; + if (isset($temObj->Name)) { + $field = "Name"; + } elseif (isset($temObj->Title)) { + $field = "Title"; + } else { + $field = "ID"; + } - // Check if the fixture object exists - if not, we create it - $obj = DataObject::get($class)->filter($field, $value)->first(); - if(!$obj) $obj = $this->fixtureFactory->createObject($class, $value); - // If has_many or many_many, add this fixture object to the relation object - // If has_one, set value to the joint field with this fixture object's ID - if($manyField) { - $relationObj->$manyField()->add($obj); - } else if($oneField) { - // E.g. $has_one = array('PanelOffer' => 'Offer'); - // then the join field is PanelOfferID. This is the common rule in the CMS - $relationObj->{$oneField . 'ID'} = $obj->ID; - } + // Check if the fixture object exists - if not, we create it + $obj = DataObject::get($class)->filter($field, $value)->first(); + if (!$obj) { + $obj = $this->fixtureFactory->createObject($class, $value); + } + // If has_many or many_many, add this fixture object to the relation object + // If has_one, set value to the joint field with this fixture object's ID + if ($manyField) { + $relationObj->$manyField()->add($obj); + } elseif ($oneField) { + // E.g. $has_one = array('PanelOffer' => 'Offer'); + // then the join field is PanelOfferID. This is the common rule in the CMS + $relationObj->{$oneField . 'ID'} = $obj->ID; + } - $relationObj->write(); - } + $relationObj->write(); + } - /** - * Example: Given the "page" "Page 1" is not published - * - * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" is (?[^"]*)$/ - */ - public function stepUpdateRecordState($type, $id, $state) { - $class = $this->convertTypeToClass($type); + /** + * Example: Given the "page" "Page 1" is not published + * + * @Given /^(?:(an|a|the) )"(?[^"]+)" "(?[^"]+)" is (?[^"]*)$/ + */ + public function stepUpdateRecordState($type, $id, $state) + { + $class = $this->convertTypeToClass($type); /** @var DataObject|Versioned $obj */ $obj = $this->fixtureFactory->get($class, $id); - if(!$obj) { - throw new \InvalidArgumentException(sprintf( - 'Can not find record "%s" with identifier "%s"', - $type, - $id - )); - } + if (!$obj) { + throw new \InvalidArgumentException(sprintf( + 'Can not find record "%s" with identifier "%s"', + $type, + $id + )); + } - switch($state) { - case 'published': - $obj->copyVersionToStage('Stage', 'Live'); - break; - case 'not published': - case 'unpublished': - $oldMode = Versioned::get_reading_mode(); - Versioned::set_stage(Versioned::LIVE); - $clone = clone $obj; - $clone->delete(); - Versioned::set_reading_mode($oldMode); - break; - case 'deleted': - $obj->delete(); - break; - default: - throw new \InvalidArgumentException(sprintf( - 'Invalid state: "%s"', $state - )); - } - } + switch ($state) { + case 'published': + $obj->copyVersionToStage('Stage', 'Live'); + break; + case 'not published': + case 'unpublished': + $oldMode = Versioned::get_reading_mode(); + Versioned::set_stage(Versioned::LIVE); + $clone = clone $obj; + $clone->delete(); + Versioned::set_reading_mode($oldMode); + break; + case 'deleted': + $obj->delete(); + break; + default: + throw new \InvalidArgumentException(sprintf( + 'Invalid state: "%s"', + $state + )); + } + } - /** - * Accepts YAML fixture definitions similar to the ones used in SilverStripe unit testing. - * - * Example: Given there are the following member records: - * member1: - * Email: member1@test.com - * member2: - * Email: member2@test.com - * - * @Given /^there are the following ([^\s]*) records$/ - */ - public function stepThereAreTheFollowingRecords($dataObject, PyStringNode $string) { - $yaml = array_merge(array($dataObject . ':'), $string->getLines()); - $yaml = implode("\n ", $yaml); + /** + * Accepts YAML fixture definitions similar to the ones used in SilverStripe unit testing. + * + * Example: Given there are the following member records: + * member1: + * Email: member1@test.com + * member2: + * Email: member2@test.com + * + * @Given /^there are the following ([^\s]*) records$/ + */ + public function stepThereAreTheFollowingRecords($dataObject, PyStringNode $string) + { + $yaml = array_merge(array($dataObject . ':'), $string->getLines()); + $yaml = implode("\n ", $yaml); - // Save fixtures into database - // TODO Run prepareAsset() for each File and Folder record - $yamlFixture = new \YamlFixture($yaml); - $yamlFixture->writeInto($this->getFixtureFactory()); - } + // Save fixtures into database + // TODO Run prepareAsset() for each File and Folder record + $yamlFixture = new \YamlFixture($yaml); + $yamlFixture->writeInto($this->getFixtureFactory()); + } - /** - * Example: Given a "member" "Admin" belonging to "Admin Group" - * - * @Given /^(?:(an|a|the) )"member" "(?[^"]+)" belonging to "(?[^"]+)"$/ - */ - public function stepCreateMemberWithGroup($id, $groupId) { - $group = $this->fixtureFactory->get('SilverStripe\\Security\\Group', $groupId); - if(!$group) $group = $this->fixtureFactory->createObject('SilverStripe\\Security\\Group', $groupId); + /** + * Example: Given a "member" "Admin" belonging to "Admin Group" + * + * @Given /^(?:(an|a|the) )"member" "(?[^"]+)" belonging to "(?[^"]+)"$/ + */ + public function stepCreateMemberWithGroup($id, $groupId) + { + $group = $this->fixtureFactory->get('SilverStripe\\Security\\Group', $groupId); + if (!$group) { + $group = $this->fixtureFactory->createObject('SilverStripe\\Security\\Group', $groupId); + } - $member = $this->fixtureFactory->createObject('SilverStripe\\Security\\Member', $id); - $member->Groups()->add($group); - } + $member = $this->fixtureFactory->createObject('SilverStripe\\Security\\Member', $id); + $member->Groups()->add($group); + } - /** - * Example: Given a "member" "Admin" belonging to "Admin Group" with "Email"="test@test.com" - * - * @Given /^(?:(an|a|the) )"member" "(?[^"]+)" belonging to "(?[^"]+)" with (?.*)$/ - */ - public function stepCreateMemberWithGroupAndData($id, $groupId, $data) { - $class = 'SilverStripe\\Security\\Member'; - preg_match_all( - '/"(?[^"]+)"\s*=\s*"(?[^"]+)"/', - $data, - $matches - ); - $fields = $this->convertFields( - $class, - array_combine($matches['key'], $matches['value']) - ); + /** + * Example: Given a "member" "Admin" belonging to "Admin Group" with "Email"="test@test.com" + * + * @Given /^(?:(an|a|the) )"member" "(?[^"]+)" belonging to "(?[^"]+)" with (?.*)$/ + */ + public function stepCreateMemberWithGroupAndData($id, $groupId, $data) + { + $class = 'SilverStripe\\Security\\Member'; + preg_match_all( + '/"(?[^"]+)"\s*=\s*"(?[^"]+)"/', + $data, + $matches + ); + $fields = $this->convertFields( + $class, + array_combine($matches['key'], $matches['value']) + ); - $group = $this->fixtureFactory->get('SilverStripe\\Security\\Group', $groupId); - if(!$group) $group = $this->fixtureFactory->createObject('SilverStripe\\Security\\Group', $groupId); + $group = $this->fixtureFactory->get('SilverStripe\\Security\\Group', $groupId); + if (!$group) { + $group = $this->fixtureFactory->createObject('SilverStripe\\Security\\Group', $groupId); + } - $member = $this->fixtureFactory->createObject($class, $id, $fields); - $member->Groups()->add($group); - } + $member = $this->fixtureFactory->createObject($class, $id, $fields); + $member->Groups()->add($group); + } - /** - * Example: Given a "group" "Admin" with permissions "Access to 'Pages' section" and "Access to 'Files' section" - * - * @Given /^(?:(an|a|the) )"group" "(?[^"]+)" (?:(with|has)) permissions (?.*)$/ - */ - public function stepCreateGroupWithPermissions($id, $permissionStr) { - // Convert natural language permissions to codes - preg_match_all('/"([^"]+)"/', $permissionStr, $matches); - $permissions = $matches[1]; - $codes = Permission::get_codes(false); + /** + * Example: Given a "group" "Admin" with permissions "Access to 'Pages' section" and "Access to 'Files' section" + * + * @Given /^(?:(an|a|the) )"group" "(?[^"]+)" (?:(with|has)) permissions (?.*)$/ + */ + public function stepCreateGroupWithPermissions($id, $permissionStr) + { + // Convert natural language permissions to codes + preg_match_all('/"([^"]+)"/', $permissionStr, $matches); + $permissions = $matches[1]; + $codes = Permission::get_codes(false); - $group = $this->fixtureFactory->get('SilverStripe\\Security\\Group', $id); - if(!$group) $group = $this->fixtureFactory->createObject('SilverStripe\\Security\\Group', $id); + $group = $this->fixtureFactory->get('SilverStripe\\Security\\Group', $id); + if (!$group) { + $group = $this->fixtureFactory->createObject('SilverStripe\\Security\\Group', $id); + } - foreach($permissions as $permission) { - $found = false; - foreach($codes as $code => $details) { - if( - $permission == $code - || $permission == $details['name'] - ) { - Permission::grant($group->ID, $code); - $found = true; - } - } - if(!$found) { - throw new \InvalidArgumentException(sprintf( - 'No permission found for "%s"', $permission - )); - } - } - } + foreach ($permissions as $permission) { + $found = false; + foreach ($codes as $code => $details) { + if ($permission == $code + || $permission == $details['name'] + ) { + Permission::grant($group->ID, $code); + $found = true; + } + } + if (!$found) { + throw new \InvalidArgumentException(sprintf( + 'No permission found for "%s"', + $permission + )); + } + } + } - /** - * Navigates to a record based on its identifier set during fixture creation, - * using its RelativeLink() method to map the record to a URL. - * Example: Given I go to the "page" "My Page" - * - * @Given /^I go to (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"/ - */ - public function stepGoToNamedRecord($type, $id) { - $class = $this->convertTypeToClass($type); - $record = $this->fixtureFactory->get($class, $id); - if(!$record) { - throw new \InvalidArgumentException(sprintf( - 'Cannot resolve reference "%s", no matching fixture found', - $id - )); - } - if(!$record->hasMethod('RelativeLink')) { - throw new \InvalidArgumentException('URL for record cannot be determined, missing RelativeLink() method'); - } + /** + * Navigates to a record based on its identifier set during fixture creation, + * using its RelativeLink() method to map the record to a URL. + * Example: Given I go to the "page" "My Page" + * + * @Given /^I go to (?:(an|a|the) )"(?[^"]+)" "(?[^"]+)"/ + */ + public function stepGoToNamedRecord($type, $id) + { + $class = $this->convertTypeToClass($type); + $record = $this->fixtureFactory->get($class, $id); + if (!$record) { + throw new \InvalidArgumentException(sprintf( + 'Cannot resolve reference "%s", no matching fixture found', + $id + )); + } + if (!$record->hasMethod('RelativeLink')) { + throw new \InvalidArgumentException('URL for record cannot be determined, missing RelativeLink() method'); + } - $this->getSession()->visit($this->getMainContext()->locatePath($record->RelativeLink())); - } + $this->getSession()->visit($this->getMainContext()->locatePath($record->RelativeLink())); + } - /** - * Checks that a file or folder exists in the webroot. - * Example: There should be a file "assets/Uploads/test.jpg" - * - * @Then /^there should be a (?(file|folder) )"(?[^"]*)"/ - */ - public function stepThereShouldBeAFileOrFolder($type, $path) { - assertFileExists($this->joinPaths(BASE_PATH, $path)); - } + /** + * Checks that a file or folder exists in the webroot. + * Example: There should be a file "assets/Uploads/test.jpg" + * + * @Then /^there should be a (?(file|folder) )"(?[^"]*)"/ + */ + public function stepThereShouldBeAFileOrFolder($type, $path) + { + assertFileExists($this->joinPaths(BASE_PATH, $path)); + } - /** - * Checks that a file exists in the asset store with a given filename and hash - * - * Example: there should be a filename "Uploads/test.jpg" with hash "59de0c841f" - * - * @Then /^there should be a filename "(?[^"]*)" with hash "(?[a-fA-Z0-9]+)"/ - */ - public function stepThereShouldBeAFileWithTuple($filename, $hash) { - $exists = $this->getAssetStore()->exists($filename, $hash); - assertTrue((bool)$exists, "A file exists with filename $filename and hash $hash"); - } + /** + * Checks that a file exists in the asset store with a given filename and hash + * + * Example: there should be a filename "Uploads/test.jpg" with hash "59de0c841f" + * + * @Then /^there should be a filename "(?[^"]*)" with hash "(?[a-fA-Z0-9]+)"/ + */ + public function stepThereShouldBeAFileWithTuple($filename, $hash) + { + $exists = $this->getAssetStore()->exists($filename, $hash); + assertTrue((bool)$exists, "A file exists with filename $filename and hash $hash"); + } - /** - * Replaces fixture references in values with their respective database IDs, - * with the notation "=>.". Example: "=>Page.My Page". - * - * @Transform /^([^"]+)$/ - */ - public function lookupFixtureReference($string) { - if(preg_match('/^=>/', $string)) { - list($className, $identifier) = explode('.', preg_replace('/^=>/', '', $string), 2); - $id = $this->fixtureFactory->getId($className, $identifier); - if(!$id) { - throw new \InvalidArgumentException(sprintf( - 'Cannot resolve reference "%s", no matching fixture found', - $string - )); - } - return $id; - } else { - return $string; - } - } + /** + * Replaces fixture references in values with their respective database IDs, + * with the notation "=>.". Example: "=>Page.My Page". + * + * @Transform /^([^"]+)$/ + */ + public function lookupFixtureReference($string) + { + if (preg_match('/^=>/', $string)) { + list($className, $identifier) = explode('.', preg_replace('/^=>/', '', $string), 2); + $id = $this->fixtureFactory->getId($className, $identifier); + if (!$id) { + throw new \InvalidArgumentException(sprintf( + 'Cannot resolve reference "%s", no matching fixture found', + $string + )); + } + return $id; + } else { + return $string; + } + } - /** - * @Given /^(?:(an|a|the) )"(?[^"]*)" "(?[^"]*)" was (?(created|last edited)) "(?