From bd6c2a3890fc7351fa13b5a9d35dde07e8302a4d Mon Sep 17 00:00:00 2001 From: Benoit Jouhaud Date: Mon, 5 Jun 2023 17:01:43 +0200 Subject: [PATCH] Add Behat tests to assert the integration with Behat works and to demonstrate how to use the lib --- behat.yaml.dist | 5 + composer.json | 2 + docs/behat.md | 143 ++++++++++++++++++ docs/evaluator.md | 5 +- docs/evaluator_builder.md | 32 ++++ docs/getting_started.md | 10 +- features/evaluate.feature | 13 ++ src/Evaluator.php | 20 +++ src/EvaluatorBuilder.php | 84 ++++++++++ src/Foundry/FactoryClassFactory.php | 10 +- tests/Behat/Context/EvaluatorContext.php | 48 ++++++ tests/Integration/EvaluatorTest.php | 48 ++++++ tests/Resources/ExpressionLanguageFactory.php | 15 +- tests/Unit/Builder/EvaluatorBuilderTest.php | 60 ++++++++ 14 files changed, 476 insertions(+), 19 deletions(-) create mode 100644 behat.yaml.dist create mode 100644 docs/behat.md create mode 100644 docs/evaluator_builder.md create mode 100644 features/evaluate.feature create mode 100644 src/EvaluatorBuilder.php create mode 100644 tests/Behat/Context/EvaluatorContext.php create mode 100644 tests/Integration/EvaluatorTest.php create mode 100644 tests/Unit/Builder/EvaluatorBuilderTest.php diff --git a/behat.yaml.dist b/behat.yaml.dist new file mode 100644 index 0000000..33fec95 --- /dev/null +++ b/behat.yaml.dist @@ -0,0 +1,5 @@ +default: + suites: + default: + contexts: + - 'Presta\BehatParser\Tests\Behat\Context\EvaluatorContext' diff --git a/composer.json b/composer.json index 06de8e3..f2d230b 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ }, "require-dev": { "ext-pdo_sqlite": "*", + "behat/behat": "^3.13", "dama/doctrine-test-bundle": "^7.2", "doctrine/doctrine-bundle": "^2.9", "doctrine/orm": "^2.15", @@ -42,6 +43,7 @@ "autoload-dev": { "psr-4": { "Presta\\BehatParser\\Tests\\Application\\": "tests/Application/src/", + "Presta\\BehatParser\\Tests\\Behat\\": "tests/Behat/", "Presta\\BehatParser\\Tests\\Extension\\": "tests/Extension/", "Presta\\BehatParser\\Tests\\Integration\\": "tests/Integration/", "Presta\\BehatParser\\Tests\\Resources\\": "tests/Resources/", diff --git a/docs/behat.md b/docs/behat.md new file mode 100644 index 0000000..d7b116f --- /dev/null +++ b/docs/behat.md @@ -0,0 +1,143 @@ +# Behat + +## Usage + +When creating a [Behat step][1], you may get inputs as parameters. + +Sometimes you'll need to evaluate these parameters to transform the placeholder(s) they may contain. + +You can do that very easily by using the [Evaluator][4] provided by this library. + +#### Using the [Friends of Behat's SymfonyExtension][3] + +This extension allows you to take advantage of the dependency injection. + +You should be able to inject the [Evaluator][4] in your custom [Behat Context][2]'s constructor and invoke it out of the box. + +```php +evaluate)($text); + + // ... + } +} +``` + +The [Evaluator][4]'s configuration can be customized through dependency injection: + +```yaml +services: + _instanceof: + Adapter: + tags: ['app.presta_behat_adapter'] + + App\Adapter\CustomAdapter: + tags: ['app.presta_behat_adapter'] + + Presta\BehatParser\Evaluator: + arguments: + $adapters: !tagged_iterator 'app.presta_behat_adapter' + + Presta\BehatParser\ExpressionLanguage\ExpressionLanguage: + arguments: + $culture: 'fr_FR' + + Presta\BehatParser\Foundry\FactoryClassFactory: + arguments: + $namespace: 'Your\Custom\Namespace' +``` + +#### Using without dependency injection + +There is also a way to use the [Evaluator][4] easily without dependency injection. + +The class exposes 2 static methods that use a default configuration which can be easily extended. + +```php + Evaluator::evaluateMany($attributes), + $data->getHash(), + ); + + // ... + } +} +``` + +The static methods create an [Evaluator][4] thank's to the [EvaluatorBuilder][5] which allows to customize the configuration. + +```php +registerAdapter($customAdapter); + +// the default is "en_US" +$builder->withCulture('fr_FR'); + +// the default is "App\\Foundry\\Factory" +$builder->withFactoryNamespace('Your\Custom\Namespace'); + +// the default is created using InflectorFactory::create()->build() +$builder->withInflector($inflector); + +$evaluator = $builder->build(); +``` + +--- + +You may return to the [README][2] or read the [evaluator][3] or the [adapters][4] guides. + +[1]: ../src/Evaluator.php +[2]: ../README.md +[3]: ./evaluator.md +[4]: ./adapters/ diff --git a/docs/getting_started.md b/docs/getting_started.md index 1fa4bd9..f29c1b5 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -106,7 +106,7 @@ All you need is to instantiate the [Presta\BehatParser\Evaluator][6] with a coll > Simple as that! -[More details here][10]. +[More details here][11]. ## How it works? @@ -123,7 +123,7 @@ In the end, the value will be returned after having been transformed (or not) by --- -You may return to the [README][9] or read the [evaluator][10] or the [adapters][11] guides. +You may return to the [README][9] or read the [behat][10], the [evaluator][11], the [evaluator builder][12] or the [adapters][13] guides. [1]: https://behat.org/en/latest/index.html [2]: https://behat.org/en/latest/quick_start.html#installation @@ -134,5 +134,7 @@ You may return to the [README][9] or read the [evaluator][10] or the [adapters][ [7]: ../src/Adapter/AdapterInterface.php [8]: https://symfony.com/doc/current/components/expression_language.html [9]: ../README.md -[10]: ./evaluator.md -[11]: ./adapters/ +[10]: ./behat.md +[11]: ./evaluator.md +[12]: ./evaluator_builder.md +[13]: ./adapters/ diff --git a/features/evaluate.feature b/features/evaluate.feature new file mode 100644 index 0000000..d0c2b99 --- /dev/null +++ b/features/evaluate.feature @@ -0,0 +1,13 @@ +Feature: + As a test user + I want to evaluate input string + So that I can transform them in more complex objects + + Scenario: + Given the local storage contains: + | | + | | + When I format the datetime entries of the local storage with "d/m/Y" + Then the local storage should contain: + | 01/01/2023 | + | | diff --git a/src/Evaluator.php b/src/Evaluator.php index a96434c..c53a9cb 100644 --- a/src/Evaluator.php +++ b/src/Evaluator.php @@ -23,4 +23,24 @@ public function __invoke(mixed $value): mixed return $value; } + + public static function evaluate(mixed $value, EvaluatorBuilder $builder = new EvaluatorBuilder()): mixed + { + $evaluate = $builder->build(); + + return $evaluate($value); + } + + /** + * @param list $values + * + * @return list + */ + public static function evaluateMany(array $values, EvaluatorBuilder $builder = new EvaluatorBuilder()): array + { + return array_map( + static fn (mixed $value): mixed => self::evaluate($value, $builder), + $values, + ); + } } diff --git a/src/EvaluatorBuilder.php b/src/EvaluatorBuilder.php new file mode 100644 index 0000000..8b2e245 --- /dev/null +++ b/src/EvaluatorBuilder.php @@ -0,0 +1,84 @@ + + */ + private array $adapters = []; + + private string $culture = 'en_US'; + + private string $factoryNamespace = 'App\\Foundry\\Factory'; + + private Inflector $inflector; + + public function __construct() + { + $this->inflector = InflectorFactory::create()->build(); + } + + public function registerAdapter(AdapterInterface $adapter): self + { + $this->adapters[] = $adapter; + + return $this; + } + + public function withCulture(string $culture): self + { + $this->culture = $culture; + + return $this; + } + + public function withFactoryNamespace(string $factoryNamespace): self + { + $this->factoryNamespace = $factoryNamespace; + + return $this; + } + + public function withInflector(Inflector $inflector): self + { + $this->inflector = $inflector; + + return $this; + } + + public function build(): Evaluator + { + $expressionLanguage = new ExpressionLanguage( + new FactoryClassFactory($this->factoryNamespace, $this->inflector), + $this->culture, + ); + + return new Evaluator( + [ + new ConstantAdapter($expressionLanguage), + new DateTimeAdapter($expressionLanguage), + new FactoryAdapter($expressionLanguage), + new NthAdapter(), + new ScalarAdapter(), + new UnescapeAdapter(), + ...$this->adapters, + ], + ); + } +} diff --git a/src/Foundry/FactoryClassFactory.php b/src/Foundry/FactoryClassFactory.php index 7bc30e5..4c7d567 100644 --- a/src/Foundry/FactoryClassFactory.php +++ b/src/Foundry/FactoryClassFactory.php @@ -5,18 +5,12 @@ namespace Presta\BehatParser\Foundry; use Doctrine\Inflector\Inflector; -use Doctrine\Inflector\InflectorFactory; use Zenstruck\Foundry\ModelFactory; final class FactoryClassFactory { - private readonly Inflector $inflector; - - public function __construct( - private readonly string $namespace = 'App\\Foundry\\Factory\\', - Inflector $inflector = null, - ) { - $this->inflector = $inflector ?? InflectorFactory::create()->build(); + public function __construct(private readonly string $namespace, private readonly Inflector $inflector) + { } public function fromName(string $name): string diff --git a/tests/Behat/Context/EvaluatorContext.php b/tests/Behat/Context/EvaluatorContext.php new file mode 100644 index 0000000..e4f052b --- /dev/null +++ b/tests/Behat/Context/EvaluatorContext.php @@ -0,0 +1,48 @@ + + */ + private array $localStorage = []; + + #[Given('the local storage contains:')] + public function set(TableNode $table): void + { + $this->localStorage = Evaluator::evaluateMany(array_keys($table->getRowsHash())); + } + + #[When('I format the datetime entries of the local storage with :format')] + public function formatDateTimes(string $format): void + { + foreach ($this->localStorage as $key => $value) { + if (!$value instanceof \DateTimeInterface) { + continue; + } + + $this->localStorage[$key] = $value->format($format); + } + } + + #[Then('the local storage should contain:')] + public function assertLocalStorageContains(TableNode $table): void + { + TestCase::assertSame( + Evaluator::evaluateMany(array_keys($table->getRowsHash())), + $this->localStorage, + ); + } +} diff --git a/tests/Integration/EvaluatorTest.php b/tests/Integration/EvaluatorTest.php new file mode 100644 index 0000000..a3eea8f --- /dev/null +++ b/tests/Integration/EvaluatorTest.php @@ -0,0 +1,48 @@ + 'John', 'lastname' => 'Doe']); + + $builder = new EvaluatorBuilder(); + $builder->withFactoryNamespace('Presta\\BehatParser\\Tests\\Application\\Foundry\\Factory\\'); + + self::assertSame([$expected], Evaluator::evaluateMany([$value], $builder)); + } + + /** + * @return iterable + */ + public function values(): iterable + { + yield 'a constant expression should not be necessary' => [ + ARRAY_FILTER_USE_KEY, + '', + ]; + yield 'a datetime expression should not be necessary' => ['2023-01-01', '']; + yield 'a factory expression should not be necessary' => [1, '']; + yield 'a nth expression should not be necessary' => [5, '5th']; + yield 'a scalar expression should not be necessary' => [null, 'null']; + yield 'an escaped expression should not be necessary' => [ + 'The double quotes around "foo" should be unescaped', + 'The double quotes around \"foo\" should be unescaped', + ]; + } +} diff --git a/tests/Resources/ExpressionLanguageFactory.php b/tests/Resources/ExpressionLanguageFactory.php index ae2f9da..7c87eba 100644 --- a/tests/Resources/ExpressionLanguageFactory.php +++ b/tests/Resources/ExpressionLanguageFactory.php @@ -4,15 +4,20 @@ namespace Presta\BehatParser\Tests\Resources; +use Doctrine\Inflector\InflectorFactory; use Presta\BehatParser\ExpressionLanguage\ExpressionLanguage; use Presta\BehatParser\Foundry\FactoryClassFactory; final class ExpressionLanguageFactory { - public static function create( - string $namespace = 'Presta\\BehatParser\\Tests\\Application\\Foundry\\Factory\\', - string $culture = 'fr_FR', - ): ExpressionLanguage { - return new ExpressionLanguage(new FactoryClassFactory($namespace), $culture); + public static function create(): ExpressionLanguage + { + return new ExpressionLanguage( + new FactoryClassFactory( + 'Presta\\BehatParser\\Tests\\Application\\Foundry\\Factory\\', + InflectorFactory::create()->build(), + ), + 'fr_FR', + ); } } diff --git a/tests/Unit/Builder/EvaluatorBuilderTest.php b/tests/Unit/Builder/EvaluatorBuilderTest.php new file mode 100644 index 0000000..23902ec --- /dev/null +++ b/tests/Unit/Builder/EvaluatorBuilderTest.php @@ -0,0 +1,60 @@ +build(); + + $adapter = new class ($culture, $factoryNamespace, $inflector) implements AdapterInterface + { + public function __construct( + private readonly string $culture, + private readonly string $factoryNamespace, + private readonly Inflector $inflector, + ) { + } + + /** + * @return array{culture: string, factory_namespace: string, inflector: Inflector} + */ + public function __invoke(mixed $value): array + { + return [ + 'culture' => $this->culture, + 'factory_namespace' => $this->factoryNamespace, + 'inflector' => $this->inflector, + ]; + } + }; + + $builder = new EvaluatorBuilder(); + $builder->withCulture($culture); + $builder->withFactoryNamespace($factoryNamespace); + $builder->withInflector($inflector); + $builder->registerAdapter($adapter); + + $evaluate = $builder->build(); + + self::assertSame( + [ + 'culture' => $culture, + 'factory_namespace' => $factoryNamespace, + 'inflector' => $inflector, + ], + $evaluate('foo'), + ); + } +}