From 6886b1ae822625a7ef935519685f5453255f262e Mon Sep 17 00:00:00 2001 From: Andrey Stukalin Date: Thu, 10 Nov 2016 13:51:07 +0100 Subject: [PATCH 1/9] Multiple example tables and tags support #117 --- src/Behat/Gherkin/Filter/LineFilter.php | 36 ++--- src/Behat/Gherkin/Filter/LineRangeFilter.php | 25 ++-- src/Behat/Gherkin/Filter/TagFilter.php | 57 +++++++- src/Behat/Gherkin/Loader/ArrayLoader.php | 38 +++++- src/Behat/Gherkin/Node/ExampleTableNode.php | 20 ++- src/Behat/Gherkin/Node/OutlineNode.php | 63 ++++++--- src/Behat/Gherkin/Node/TableNode.php | 24 ++++ src/Behat/Gherkin/Parser.php | 49 ++++++- tests/Behat/Gherkin/Filter/FilterTest.php | 6 + tests/Behat/Gherkin/Filter/LineFilterTest.php | 27 ++-- .../Gherkin/Filter/LineRangeFilterTest.php | 50 ++++++- tests/Behat/Gherkin/Filter/TagFilterTest.php | 126 ++++++++++++++++++ .../Gherkin/Fixtures/etalons/ja_addition.yml | 8 +- .../outline_with_multiple_examples.yml | 55 ++++++++ .../Gherkin/Fixtures/etalons/tags_sample.yml | 36 ++++- .../outline_with_multiple_examples.feature | 36 +++++ .../Fixtures/features/tags_sample.feature | 41 ++++-- tests/Behat/Gherkin/Keywords/KeywordsTest.php | 2 +- .../Behat/Gherkin/Loader/ArrayLoaderTest.php | 9 +- tests/Behat/Gherkin/Node/ExampleNodeTest.php | 4 +- tests/Behat/Gherkin/Node/OutlineNodeTest.php | 70 ++++++++-- tests/Behat/Gherkin/Node/TableNodeTest.php | 84 ++++++++++++ 22 files changed, 758 insertions(+), 108 deletions(-) create mode 100644 tests/Behat/Gherkin/Fixtures/etalons/outline_with_multiple_examples.yml create mode 100644 tests/Behat/Gherkin/Fixtures/features/outline_with_multiple_examples.feature diff --git a/src/Behat/Gherkin/Filter/LineFilter.php b/src/Behat/Gherkin/Filter/LineFilter.php index 455e9ac3..ba37adde 100644 --- a/src/Behat/Gherkin/Filter/LineFilter.php +++ b/src/Behat/Gherkin/Filter/LineFilter.php @@ -83,24 +83,26 @@ public function filterFeature(FeatureNode $feature) } if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { - $table = $scenario->getExampleTable()->getTable(); - $lines = array_keys($table); - - if (in_array($this->filterLine, $lines)) { - $filteredTable = array($lines[0] => $table[$lines[0]]); - - if ($lines[0] !== $this->filterLine) { - $filteredTable[$this->filterLine] = $table[$this->filterLine]; + foreach ($scenario->getExampleTables() as $exampleTable) { + $table = $exampleTable->getTable(); + $lines = array_keys($table); + + if (in_array($this->filterLine, $lines)) { + $filteredTable = array($lines[0] => $table[$lines[0]]); + + if ($lines[0] !== $this->filterLine) { + $filteredTable[$this->filterLine] = $table[$this->filterLine]; + } + + $scenario = new OutlineNode( + $scenario->getTitle(), + $scenario->getTags(), + $scenario->getSteps(), + array(new ExampleTableNode($filteredTable, $exampleTable->getKeyword(), $exampleTable->getTags())), + $scenario->getKeyword(), + $scenario->getLine() + ); } - - $scenario = new OutlineNode( - $scenario->getTitle(), - $scenario->getTags(), - $scenario->getSteps(), - new ExampleTableNode($filteredTable, $scenario->getExampleTable()->getKeyword()), - $scenario->getKeyword(), - $scenario->getLine() - ); } } diff --git a/src/Behat/Gherkin/Filter/LineRangeFilter.php b/src/Behat/Gherkin/Filter/LineRangeFilter.php index b8062bed..297af983 100644 --- a/src/Behat/Gherkin/Filter/LineRangeFilter.php +++ b/src/Behat/Gherkin/Filter/LineRangeFilter.php @@ -94,15 +94,24 @@ public function filterFeature(FeatureNode $feature) } if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { - $table = $scenario->getExampleTable()->getTable(); - $lines = array_keys($table); + // first accumulate examples and then create scenario + $exampleTableNodes = array(); - $filteredTable = array($lines[0] => $table[$lines[0]]); - unset($table[$lines[0]]); + foreach ($scenario->getExampleTables() as $exampleTable) { + $table = $exampleTable->getTable(); + $lines = array_keys($table); - foreach ($table as $line => $row) { - if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) { - $filteredTable[$line] = $row; + $filteredTable = array($lines[0] => $table[$lines[0]]); + unset($table[$lines[0]]); + + foreach ($table as $line => $row) { + if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) { + $filteredTable[$line] = $row; + } + } + + if (count($filteredTable) > 1) { + $exampleTableNodes[] = new ExampleTableNode($filteredTable, $exampleTable->getKeyword(), $exampleTable->getTags()); } } @@ -110,7 +119,7 @@ public function filterFeature(FeatureNode $feature) $scenario->getTitle(), $scenario->getTags(), $scenario->getSteps(), - new ExampleTableNode($filteredTable, $scenario->getExampleTable()->getKeyword()), + $exampleTableNodes, $scenario->getKeyword(), $scenario->getLine() ); diff --git a/src/Behat/Gherkin/Filter/TagFilter.php b/src/Behat/Gherkin/Filter/TagFilter.php index fed6c1af..509ba92c 100644 --- a/src/Behat/Gherkin/Filter/TagFilter.php +++ b/src/Behat/Gherkin/Filter/TagFilter.php @@ -11,6 +11,7 @@ namespace Behat\Gherkin\Filter; use Behat\Gherkin\Node\FeatureNode; +use Behat\Gherkin\Node\OutlineNode; use Behat\Gherkin\Node\ScenarioInterface; /** @@ -32,6 +33,50 @@ public function __construct($filterString) $this->filterString = trim($filterString); } + public function filterFeature(FeatureNode $feature) + { + $scenarios = array(); + foreach ($feature->getScenarios() as $scenario) { + if (!$this->isScenarioMatch($feature, $scenario)) { + continue; + } + + if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { + + $exampleTables = array(); + + foreach ($scenario->getExampleTables() as $exampleTable) { + if ($this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags(), $exampleTable->getTags()))) { + $exampleTables[] = $exampleTable; + } + } + + $scenario = new OutlineNode( + $scenario->getTitle(), + $scenario->getTags(), + $scenario->getSteps(), + $exampleTables, + $scenario->getKeyword(), + $scenario->getLine() + ); + } + + $scenarios[] = $scenario; + } + + return new FeatureNode( + $feature->getTitle(), + $feature->getDescription(), + $feature->getTags(), + $feature->getBackground(), + $scenarios, + $feature->getKeyword(), + $feature->getLanguage(), + $feature->getFile(), + $feature->getLine() + ); + } + /** * Checks if Feature matches specified filter. * @@ -47,13 +92,23 @@ public function isFeatureMatch(FeatureNode $feature) /** * Checks if scenario or outline matches specified filter. * - * @param FeatureNode $feature Feature node instance + * @param FeatureNode $feature Feature node instance * @param ScenarioInterface $scenario Scenario or Outline node instance * * @return Boolean */ public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario) { + if ($scenario instanceof OutlineNode && $scenario->hasExamples()) { + foreach ($scenario->getExampleTables() as $example) { + if ($this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags(), $example->getTags()))) { + return true; + } + } + + return false; + } + return $this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags())); } diff --git a/src/Behat/Gherkin/Loader/ArrayLoader.php b/src/Behat/Gherkin/Loader/ArrayLoader.php index 3492d6e6..315f12c0 100644 --- a/src/Behat/Gherkin/Loader/ArrayLoader.php +++ b/src/Behat/Gherkin/Loader/ArrayLoader.php @@ -179,7 +179,43 @@ protected function loadOutlineHash(array $hash, $line = 0) $examplesKeyword = 'Examples'; } - $examples = new ExampleTableNode($hash['examples'], $examplesKeyword); + $exHash = $hash['examples']; + $examples = array(); + + // there are 3 cases + // first is examples as a single table - we create an array with the only one element + // examples + // 11: abc + // 12: cde + // + // second is array of arrays + // examples + // - + // 11: abc + // 12: cde + // + // and the 3rd is array of objects + // examples + // - + // tags: [] + // table: + // 11: abc + // 12: cde + + if (isset($exHash[0])) { + // cases #2 & 3 + for ($i = 0; $i < count($exHash); $i++) { + if (isset($exHash[$i]['table'])) { + // we have examples as objects + $exHashTags = isset($exHash[$i]['tags']) ? $exHash[$i]['tags'] : array(); + $examples[] = new ExampleTableNode($exHash[$i]['table'], $examplesKeyword, $exHashTags); + } else { + $examples[] = new ExampleTableNode($exHash[$i], $examplesKeyword); + } + } + } else { + $examples[] = new ExampleTableNode($exHash, $examplesKeyword);; + } return new OutlineNode($hash['title'], $hash['tags'], $steps, $examples, $hash['keyword'], $hash['line']); } diff --git a/src/Behat/Gherkin/Node/ExampleTableNode.php b/src/Behat/Gherkin/Node/ExampleTableNode.php index 805e659e..91753511 100644 --- a/src/Behat/Gherkin/Node/ExampleTableNode.php +++ b/src/Behat/Gherkin/Node/ExampleTableNode.php @@ -17,6 +17,11 @@ */ class ExampleTableNode extends TableNode { + /** + * @var string[] + */ + private $tags; + /** * @var string */ @@ -25,12 +30,14 @@ class ExampleTableNode extends TableNode /** * Initializes example table. * - * @param array $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]] + * @param array $table Table in form of [$rowLineNumber => [$val1, $val2, $val3]] * @param string $keyword + * @param string[] $tags */ - public function __construct(array $table, $keyword) + public function __construct(array $table, $keyword, array $tags = array()) { $this->keyword = $keyword; + $this->tags = $tags; parent::__construct($table); } @@ -45,6 +52,15 @@ public function getNodeType() return 'ExampleTable'; } + /** + * Returns attached tags + * @return \string[] + */ + public function getTags() + { + return $this->tags; + } + /** * Returns example table keyword. * diff --git a/src/Behat/Gherkin/Node/OutlineNode.php b/src/Behat/Gherkin/Node/OutlineNode.php index 62f55181..7764032c 100644 --- a/src/Behat/Gherkin/Node/OutlineNode.php +++ b/src/Behat/Gherkin/Node/OutlineNode.php @@ -30,9 +30,9 @@ class OutlineNode implements ScenarioInterface */ private $steps; /** - * @var ExampleTableNode + * @var ExampleTableNode[] */ - private $table; + private $tables; /** * @var string */ @@ -52,7 +52,7 @@ class OutlineNode implements ScenarioInterface * @param null|string $title * @param string[] $tags * @param StepNode[] $steps - * @param ExampleTableNode $table + * @param ExampleTableNode[] $tables * @param string $keyword * @param integer $line */ @@ -60,14 +60,14 @@ public function __construct( $title, array $tags, array $steps, - ExampleTableNode $table, + array $tables, $keyword, $line ) { $this->title = $title; $this->tags = $tags; $this->steps = $steps; - $this->table = $table; + $this->tables = $tables; $this->keyword = $keyword; $this->line = $line; } @@ -151,27 +151,50 @@ public function getSteps() */ public function hasExamples() { - return 0 < count($this->table->getColumnsHash()); + return 0 < count($this->tables); } /** - * Returns examples table. + * Builds and returns examples table for the outline. * + * WARNING: it returns a merged table with tags lost. + * + * @deprecated use getExampleTables instead * @return ExampleTableNode */ public function getExampleTable() { - return $this->table; + $table = array(); + + foreach ($this->tables[0]->getTable() as $k => $v) { + $table[$k] = $v; + } + + /** @var ExampleTableNode $exampleTableNode */ + $exampleTableNode = new ExampleTableNode($table, $this->tables[0]->getKeyword()); + for ($i = 1; $i < count($this->tables); $i++) { + $exampleTableNode->mergeRowsFromTable($this->tables[$i]); + } + + return $exampleTableNode; } /** * Returns list of examples for the outline. - * * @return ExampleNode[] */ public function getExamples() { - return $this->examples = $this->examples ? : $this->createExamples(); + return $this->examples = $this->examples ?: $this->createExamples(); + } + + /** + * Returns examples tables array for the outline. + * @return ExampleTableNode[] + */ + public function getExampleTables() + { + return $this->tables; } /** @@ -202,15 +225,17 @@ public function getLine() protected function createExamples() { $examples = array(); - foreach ($this->table->getColumnsHash() as $rowNum => $row) { - $examples[] = new ExampleNode( - $this->table->getRowAsString($rowNum + 1), - $this->tags, - $this->getSteps(), - $row, - $this->table->getRowLine($rowNum + 1), - $this->getTitle() - ); + + foreach ($this->getExampleTables() as $exampleTable) { + foreach ($exampleTable->getColumnsHash() as $rowNum => $row) { + $examples[] = new ExampleNode( + $exampleTable->getRowAsString($rowNum + 1), + array_merge($this->tags, $exampleTable->getTags()), + $this->getSteps(), + $row, + $exampleTable->getRowLine($rowNum + 1) + ); + } } return $examples; diff --git a/src/Behat/Gherkin/Node/TableNode.php b/src/Behat/Gherkin/Node/TableNode.php index 735e688e..3a73dcc1 100644 --- a/src/Behat/Gherkin/Node/TableNode.php +++ b/src/Behat/Gherkin/Node/TableNode.php @@ -328,6 +328,30 @@ public function getIterator() return new ArrayIterator($this->getHash()); } + /** + * Obtains and adds rows from another table to the current table. + * The second table should have the same structure as the current one. + * @param TableNode $node + * + * @deprecated remove together with OutlineNode::getExampleTable + */ + public function mergeRowsFromTable(TableNode $node) + { + // check structure + if ($this->getRow(0) !== $node->getRow(0)) { + throw new NodeException("Tables have different structure. Cannot merge one into another"); + } + + $firstLine = $node->getLine(); + foreach ($node->getTable() as $line => $value) { + if ($line === $firstLine) { + continue; + } + + $this->table[$line] = $value; + } + } + /** * Pads string right. * diff --git a/src/Behat/Gherkin/Parser.php b/src/Behat/Gherkin/Parser.php index 5cc85424..cb96cd1f 100644 --- a/src/Behat/Gherkin/Parser.php +++ b/src/Behat/Gherkin/Parser.php @@ -39,6 +39,8 @@ class Parser private $tags = array(); private $languageSpecifierLine; + private $stack = array(); + /** * Initializes parser. * @@ -236,6 +238,8 @@ protected function parseFeature() $file = $this->file; $line = $token['line']; + array_push($this->stack, 'Feature'); + // Parse description, background, scenarios & outlines while ('EOS' !== $this->predictTokenType()) { $node = $this->parseExpression(); @@ -369,6 +373,8 @@ protected function parseScenario() $keyword = $token['keyword']; $line = $token['line']; + array_push($this->stack, 'Scenario'); + // Parse description and steps $steps = array(); while (in_array($this->predictTokenType(), array('Step', 'Newline', 'Text', 'Comment'))) { @@ -407,6 +413,8 @@ protected function parseScenario() } } + array_pop($this->stack); + return new ScenarioNode(rtrim($title) ?: null, $tags, $steps, $keyword, $line); } @@ -424,12 +432,17 @@ protected function parseOutline() $title = trim($token['value']); $tags = $this->popTags(); $keyword = $token['keyword']; - $examples = null; + + /** @var ExampleTableNode $examples */ + $examples = array(); $line = $token['line']; // Parse description, steps and examples $steps = array(); - while (in_array($this->predictTokenType(), array('Step', 'Examples', 'Newline', 'Text', 'Comment'))) { + + array_push($this->stack, 'Outline'); + + while (in_array($this->predictTokenType(), array('Step', 'Examples', 'Newline', 'Text', 'Comment', 'Tag'))) { $node = $this->parseExpression(); if ($node instanceof StepNode) { @@ -438,7 +451,8 @@ protected function parseOutline() } if ($node instanceof ExampleTableNode) { - $examples = $node; + $examples[] = $node; + continue; } @@ -470,7 +484,7 @@ protected function parseOutline() } } - if (null === $examples) { + if (empty($examples)) { throw new ParserException(sprintf( 'Outline should have examples table, but got none for outline "%s" on line: %d%s', rtrim($title), @@ -496,6 +510,8 @@ protected function parseStep() $text = trim($token['text']); $line = $token['line']; + array_push($this->stack, 'Step'); + $arguments = array(); while (in_array($predicted = $this->predictTokenType(), array('PyStringOp', 'TableRow', 'Newline', 'Comment'))) { if ('Comment' === $predicted || 'Newline' === $predicted) { @@ -510,6 +526,8 @@ protected function parseStep() } } + array_pop($this->stack); + return new StepNode($keyword, $text, $arguments, $line, $keywordType); } @@ -524,7 +542,9 @@ protected function parseExamples() $keyword = $token['keyword']; - return new ExampleTableNode($this->parseTableRows(), $keyword); + $tags = empty($this->tags) ? array() : $this->popTags(); + + return new ExampleTableNode($this->parseTableRows(), $keyword, $tags); } /** @@ -570,7 +590,24 @@ protected function parseTags() $token = $this->expectTokenType('Tag'); $this->tags = array_merge($this->tags, $token['tags']); - return $this->parseExpression(); + $possibleTransitions = array( + 'Outline' => array( + 'Examples', + 'Step' + ) + ); + + $currentType = '-1'; + // check if that is ok to go inside: + if (!empty($this->stack)) { + $currentType = $this->stack[count($this->stack) - 1]; + } + $nextType = $this->predictTokenType(); + if (!isset($possibleTransitions[$currentType]) || in_array($nextType, $possibleTransitions[$currentType])) { + return $this->parseExpression(); + } + + return "\n"; } /** diff --git a/tests/Behat/Gherkin/Filter/FilterTest.php b/tests/Behat/Gherkin/Filter/FilterTest.php index 994e6564..abd71dad 100644 --- a/tests/Behat/Gherkin/Filter/FilterTest.php +++ b/tests/Behat/Gherkin/Filter/FilterTest.php @@ -49,11 +49,17 @@ protected function getGherkinFeature() When occurs Then should be visible + @etag1 Examples: | action | outcome | | act#1 | out#1 | | act#2 | out#2 | + + @etag2 + Examples: + | action | outcome | | act#3 | out#3 | + GHERKIN; } diff --git a/tests/Behat/Gherkin/Filter/LineFilterTest.php b/tests/Behat/Gherkin/Filter/LineFilterTest.php index 846a719d..853b8a1f 100644 --- a/tests/Behat/Gherkin/Filter/LineFilterTest.php +++ b/tests/Behat/Gherkin/Filter/LineFilterTest.php @@ -37,7 +37,7 @@ public function testIsScenarioMatchFilter() $filter = new LineFilter(5); $this->assertFalse($filter->isScenarioMatch($scenario)); - $outline = new OutlineNode(null, array(), array(), new ExampleTableNode(array(), null), null, 20); + $outline = new OutlineNode(null, array(), array(), array(new ExampleTableNode(array(), null)), null, 20); $filter = new LineFilter(5); $this->assertFalse($filter->isScenarioMatch($outline)); @@ -67,37 +67,42 @@ public function testFilterFeatureOutline() { $filter = new LineFilter(13); $feature = $filter->filterFeature($this->getParsedFeature()); + /** @var OutlineNode[] $scenarios */ $this->assertCount(1, $scenarios = $feature->getScenarios()); $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); $this->assertCount(4, $scenarios[0]->getExampleTable()->getRows()); - $filter = new LineFilter(19); + $filter = new LineFilter(20); $feature = $filter->filterFeature($this->getParsedFeature()); $this->assertCount(1, $scenarios = $feature->getScenarios()); $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); - $this->assertCount(2, $scenarios[0]->getExampleTable()->getRows()); + $exampleTableNodes = $scenarios[0]->getExampleTables(); + $this->assertEquals(1, count($exampleTableNodes)); + $this->assertCount(2, $exampleTableNodes[0]->getRows()); $this->assertSame(array( array('action', 'outcome'), array('act#1', 'out#1'), - ), $scenarios[0]->getExampleTable()->getRows()); + ), $exampleTableNodes[0]->getRows()); + $this->assertEquals(array('etag1'), $exampleTableNodes[0]->getTags()); - $filter = new LineFilter(21); + $filter = new LineFilter(26); $feature = $filter->filterFeature($this->getParsedFeature()); $this->assertCount(1, $scenarios = $feature->getScenarios()); $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); - $this->assertCount(2, $scenarios[0]->getExampleTable()->getRows()); + $exampleTableNodes = $scenarios[0]->getExampleTables(); + $this->assertEquals(1, count($exampleTableNodes)); + $this->assertCount(2, $exampleTableNodes[0]->getRows()); $this->assertSame(array( array('action', 'outcome'), array('act#3', 'out#3'), - ), $scenarios[0]->getExampleTable()->getRows()); + ), $exampleTableNodes[0]->getRows()); + $this->assertEquals(array('etag2'), $exampleTableNodes[0]->getTags()); - $filter = new LineFilter(18); + $filter = new LineFilter(19); $feature = $filter->filterFeature($this->getParsedFeature()); $this->assertCount(1, $scenarios = $feature->getScenarios()); $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); $this->assertCount(1, $scenarios[0]->getExampleTable()->getRows()); - $this->assertSame(array( - array('action', 'outcome'), - ), $scenarios[0]->getExampleTable()->getRows()); + $this->assertSame(array(array('action', 'outcome')), $scenarios[0]->getExampleTable()->getRows()); } } diff --git a/tests/Behat/Gherkin/Filter/LineRangeFilterTest.php b/tests/Behat/Gherkin/Filter/LineRangeFilterTest.php index fb8abe1c..a27c9a71 100644 --- a/tests/Behat/Gherkin/Filter/LineRangeFilterTest.php +++ b/tests/Behat/Gherkin/Filter/LineRangeFilterTest.php @@ -53,7 +53,7 @@ public function scenarioLineRangeProvider() public function testIsScenarioMatchFilter($filterMinLine, $filterMaxLine, $expectedNumberOfMatches) { $scenario = new ScenarioNode(null, array(), array(), null, 2); - $outline = new OutlineNode(null, array(), array(), new ExampleTableNode(array(), null), null, 3); + $outline = new OutlineNode(null, array(), array(), array(new ExampleTableNode(array(), null)), null, 3); $filter = new LineRangeFilter($filterMinLine, $filterMaxLine); $this->assertEquals( @@ -83,19 +83,59 @@ public function testFilterFeatureOutline() { $filter = new LineRangeFilter(12, 14); $feature = $filter->filterFeature($this->getParsedFeature()); + /** @var OutlineNode[] $scenarios */ $this->assertCount(1, $scenarios = $feature->getScenarios()); $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); - $this->assertCount(1, $scenarios[0]->getExampleTable()->getRows()); + $this->assertFalse($scenarios[0]->hasExamples()); - $filter = new LineRangeFilter(15, 20); + $filter = new LineRangeFilter(16, 21); $feature = $filter->filterFeature($this->getParsedFeature()); $this->assertCount(1, $scenarios = $feature->getScenarios()); $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); - $this->assertCount(3, $scenarios[0]->getExampleTable()->getRows()); + $exampleTableNodes = $scenarios[0]->getExampleTables(); + $this->assertEquals(1, count($exampleTableNodes)); + $this->assertCount(3, $exampleTableNodes[0]->getRows()); $this->assertSame(array( array('action', 'outcome'), array('act#1', 'out#1'), array('act#2', 'out#2'), - ), $scenarios[0]->getExampleTable()->getRows()); + ), $exampleTableNodes[0]->getRows()); + $this->assertEquals(array('etag1'), $exampleTableNodes[0]->getTags()); + + $filter = new LineRangeFilter(16, 26); + $feature = $filter->filterFeature($this->getParsedFeature()); + $this->assertCount(1, $scenarios = $feature->getScenarios()); + $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); + $exampleTableNodes = $scenarios[0]->getExampleTables(); + $this->assertEquals(2, count($exampleTableNodes)); + + $this->assertCount(3, $exampleTableNodes[0]->getRows()); + $this->assertSame(array( + array('action', 'outcome'), + array('act#1', 'out#1'), + array('act#2', 'out#2'), + ), $exampleTableNodes[0]->getRows()); + $this->assertEquals(array('etag1'), $exampleTableNodes[0]->getTags()); + + $this->assertCount(2, $exampleTableNodes[1]->getRows()); + $this->assertSame(array( + array('action', 'outcome'), + array('act#3', 'out#3') + ), $exampleTableNodes[1]->getRows()); + + $this->assertEquals(array('etag2'), $exampleTableNodes[1]->getTags()); + + $filter = new LineRangeFilter(25, 26); + $feature = $filter->filterFeature($this->getParsedFeature()); + $this->assertCount(1, $scenarios = $feature->getScenarios()); + $this->assertSame('Scenario#3', $scenarios[0]->getTitle()); + $exampleTableNodes = $scenarios[0]->getExampleTables(); + $this->assertEquals(1, count($exampleTableNodes)); + $this->assertCount(2, $exampleTableNodes[0]->getRows()); + $this->assertSame(array( + array('action', 'outcome'), + array('act#3', 'out#3'), + ), $exampleTableNodes[0]->getRows()); + $this->assertEquals(array('etag2'), $exampleTableNodes[0]->getTags()); } } diff --git a/tests/Behat/Gherkin/Filter/TagFilterTest.php b/tests/Behat/Gherkin/Filter/TagFilterTest.php index 5c6c9ab2..ce2229cc 100644 --- a/tests/Behat/Gherkin/Filter/TagFilterTest.php +++ b/tests/Behat/Gherkin/Filter/TagFilterTest.php @@ -3,7 +3,9 @@ namespace Tests\Behat\Gherkin\Filter; use Behat\Gherkin\Filter\TagFilter; +use Behat\Gherkin\Node\ExampleTableNode; use Behat\Gherkin\Node\FeatureNode; +use Behat\Gherkin\Node\OutlineNode; use Behat\Gherkin\Node\ScenarioNode; class TagFilterTest extends \PHPUnit_Framework_TestCase @@ -140,5 +142,129 @@ public function testIsScenarioMatchFilter() $filter = new TagFilter('@feature-tag&&@user'); $scenario = new ScenarioNode(null, array('wip'), array(), null, 2); $this->assertFalse($filter->isScenarioMatch($feature, $scenario)); + + $scenario = new OutlineNode(null, array('wip'), array(), array( + new ExampleTableNode(array(), null, array('etag1', 'etag2')), + new ExampleTableNode(array(), null, array('etag2', 'etag3')), + ), null, 2); + + $tagFilter = new TagFilter('@etag3'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('~@etag3'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('@wip'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('@wip&&@etag3'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('@feature-tag&&@etag1&&@wip'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('@feature-tag&&~@etag11111&&@wip'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('@feature-tag&&~@etag1&&@wip'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('@feature-tag&&@etag2'); + $this->assertTrue($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('~@etag1&&~@etag3'); + $this->assertFalse($tagFilter->isScenarioMatch($feature, $scenario)); + + $tagFilter = new TagFilter('@etag1&&@etag3'); + $this->assertFalse($tagFilter->isScenarioMatch($feature, $scenario), "Tags from different examples tables"); + } + + public function testFilterFeatureWithTaggedExamples() + { + $exampleTableNode1 = new ExampleTableNode(array(), null, array('etag1', 'etag2')); + $exampleTableNode2 = new ExampleTableNode(array(), null, array('etag2', 'etag3')); + $scenario = new OutlineNode(null, array('wip'), array(), array( + $exampleTableNode1, + $exampleTableNode2, + ), null, 2); + $feature = new FeatureNode(null, null, array('feature-tag'), null, array($scenario), null, null, null, 1); + + $tagFilter = new TagFilter('@etag2'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + $this->assertEquals($scenario, $scenarioInterfaces[0]); + + $tagFilter = new TagFilter('@etag1'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals(array($exampleTableNode1), $scenarioInterfaces[0]->getExampleTables()); + + $tagFilter = new TagFilter('~@etag3'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals(array($exampleTableNode1), $scenarioInterfaces[0]->getExampleTables()); + + $tagFilter = new TagFilter('@wip'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + $this->assertEquals($scenario, $scenarioInterfaces[0]); + + $tagFilter = new TagFilter('@wip&&@etag3'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals(array($exampleTableNode2), $scenarioInterfaces[0]->getExampleTables()); + + $tagFilter = new TagFilter('@feature-tag&&@etag1&&@wip'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals(array($exampleTableNode1), $scenarioInterfaces[0]->getExampleTables()); + + $tagFilter = new TagFilter('@feature-tag&&~@etag11111&&@wip'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + $this->assertEquals($scenario, $scenarioInterfaces[0]); + + $tagFilter = new TagFilter('@feature-tag&&~@etag1&&@wip'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals(array($exampleTableNode2), $scenarioInterfaces[0]->getExampleTables()); + + $tagFilter = new TagFilter('@feature-tag&&@etag2'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + $this->assertEquals($scenario, $scenarioInterfaces[0]); + + $exampleTableNode1 = new ExampleTableNode(array(), null, array('etag1', 'etag')); + $exampleTableNode2 = new ExampleTableNode(array(), null, array('etag2', 'etag22', 'etag')); + $exampleTableNode3 = new ExampleTableNode(array(), null, array('etag3', 'etag22', 'etag')); + $exampleTableNode4 = new ExampleTableNode(array(), null, array('etag4', 'etag')); + $scenario1 = new OutlineNode(null, array('wip'), array(), array( + $exampleTableNode1, + $exampleTableNode2, + ), null, 2); + $scenario2 = new OutlineNode(null, array('wip'), array(), array( + $exampleTableNode3, + $exampleTableNode4, + ), null, 2); + $feature = new FeatureNode(null, null, array('feature-tag'), null, array($scenario1, $scenario2), null, null, null, 1); + + $tagFilter = new TagFilter('@etag'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + $this->assertEquals(array($scenario1, $scenario2), $scenarioInterfaces); + + $tagFilter = new TagFilter('@etag22'); + $matched = $tagFilter->filterFeature($feature); + $scenarioInterfaces = $matched->getScenarios(); + $this->assertEquals(2, count($scenarioInterfaces)); + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals(array($exampleTableNode2), $scenarioInterfaces[0]->getExampleTables()); + /** @noinspection PhpUndefinedMethodInspection */ + $this->assertEquals(array($exampleTableNode3), $scenarioInterfaces[1]->getExampleTables()); } } diff --git a/tests/Behat/Gherkin/Fixtures/etalons/ja_addition.yml b/tests/Behat/Gherkin/Fixtures/etalons/ja_addition.yml index 6ac42d4a..c2b7a2b0 100644 --- a/tests/Behat/Gherkin/Fixtures/etalons/ja_addition.yml +++ b/tests/Behat/Gherkin/Fixtures/etalons/ja_addition.yml @@ -15,7 +15,7 @@ feature: title: '2つの数の加算について' line: 7 steps: - - { keyword_type: 'Given', type: '前提', text: '50 を入力', line: 8 } - - { keyword_type: 'Given', type: 'かつ', text: '70 を入力', line: 9 } - - { keyword_type: 'When', type: 'もし', text: 'add ボタンを押した', line: 10 } - - { keyword_type: 'Then', type: 'ならば', text: '結果は 120 を表示', line: 11 } + - { keyword_type: 'Given', type: '前提', text: '50 を入力', line: 8 } + - { keyword_type: 'Given', type: 'かつ', text: '70 を入力', line: 9 } + - { keyword_type: 'When', type: 'もし', text: 'add ボタンを押した', line: 10 } + - { keyword_type: 'Then', type: 'ならば', text: '結果は 120 を表示', line: 11 } diff --git a/tests/Behat/Gherkin/Fixtures/etalons/outline_with_multiple_examples.yml b/tests/Behat/Gherkin/Fixtures/etalons/outline_with_multiple_examples.yml new file mode 100644 index 00000000..e980b297 --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/etalons/outline_with_multiple_examples.yml @@ -0,0 +1,55 @@ +feature: + title: Unsubstituted argument placeholder + language: en + line: 1 + description: ~ + + scenarios: + - + type: outline + title: 'See Annual Leave Details (as Management & Human Resource)' + line: 3 + steps: + - + keyword_type: Given + type: Given + text: the exist in the system + line: 4 + examples: + - + 7: [ role, name ] + 8: [ HUMAN RESOURCE, abc ] + - + 11: [ role, name ] + 12: [ MANAGER, cde ] + - + 15: [ role, name ] + 16: [ CEO, qqq ] + 17: [ CTO, xxx ] + - + type: outline + title: 'See Annual Leave Details (as Management & Human Resource)' + line: 20 + steps: + - + keyword_type: Given + type: Given + text: the exist in the system + line: 21 + examples: + - + tags: [tag1, tag2] + table: + 25: [ role, name ] + 26: [ HUMAN RESOURCE, abc ] + - + tags: [tag1, tag3] + table: + 30: [ role, name ] + 31: [ MANAGER, cde ] + - + tags: [tag4] + table: + 34: [ role, name ] + 35: [ CEO, qqq ] + 36: [ CTO, xxx ] \ No newline at end of file diff --git a/tests/Behat/Gherkin/Fixtures/etalons/tags_sample.yml b/tests/Behat/Gherkin/Fixtures/etalons/tags_sample.yml index 486d9585..82f25e1a 100644 --- a/tests/Behat/Gherkin/Fixtures/etalons/tags_sample.yml +++ b/tests/Behat/Gherkin/Fixtures/etalons/tags_sample.yml @@ -23,13 +23,41 @@ feature: - { keyword_type: 'Given', type: 'Given', text: '', line: 10 } examples: - 12: [state] - 13: [missing] + - + tags: [examples_tag, examples_tag2] + table: + 13: [state] + 14: [missing] - type: scenario title: Skipped tags: [sample_three, sample_four] - line: 16 + line: 17 steps: - - { keyword_type: 'Given', type: 'Given', text: 'missing', line: 17 } + - { keyword_type: 'Given', type: 'Given', text: 'missing', line: 18 } + + - + type: outline + title: passing + tags: [sample_5] + line: 22 + steps: + - { keyword_type: 'Given', type: 'Given', text: '', line: 23 } + examples: + 25: [state] + 26: [missing] + + - + type: outline + title: passing + tags: [sample_6, sample_7] + line: 29 + steps: + - { keyword_type: 'Given', type: 'Given', text: '', line: 30 } + examples: + - + tags: [examples_tag3, examples_tag4] + table: + 33: [state] + 34: [missing] \ No newline at end of file diff --git a/tests/Behat/Gherkin/Fixtures/features/outline_with_multiple_examples.feature b/tests/Behat/Gherkin/Fixtures/features/outline_with_multiple_examples.feature new file mode 100644 index 00000000..a40c3164 --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/features/outline_with_multiple_examples.feature @@ -0,0 +1,36 @@ +Feature: Unsubstituted argument placeholder + + Scenario Outline: See Annual Leave Details (as Management & Human Resource) + Given the exist in the system + + Examples: + | role | name | + | HUMAN RESOURCE | abc | + + Examples: + | role | name | + | MANAGER | cde | + + Examples: + | role | name | + | CEO | qqq | + | CTO | xxx | + + + Scenario Outline: See Annual Leave Details (as Management & Human Resource) + Given the exist in the system + + @tag1 @tag2 + Examples: + | role | name | + | HUMAN RESOURCE | abc | + + @tag1 @tag3 + Examples: + | role | name | + | MANAGER | cde | + @tag4 + Examples: + | role | name | + | CEO | qqq | + | CTO | xxx | \ No newline at end of file diff --git a/tests/Behat/Gherkin/Fixtures/features/tags_sample.feature b/tests/Behat/Gherkin/Fixtures/features/tags_sample.feature index 21eaaab1..ef3bd626 100644 --- a/tests/Behat/Gherkin/Fixtures/features/tags_sample.feature +++ b/tests/Behat/Gherkin/Fixtures/features/tags_sample.feature @@ -1,17 +1,34 @@ @sample_one Feature: Tag samples - @sample_two @sample_four - Scenario: Passing - Given missing + @sample_two @sample_four + Scenario: Passing + Given missing - @sample_three - Scenario Outline: - Given - Examples: - |state| - |missing| + @sample_three + Scenario Outline: + Given + @examples_tag @examples_tag2 + Examples: + | state | + | missing | - @sample_three @sample_four - Scenario: Skipped - Given missing \ No newline at end of file + @sample_three @sample_four + Scenario: Skipped + Given missing + + + @sample_5 + Scenario Outline: passing + Given + Examples: + | state | + | missing | + + @sample_6 @sample_7 + Scenario Outline: passing + Given + @examples_tag3 @examples_tag4 + Examples: + | state | + | missing | diff --git a/tests/Behat/Gherkin/Keywords/KeywordsTest.php b/tests/Behat/Gherkin/Keywords/KeywordsTest.php index 822ac63a..182df509 100644 --- a/tests/Behat/Gherkin/Keywords/KeywordsTest.php +++ b/tests/Behat/Gherkin/Keywords/KeywordsTest.php @@ -85,7 +85,7 @@ public function translationTestDataProvider() ), $keywords[0]); $line += 1; - $scenarios[] = new OutlineNode('Erasing other agents\' memory', array(), $steps, $table, $outlineKeyword, $outlineLine); + $scenarios[] = new OutlineNode('Erasing other agents\' memory', array(), $steps, array($table), $outlineKeyword, $outlineLine); $line += 1; } diff --git a/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php b/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php index 697d1d3d..c5bad104 100644 --- a/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php +++ b/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php @@ -3,9 +3,11 @@ namespace Tests\Behat\Gherkin\Loader; use Behat\Gherkin\Loader\ArrayLoader; +use Behat\Gherkin\Node\OutlineNode; class ArrayLoaderTest extends \PHPUnit_Framework_TestCase { + /** @var ArrayLoader */ private $loader; protected function setUp() @@ -157,9 +159,9 @@ public function testOutlineExamples() 'title' => 'First outline', 'line' => 2, 'examples' => array( - array('user', 'pass'), - array('ever', 'sdsd'), - array('anto', 'fdfd') + 11 => array('user', 'pass'), + 12 => array('ever', 'sdsd'), + 13 => array('anto', 'fdfd') ) ), array( @@ -173,6 +175,7 @@ public function testOutlineExamples() $this->assertEquals(1, count($features)); + /** @var OutlineNode[] $scenarios */ $scenarios = $features[0]->getScenarios(); $scenario = $scenarios[0]; diff --git a/tests/Behat/Gherkin/Node/ExampleNodeTest.php b/tests/Behat/Gherkin/Node/ExampleNodeTest.php index c6f46be5..059fc05a 100644 --- a/tests/Behat/Gherkin/Node/ExampleNodeTest.php +++ b/tests/Behat/Gherkin/Node/ExampleNodeTest.php @@ -25,7 +25,7 @@ public function testCreateExampleSteps() array('example', 'example@example.com') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, $table, null, null); + $outline = new OutlineNode(null, array(), $steps, array($table), null, null); $examples = $outline->getExamples(); $this->assertCount(4, $steps = $examples[0]->getSteps()); @@ -78,7 +78,7 @@ public function testCreateExampleStepsWithArguments() array('example', 'example@example.com', 'other page') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, $table, null, null); + $outline = new OutlineNode(null, array(), $steps, array($table), null, null); $examples = $outline->getExamples(); $steps = $examples[0]->getSteps(); diff --git a/tests/Behat/Gherkin/Node/OutlineNodeTest.php b/tests/Behat/Gherkin/Node/OutlineNodeTest.php index 1e889b80..ff1fead7 100644 --- a/tests/Behat/Gherkin/Node/OutlineNodeTest.php +++ b/tests/Behat/Gherkin/Node/OutlineNodeTest.php @@ -14,22 +14,68 @@ public function testCreatesExamplesForExampleTable() new StepNode('Gangway!', 'I am ', array(), null, 'Given'), new StepNode('Aye!', 'my email is ', array(), null, 'And'), new StepNode('Blimey!', 'I open homepage', array(), null, 'When'), - new StepNode('Let go and haul', 'website should recognise me', array(), null, 'Then'), + new StepNode('Let go and haul', 'website should recognise me', array(), null, 'Then'), ); $table = new ExampleTableNode(array( - array('name', 'email'), - array('everzet', 'ever.zet@gmail.com'), - array('example', 'example@example.com') + 2 => array('name', 'email'), + 22 => array('everzet', 'ever.zet@gmail.com'), + 23 => array('example', 'example@example.com') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, $table, null, null); + $outline = new OutlineNode(null, array(), $steps, array($table), null, null); $this->assertCount(2, $examples = $outline->getExamples()); - $this->assertEquals(1, $examples[0]->getLine()); - $this->assertEquals(2, $examples[1]->getLine()); + $this->assertEquals(22, $examples[0]->getLine()); + $this->assertEquals(23, $examples[1]->getLine()); $this->assertEquals(array('name' => 'everzet', 'email' => 'ever.zet@gmail.com'), $examples[0]->getTokens()); - $this->assertEquals(array('name' => 'example', 'email' => 'example@example.com'), $examples[1]->getTokens()); + $this->assertEquals(array('name' => 'example', 'email' => 'example@example.com'), $examples[1]->getTokens()); + } + + public function testCreatesExamplesForExampleTableWithSeveralExamplesAndTags() + { + $steps = array( + new StepNode('Gangway!', 'I am ', array(), null, 'Given'), + new StepNode('Aye!', 'my email is ', array(), null, 'And'), + new StepNode('Blimey!', 'I open homepage', array(), null, 'When'), + new StepNode('Let go and haul', 'website should recognise me', array(), null, 'Then'), + ); + + $table = new ExampleTableNode(array( + 2 => array('name', 'email'), + 22 => array('everzet', 'ever.zet@gmail.com'), + 23 => array('example', 'example@example.com') + ), 'Examples', array()); + + $table2 = new ExampleTableNode(array( + 3 => array('name', 'email'), + 32 => array('everzet2', 'ever.zet2@gmail.com'), + 33 => array('example2', 'example2@example.com') + ), 'Examples', array('etag1', 'etag2')); + + $outline = new OutlineNode(null, array('otag1', 'otag2'), $steps, array($table, $table2), null, null); + + $this->assertCount(4, $examples = $outline->getExamples()); + $this->assertEquals(22, $examples[0]->getLine()); + $this->assertEquals(23, $examples[1]->getLine()); + $this->assertEquals(32, $examples[2]->getLine()); + $this->assertEquals(33, $examples[3]->getLine()); + $this->assertEquals(array('name' => 'everzet', 'email' => 'ever.zet@gmail.com'), $examples[0]->getTokens()); + $this->assertEquals(array('name' => 'example', 'email' => 'example@example.com'), $examples[1]->getTokens()); + $this->assertEquals(array('name' => 'everzet2', 'email' => 'ever.zet2@gmail.com'), $examples[2]->getTokens()); + $this->assertEquals(array('name' => 'example2', 'email' => 'example2@example.com'), $examples[3]->getTokens()); + + for ($i = 0; $i < 2; $i++) { + foreach (array('otag1', 'otag2') as $tag) { + $this->assertTrue($examples[$i]->hasTag($tag), "there is no tag " . $tag . " in example #" . $i); + } + } + + for ($i = 2; $i < 4; $i++) { + foreach (array('otag1', 'otag2', 'etag1', 'etag2') as $tag) { + $this->assertTrue($examples[$i]->hasTag($tag), "there is no tag " . $tag . " in example #" . $i); + } + } } public function testCreatesEmptyExamplesForEmptyExampleTable() @@ -38,14 +84,14 @@ public function testCreatesEmptyExamplesForEmptyExampleTable() new StepNode('Gangway!', 'I am ', array(), null, 'Given'), new StepNode('Aye!', 'my email is ', array(), null, 'And'), new StepNode('Blimey!', 'I open homepage', array(), null, 'When'), - new StepNode('Let go and haul', 'website should recognise me', array(), null, 'Then'), + new StepNode('Let go and haul', 'website should recognise me', array(), null, 'Then'), ); $table = new ExampleTableNode(array( array('name', 'email') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, $table, null, null); + $outline = new OutlineNode(null, array(), $steps, array($table), null, null); $this->assertCount(0, $examples = $outline->getExamples()); } @@ -56,12 +102,12 @@ public function testCreatesEmptyExamplesForNoExampleTable() new StepNode('Gangway!', 'I am ', array(), null, 'Given'), new StepNode('Aye!', 'my email is ', array(), null, 'And'), new StepNode('Blimey!', 'I open homepage', array(), null, 'When'), - new StepNode('Let go and haul', 'website should recognise me', array(), null, 'Then'), + new StepNode('Let go and haul', 'website should recognise me', array(), null, 'Then'), ); $table = new ExampleTableNode(array(), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, $table, null, null); + $outline = new OutlineNode(null, array(), $steps, array($table), null, null); $this->assertCount(0, $examples = $outline->getExamples()); } diff --git a/tests/Behat/Gherkin/Node/TableNodeTest.php b/tests/Behat/Gherkin/Node/TableNodeTest.php index a2299551..f19f45ea 100644 --- a/tests/Behat/Gherkin/Node/TableNodeTest.php +++ b/tests/Behat/Gherkin/Node/TableNodeTest.php @@ -254,6 +254,53 @@ public function testFromList() )); $this->assertEquals($expected, $table); } + public function testMergeRowsFromTablePassSeveralTablesShouldBeMerged() + { + $table = new TableNode(array( + 5 => array('id', 'username', 'password'), + 10 => array('42', 'everzet', 'qwerty'), + 13 => array('2', 'antono', 'pa$sword') + )); + + $new = new TableNode(array( + 25 => array('id', 'username', 'password'), + 210 => array('242', '2everzet', '2qwerty'), + 213 => array('22', '2antono', '2pa$sword') + )); + + $new2 = new TableNode(array( + 35 => array('id', 'username', 'password'), + 310 => array('342', '3everzet', '3qwerty'), + 313 => array('32', '3antono', '3pa$sword') + )); + + $table->mergeRowsFromTable($new); + $table->mergeRowsFromTable($new2); + + $this->assertEquals(array('id', 'username', 'password'), $table->getRow(0)); + $this->assertEquals(array('2', 'antono', 'pa$sword'), $table->getRow(2)); + $this->assertEquals(array('242', '2everzet', '2qwerty'), $table->getRow(3)); + $this->assertEquals(array('32', '3antono', '3pa$sword'), $table->getRow(6)); + } + + /** + * @expectedException \Behat\Gherkin\Exception\NodeException + */ + public function testMergeRowsFromTableWrongHeaderNameExceptionThrown() + { + $table = new TableNode(array( + 5 => array('id', 'username', 'password'), + 10 => array('42', 'everzet', 'qwerty'), + 13 => array('2', 'antono', 'pa$sword') + )); + + $new = new TableNode(array( + 25 => array('id', 'QWE', 'password'), + 210 => array('242', '2everzet', '2qwerty') + )); + + $table->mergeRowsFromTable($new); + } /** * @expectedException \Behat\Gherkin\Exception\NodeException @@ -266,4 +313,41 @@ public function testGetTableFromListWithMultidimensionalArrayArgument() )); } + /** + * @expectedException \Behat\Gherkin\Exception\NodeException + */ + public function testMergeRowsFromTableWrongHeaderOrderExceptionThrown() + { + $table = new TableNode(array( + 5 => array('id', 'username', 'password'), + 10 => array('42', 'everzet', 'qwerty'), + 13 => array('2', 'antono', 'pa$sword') + )); + + $new = new TableNode(array( + 25 => array('id', 'password', 'username'), + 210 => array('242', '2everzet', '2qwerty') + )); + + $table->mergeRowsFromTable($new); + } + + /** + * @expectedException \Behat\Gherkin\Exception\NodeException + */ + public function testMergeRowsFromTableWrongHeaderSizeExceptionThrown() + { + $table = new TableNode(array( + 5 => array('id', 'username', 'password'), + 10 => array('42', 'everzet', 'qwerty'), + 13 => array('2', 'antono', 'pa$sword') + )); + + $new = new TableNode(array( + 25 => array('id', 'username'), + 210 => array('242', '2everzet') + )); + + $table->mergeRowsFromTable($new); + } } From 628d53edb45826bfa1c0c06fbff96724df715ae9 Mon Sep 17 00:00:00 2001 From: Andrey Stukalin Date: Fri, 23 Dec 2016 09:44:25 +0100 Subject: [PATCH 2/9] #117 phpdoc and some rewording --- src/Behat/Gherkin/Filter/TagFilter.php | 9 ++++++++- src/Behat/Gherkin/Parser.php | 19 ++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Behat/Gherkin/Filter/TagFilter.php b/src/Behat/Gherkin/Filter/TagFilter.php index 509ba92c..d25cd2fe 100644 --- a/src/Behat/Gherkin/Filter/TagFilter.php +++ b/src/Behat/Gherkin/Filter/TagFilter.php @@ -32,7 +32,14 @@ public function __construct($filterString) { $this->filterString = trim($filterString); } - + + /** + * Filters feature according to the filter. + * + * @param FeatureNode $feature + * + * @return FeatureNode + */ public function filterFeature(FeatureNode $feature) { $scenarios = array(); diff --git a/src/Behat/Gherkin/Parser.php b/src/Behat/Gherkin/Parser.php index cb96cd1f..c9a7dff6 100644 --- a/src/Behat/Gherkin/Parser.php +++ b/src/Behat/Gherkin/Parser.php @@ -39,7 +39,7 @@ class Parser private $tags = array(); private $languageSpecifierLine; - private $stack = array(); + private $passedNodesStack = array(); /** * Initializes parser. @@ -238,7 +238,7 @@ protected function parseFeature() $file = $this->file; $line = $token['line']; - array_push($this->stack, 'Feature'); + array_push($this->passedNodesStack, 'Feature'); // Parse description, background, scenarios & outlines while ('EOS' !== $this->predictTokenType()) { @@ -373,7 +373,7 @@ protected function parseScenario() $keyword = $token['keyword']; $line = $token['line']; - array_push($this->stack, 'Scenario'); + array_push($this->passedNodesStack, 'Scenario'); // Parse description and steps $steps = array(); @@ -413,7 +413,7 @@ protected function parseScenario() } } - array_pop($this->stack); + array_pop($this->passedNodesStack); return new ScenarioNode(rtrim($title) ?: null, $tags, $steps, $keyword, $line); } @@ -440,7 +440,7 @@ protected function parseOutline() // Parse description, steps and examples $steps = array(); - array_push($this->stack, 'Outline'); + array_push($this->passedNodesStack, 'Outline'); while (in_array($this->predictTokenType(), array('Step', 'Examples', 'Newline', 'Text', 'Comment', 'Tag'))) { $node = $this->parseExpression(); @@ -510,7 +510,7 @@ protected function parseStep() $text = trim($token['text']); $line = $token['line']; - array_push($this->stack, 'Step'); + array_push($this->passedNodesStack, 'Step'); $arguments = array(); while (in_array($predicted = $this->predictTokenType(), array('PyStringOp', 'TableRow', 'Newline', 'Comment'))) { @@ -526,7 +526,7 @@ protected function parseStep() } } - array_pop($this->stack); + array_pop($this->passedNodesStack); return new StepNode($keyword, $text, $arguments, $line, $keywordType); } @@ -599,9 +599,10 @@ protected function parseTags() $currentType = '-1'; // check if that is ok to go inside: - if (!empty($this->stack)) { - $currentType = $this->stack[count($this->stack) - 1]; + if (!empty($this->passedNodesStack)) { + $currentType = $this->passedNodesStack[count($this->passedNodesStack) - 1]; } + $nextType = $this->predictTokenType(); if (!isset($possibleTransitions[$currentType]) || in_array($nextType, $possibleTransitions[$currentType])) { return $this->parseExpression(); From d78c6d595c3d9f208c1ea55dfa61adc9c62c77eb Mon Sep 17 00:00:00 2001 From: Andrey Stukalin Date: Thu, 19 Jan 2017 15:05:25 +0100 Subject: [PATCH 3/9] nesting reducing --- src/Behat/Gherkin/Loader/ArrayLoader.php | 69 +++++++++++++----------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/Behat/Gherkin/Loader/ArrayLoader.php b/src/Behat/Gherkin/Loader/ArrayLoader.php index 315f12c0..1f057170 100644 --- a/src/Behat/Gherkin/Loader/ArrayLoader.php +++ b/src/Behat/Gherkin/Loader/ArrayLoader.php @@ -182,38 +182,10 @@ protected function loadOutlineHash(array $hash, $line = 0) $exHash = $hash['examples']; $examples = array(); - // there are 3 cases - // first is examples as a single table - we create an array with the only one element - // examples - // 11: abc - // 12: cde - // - // second is array of arrays - // examples - // - - // 11: abc - // 12: cde - // - // and the 3rd is array of objects - // examples - // - - // tags: [] - // table: - // 11: abc - // 12: cde - - if (isset($exHash[0])) { - // cases #2 & 3 - for ($i = 0; $i < count($exHash); $i++) { - if (isset($exHash[$i]['table'])) { - // we have examples as objects - $exHashTags = isset($exHash[$i]['tags']) ? $exHash[$i]['tags'] : array(); - $examples[] = new ExampleTableNode($exHash[$i]['table'], $examplesKeyword, $exHashTags); - } else { - $examples[] = new ExampleTableNode($exHash[$i], $examplesKeyword); - } - } + if ($this->examplesAreInArray($exHash)) { + $examples = $this->processExamplesArray($exHash, $examplesKeyword, $examples); } else { + // examples as a single table - we create an array with the only one element $examples[] = new ExampleTableNode($exHash, $examplesKeyword);; } @@ -302,4 +274,39 @@ protected function loadPyStringHash(array $hash, $line = 0) return new PyStringNode($strings, $line); } + + /** + * Checks if examples node is an array + * @param $exHash object hash + * @return bool + */ + private function examplesAreInArray($exHash) + { + return isset($exHash[0]); + } + + /** + * Processes cases when examples are in the form of array of arrays + * OR in the form of array of objects + * + * @param $exHash array hash + * @param $examplesKeyword string + * @param $examples array + * @return array + */ + private function processExamplesArray($exHash, $examplesKeyword, $examples) + { + for ($i = 0; $i < count($exHash); $i++) { + if (isset($exHash[$i]['table'])) { + // we have examples as objects, hence there could be tags + $exHashTags = isset($exHash[$i]['tags']) ? $exHash[$i]['tags'] : array(); + $examples[] = new ExampleTableNode($exHash[$i]['table'], $examplesKeyword, $exHashTags); + } else { + // we have examples as arrays + $examples[] = new ExampleTableNode($exHash[$i], $examplesKeyword); + } + } + + return $examples; + } } From 2ed729d217eaa837e3882b8067635186c19ea432 Mon Sep 17 00:00:00 2001 From: Dipak Acharya Date: Thu, 12 Dec 2019 16:17:02 +0545 Subject: [PATCH 4/9] Fix the OutlineNode object for backward compatibility --- src/Behat/Gherkin/Filter/TagFilter.php | 2 +- src/Behat/Gherkin/Node/OutlineNode.php | 14 ++++++++------ tests/Behat/Gherkin/Filter/LineFilterTest.php | 2 +- tests/Behat/Gherkin/Keywords/KeywordsTest.php | 2 +- tests/Behat/Gherkin/Node/ExampleNodeTest.php | 4 ++-- tests/Behat/Gherkin/Node/OutlineNodeTest.php | 4 ++-- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Behat/Gherkin/Filter/TagFilter.php b/src/Behat/Gherkin/Filter/TagFilter.php index d25cd2fe..51f1cfde 100644 --- a/src/Behat/Gherkin/Filter/TagFilter.php +++ b/src/Behat/Gherkin/Filter/TagFilter.php @@ -32,7 +32,7 @@ public function __construct($filterString) { $this->filterString = trim($filterString); } - + /** * Filters feature according to the filter. * diff --git a/src/Behat/Gherkin/Node/OutlineNode.php b/src/Behat/Gherkin/Node/OutlineNode.php index 7764032c..818fbe6b 100644 --- a/src/Behat/Gherkin/Node/OutlineNode.php +++ b/src/Behat/Gherkin/Node/OutlineNode.php @@ -30,7 +30,7 @@ class OutlineNode implements ScenarioInterface */ private $steps; /** - * @var ExampleTableNode[] + * @var ExampleTableNode|ExampleTableNode[] */ private $tables; /** @@ -52,7 +52,7 @@ class OutlineNode implements ScenarioInterface * @param null|string $title * @param string[] $tags * @param StepNode[] $steps - * @param ExampleTableNode[] $tables + * @param ExampleTableNode|ExampleTableNode[] $tables * @param string $keyword * @param integer $line */ @@ -60,16 +60,20 @@ public function __construct( $title, array $tags, array $steps, - array $tables, + $tables, $keyword, $line ) { $this->title = $title; $this->tags = $tags; $this->steps = $steps; - $this->tables = $tables; $this->keyword = $keyword; $this->line = $line; + if (!is_array($tables)) { + $this->tables = array($tables); + } else { + $this->tables = $tables; + } } /** @@ -165,7 +169,6 @@ public function hasExamples() public function getExampleTable() { $table = array(); - foreach ($this->tables[0]->getTable() as $k => $v) { $table[$k] = $v; } @@ -175,7 +178,6 @@ public function getExampleTable() for ($i = 1; $i < count($this->tables); $i++) { $exampleTableNode->mergeRowsFromTable($this->tables[$i]); } - return $exampleTableNode; } diff --git a/tests/Behat/Gherkin/Filter/LineFilterTest.php b/tests/Behat/Gherkin/Filter/LineFilterTest.php index 853b8a1f..5e8be7d3 100644 --- a/tests/Behat/Gherkin/Filter/LineFilterTest.php +++ b/tests/Behat/Gherkin/Filter/LineFilterTest.php @@ -37,7 +37,7 @@ public function testIsScenarioMatchFilter() $filter = new LineFilter(5); $this->assertFalse($filter->isScenarioMatch($scenario)); - $outline = new OutlineNode(null, array(), array(), array(new ExampleTableNode(array(), null)), null, 20); + $outline = new OutlineNode(null, array(), array(), new ExampleTableNode(array(), null), null, 20); $filter = new LineFilter(5); $this->assertFalse($filter->isScenarioMatch($outline)); diff --git a/tests/Behat/Gherkin/Keywords/KeywordsTest.php b/tests/Behat/Gherkin/Keywords/KeywordsTest.php index 182df509..822ac63a 100644 --- a/tests/Behat/Gherkin/Keywords/KeywordsTest.php +++ b/tests/Behat/Gherkin/Keywords/KeywordsTest.php @@ -85,7 +85,7 @@ public function translationTestDataProvider() ), $keywords[0]); $line += 1; - $scenarios[] = new OutlineNode('Erasing other agents\' memory', array(), $steps, array($table), $outlineKeyword, $outlineLine); + $scenarios[] = new OutlineNode('Erasing other agents\' memory', array(), $steps, $table, $outlineKeyword, $outlineLine); $line += 1; } diff --git a/tests/Behat/Gherkin/Node/ExampleNodeTest.php b/tests/Behat/Gherkin/Node/ExampleNodeTest.php index 059fc05a..c6f46be5 100644 --- a/tests/Behat/Gherkin/Node/ExampleNodeTest.php +++ b/tests/Behat/Gherkin/Node/ExampleNodeTest.php @@ -25,7 +25,7 @@ public function testCreateExampleSteps() array('example', 'example@example.com') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, array($table), null, null); + $outline = new OutlineNode(null, array(), $steps, $table, null, null); $examples = $outline->getExamples(); $this->assertCount(4, $steps = $examples[0]->getSteps()); @@ -78,7 +78,7 @@ public function testCreateExampleStepsWithArguments() array('example', 'example@example.com', 'other page') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, array($table), null, null); + $outline = new OutlineNode(null, array(), $steps, $table, null, null); $examples = $outline->getExamples(); $steps = $examples[0]->getSteps(); diff --git a/tests/Behat/Gherkin/Node/OutlineNodeTest.php b/tests/Behat/Gherkin/Node/OutlineNodeTest.php index ff1fead7..bbfcc675 100644 --- a/tests/Behat/Gherkin/Node/OutlineNodeTest.php +++ b/tests/Behat/Gherkin/Node/OutlineNodeTest.php @@ -23,7 +23,7 @@ public function testCreatesExamplesForExampleTable() 23 => array('example', 'example@example.com') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, array($table), null, null); + $outline = new OutlineNode(null, array(), $steps, $table, null, null); $this->assertCount(2, $examples = $outline->getExamples()); $this->assertEquals(22, $examples[0]->getLine()); @@ -91,7 +91,7 @@ public function testCreatesEmptyExamplesForEmptyExampleTable() array('name', 'email') ), 'Examples'); - $outline = new OutlineNode(null, array(), $steps, array($table), null, null); + $outline = new OutlineNode(null, array(), $steps, $table, null, null); $this->assertCount(0, $examples = $outline->getExamples()); } From 3b11b185bd88fff3fa7f4fa08a4b50892373b3cb Mon Sep 17 00:00:00 2001 From: Dipak Acharya Date: Wed, 15 Jan 2020 09:06:32 +0545 Subject: [PATCH 5/9] Add support for Outline on background --- src/Behat/Gherkin/Loader/ArrayLoader.php | 18 ++++++- src/Behat/Gherkin/Loader/YamlFileLoader.php | 8 ++++ src/Behat/Gherkin/Node/BackgroundNode.php | 28 ++++++++++- src/Behat/Gherkin/Node/OutlineNode.php | 14 +++++- src/Behat/Gherkin/Parser.php | 39 ++++++++++----- .../etalons/background_with_outline.yml | 31 ++++++++++++ .../etalons/background_with_outline_multi.yml | 48 +++++++++++++++++++ .../features/background_with_outline.feature | 13 +++++ .../background_with_outline_multi.feature | 27 +++++++++++ .../Behat/Gherkin/Loader/ArrayLoaderTest.php | 6 ++- 10 files changed, 216 insertions(+), 16 deletions(-) create mode 100644 tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml create mode 100644 tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml create mode 100644 tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature create mode 100644 tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature diff --git a/src/Behat/Gherkin/Loader/ArrayLoader.php b/src/Behat/Gherkin/Loader/ArrayLoader.php index 1f057170..e963dbdc 100644 --- a/src/Behat/Gherkin/Loader/ArrayLoader.php +++ b/src/Behat/Gherkin/Loader/ArrayLoader.php @@ -119,7 +119,23 @@ protected function loadBackgroundHash(array $hash) $steps = $this->loadStepsHash($hash['steps']); - return new BackgroundNode($hash['title'], $steps, $hash['keyword'], $hash['line']); + if (isset($hash['examples']['keyword'])) { + $examplesKeyword = $hash['examples']['keyword']; + unset($hash['examples']['keyword']); + } else { + $examplesKeyword = 'Examples'; + } + if (isset($hash['examples'])) { + $examplesTable = $hash['examples']; + } else { + $examplesTable = array(); + } + if (\count($examplesTable) === 0) { + $examples = null; + } else { + $examples = new ExampleTableNode($examplesTable, $examplesKeyword); + } + return new BackgroundNode($hash['title'], $steps, $hash['keyword'], $hash['line'], $examples); } /** diff --git a/src/Behat/Gherkin/Loader/YamlFileLoader.php b/src/Behat/Gherkin/Loader/YamlFileLoader.php index 0c268fd3..52808f55 100644 --- a/src/Behat/Gherkin/Loader/YamlFileLoader.php +++ b/src/Behat/Gherkin/Loader/YamlFileLoader.php @@ -11,6 +11,7 @@ namespace Behat\Gherkin\Loader; use Behat\Gherkin\Node\FeatureNode; +use Behat\Gherkin\Node\OutlineNode; use Symfony\Component\Yaml\Yaml; /** @@ -57,6 +58,13 @@ public function load($path) $filename = $this->findRelativePath($path); return array_map(function (FeatureNode $feature) use ($filename) { + if ($feature->getBackground() !== null && $feature->getBackground()->hasExamples()) { + foreach ($feature->getScenarios() as $scenario) { + if ($scenario instanceof OutlineNode && !$scenario->hasExamples()){ + $scenario->setExampleTable($feature->getBackground()->getExamples()); + } + } + } return new FeatureNode( $feature->getTitle(), $feature->getDescription(), diff --git a/src/Behat/Gherkin/Node/BackgroundNode.php b/src/Behat/Gherkin/Node/BackgroundNode.php index fb1edb4b..a6b1aa8a 100644 --- a/src/Behat/Gherkin/Node/BackgroundNode.php +++ b/src/Behat/Gherkin/Node/BackgroundNode.php @@ -33,6 +33,10 @@ class BackgroundNode implements ScenarioLikeInterface * @var integer */ private $line; + /** + * @var ExampleTableNode + */ + private $exampleTable; /** * Initializes background. @@ -41,13 +45,15 @@ class BackgroundNode implements ScenarioLikeInterface * @param StepNode[] $steps * @param string $keyword * @param integer $line + * @param null|ExampleTableNode $exampleTable */ - public function __construct($title, array $steps, $keyword, $line) + public function __construct($title, array $steps, $keyword, $line, $exampleTable=null) { $this->title = $title; $this->steps = $steps; $this->keyword = $keyword; $this->line = $line; + $this->exampleTable = $exampleTable; } /** @@ -109,4 +115,24 @@ public function getLine() { return $this->line; } + + /** + * Returns if background has ExampleTable + * + * @return boolean + */ + public function hasExamples() + { + return $this->exampleTable !== null; + } + + /** + * Returns if background has ExampleTable + * + * @return null|ExampleTableNode + */ + public function getExamples() + { + return $this->exampleTable; + } } diff --git a/src/Behat/Gherkin/Node/OutlineNode.php b/src/Behat/Gherkin/Node/OutlineNode.php index 818fbe6b..782350bc 100644 --- a/src/Behat/Gherkin/Node/OutlineNode.php +++ b/src/Behat/Gherkin/Node/OutlineNode.php @@ -52,7 +52,7 @@ class OutlineNode implements ScenarioInterface * @param null|string $title * @param string[] $tags * @param StepNode[] $steps - * @param ExampleTableNode|ExampleTableNode[] $tables + * @param null|ExampleTableNode|ExampleTableNode[] $tables * @param string $keyword * @param integer $line */ @@ -158,6 +158,18 @@ public function hasExamples() return 0 < count($this->tables); } + /** + * Add Example to the outline. + * + * @param ExampleTableNode + * + * @return void + */ + public function setExampleTable($table) + { + $this->tables = $table; + } + /** * Builds and returns examples table for the outline. * diff --git a/src/Behat/Gherkin/Parser.php b/src/Behat/Gherkin/Parser.php index c9a7dff6..1030ef9d 100644 --- a/src/Behat/Gherkin/Parser.php +++ b/src/Behat/Gherkin/Parser.php @@ -279,6 +279,26 @@ protected function parseFeature() } } + if ($background === null || !$background->hasExamples()) { + foreach ($scenarios as $scenario) { + if ($scenario instanceof OutlineNode && !$scenario->hasExamples()) { + throw new ParserException(sprintf( + 'Outline should have examples table, but got none for outline "%s" on line: %d%s', + rtrim($scenario->getTitle()), + $scenario->getLine(), + $this->file ? ' in file: ' . $this->file : '' + )); + } + } + } + if ($background !== null && $background->hasExamples()) {; + foreach ($scenarios as $scenario) { + if ($scenario instanceof OutlineNode && !$scenario->hasExamples()) { + $scenario->setExampleTable($background->getExamples()); + } + } + } + return new FeatureNode( rtrim($title) ?: null, rtrim($description) ?: null, @@ -306,6 +326,7 @@ protected function parseBackground() $title = trim($token['value']); $keyword = $token['keyword']; $line = $token['line']; + $example = null; if (count($this->popTags())) { throw new ParserException(sprintf( @@ -317,10 +338,15 @@ protected function parseBackground() // Parse description and steps $steps = array(); - $allowedTokenTypes = array('Step', 'Newline', 'Text', 'Comment'); + $allowedTokenTypes = array('Step', 'Newline', 'Text', 'Comment', 'Examples'); while (in_array($this->predictTokenType(), $allowedTokenTypes)) { $node = $this->parseExpression(); + if ($node instanceof ExampleTableNode) { + $example = $node; + continue; + } + if ($node instanceof StepNode) { $steps[] = $this->normalizeStepNodeKeywordType($node, $steps); continue; @@ -354,7 +380,7 @@ protected function parseBackground() } } - return new BackgroundNode(rtrim($title) ?: null, $steps, $keyword, $line); + return new BackgroundNode(rtrim($title) ?: null, $steps, $keyword, $line, $example); } /** @@ -484,15 +510,6 @@ protected function parseOutline() } } - if (empty($examples)) { - throw new ParserException(sprintf( - 'Outline should have examples table, but got none for outline "%s" on line: %d%s', - rtrim($title), - $line, - $this->file ? ' in file: ' . $this->file : '' - )); - } - return new OutlineNode(rtrim($title) ?: null, $tags, $steps, $examples, $keyword, $line); } diff --git a/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml new file mode 100644 index 00000000..5fe2f0b1 --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml @@ -0,0 +1,31 @@ +feature: + title: Feature with background and example + language: en + line: 1 + description: ~ + + background: + line: 3 + steps: + - { keyword_type: Given, type: Given, text: a passing step, line: 4 } + examples: + 6: [login, password] + 7: ['', ''] + 8: [unknown_user, ''] + + scenarios: + - + type: outline + title: ~ + line: 10 + steps: + - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 11 } + - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 12 } + - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 13 } + arguments: + - + type: table + rows: + 6: [ login, password ] + 7: [ '' , '' ] + 8: [ unknown_user , '' ] \ No newline at end of file diff --git a/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml new file mode 100644 index 00000000..97a23e9d --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml @@ -0,0 +1,48 @@ +feature: + title: Feature with background and example + language: en + line: 1 + description: ~ + + background: + line: 3 + steps: + - { keyword_type: Given, type: Given, text: a passing step, line: 4 } + examples: + 6: [login, password] + 7: ['', ''] + 8: [unknown_user, ''] + + scenarios: + - + type: outline + title: scenario1 + line: 10 + steps: + - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 11 } + - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 12 } + - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 13 } + examples: + 6: [ login, password ] + 7: [ '' , '' ] + 8: [ unknown_user , '' ] + + - type: scenario + title: scenario2 + line: 15 + steps: + - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 16 } + - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with "user"', line: 17 } + - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with "password"', line: 18 } + + - type: outline + title: scenario3 + line: 20 + steps: + - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 21 } + - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 22 } + - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 23 } + examples: + 25: [ login, password ] + 26: [ hello, world ] + 27: [ user , pass ] \ No newline at end of file diff --git a/tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature b/tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature new file mode 100644 index 00000000..eebd708f --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature @@ -0,0 +1,13 @@ +Feature: Feature with background and example + + Background: + Given a passing step + Examples: + | login | password | + | | | + | unknown_user | | + + Scenario Outline: + Given a failing step + When I fill in "login" with "" + And I fill in "password" with "" diff --git a/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature b/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature new file mode 100644 index 00000000..a027a653 --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature @@ -0,0 +1,27 @@ +Feature: Feature with background and example + + Background: + Given a passing step + Examples: + | login | password | + | | | + | unknown_user | | + + Scenario Outline: scenario1 + Given a failing step + When I fill in "login" with "" + And I fill in "password" with "" + + Scenario: scenario2 + Given a failing step + When I fill in "login" with "user" + And I fill in "password" with "password" + + Scenario Outline: scenario3 + Given a failing step + When I fill in "login" with "" + And I fill in "password" with "" + Examples: + | login | password | + | hello | world | + | user | pass | \ No newline at end of file diff --git a/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php b/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php index c5bad104..4d38b7c5 100644 --- a/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php +++ b/tests/Behat/Gherkin/Loader/ArrayLoaderTest.php @@ -220,7 +220,8 @@ public function testLoadSteps() 'steps' => array( array('type' => 'Gangway!', 'keyword_type' => 'Given', 'text' => 'bg step 1', 'line' => 3), array('type' => 'Blimey!', 'keyword_type' => 'When', 'text' => 'bg step 2') - ) + ), + 'examples' => null ), 'scenarios' => array( array( @@ -329,7 +330,8 @@ public function testLoadStepArguments() ) ) ) - ) + ), + 'examples' => null ) ) ) From d03227db480202d928088c69f84429314d28aeb0 Mon Sep 17 00:00:00 2001 From: Dipak Acharya Date: Thu, 16 Jan 2020 11:00:45 +0545 Subject: [PATCH 6/9] Improve features in etalon tests --- src/Behat/Gherkin/Loader/YamlFileLoader.php | 8 -------- .../etalons/background_with_outline.yml | 19 ++++++++---------- .../etalons/background_with_outline_multi.yml | 18 ++++++++--------- .../features/background_with_outline.feature | 12 +++++------ .../background_with_outline_multi.feature | 20 +++++++++---------- 5 files changed, 33 insertions(+), 44 deletions(-) diff --git a/src/Behat/Gherkin/Loader/YamlFileLoader.php b/src/Behat/Gherkin/Loader/YamlFileLoader.php index 52808f55..0c268fd3 100644 --- a/src/Behat/Gherkin/Loader/YamlFileLoader.php +++ b/src/Behat/Gherkin/Loader/YamlFileLoader.php @@ -11,7 +11,6 @@ namespace Behat\Gherkin\Loader; use Behat\Gherkin\Node\FeatureNode; -use Behat\Gherkin\Node\OutlineNode; use Symfony\Component\Yaml\Yaml; /** @@ -58,13 +57,6 @@ public function load($path) $filename = $this->findRelativePath($path); return array_map(function (FeatureNode $feature) use ($filename) { - if ($feature->getBackground() !== null && $feature->getBackground()->hasExamples()) { - foreach ($feature->getScenarios() as $scenario) { - if ($scenario instanceof OutlineNode && !$scenario->hasExamples()){ - $scenario->setExampleTable($feature->getBackground()->getExamples()); - } - } - } return new FeatureNode( $feature->getTitle(), $feature->getDescription(), diff --git a/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml index 5fe2f0b1..622d404b 100644 --- a/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml +++ b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline.yml @@ -1,5 +1,5 @@ feature: - title: Feature with background and example + title: Feature with example in the background language: en line: 1 description: ~ @@ -11,21 +11,18 @@ feature: examples: 6: [login, password] 7: ['', ''] - 8: [unknown_user, ''] + 8: [unknown_user, 'known_pass'] scenarios: - type: outline - title: ~ + title: Scenario outline without example table line: 10 steps: - - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 11 } + - { keyword_type: 'Given', type: 'Given', text: 'I browse to the login page', line: 11 } - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 12 } - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 13 } - arguments: - - - type: table - rows: - 6: [ login, password ] - 7: [ '' , '' ] - 8: [ unknown_user , '' ] \ No newline at end of file + examples: + 6: [login, password] + 7: ['', ''] + 8: [unknown_user, 'known_pass'] \ No newline at end of file diff --git a/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml index 97a23e9d..df7d337b 100644 --- a/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml +++ b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi.yml @@ -1,5 +1,5 @@ feature: - title: Feature with background and example + title: Feature with example in background and multiple scenarios language: en line: 1 description: ~ @@ -11,35 +11,35 @@ feature: examples: 6: [login, password] 7: ['', ''] - 8: [unknown_user, ''] + 8: [unknown_user, known_pass] scenarios: - type: outline - title: scenario1 + title: Scenario outline without example line: 10 steps: - - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 11 } + - { keyword_type: 'Given', type: 'Given', text: 'I have browsed to login page', line: 11 } - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 12 } - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 13 } examples: 6: [ login, password ] 7: [ '' , '' ] - 8: [ unknown_user , '' ] + 8: [ unknown_user , known_pass ] - type: scenario - title: scenario2 + title: A regular scenario line: 15 steps: - - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 16 } + - { keyword_type: 'Given', type: 'Given', text: 'I have browsed to login page', line: 16 } - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with "user"', line: 17 } - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with "password"', line: 18 } - type: outline - title: scenario3 + title: Scenario outline with it's own example table line: 20 steps: - - { keyword_type: 'Given', type: 'Given', text: 'a failing step', line: 21 } + - { keyword_type: 'Given', type: 'Given', text: 'I have browsed to login page', line: 21 } - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 22 } - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 23 } examples: diff --git a/tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature b/tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature index eebd708f..d36fac60 100644 --- a/tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature +++ b/tests/Behat/Gherkin/Fixtures/features/background_with_outline.feature @@ -1,13 +1,13 @@ -Feature: Feature with background and example +Feature: Feature with example in the background Background: Given a passing step Examples: - | login | password | - | | | - | unknown_user | | + | login | password | + | | | + | unknown_user | known_pass | - Scenario Outline: - Given a failing step + Scenario Outline: Scenario outline without example table + Given I browse to the login page When I fill in "login" with "" And I fill in "password" with "" diff --git a/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature b/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature index a027a653..e4704463 100644 --- a/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature +++ b/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi.feature @@ -1,24 +1,24 @@ -Feature: Feature with background and example +Feature: Feature with example in background and multiple scenarios Background: Given a passing step Examples: - | login | password | - | | | - | unknown_user | | + | login | password | + | | | + | unknown_user | known_pass | - Scenario Outline: scenario1 - Given a failing step + Scenario Outline: Scenario outline without example + Given I have browsed to login page When I fill in "login" with "" And I fill in "password" with "" - Scenario: scenario2 - Given a failing step + Scenario: A regular scenario + Given I have browsed to login page When I fill in "login" with "user" And I fill in "password" with "password" - Scenario Outline: scenario3 - Given a failing step + Scenario Outline: Scenario outline with it's own example table + Given I have browsed to login page When I fill in "login" with "" And I fill in "password" with "" Examples: From 52e002c552a1ac84bd65bbca46f31dd30c9dd11b Mon Sep 17 00:00:00 2001 From: Dipak Acharya Date: Fri, 21 Feb 2020 10:32:21 +0545 Subject: [PATCH 7/9] Change if condition to else in Parser.php --- src/Behat/Gherkin/Parser.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Behat/Gherkin/Parser.php b/src/Behat/Gherkin/Parser.php index 1030ef9d..2e943871 100644 --- a/src/Behat/Gherkin/Parser.php +++ b/src/Behat/Gherkin/Parser.php @@ -290,8 +290,7 @@ protected function parseFeature() )); } } - } - if ($background !== null && $background->hasExamples()) {; + } else { foreach ($scenarios as $scenario) { if ($scenario instanceof OutlineNode && !$scenario->hasExamples()) { $scenario->setExampleTable($background->getExamples()); From 03c58c44f699acca224c11213ccf5d4baa115fed Mon Sep 17 00:00:00 2001 From: Dipak Acharya Date: Fri, 21 Feb 2020 11:48:17 +0545 Subject: [PATCH 8/9] Fix method setExampleTable in OutlineNode.php --- src/Behat/Gherkin/Node/OutlineNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Behat/Gherkin/Node/OutlineNode.php b/src/Behat/Gherkin/Node/OutlineNode.php index 782350bc..04720750 100644 --- a/src/Behat/Gherkin/Node/OutlineNode.php +++ b/src/Behat/Gherkin/Node/OutlineNode.php @@ -167,7 +167,7 @@ public function hasExamples() */ public function setExampleTable($table) { - $this->tables = $table; + $this->tables[] = $table; } /** From 41f32d2d963bac716a46c16a6678e17c98e320cd Mon Sep 17 00:00:00 2001 From: Dipak Acharya Date: Fri, 21 Feb 2020 16:58:56 +0545 Subject: [PATCH 9/9] Add support for multiple example tables in background --- src/Behat/Gherkin/Loader/ArrayLoader.php | 31 +++++----- src/Behat/Gherkin/Node/BackgroundNode.php | 28 +++++++-- src/Behat/Gherkin/Parser.php | 17 ++++- ...background_with_outline_multi_examples.yml | 62 +++++++++++++++++++ ...ground_with_outline_multi_examples.feature | 33 ++++++++++ tests/Behat/Gherkin/ParserTest.php | 2 + 6 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi_examples.yml create mode 100644 tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi_examples.feature diff --git a/src/Behat/Gherkin/Loader/ArrayLoader.php b/src/Behat/Gherkin/Loader/ArrayLoader.php index e963dbdc..314c3a07 100644 --- a/src/Behat/Gherkin/Loader/ArrayLoader.php +++ b/src/Behat/Gherkin/Loader/ArrayLoader.php @@ -119,22 +119,25 @@ protected function loadBackgroundHash(array $hash) $steps = $this->loadStepsHash($hash['steps']); - if (isset($hash['examples']['keyword'])) { - $examplesKeyword = $hash['examples']['keyword']; - unset($hash['examples']['keyword']); - } else { - $examplesKeyword = 'Examples'; - } + $examples = array(); if (isset($hash['examples'])) { - $examplesTable = $hash['examples']; - } else { - $examplesTable = array(); - } - if (\count($examplesTable) === 0) { - $examples = null; - } else { - $examples = new ExampleTableNode($examplesTable, $examplesKeyword); + if (isset($hash['examples']['keyword'])) { + $examplesKeyword = $hash['examples']['keyword']; + unset($hash['examples']['keyword']); + } else { + $examplesKeyword = 'Examples'; + } + + $exHash = $hash['examples']; + + if ($this->examplesAreInArray($exHash)) { + $examples = $this->processExamplesArray($exHash, $examplesKeyword, $examples); + } else { + // examples as a single table - we create an array with the only one element + $examples[] = new ExampleTableNode($exHash, $examplesKeyword);; + } } + return new BackgroundNode($hash['title'], $steps, $hash['keyword'], $hash['line'], $examples); } diff --git a/src/Behat/Gherkin/Node/BackgroundNode.php b/src/Behat/Gherkin/Node/BackgroundNode.php index a6b1aa8a..909963f3 100644 --- a/src/Behat/Gherkin/Node/BackgroundNode.php +++ b/src/Behat/Gherkin/Node/BackgroundNode.php @@ -34,7 +34,7 @@ class BackgroundNode implements ScenarioLikeInterface */ private $line; /** - * @var ExampleTableNode + * @var ExampleTableNode[] */ private $exampleTable; @@ -45,7 +45,7 @@ class BackgroundNode implements ScenarioLikeInterface * @param StepNode[] $steps * @param string $keyword * @param integer $line - * @param null|ExampleTableNode $exampleTable + * @param null|ExampleTableNode|ExampleTableNode[] $exampleTable */ public function __construct($title, array $steps, $keyword, $line, $exampleTable=null) { @@ -53,7 +53,15 @@ public function __construct($title, array $steps, $keyword, $line, $exampleTable $this->steps = $steps; $this->keyword = $keyword; $this->line = $line; - $this->exampleTable = $exampleTable; + if ($exampleTable === null) { + $this->exampleTable = null; + }else if (is_array($exampleTable)) { + if (!empty($exampleTable)) { + $this->exampleTable = $exampleTable; + } + } else { + $this->$exampleTable = array($exampleTable); + } } /** @@ -123,16 +131,26 @@ public function getLine() */ public function hasExamples() { - return $this->exampleTable !== null; + return $this->exampleTable !== null && count($this->exampleTable) > 0; } /** * Returns if background has ExampleTable * - * @return null|ExampleTableNode + * @return null|ExampleTableNode[] */ public function getExamples() { return $this->exampleTable; } + + /** + * Add example Table to the background Node + * + * @param ExampleTableNode + */ + public function addExample($table) + { + $this->exampleTable[] = $table; + } } diff --git a/src/Behat/Gherkin/Parser.php b/src/Behat/Gherkin/Parser.php index 2e943871..4046ad77 100644 --- a/src/Behat/Gherkin/Parser.php +++ b/src/Behat/Gherkin/Parser.php @@ -260,6 +260,11 @@ protected function parseFeature() continue; } + if ($node instanceof ExampleTableNode) { + $background->addExample($node); + continue; + } + if ($background instanceof BackgroundNode && $node instanceof BackgroundNode) { throw new ParserException(sprintf( 'Each Feature could have only one Background, but found multiple on lines %d and %d%s', @@ -293,7 +298,13 @@ protected function parseFeature() } else { foreach ($scenarios as $scenario) { if ($scenario instanceof OutlineNode && !$scenario->hasExamples()) { - $scenario->setExampleTable($background->getExamples()); + if (is_array($background->getExamples())) { + foreach ($background->getExamples() as $ex) { + $scenario->setExampleTable($ex); + } + } else { + $scenario->setExampleTable($background->getExamples()); + } } } } @@ -325,7 +336,7 @@ protected function parseBackground() $title = trim($token['value']); $keyword = $token['keyword']; $line = $token['line']; - $example = null; + $example = []; if (count($this->popTags())) { throw new ParserException(sprintf( @@ -342,7 +353,7 @@ protected function parseBackground() $node = $this->parseExpression(); if ($node instanceof ExampleTableNode) { - $example = $node; + $example[] = $node; continue; } diff --git a/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi_examples.yml b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi_examples.yml new file mode 100644 index 00000000..cfec81e4 --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/etalons/background_with_outline_multi_examples.yml @@ -0,0 +1,62 @@ +feature: + title: Feature with multiple example in background and multiple scenarios + language: en + line: 1 + description: ~ + + background: + line: 3 + steps: + - { keyword_type: Given, type: Given, text: a passing step, line: 4 } + examples: + - 6: [login, password] + 7: ['', ''] + 8: [unknown_user, known_pass] + + - + tags: [specialChars] + table: + 12: [login, password] + 13: [$hello##, normalPass] + 14: [normalUser, p@$$word] + + scenarios: + - + type: outline + title: Scenario outline without example + line: 16 + steps: + - { keyword_type: 'Given', type: 'Given', text: 'I have browsed to login page', line: 17 } + - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 18 } + - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 19 } + examples: + - 6: [login, password] + 7: ['', ''] + 8: [unknown_user, known_pass] + + - + tags: [specialChars] + table: + 12: [login, password] + 13: [$hello##, normalPass] + 14: [normalUser, p@$$word] + + - type: scenario + title: A regular scenario + line: 21 + steps: + - { keyword_type: 'Given', type: 'Given', text: 'I have browsed to login page', line: 22 } + - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with "user"', line: 23 } + - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with "password"', line: 24 } + + - type: outline + title: Scenario outline with it's own example table + line: 26 + steps: + - { keyword_type: 'Given', type: 'Given', text: 'I have browsed to login page', line: 27 } + - { keyword_type: 'When', type: 'When', text: 'I fill in "login" with ""', line: 28 } + - { keyword_type: 'When', type: 'And', text: 'I fill in "password" with ""', line: 29 } + examples: + 31: [ login, password ] + 32: [ hello, world ] + 33: [ user , pass ] \ No newline at end of file diff --git a/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi_examples.feature b/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi_examples.feature new file mode 100644 index 00000000..4fa01e40 --- /dev/null +++ b/tests/Behat/Gherkin/Fixtures/features/background_with_outline_multi_examples.feature @@ -0,0 +1,33 @@ +Feature: Feature with multiple example in background and multiple scenarios + + Background: + Given a passing step + Examples: + | login | password | + | | | + | unknown_user | known_pass | + + @specialChars + Examples: + | login | password | + | $hello## | normalPass | + | normalUser | p@$$word | + + Scenario Outline: Scenario outline without example + Given I have browsed to login page + When I fill in "login" with "" + And I fill in "password" with "" + + Scenario: A regular scenario + Given I have browsed to login page + When I fill in "login" with "user" + And I fill in "password" with "password" + + Scenario Outline: Scenario outline with it's own example table + Given I have browsed to login page + When I fill in "login" with "" + And I fill in "password" with "" + Examples: + | login | password | + | hello | world | + | user | pass | \ No newline at end of file diff --git a/tests/Behat/Gherkin/ParserTest.php b/tests/Behat/Gherkin/ParserTest.php index 75ea45a2..2a772860 100644 --- a/tests/Behat/Gherkin/ParserTest.php +++ b/tests/Behat/Gherkin/ParserTest.php @@ -36,6 +36,8 @@ public function testParser($fixtureName) $etalon = $this->parseEtalon($fixtureName . '.yml'); $features = $this->parseFixture($fixtureName . '.feature'); +// print_r($etalon->getBackground()); +// print_r($features[0]->getBackground()); $this->assertInternalType('array', $features); $this->assertEquals(1, count($features)); $fixture = $features[0];