Skip to content

Commit

Permalink
Merge pull request #81 from greenpeace/planet-5167-bdd
Browse files Browse the repository at this point in the history
PLANET-5167: Introduction of features descriptions as tests
  • Loading branch information
lithrel committed Jul 15, 2020
2 parents cb5c0f5 + 7b98656 commit e8be19d
Show file tree
Hide file tree
Showing 18 changed files with 1,287 additions and 114 deletions.
2 changes: 2 additions & 0 deletions codeception.dist.yml
Expand Up @@ -29,5 +29,7 @@ extensions:
- Codeception\Command\GenerateWPAjax
- Codeception\Command\GenerateWPCanonical
- Codeception\Command\GenerateWPXMLRPC
- Command\Selectors
- Command\Steps
params:
- .env.codeception
6 changes: 5 additions & 1 deletion tests/_support/AcceptanceTester.php
Expand Up @@ -2,6 +2,9 @@

use tad\WPBrowser\Generators\Date;

/**
* AcceptanceTester: helpers for the AcceptanceTester actor
*/
class AcceptanceTester extends \Codeception\Actor
{
use _generated\AcceptanceTesterActions;
Expand All @@ -23,6 +26,8 @@ public function fillFields($data)
/**
* Login to WordPress as the admin user saving the session as snapshot to make
* subsequent admin logins reuse the session to save time.
*
* @Given I am logged in as administrator
*/
public function loginAsAdminCached()
{
Expand Down Expand Up @@ -93,5 +98,4 @@ public function cleanupComments($email)
$I = $this;
$I->dontHaveCommentInDatabase(['comment_author_email' => $email]);
}

}
122 changes: 122 additions & 0 deletions tests/_support/Command/Selectors.php
@@ -0,0 +1,122 @@
<?php

namespace Command;

use \Symfony\Component\Console\Command\Command;
use \Codeception\CustomCommandInterface;
use \Selector\Admin\GutenbergEditor;
use \Selector\Admin\GutenbergEditor\BlockSelector;
use Selector\Admin\GutenbergEditor\Sidebar;
use \Symfony\Component\Console\Input\InputInterface;
use \Symfony\Component\Console\Output\OutputInterface;
use \Symfony\Component\Console\Helper\Table;
use \Symfony\Component\Console\Helper\TableCell;
use \Symfony\Component\Console\Helper\TableSeparator;

/**
* Prints all css/xpath selectors available
*/
class Selectors extends Command implements CustomCommandInterface
{
/**
* Selectors classes to list
*
* @var string[]
*/
private $selectorClasses = [
GutenbergEditor::class,
BlockSelector::class,
Sidebar::class,
];

/**
* Returns the name of the command
*
* @return string
*/
public static function getCommandName(): string
{
return "p4:selectors";
}

/**
* Configures the current command.
*/
protected function configure(): void
{
parent::configure();
}

/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription(): string
{
return "Listing P4 selectors";
}

/**
* Displays a list of available selectors
* Highlights variable parts
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$table = new Table($output);
$table->setHeaders(['Method', 'Selector']);
//$table->setColumnMaxWidth(1, 70);

while ($class = current($this->selectorClasses)) {
$table->addRows([
[new TableCell(sprintf('<options=bold>%s</>', $class), ['colspan' => 2])],
new TableSeparator()
]);
$table->addRows(array_map(
function ($key, $val) use ($class) { return [
$this->constToMethod($key, $class),
str_replace('%s', '<fg=yellow;options=bold>%s</>', $val)
]; } ,
$class::keys(),
$class::values()
));

next($this->selectorClasses);
if (current($this->selectorClasses)) {
$table->addRows([new TableSeparator()]);
}
}
$table->render();

return self::SUCCESS;
}

/**
* Qualifies php-enum const as methods, check if they have specific parameters declaration
*
* @param string $const Constant name
* @param string $class Class name
*
* @return string
*/
private function constToMethod(string $const, string $class): string
{
try {
$rfl = new \ReflectionMethod($class, $const);
$params = implode(', ', array_map(
function ($param) use ($class) {
$ns = (new \ReflectionClass($class))->getNamespaceName();
return str_replace($ns . '\\', '', $param->getType())
. ' $' . $param->getName();
},
$rfl->getParameters()
));

return $const . '(<fg=yellow>' . $params . '</>)';
} catch (\ReflectionException $e) {
return $const . '()';
}
}
}
143 changes: 143 additions & 0 deletions tests/_support/Command/Steps.php
@@ -0,0 +1,143 @@
<?php

namespace Command;

use \Codeception\CustomCommandInterface;
use \Codeception\Test\Loader\Gherkin;
use Codeception\Util\Annotation;
use \Symfony\Component\Console\Command\Command;
use \Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use \Symfony\Component\Console\Input\InputArgument;
use \Symfony\Component\Console\Input\InputInterface;
use \Symfony\Component\Console\Input\InputOption;
use \Symfony\Component\Console\Output\OutputInterface;

/**
* Prints all steps from all Gherkin contexts for a specific suite
* Extended `gherkin:steps` to categorize steps and print examples
*
* ```
* codecept p4:steps acceptance
* ```
*
*/
class Steps extends Command implements CustomCommandInterface
{
use \Codeception\Command\Shared\Config;
use \Codeception\Command\Shared\Style;

/**
* returns the name of the command
*
* @return string
*/
public static function getCommandName(): string
{
return "p4:steps";
}

/**
* Configures the current command.
*/
protected function configure(): void
{
$this->setDefinition(
[
new InputArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files'),
new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'),
new InputOption('implementation', 'i', InputOption::VALUE_NONE, 'Display implementation'),
]
);
parent::configure();
}

/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription(): string
{
return 'Listing P4 steps';
}

/**
* Displays a list of implemented steps
*
* @return int
*/
public function execute(InputInterface $input, OutputInterface $output): int
{

$this->addStyles($output);
$suite = $input->getArgument('suite');
$config = $this->getSuiteConfig($suite);
$config['describe_steps'] = true;
$implementation = $input->getOption('implementation');

$loader = new Gherkin($config);
$steps = $loader->getSteps();

$colspan = ['colspan' => $implementation ? 3 : 2];
$prevClass = null;
foreach ($steps as $name => $context) {
$table = new Table($output);
$table->setHeaders($implementation
? ['Step', 'Examples', 'Implementation']
: ['Step', 'Examples']
);
$output->writeln("Steps from <bold>$name</bold> context:");

foreach ($context as $step => $callable) {
if (count($callable) < 2) {
continue;
}

[$class, $method] = $callable;
if ($class !== $prevClass) {
$this->addClassheader($table, $class, $colspan, null !== $prevClass);
$prevClass = $class;
}

$methodAnnotation = Annotation::forMethod($class, $method);
$examples = $methodAnnotation->fetchAll('example');
$method = $class . '::' . $method;

$implementation
? $table->addRow([$step, implode("\n", $examples), $method])
: $table->addRow([$step, implode("\n", $examples)]);
}
$table->render();
}

if (!isset($table)) {
$output->writeln("No steps are defined, start creating them by running <bold>gherkin:snippets</bold>");
}

return self::SUCCESS;
}

private function addClassheader(
Table $table,
string $class,
array $attrs,
bool $addTopSeparator
): void {
if ($addTopSeparator) {
$table->addRow([new TableSeparator($attrs)]);
}

$classComments = (new \ReflectionClass($class))->getDocComment();
preg_match_all('/ \* (.*)/', $classComments, $matches);

$table->addRows([
[new TableCell(
sprintf('<options=bold>%s</>', $matches[1][0] ?? $class),
$attrs
)],
[new TableSeparator($attrs)]
]);
}
}
13 changes: 13 additions & 0 deletions tests/_support/Helper/Acceptance.php
Expand Up @@ -2,6 +2,8 @@

namespace Helper;

use Codeception\Module\WPWebDriver;

// here you can define custom actions
// all public methods declared in helper class will be available in $I

Expand Down Expand Up @@ -60,4 +62,15 @@ function generateGutenberg($name, $data)
$result .= ' /-->';
return $result;
}

/**
* @param string $selector
* @return bool
*/
public function checkIfElementExists(string $selector): bool
{
$wd = $this->getModule('WPWebDriver');
return $wd instanceof WPWebDriver
&& !empty($wd->_findElements($selector));
}
}
40 changes: 40 additions & 0 deletions tests/_support/Page/Acceptance/Admin/GutenbergEditor.php
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Page\Acceptance\Admin;

use Selector\Admin\GutenbergEditor as EditorSelector;
use Selector\Admin\GutenbergEditor\BlockName;
use Selector\Admin\GutenbergEditor\BlockSection;
use Selector\Admin\GutenbergEditor\BlockSelector;

class GutenbergEditor
{
/**
* @var \AcceptanceTester;
*/
protected $tester;

public function __construct(\AcceptanceTester $I)
{
$this->tester = $I;
}

public function addBlock(BlockSection $blockSection, BlockName $blockName): void
{
$I = $this->tester;
$blockButton = (string) BlockSelector::BLOCK($blockName);

$this->openBlockSelector();
$I->click((string) BlockSelector::SECTION($blockSection));
$I->waitForElement($blockButton, 1);
$I->click($blockButton);
}

public function openBlockSelector(): void
{
$I = $this->tester;
$I->click((string) BlockSelector::MAIN_BUTTON());
}
}

0 comments on commit e8be19d

Please sign in to comment.