Skip to content

Commit 7936a3b

Browse files
committed
Add a new test covering the correct parsing of translated keywords
This test is similar to the CachedArrayKeywords test but relies on the new Dialect API and covers translations defined in the gherkin-languages.json file.
1 parent ea99f21 commit 7936a3b

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed

src/Keywords/DialectKeywords.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Behat Gherkin Parser.
5+
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
namespace Behat\Gherkin\Keywords;
12+
13+
use Behat\Gherkin\Dialect\DialectProviderInterface;
14+
use Behat\Gherkin\Dialect\GherkinDialect;
15+
16+
/**
17+
* An adapter around a DialectProviderInterface to be able to use it with the KeywordsDumper.
18+
*
19+
* TODO add support for dumping an example feature for a dialect directly instead.
20+
*
21+
* @internal
22+
*/
23+
final class DialectKeywords implements KeywordsInterface
24+
{
25+
private GherkinDialect $currentDialect;
26+
27+
public function __construct(
28+
private readonly DialectProviderInterface $dialectProvider,
29+
) {
30+
$this->currentDialect = $this->dialectProvider->getDefaultDialect();
31+
}
32+
33+
public function setLanguage($language): void
34+
{
35+
if ($language === '') {
36+
throw new \InvalidArgumentException('Language cannot be empty');
37+
}
38+
39+
$this->currentDialect = $this->dialectProvider->getDialect($language);
40+
}
41+
42+
public function getFeatureKeywords(): string
43+
{
44+
return $this->getKeywordString($this->currentDialect->getFeatureKeywords());
45+
}
46+
47+
public function getBackgroundKeywords(): string
48+
{
49+
return $this->getKeywordString($this->currentDialect->getBackgroundKeywords());
50+
}
51+
52+
public function getScenarioKeywords(): string
53+
{
54+
return $this->getKeywordString($this->currentDialect->getScenarioKeywords());
55+
}
56+
57+
public function getOutlineKeywords(): string
58+
{
59+
return $this->getKeywordString($this->currentDialect->getScenarioOutlineKeywords());
60+
}
61+
62+
public function getExamplesKeywords(): string
63+
{
64+
return $this->getKeywordString($this->currentDialect->getExamplesKeywords());
65+
}
66+
67+
public function getGivenKeywords(): string
68+
{
69+
return $this->getStepKeywordString($this->currentDialect->getGivenKeywords());
70+
}
71+
72+
public function getWhenKeywords(): string
73+
{
74+
return $this->getStepKeywordString($this->currentDialect->getWhenKeywords());
75+
}
76+
77+
public function getThenKeywords(): string
78+
{
79+
return $this->getStepKeywordString($this->currentDialect->getThenKeywords());
80+
}
81+
82+
public function getAndKeywords(): string
83+
{
84+
return $this->getStepKeywordString($this->currentDialect->getAndKeywords());
85+
}
86+
87+
public function getButKeywords(): string
88+
{
89+
return $this->getStepKeywordString($this->currentDialect->getButKeywords());
90+
}
91+
92+
public function getStepKeywords(): string
93+
{
94+
return $this->getStepKeywordString($this->currentDialect->getStepKeywords());
95+
}
96+
97+
/**
98+
* @param list<string> $keywords
99+
*/
100+
private function getKeywordString(array $keywords): string
101+
{
102+
return implode('|', $keywords);
103+
}
104+
105+
/**
106+
* @param list<string> $keywords
107+
*/
108+
private function getStepKeywordString(array $keywords): string
109+
{
110+
$legacyKeywords = [];
111+
foreach ($keywords as $keyword) {
112+
if (str_ends_with($keyword, ' ')) {
113+
$legacyKeywords[] = substr($keyword, 0, -1);
114+
} else {
115+
$legacyKeywords[] = $keyword . '<';
116+
}
117+
}
118+
119+
return implode('|', $legacyKeywords);
120+
}
121+
}

tests/TranslationTest.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Behat Gherkin Parser.
5+
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
namespace Tests\Behat\Gherkin;
12+
13+
use Behat\Gherkin\Dialect\CucumberDialectProvider;
14+
use Behat\Gherkin\Dialect\GherkinDialect;
15+
use Behat\Gherkin\Keywords\DialectKeywords;
16+
use Behat\Gherkin\Keywords\KeywordsDumper;
17+
use Behat\Gherkin\Lexer;
18+
use Behat\Gherkin\Node\BackgroundNode;
19+
use Behat\Gherkin\Node\ExampleTableNode;
20+
use Behat\Gherkin\Node\FeatureNode;
21+
use Behat\Gherkin\Node\OutlineNode;
22+
use Behat\Gherkin\Node\ScenarioNode;
23+
use Behat\Gherkin\Node\StepNode;
24+
use Behat\Gherkin\Parser;
25+
use PHPUnit\Framework\Attributes\DataProvider;
26+
use PHPUnit\Framework\TestCase;
27+
28+
/**
29+
* @phpstan-import-type TDialectData from GherkinDialect
30+
*/
31+
class TranslationTest extends TestCase
32+
{
33+
/**
34+
* @param list<string> $keywords
35+
*
36+
* @return list<StepNode>
37+
*/
38+
private static function getSteps(array $keywords, string $text, int &$line, ?string $keywordType): array
39+
{
40+
$steps = [];
41+
foreach ($keywords as $keyword) {
42+
if ($keyword === '* ') {
43+
continue;
44+
}
45+
46+
$steps[] = new StepNode(trim($keyword), $text, [], $line++, $keywordType);
47+
}
48+
49+
return $steps;
50+
}
51+
52+
/**
53+
* @return iterable<string, array{language: string, num: int, etalon: FeatureNode, source: string}>
54+
*/
55+
public static function translationTestDataProvider(): iterable
56+
{
57+
$dumper = new KeywordsDumper(new DialectKeywords(new CucumberDialectProvider()));
58+
/** @var non-empty-array<non-empty-string, TDialectData> $keywordsArray */
59+
$keywordsArray = json_decode(Filesystem::readFile(__DIR__ . '/../resources/gherkin-languages.json'), true, flags: \JSON_THROW_ON_ERROR);
60+
61+
foreach ($keywordsArray as $lang => $i18nKeywords) {
62+
$features = [];
63+
foreach ($i18nKeywords['feature'] as $transNum => $featureKeyword) {
64+
$line = 1;
65+
if ($lang !== 'en') {
66+
$line = 2;
67+
}
68+
69+
$featureLine = $line;
70+
$line += 5;
71+
72+
$keywords = $i18nKeywords['background'];
73+
$backgroundLine = $line;
74+
++$line;
75+
$background = new BackgroundNode(null, array_merge(
76+
self::getSteps($i18nKeywords['given'], 'there is agent A', $line, 'Given'),
77+
self::getSteps($i18nKeywords['and'], 'there is agent B', $line, 'Given')
78+
), $keywords[0], $backgroundLine);
79+
80+
++$line;
81+
82+
$scenarios = [];
83+
84+
foreach ($i18nKeywords['scenario'] as $scenarioKeyword) {
85+
$scenarioLine = $line;
86+
++$line;
87+
88+
$steps = array_merge(
89+
self::getSteps($i18nKeywords['given'], 'there is agent J', $line, 'Given'),
90+
self::getSteps($i18nKeywords['and'], 'there is agent K', $line, 'Given'),
91+
self::getSteps($i18nKeywords['when'], 'I erase agent K\'s memory', $line, 'When'),
92+
self::getSteps($i18nKeywords['then'], 'there should be agent J', $line, 'Then'),
93+
self::getSteps($i18nKeywords['but'], 'there should not be agent K', $line, 'Then')
94+
);
95+
96+
$scenarios[] = new ScenarioNode('Erasing agent memory', [], $steps, $scenarioKeyword, $scenarioLine);
97+
++$line;
98+
}
99+
foreach ($i18nKeywords['scenarioOutline'] as $outlineKeyword) {
100+
$outlineLine = $line;
101+
++$line;
102+
103+
$steps = array_merge(
104+
self::getSteps($i18nKeywords['given'], 'there is agent <agent1>', $line, 'Given'),
105+
self::getSteps($i18nKeywords['and'], 'there is agent <agent2>', $line, 'Given'),
106+
self::getSteps($i18nKeywords['when'], 'I erase agent <agent2>\'s memory', $line, 'When'),
107+
self::getSteps($i18nKeywords['then'], 'there should be agent <agent1>', $line, 'Then'),
108+
self::getSteps($i18nKeywords['but'], 'there should not be agent <agent2>', $line, 'Then')
109+
);
110+
++$line;
111+
112+
$keywords = $i18nKeywords['examples'];
113+
$table = new ExampleTableNode([
114+
++$line => ['agent1', 'agent2'],
115+
++$line => ['D', 'M'],
116+
], $keywords[0]);
117+
++$line;
118+
119+
$scenarios[] = new OutlineNode('Erasing other agents\' memory', [], $steps, $table, $outlineKeyword, $outlineLine);
120+
++$line;
121+
}
122+
123+
$features[] = new FeatureNode(
124+
'Internal operations',
125+
<<<'DESC'
126+
In order to stay secret
127+
As a secret organization
128+
We need to be able to erase past agents' memory
129+
DESC,
130+
[],
131+
$background,
132+
$scenarios,
133+
$featureKeyword,
134+
$lang,
135+
__DIR__ . DIRECTORY_SEPARATOR . $lang . '_' . ($transNum + 1) . '.feature',
136+
$featureLine
137+
);
138+
}
139+
140+
$dumped = $dumper->dump($lang, false, true);
141+
142+
foreach ($dumped as $num => $dumpedFeature) {
143+
yield $lang . '_' . $num => [
144+
'language' => $lang,
145+
'num' => $num,
146+
'etalon' => $features[$num],
147+
'source' => $dumpedFeature,
148+
];
149+
}
150+
}
151+
}
152+
153+
/**
154+
* @param string $language language name
155+
* @param int $num Fixture index for that language
156+
* @param FeatureNode $etalon etalon features (to test against)
157+
* @param string $source gherkin source
158+
*/
159+
#[DataProvider('translationTestDataProvider')]
160+
public function testTranslation(string $language, int $num, FeatureNode $etalon, string $source): void
161+
{
162+
$lexer = new Lexer(new CucumberDialectProvider());
163+
$parser = new Parser($lexer);
164+
165+
try {
166+
$parsed = $parser->parse($source, __DIR__ . DIRECTORY_SEPARATOR . $language . '_' . ($num + 1) . '.feature');
167+
} catch (\Throwable $e) {
168+
throw new \RuntimeException($e->getMessage() . ":\n" . $source, 0, $e);
169+
}
170+
171+
$this->assertEquals($etalon, $parsed, $source);
172+
}
173+
}

0 commit comments

Comments
 (0)