diff --git a/.gitignore b/.gitignore index 8562fb1c7..3566ccd6d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ composer.lock # IDE Files .idea +.vscode diff --git a/contribution/documentation/generator/README.md b/contribution/documentation/generator/README.md new file mode 100644 index 000000000..d4b160c98 --- /dev/null +++ b/contribution/documentation/generator/README.md @@ -0,0 +1,126 @@ +# PHP code generator for Exercism PHP track exercises + +- [Introduction](#introduction) +- [Architecture](#architecture) +- [Contribution](#contribution) + +## Introduction + +This is a simple code generator for practice exercises in the PHP track based on the [Exercism common problem specifications][exercism-problem-specifications]. + +> Please read and think about the exercise instructions! +> Many problems require additional test failure messages and useful information to help students solve the exercise. +> Generating code is not "being done"! + +The majority of problems in problem specifications are *function oriented*. +That means, all input goes into a single function call and no state of an object changes expected results. +So the generator generates *function oriented* code. +A fresh instance is created with no constructor arguments in `setUp()` for each test. +The tests invoke methods with the input and compare actual results with expectations. + +If the problem you generate code for requires object orientation, adjust the tests manually (e.g. replace `$this->subject->`). + +The next decision to make is: How much freedom of implementation shall students have? +For practice exercises we usually give maximum freedom of implementation. +This freedom must be designed into the student facing interface. +But there are good reasons to limit the freadom of choice. +Has PHP an idiomatic way to solve such a class of problems? +Like using an `enum` for certain result types. +Then you should design that into the interface. + +Mentoring is done to guide students towards "recommended" implementations. +Some exercises do require stricter boundaries, like "Do not use language provided functions to solve this". +Such restrictions need to be implemented manually. + +Another decision required is the amount of prepared student interface code you want. +The generator only produces the bare minimum, an empty class with a throwing constructor. +This is a pragmatic choice, it is easy to implement. 🙂 +Adding predefined methods lowers the difficulty of the exercise. +Testing for type declarations being set and / or testing for type safety raises the difficulty. +So it is your choice, if you want to do so or not. + +Now you have made the basic decisions. Time to use the generator! + +- Follow the track README to install track tooling (`configlet`) +- Run from track root: + + ```shell + bin/configlet create --practice-exercise '' + composer -d contribution/generator install + contribution/generator/bin/console app:create-tests '' + composer lint:fix + vendor/bin/phpunit exercises/practice/''/*Test.php + ``` + +- Run `git status` to see all the generated files. + These are yours now. +- Adjust the code as required. +- Add more information to `.meta/*.append.md`. + The generated files `.meta/*.md` are kept in sync with problem specifications. +- Mark tests not implemented with `include = false` in `tests.toml`. +- Open a PR to get feedback on your exercise **early**. + +## Architecture + +The `Exercise` interface supports both types of exercises, `PracticeExercise` and the planned `ConceptExercise`. +For both exercise types, the test file and the students file are generated from `configlet` generated directory structures and files. +It is planned to use a `canonical-data.json` similar to the problem specification for the concept exercises, too. + +Between the symfony command(s) and the actual test / students file generation is the test boundary `ItemFactory`. +By this, the integration testing (not yet implemented) needs to test only, that the expected files are generated with some sample text. +The actual details of test / student file generation can be tested without mocking the filesystem or booting the symfony kernel. + +![Top level sequence](sequence-diagram-top-level.svg) + +`configlet` is used by `PracticeExercise`, wrapped in an own class. +This is because the [cached problem specifications][exercism-problem-specifications] is required and `configlet` knows best where to find that. +The actual path differs depending on the underlying operating system, and we shouldn't copy that from `configlet` sources. + +We started with an implementation walking through the raw JSON data and used `nikic/php-parser` to produce PHP code from that. +This hided the underlying data structures used to construct the varying canonical data sets in the problem specification. +So we made them explicit: + +- `Item`s are all data structures to construct the varying canonical data sets (interface). +- `ItemFactory` turning raw data into the matching `Item` (object). +- `Unknown` represents data structures that do not follow a known schema (object). +- `TestCase`s represent tests with input and expected outcome of the students code (object). +- `Group`s are sets of `Item`s, that share a common theme and may have some title, explanation and folding section markers (object). +- `InnerGroup`s are the pure lists of `Item`s to convert to tests (object). +- `CanonicalData` is the outermost group with the expected extra data for an exercise and an `InnerGroup` with `Item`s (object). + +![Second level sequence](sequence-diagram-second-level.svg) + +This allows to represent the JSON data as a tree of groups and test cases. +Every data structure object examines the raw data and produces an instance only, if it can handle that structure. +The tree is built using `ItemFactory`, which starts with the most specific data structure `CanonicalData` and goes out towards `Unknown` until an instance is made from the raw data given. +Having all unknown data catched into an `Unknown` allows rendering any structure found into the output files. + +Such a tree structure looks like doing the code production with the "visitor" pattern. +But that pattern comes with a huge cost of complexity and should only be used, if the tree will have multiple visitors. +So the simple way of iterating directly over the tree to produce the output from the data objects is prefered. + +Also it is tempting to implement a transformation logic "data -> abstract syntax tree", which we had with `nikic/php-parser`. +But this also adds a huge amount of complexity for no real win - the AST would only be used for pre-defined code output. +Predefined code snippets - code templates with placeholders for the data given - are much simpler and allow direct control of formatting. +Templates also don't require special knowledge about the AST library used. +Simply edit the template using IDEs and other tools like any other file. +For our purpose of producing code, that **should be** verified and corrected by humans, templates are the best solution. + +[exercism-problem-specifications]: https://github.com/exercism/problem-specifications/ + +## Contribution + +To get ready to contribute to the generator, run these commands: + +```shell +cd contribution/generator +composer install +vendor/bin/phpunit +``` + +- Add unit tests for changes to `TrackData` classes +- Document changes in [Introduction](#introduction) and [Architecture](#architecture) + +### Architecture Diagrams + +Use [PlantUML](http:://plantuml.com/) to add / modify diagrams. diff --git a/contribution/documentation/generator/sequence-diagram-second-level.plantuml b/contribution/documentation/generator/sequence-diagram-second-level.plantuml new file mode 100644 index 000000000..9e6779803 --- /dev/null +++ b/contribution/documentation/generator/sequence-diagram-second-level.plantuml @@ -0,0 +1,18 @@ +@startuml +participant Exercise as exercise +participant CanonicalData as data +participant InnerGroup as group +participant Item as item + +exercise -> data : ItemFactory::from($data) +data -> group : ItemFactory::from($data) +group -> group : ItemFactory::from($data) +group -> item : ItemFactory::from($data) + +exercise -> data : renderTestCode() +data -> group : renderTestCode() +group -> group : renderTestCode() +group -> item : renderTestCode() + +exercise -> data : renderSolutionCode() +@enduml \ No newline at end of file diff --git a/contribution/documentation/generator/sequence-diagram-second-level.svg b/contribution/documentation/generator/sequence-diagram-second-level.svg new file mode 100644 index 000000000..3b351cd6f --- /dev/null +++ b/contribution/documentation/generator/sequence-diagram-second-level.svg @@ -0,0 +1 @@ +ExerciseExerciseCanonicalDataCanonicalDataInnerGroupInnerGroupItemItemItemFactory::from($data)ItemFactory::from($data)ItemFactory::from($data)ItemFactory::from($data)renderTestCode()renderTestCode()renderTestCode()renderTestCode()renderSolutionCode() \ No newline at end of file diff --git a/contribution/documentation/generator/sequence-diagram-top-level.plantuml b/contribution/documentation/generator/sequence-diagram-top-level.plantuml new file mode 100644 index 000000000..150f3aa16 --- /dev/null +++ b/contribution/documentation/generator/sequence-diagram-top-level.plantuml @@ -0,0 +1,17 @@ +@startuml +actor Developer as dev +participant Command as command +participant Exercise as exercise +participant CanonicalData as data + +dev -> command : create-exercise $slug +command -> exercise : create($slug) +exercise -> exercise : loadData($slug) +exercise -> data : ItemFactory::from($data) +command -> exercise : testFileName() +command -> exercise : testFileContent() +exercise -> data : renderTestCode() +command -> exercise : solutionFileName() +command -> exercise : solutionFileContent() +exercise -> data : renderSolutionCode() +@enduml diff --git a/contribution/documentation/generator/sequence-diagram-top-level.svg b/contribution/documentation/generator/sequence-diagram-top-level.svg new file mode 100644 index 000000000..701d4a8ca --- /dev/null +++ b/contribution/documentation/generator/sequence-diagram-top-level.svg @@ -0,0 +1 @@ +DeveloperDeveloperCommandCommandExerciseExerciseCanonicalDataCanonicalDatacreate-exercise $slugcreate($slug)loadData($slug)ItemFactory::from($data)testFileName()testFileContent()renderTestCode()solutionFileName()solutionFileContent()renderSolutionCode() \ No newline at end of file diff --git a/contribution/generator/README.md b/contribution/generator/README.md index 09fa6828e..36da08cac 100644 --- a/contribution/generator/README.md +++ b/contribution/generator/README.md @@ -1,20 +1,7 @@ -# Auto Creating of tests for Exercism PHP Track +# PHP code generator for Exercism PHP track exercises -This is a small poc on how we could auto generate tests for the PHP track based on the https://github.com/exercism/problem-specifications/. +See the documentation here: -How to test it: - -``` -git clone https://github.com/tomasnorre/exercism-tests-generation.git -cd exercism-tests-generation -composer install -bin/console app:create-tests -vendor/bin/phpunit src/Command/NucleotideCountTest.php -``` - -If you now make a `git status` you will see that the `src/Command/NucleotideCountTest.php` and you can now inspect the auto generated tests. - -It's all based on the `nikic/php-parser` and the https://github.com/exercism/problem-specifications/ repository, I have made a local copy of that on file -for now to spare the http-requests. - -Let me know what you think. +- [Introduction](../documentation/generator/README.md#introduction) +- [Architecture](../documentation/generator/README.md#architecture) +- [Contribution](../documentation/generator/README.md#contribution) diff --git a/contribution/generator/config/services.yaml b/contribution/generator/config/services.yaml index ba14e9fae..db6749260 100644 --- a/contribution/generator/config/services.yaml +++ b/contribution/generator/config/services.yaml @@ -4,6 +4,9 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: + app.env_track_root: '%env(TRACK_ROOT)%' + app.default_track_root: '%kernel.project_dir%/../..' + app.track_root: '%env(realpath:default:app.default_track_root:env_track_root)%' services: # default configuration for services in *this* file @@ -11,7 +14,7 @@ services: autowire: true # Automatically injects dependencies in your services. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. bind: - $projectDir: '%kernel.project_dir%' + $trackRoot: '%app.track_root%' # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name diff --git a/contribution/generator/src/Command/CreateTestsCommand.php b/contribution/generator/src/Command/CreateTestsCommand.php index ae9604f4f..558e6f4db 100644 --- a/contribution/generator/src/Command/CreateTestsCommand.php +++ b/contribution/generator/src/Command/CreateTestsCommand.php @@ -4,8 +4,7 @@ namespace App\Command; -use App\TestGenerator; -use App\TrackData\PracticeExercise; +use App\TrackData\Exercise; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -19,13 +18,9 @@ )] class CreateTestsCommand extends Command { - private string $trackRoot; - public function __construct( - private string $projectDir, + private Exercise $exercise, ) { - $this->trackRoot = realpath($projectDir . '/../..'); - parent::__construct(); } @@ -37,35 +32,22 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { $exerciseSlug = $input->getArgument('exercise'); - $exercise = new PracticeExercise( - $this->trackRoot, - $exerciseSlug, - ); - $testGenerator = new TestGenerator(); + $this->exercise->forSlug($exerciseSlug); $io = new SymfonyStyle($input, $output); - $io->writeln('Generating tests for ' . $exerciseSlug . ' in ' . $exercise->pathToExercise()); + $io->writeln('Generating tests for ' . $exerciseSlug . ' in ' . $this->exercise->pathToExercise()); \file_put_contents( - // TODO: Make '$exercise->pathToTestFile()' - $exercise->pathToExercise() - . '/' - . $this->inPascalCase($exerciseSlug) - . 'Test.php', - $testGenerator->createTestsFor( - $exercise->canonicalData(), - $this->inPascalCase($exerciseSlug) - ), + $this->exercise->pathToTestFile(), + $this->exercise->testFileContent(), + ); + + \file_put_contents( + $this->exercise->pathToSolutionFile(), + $this->exercise->solutionFileContent(), ); - // TODO: Make '$exercise->pathToStudentsFile()' - // TODO: Make '$testGenerator->studentsFileFor()' $io->success('Generating Tests - Finished'); return Command::SUCCESS; } - - private function inPascalCase(string $text): string - { - return \str_replace(" ", "", \ucwords(\str_replace("-", " ", $text))); - } } diff --git a/contribution/generator/src/Configlet.php b/contribution/generator/src/Configlet.php new file mode 100644 index 000000000..349c79e9b --- /dev/null +++ b/contribution/generator/src/Configlet.php @@ -0,0 +1,103 @@ +pathToConfiglet = $trackRoot . '/bin/configlet'; + } + + public function pathToCanonicalData(string $slug): string + { + $this->ensureConfigletCanBeUsed(); + $canonicalData = $this->cachedCanonicalData($slug); + $this->ensureCanonicalDataCanBeUsed($canonicalData); + + return $canonicalData; + } + + private function ensureConfigletCanBeUsed(): void + { + if ( + !( + \is_executable($this->pathToConfiglet) + && \is_file($this->pathToConfiglet) + ) + ) { + throw new RuntimeException( + 'configlet not found. Run the generator from track root.' + . ' Fetch configlet and create exercise with configlet first!' + ); + } + } + + private function cachedCanonicalData(string $slug): string + { + return \sprintf( + '%1$s/exercises/%2$s/canonical-data.json', + $this->pathToConfigletCache(), + $slug + ); + } + + private function ensureCanonicalDataCanBeUsed(string $canonicalData): void + { + if ( + !( + \is_readable($canonicalData) + && \is_file($canonicalData) + ) + ) { + throw new RuntimeException( + 'Cannot read "configlet" provided cached canonical data from ' + . $canonicalData + . '. Maybe the exercise has no canonical data?' + . ' Or check exercise slug and access rights!' + ); + } + } + + private function pathToConfigletCache(): string + { + /* + When running configlet with detailed output (-v d) and a command that + requires problem specification data (e.g. info), it prints the location + of the cache as the first line. To avoid an HTTP call, use the offline + mode (-o). + + Pipe the output through 'head' to get the first line only, then 'cut' + the 5th field to get the path only. + + configlet may fail when there is no cached data (offline mode), which + tells us, that the exercise hasn't been generated before (the cache is + required for that, too). So BASH must use `-eo pipefail` to get the + failure code back. + */ + $command = 'bash -c \'set -eo pipefail; ' + . $this->pathToConfiglet + . ' -v d -t ' + . $this->trackRoot + . ' info -o | head -1 | cut -d " " -f 5\'' + ; + $resultCode = 1; + + $configletCache = \exec(command: $command, result_code: $resultCode); + if ($configletCache === false || $resultCode !== 0) { + throw new RuntimeException( + '"configlet" could not provide cached canonical data.' + . ' Create exercise with configlet first!' + ); + } + + return $configletCache; + } +} diff --git a/contribution/generator/src/EnvVarProcessor/Realpath.php b/contribution/generator/src/EnvVarProcessor/Realpath.php new file mode 100644 index 000000000..1cde4245a --- /dev/null +++ b/contribution/generator/src/EnvVarProcessor/Realpath.php @@ -0,0 +1,24 @@ + 'string', + ]; + } +} diff --git a/contribution/generator/src/TestGenerator.php b/contribution/generator/src/TestGenerator.php deleted file mode 100644 index e65ea06ff..000000000 --- a/contribution/generator/src/TestGenerator.php +++ /dev/null @@ -1,92 +0,0 @@ -builderFactory = new BuilderFactory(); - - $class = $this->builderFactory->class( - $exerciseClass . "Test" - )->makeFinal()->extend('TestCase'); - $class->setDocComment( - "/**\n * " . implode("\n * ", $canonicalData->comments) . "\n */" - ); - - // Include Setup Method - $methodSetup = 'setUpBeforeClass'; - $method = $this->builderFactory->method($methodSetup) - ->makePublic() - ->makeStatic() - ->setReturnType('void') - ->addStmt( - $this->builderFactory->funcCall( - "require_once", - [$exerciseClass . ".php"] - ), - ); - - $class->addStmt($method); - - foreach ($canonicalData->testCases as $case) { - // Generate a method for each test case - $description = \ucfirst($case->description); - $methodName = ucfirst(str_replace('-', ' ', $description)); - $methodName = 'test' . str_replace(' ', '', ucwords($methodName)); - - // $exceptionClassName = new Node\Name\FullyQualified('Exception'); - $method = $this->builderFactory->method($methodName) - ->makePublic() - ->setReturnType('void') - ->setDocComment("/**\n * uuid: {$case->uuid}\n * @testdox {$description}\n */") - ->addStmt( - $this->builderFactory->funcCall( - '$this->markTestIncomplete', - [ 'This test has not been implemented yet.' ], - ) - ) - ; - // if (isset($case->expected->error)) { - // $method->addStmt( - // $this->builderFactory->funcCall('$this->expectException', - // [new Node\Arg(new Node\Expr\ClassConstFetch($exceptionClassName, 'class'))] - // ) - // ) - // ->addStmt($this->builderFactory->funcCall($case->property, [$case->input->strand ?? 'unknown'])) - // ; - // } else { - // $method->addStmt( - // $this->builderFactory->funcCall('$this->assertEquals', [ - // $case->expected, - // $this->builderFactory->funcCall($case->property, [$case->input->strand ?? 'unknown']) - // ]) - // ); - // } - - $class->addStmt($method); - } - - $namespace = new Namespace_(new Node\Name('Tests')); - $namespace->stmts[] = $this->builderFactory->use(TestCase::class)->getNode(); - $namespace->stmts[] = $class->getNode(); - - $printer = new PrettyPrinter\Standard(); - - return $printer->prettyPrintFile([$namespace]) . PHP_EOL; - } -} diff --git a/contribution/generator/src/TrackData/CanonicalData.php b/contribution/generator/src/TrackData/CanonicalData.php index cedcee7a1..f2210b47c 100644 --- a/contribution/generator/src/TrackData/CanonicalData.php +++ b/contribution/generator/src/TrackData/CanonicalData.php @@ -4,18 +4,148 @@ namespace App\TrackData; -use App\TrackData\CanonicalData\TestCase; +use App\TrackData\InnerGroup; +use App\TrackData\Item; -class CanonicalData +class CanonicalData implements Item { /** - * @param TestCase[] $testCases + * PHP_EOL is CRLF on Windows, we always want LF + * @see https://www.php.net/manual/en/reserved.constants.php#constant.php-eol + */ + private const LF = "\n"; + + /** * @param string[] $comments */ public function __construct( - public string $exercise, - public array $testCases = [], - public array $comments = [], + private string $testClassName, + private string $solutionFileName, + private string $solutionClassName, + private InnerGroup $cases, + private array $comments = [], + private ?object $unknown = null, ) { } + + public static function from(mixed $rawData): ?static + { + if (!\is_object($rawData)) + return null; + + $requiredProperties = [ + 'testClassName', + 'solutionFileName', + 'solutionClassName', + ]; + $actualProperties = \array_keys(\get_object_vars($rawData)); + $requiredData = []; + foreach ($requiredProperties as $requiredProperty) { + if (!( + \in_array($requiredProperty, $actualProperties) + && \is_string($rawData->{$requiredProperty}) + )) { + return null; + } + $requiredData[$requiredProperty] = $rawData->{$requiredProperty}; + unset($rawData->{$requiredProperty}); + } + + $comments = $rawData->comments ?? []; + unset($rawData->comments); + + // Ignore "exercise" key (not required) + unset($rawData->exercise); + + $cases = InnerGroup::from($rawData->cases ?? []); + unset($rawData->cases); + + return new static( + ...$requiredData, + cases: $cases, + comments: $comments, + unknown: empty(\get_object_vars($rawData)) ? null : $rawData, + ); + } + + public function renderPhpCode(): string + { + return \sprintf( + $this->template(), + $this->renderUnknownData(), + $this->renderTests(), + $this->renderComments(), + $this->testClassName, + $this->solutionFileName, + $this->solutionClassName, + ); + } + + /** + * %1$s Unknow data + * %2$s Pre-rendered list of tests + * %3$s Comments for DocBlock + * %4$s Test class name + * %5$s Solution file name + * %6$s Solution class name + */ + private function template(): string + { + return \file_get_contents(__DIR__ . '/canonical-data.txt'); + } + + private function renderUnknownData(): string + { + if ($this->unknown === null) + return ''; + return $this->asMultiLineComment([\json_encode($this->unknown)]); + } + + private function renderTests(): string + { + $tests = $this->cases->renderPhpCode(); + + return empty($tests) ? '' : $this->indent($tests) . self::LF; + } + + private function renderComments(): string + { + return empty($this->comments) ? '' : $this->forBlockComment([...$this->comments, '', '']); + } + + private function forBlockComment(array $lines): string + { + return \implode(self::LF . ' * ', $lines); + } + + private function asMultiLineComment(array $lines): string + { + return self::LF + . '/* Unknown data:' . self::LF + . ' * ' . implode(self::LF . ' * ', $lines) . self::LF + . ' */' . self::LF + ; + } + + private function indent(string $lines): string + { + $toUnindent = [ + ' // {{{', + ' // }}}', + ]; + $unindented = [ + '// {{{', + '// }}}', + ]; + + $indent = ' '; + $indentedLines = + $indent + . \implode(self::LF . $indent, \explode(self::LF, $lines)) + ; + + $indentedLines = \str_replace($toUnindent, $unindented, $indentedLines); + + return $indentedLines; + } } diff --git a/contribution/generator/src/TrackData/CanonicalData/TestCase.php b/contribution/generator/src/TrackData/CanonicalData/TestCase.php deleted file mode 100644 index 3b29ec400..000000000 --- a/contribution/generator/src/TrackData/CanonicalData/TestCase.php +++ /dev/null @@ -1,18 +0,0 @@ -cases) + ) + ) { + return null; + } + + $cases = InnerGroup::from($rawData->cases); + unset($rawData->cases); + $description = $rawData->description ?? ''; + unset($rawData->description); + $comments = $rawData->comments ?? []; + unset($rawData->comments); + $unknown = empty(\get_object_vars($rawData)) ? null : Unknown::from($rawData); + + return new static($cases, $description, $comments, $unknown); + } + + public function renderPhpCode(): string + { + return \sprintf( + $this->template(), + $this->renderTests(), + $this->renderHeadingComment(), + $this->renderUnknown(), + ); + } + + /** + * %1$s Pre-rendered list of tests + * %2$s Multiline comment + */ + private function template(): string + { + return \file_get_contents(__DIR__ . '/group.txt'); + } + + private function renderTests(): string + { + $tests = $this->cases->renderPhpCode(); + + return empty($tests) ? '' : $tests . self::LF . self::LF; + } + + private function renderHeadingComment(): string + { + $lines = []; + if (!empty($this->description)) { + $lines[] = $this->description; + } + if (!empty($this->description) && !empty($this->comments)) { + $lines[] = ''; + } + $lines = [...$lines, ...$this->comments]; + + return empty($lines) ? '' : $this->asMultiLineComment($lines); + } + + private function renderUnknown(): string + { + return $this->unknown === null + ? '' + : $this->unknown->renderPhpCode() . self::LF + ; + } + + private function asMultiLineComment(array $lines): string + { + return self::LF + . '/*' . self::LF + . ' * ' . implode(self::LF . ' * ', $lines) . self::LF + . ' */' . self::LF + ; + } +} diff --git a/contribution/generator/src/TrackData/InnerGroup.php b/contribution/generator/src/TrackData/InnerGroup.php new file mode 100644 index 000000000..66d365a53 --- /dev/null +++ b/contribution/generator/src/TrackData/InnerGroup.php @@ -0,0 +1,45 @@ + $itemFactory->from($rawCase), + $rawData, + )); + } + + public function renderPhpCode(): string + { + return \implode(self::LF, \array_map( + fn ($case): string => $case->renderPhpCode(), + $this->cases, + )); + } +} diff --git a/contribution/generator/src/TrackData/Item.php b/contribution/generator/src/TrackData/Item.php new file mode 100644 index 000000000..69638b630 --- /dev/null +++ b/contribution/generator/src/TrackData/Item.php @@ -0,0 +1,18 @@ +pathToConfiglet = $trackRoot . '/bin/configlet'; - $this->pathToPracticeExercises = $trackRoot . '/exercises/practice/'; + private ItemFactory $itemFactory, + ) {} + + #[Required] + public function setConfiglet(Configlet $configlet): void + { + $this->configlet = $configlet; + } + + public function forSlug(string $slug): void + { + $this->exerciseSlug = $slug; $this->pathToExercise = - $this->pathToPracticeExercises . $this->exerciseSlug; + $this->trackRoot . self::PATH_TO_EXERCISES . $slug; } public function pathToExercise(): string @@ -31,65 +44,75 @@ public function pathToExercise(): string return $this->pathToExercise; } - public function canonicalData(): CanonicalData + public function pathToTestFile(): string + { + return $this->pathToExercise . '/' . $this->testFileName(); + } + + public function testFileContent(): string { - $this->ensureConfigletCanBeUsed(); $this->ensurePracticeExerciseCanBeUsed(); - $this->pathToCachedCanonicalDataFromConfiglet(); - $this->ensurePathToCanonicalDataCanBeUsed(); - return $this->hydratedCanonicalData(); + // This returns an object derived from stdClass, so adding keys is safe + $rawData = $this->loadCanonicalData(); + $rawData->testClassName = $this->classNameFrom($this->testFileName()); + $rawData->solutionFileName = $this->solutionFileName(); + $rawData->solutionClassName = $this->classNameFrom($this->solutionFileName()); + + return $this->itemFactory->from($rawData)->renderPhpCode(); } - private function hydratedCanonicalData(): CanonicalData + public function pathToSolutionFile(): string { - $canonicalData = \json_decode( - json: \file_get_contents($this->pathToCanonicalData), - flags: JSON_THROW_ON_ERROR - ); + return $this->pathToExercise . '/' . $this->solutionFileName(); + } - // TODO: Validate - return new CanonicalData( - $canonicalData->exercise, - $this->hydrateTestCasesFrom($canonicalData->cases), - $canonicalData->comments, - ); + public function solutionFileContent(): string + { + $this->ensurePracticeExerciseCanBeUsed(); + + // TODO: Implement this... + return 'To be defined'; } - private function hydrateTestCasesFrom(array $rawData): array + private function testFileName(): string { - // TODO: Validate - return array_map(fn ($case) => new TestCase( - $case->uuid ?? null, - $case->description ?? null, - $case->property ?? null, - $case->input ?? null, - $case->expected ?? null, - $case->comments ?? [], - ), $rawData); + return $this->metaConfigFiles()->testFiles[0]; } - private function ensureConfigletCanBeUsed(): void + private function solutionFileName(): string { - if ( - !( - is_executable($this->pathToConfiglet) - && is_file($this->pathToConfiglet) - ) - ) { - throw new RuntimeException( - 'configlet not found. Run the generator from track root.' - . ' Fetch configlet and create exercise with configlet first!' + return $this->metaConfigFiles()->solutionFiles[0]; + } + + private function classNameFrom(string $fileName): string + { + // This track follows PSR-4 naming convention + return \str_replace(".php", "", $fileName); + } + + private function metaConfigFiles(): MetaConfigFiles + { + if (!$this->configFiles instanceof MetaConfigFiles) + { + $metaConfig = $this->loadMetaConfig(); + + $this->configFiles = new MetaConfigFiles( + $metaConfig->files->solution, + $metaConfig->files->test, + $metaConfig->files->example, ); } + + return $this->configFiles; } private function ensurePracticeExerciseCanBeUsed(): void { if ( !( - is_writable($this->pathToExercise) - && is_dir($this->pathToExercise) + \is_writable($this->pathToExercise) + && \is_dir($this->pathToExercise) ) ) { throw new RuntimeException( @@ -97,60 +120,26 @@ private function ensurePracticeExerciseCanBeUsed(): void . ' configlet first or check access rights!' ); } + // TODO: Validate metaConfigFiles(): one test file, one solution file, one example file } - private function pathToCachedCanonicalDataFromConfiglet(): void + private function loadCanonicalData(): object { - /* - When running configlet with detailed output (-v d) and a command that - requires problem specification data (e.g. info), it prints the location - of the cache as the first line. To avoid an HTTP call, use the offline - mode (-o). - - Pipe the output through 'head' to get the first line only, then 'cut' - the 5th field to get the path only. - - configlet may fail when there is no cached data (offline mode), which - tells us, that the exercise hasn't been generated before (the cache is - required for that, too). So BASH must use `-eo pipefail` to get the - failure code back. - */ - $command = 'bash -c \'set -eo pipefail; ' - . $this->pathToConfiglet - . ' -v d -t ' - . $this->trackRoot - . ' info -o | head -1 | cut -d " " -f 5\'' - ; - $resultCode = 1; - - $configletCache = \exec(command: $command, result_code: $resultCode); - if ($configletCache === false || $resultCode !== 0) { - throw new RuntimeException( - '"configlet" could not provide cached canonical data.' - . ' Create exercise with configlet first!' - ); - } - - $this->pathToCanonicalData = \sprintf( - '%1$s/exercises/%2$s/canonical-data.json', - $configletCache, - $this->exerciseSlug + return \json_decode( + json: \file_get_contents( + $this->configlet->pathToCanonicalData($this->exerciseSlug) + ), + flags: JSON_THROW_ON_ERROR ); } - private function ensurePathToCanonicalDataCanBeUsed(): void + private function loadMetaConfig(): object { - if ( - !( - is_readable($this->pathToCanonicalData) - && is_file($this->pathToCanonicalData) - ) - ) { - throw new RuntimeException( - 'Cannot read "configlet" provided cached canonical data from ' - . $this->pathToCanonicalData - . '. Check exercise slug or access rights!' - ); - } + return \json_decode( + json: \file_get_contents( + $this->pathToExercise . self::PATH_TO_CONFIG + ), + flags: JSON_THROW_ON_ERROR + ); } } diff --git a/contribution/generator/src/TrackData/TestCase.php b/contribution/generator/src/TrackData/TestCase.php new file mode 100644 index 000000000..ed59c9c9d --- /dev/null +++ b/contribution/generator/src/TrackData/TestCase.php @@ -0,0 +1,156 @@ +{$requiredProperty}; + unset($rawData->{$requiredProperty}); + } + + return new static( + ...$data, + unknown: empty(\get_object_vars($rawData)) ? null : $rawData, + ); + } + + public function renderPhpCode(): string + { + return \sprintf( + $this->template(), + $this->testMethodName(), + $this->renderUnknownData(), + $this->indentTrailingLines(\var_export((array)$this->input, true)), + $this->invocationReturnsValue() + ? $this->renderAssignExpected() + : $this->renderAssertionOnException() + , + $this->uuid, + \ucfirst($this->description), + $this->property, + $this->invocationReturnsValue() + ? $this->renderAssertionOnExpected() + : '' + , + ); + } + + private function invocationReturnsValue(): bool + { + return !( + \is_object($this->expected) + && isset($this->expected->error) + && gettype($this->expected->error) === 'string' + ); + } + + /** + * %1$s Method name + * %2$s Unknow data + * %3$s Input data + * %4$s Expected data or exception + * %5$s UUID + * %6$s Testdox + * %7$s Property (method to call) + * %8$s Assertion on expected + */ + private function template(): string + { + return \file_get_contents(__DIR__ . '/test-case.txt'); + } + + private function testMethodName(): string + { + $sanitizedDescription = \preg_replace('/\W+/', ' ', $this->description); + + $methodNameParts = \explode(' ', $sanitizedDescription); + $upperCasedParts = \array_map( + fn ($part) => \ucfirst($part), + $methodNameParts + ); + + return \lcfirst(\implode('', $upperCasedParts)); + } + + private function renderUnknownData(): string + { + if ($this->unknown === null) + return ''; + return 'Unknown data:' . self::LF + . ' * ' . \json_encode($this->unknown) . self::LF + . ' * ' . self::LF + . ' * ' + ; + } + + private function renderAssignExpected(): string + { + return $this->indentTrailingLines('$expected = ' . \var_export($this->expected, true)); + } + + private function renderAssertionOnExpected(): string + { + return + ' ' . self::LF + . ' $this->assertSame($expected, $actual);' . self::LF + ; + } + + private function renderAssertionOnException(): string + { + return $this->indentTrailingLines( + '$this->expectException(\Exception::class);' . self::LF + . '$this->expectExceptionMessage(\'' + . $this->expected->error + . '\')' + ); + } + + private function indentTrailingLines(string $lines): string + { + $indent = ' '; + return \implode(self::LF . $indent, \explode(self::LF, $lines)); + } +} diff --git a/contribution/generator/src/TrackData/Unknown.php b/contribution/generator/src/TrackData/Unknown.php new file mode 100644 index 000000000..61858516b --- /dev/null +++ b/contribution/generator/src/TrackData/Unknown.php @@ -0,0 +1,36 @@ +template(), + \json_encode($this->data), + ); + } + + private function template(): string + { + return \file_get_contents(__DIR__ . '/unknown.txt'); + } +} diff --git a/contribution/generator/src/TrackData/canonical-data.txt b/contribution/generator/src/TrackData/canonical-data.txt new file mode 100644 index 000000000..89b37fc43 --- /dev/null +++ b/contribution/generator/src/TrackData/canonical-data.txt @@ -0,0 +1,33 @@ +subject = new %6$s(); + } +%2$s} diff --git a/contribution/generator/src/TrackData/group.txt b/contribution/generator/src/TrackData/group.txt new file mode 100644 index 000000000..df56f726a --- /dev/null +++ b/contribution/generator/src/TrackData/group.txt @@ -0,0 +1,2 @@ +%2$s// {{{ +%3$s%1$s// }}} \ No newline at end of file diff --git a/contribution/generator/src/TrackData/test-case.txt b/contribution/generator/src/TrackData/test-case.txt new file mode 100644 index 000000000..fba305da2 --- /dev/null +++ b/contribution/generator/src/TrackData/test-case.txt @@ -0,0 +1,15 @@ + +/** + * %2$suuid: %5$s + * @testdox %6$s + * @test + */ +public function %1$s(): void +{ + $this->markTestSkipped('This test has not been verified yet.'); + + $input = %3$s; + %4$s; + + $actual = $this->subject->%7$s(...$input); +%8$s} \ No newline at end of file diff --git a/contribution/generator/src/TrackData/unknown.txt b/contribution/generator/src/TrackData/unknown.txt new file mode 100644 index 000000000..6bb0af068 --- /dev/null +++ b/contribution/generator/src/TrackData/unknown.txt @@ -0,0 +1,5 @@ + +/* + * Unknown data: + * %1$s + */ \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/AssertStringOrder.php b/contribution/generator/tests/TestGeneration/AssertStringOrder.php new file mode 100644 index 000000000..ed600248b --- /dev/null +++ b/contribution/generator/tests/TestGeneration/AssertStringOrder.php @@ -0,0 +1,36 @@ +assertStringContainsString($firstString, $actualString); + $this->assertStringContainsString($secondString, $actualString); + + $firstPosition = \strpos($actualString, $firstString); + $secondPosition = \strpos($actualString, $secondString); + + if ($firstPosition > $secondPosition) { + $this->fail( + empty($message) + ? 'Failed asserting that "' + . $actualString + . '" contains "' + . $firstString + . '" before "' + . $secondString + . '"' + : $message + ); + } + // Count the above conditional as assertion + // Using this assertion directly results in misleading failure message + $this->assertTrue(true); + } +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/CanonicalDataTest.php b/contribution/generator/tests/TestGeneration/CanonicalData/CanonicalDataTest.php new file mode 100644 index 000000000..43b109384 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/CanonicalDataTest.php @@ -0,0 +1,118 @@ +subjectFor('non-varying-parts'); + + $this->assertInstanceOf(Item::class, $subject); + } + + #[Test] + #[TestDox('$_dataName')] + #[DataProvider('nonRenderingScenarios')] + public function testNonRenderingScenario( + string $scenario, + ): void { + $subject = $this->subjectFor($scenario); + + $this->assertNull($subject); + } + + public static function nonRenderingScenarios(): array + { + return [ + 'When given no object, then returns null' + => [ 'no-object' ], + 'When given object without "testClassName", then returns null' + => [ 'no-test-class-name' ], + 'When given object without "solutionFileName", then returns null' + => [ 'no-solution-file-name' ], + 'When given object without "solutionClassName", then returns null' + => [ 'no-solution-class-name' ], + ]; + } + + #[Test] + #[TestDox('$_dataName')] + #[DataProvider('renderingScenarios')] + public function testRenderingScenario( + string $scenario, + ): void { + $subject = $this->subjectFor($scenario); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsAllOfScenario($scenario, $actual); + } + + public static function renderingScenarios(): array + { + return [ + // This scenario asserts on the constant parts and their position in relation to the varying part(s) + 'When given a valid object with all keys, then renders all non-varying parts where they belong' + => [ 'non-varying-parts' ], + + // These scenarios assert on the varying part(s) + + 'When given a test class name, then renders that test class name into stub' + => [ 'test-class-name' ], + 'When given a solution file name, then renders that file name into stub' + => [ 'solution-file-name'], + 'When given a solution class name, then renders that class name into stub' + => [ 'solution-class-name' ], + + // "exercise" is ignored + 'When given object with no "exercise", then renders only test class stub' + => [ 'no-exercise' ], + 'When given object with "exercise", then renders only test class stub' + => [ 'exercise' ], + + 'When given object with no unknown key, then renders no multi-line comment' + => [ 'no-unknown-key' ], + 'When given object with an unknown key, then renders the key as JSON in multi-line comment' + => [ 'one-unknown-key' ], + 'When given object with many unknown keys, then renders all keys as JSON in multi-line comment' + => [ 'many-unknown-keys' ], + + 'When given a valid object with no "comments", then renders no comments part' + => [ 'no-comments' ], + 'When given object with singleline "comments", then renders comment in class DocBlock' + => [ 'one-line-comments' ], + 'When given object with multiline "comments", then renders test class with comments in class DocBlock' + => [ 'many-line-comments' ], + + // Here we need to check for rendering / not rendering only + 'When given a valid object with no "cases", then renders no cases' + => [ 'no-cases' ], + 'When given a valid object with "cases", then renders cases' + => [ 'cases' ], + ]; + } + + private function subjectFor(string $scenario): ?CanonicalData + { + return CanonicalData::from($this->rawDataFor($scenario)); + } +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/01-start-to-list-item.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/01-start-to-list-item.txt new file mode 100644 index 000000000..9c73870d1 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/01-start-to-list-item.txt @@ -0,0 +1,3 @@ + } + + /* diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/02-list-item-to-end.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/02-list-item-to-end.txt new file mode 100644 index 000000000..aed864332 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/02-list-item-to-end.txt @@ -0,0 +1,2 @@ + */ +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/input.json new file mode 100644 index 000000000..d78975e03 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/input.json @@ -0,0 +1,8 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "cases": [ + { "an-unknown-item": "will render as multiline comment with JSON" } + ] +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/list-item.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/list-item.txt new file mode 100644 index 000000000..831509497 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/cases/list-item.txt @@ -0,0 +1 @@ + * Unknown data: diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/exercise/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/exercise/expected.txt new file mode 100644 index 000000000..f4096f4cd --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/exercise/expected.txt @@ -0,0 +1,33 @@ +subject = new SomeSolutionClass(); + } +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/exercise/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/exercise/input.json new file mode 100644 index 000000000..65a8eee66 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/exercise/input.json @@ -0,0 +1,6 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "exercise": "This is ignored" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-line-comments/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-line-comments/expected.txt new file mode 100644 index 000000000..0535b1b63 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-line-comments/expected.txt @@ -0,0 +1,4 @@ + * Some lines of comments + * + * Just to have something in here. + * diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-line-comments/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-line-comments/input.json new file mode 100644 index 000000000..79ae8ab9a --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-line-comments/input.json @@ -0,0 +1,10 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "comments": [ + "Some lines of comments", + "", + "Just to have something in here." + ] +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-cases/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-cases/expected.txt new file mode 100644 index 000000000..bf7329b6c --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-cases/expected.txt @@ -0,0 +1,17 @@ + } + + /* + * Unknown data: + * {"an-unknown-item":"will render as multiline comment with JSON"} + */ + + /* + * Unknown data: + * {"another-unknown-item":"will render as multiline comment with JSON"} + */ + + /* + * Unknown data: + * {"a-last-unknown-item":"will render as multiline comment with JSON"} + */ +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-cases/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-cases/input.json new file mode 100644 index 000000000..3ec9fe90c --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-cases/input.json @@ -0,0 +1,10 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "cases": [ + { "an-unknown-item": "will render as multiline comment with JSON" }, + { "another-unknown-item": "will render as multiline comment with JSON" }, + { "a-last-unknown-item": "will render as multiline comment with JSON" } + ] +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-keys/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-keys/expected.txt new file mode 100644 index 000000000..4fdeb21e9 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-keys/expected.txt @@ -0,0 +1,5 @@ + +/* Unknown data: + * {"some-unknown-key":"with a value","another-unknown-key":"and another value"} + */ + diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-keys/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-keys/input.json new file mode 100644 index 000000000..36711452a --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/many-unknown-keys/input.json @@ -0,0 +1,7 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "some-unknown-key": "with a value", + "another-unknown-key": "and another value" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-cases/01-solution-class-name-to-end.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-cases/01-solution-class-name-to-end.txt new file mode 100644 index 000000000..f9122f4bc --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-cases/01-solution-class-name-to-end.txt @@ -0,0 +1,3 @@ + $this->subject = new SomeSolutionClass(); + } +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-cases/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-cases/input.json new file mode 100644 index 000000000..c9290a49e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-cases/input.json @@ -0,0 +1,9 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "exercise": "some-exercise-slug", + "comments": [ + "One line in comments" + ] +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-comments/01-start-to-test-class-name.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-comments/01-start-to-test-class-name.txt new file mode 100644 index 000000000..f16ee949f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-comments/01-start-to-test-class-name.txt @@ -0,0 +1,20 @@ +subject = new SomeSolutionClass(); + } +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-exercise/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-exercise/input.json new file mode 100644 index 000000000..30a4e5419 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-exercise/input.json @@ -0,0 +1,5 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-object/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-object/input.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-object/input.json @@ -0,0 +1 @@ +[] diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-solution-class-name/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-solution-class-name/input.json new file mode 100644 index 000000000..5439d7b7e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-solution-class-name/input.json @@ -0,0 +1,4 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-solution-file-name/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-solution-file-name/input.json new file mode 100644 index 000000000..dedfe1b6b --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-solution-file-name/input.json @@ -0,0 +1,4 @@ +{ + "testClassName": "SomeTestClass", + "solutionClassName": "SomeSolutionClass" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-test-class-name/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-test-class-name/input.json new file mode 100644 index 000000000..6c93dca7e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-test-class-name/input.json @@ -0,0 +1,4 @@ +{ + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-unknown-key/01-start-to-declare.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-unknown-key/01-start-to-declare.txt new file mode 100644 index 000000000..f12df8900 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/no-unknown-key/01-start-to-declare.txt @@ -0,0 +1,3 @@ +subject = new SomeSolutionClass(); diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/07-solution-class-name-to-cases.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/07-solution-class-name-to-cases.txt new file mode 100644 index 000000000..53f1de062 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/07-solution-class-name-to-cases.txt @@ -0,0 +1,4 @@ + $this->subject = new SomeSolutionClass(); + } + + /* diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/08-cases-indented-but-not-closing-folding-marks.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/08-cases-indented-but-not-closing-folding-marks.txt new file mode 100644 index 000000000..6352561c1 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/08-cases-indented-but-not-closing-folding-marks.txt @@ -0,0 +1,2 @@ + +// }}} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/08-cases-indented-but-not-openning-folding-marks.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/08-cases-indented-but-not-openning-folding-marks.txt new file mode 100644 index 000000000..89563d683 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/08-cases-indented-but-not-openning-folding-marks.txt @@ -0,0 +1,2 @@ + +// {{{ diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/09-cases-to-end.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/09-cases-to-end.txt new file mode 100644 index 000000000..b40164468 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/09-cases-to-end.txt @@ -0,0 +1,2 @@ +// }}} +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/input.json new file mode 100644 index 000000000..d4fe68d26 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/non-varying-parts/input.json @@ -0,0 +1,16 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "exercise": "some-exercise-slug", + "comments": [ + "One line in comments" + ], + "cases": [ + { + "cases": [], + "description": "subgroup has unindented folding marks" + } + ], + "some-unknown-key": "some value" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-line-comments/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-line-comments/expected.txt new file mode 100644 index 000000000..9c5935238 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-line-comments/expected.txt @@ -0,0 +1,2 @@ + * A line in comments + * diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-line-comments/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-line-comments/input.json new file mode 100644 index 000000000..32f40a50f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-line-comments/input.json @@ -0,0 +1,8 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "comments": [ + "A line in comments" + ] +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-unknown-key/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-unknown-key/expected.txt new file mode 100644 index 000000000..94e19b5e3 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-unknown-key/expected.txt @@ -0,0 +1,5 @@ + +/* Unknown data: + * {"unknown-key":"does not matter"} + */ + diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-unknown-key/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-unknown-key/input.json new file mode 100644 index 000000000..88120c56f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/one-unknown-key/input.json @@ -0,0 +1,6 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "unknown-key": "does not matter" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/input.json new file mode 100644 index 000000000..26e6acd39 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/input.json @@ -0,0 +1,5 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "DifferentSolutionClassName" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/property-type-declaration.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/property-type-declaration.txt new file mode 100644 index 000000000..6786a18c8 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/property-type-declaration.txt @@ -0,0 +1 @@ + private DifferentSolutionClassName $subject; diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/subject-instantiation.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/subject-instantiation.txt new file mode 100644 index 000000000..2943f7a01 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-class-name/subject-instantiation.txt @@ -0,0 +1 @@ + $this->subject = new DifferentSolutionClassName(); diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-file-name/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-file-name/expected.txt new file mode 100644 index 000000000..2fe9c023c --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-file-name/expected.txt @@ -0,0 +1 @@ + require_once('DifferentSolutionFile.php'); diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-file-name/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-file-name/input.json new file mode 100644 index 000000000..ec958e08f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/solution-file-name/input.json @@ -0,0 +1,5 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "DifferentSolutionFile.php", + "solutionClassName": "SomeSolutionClass" +} diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/test-class-name/expected.txt b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/test-class-name/expected.txt new file mode 100644 index 000000000..f79613be9 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/test-class-name/expected.txt @@ -0,0 +1 @@ +final class DifferentTestClassName extends TestCase diff --git a/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/test-class-name/input.json b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/test-class-name/input.json new file mode 100644 index 000000000..1579e324e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/CanonicalData/fixtures/test-class-name/input.json @@ -0,0 +1,5 @@ +{ + "testClassName": "DifferentTestClassName", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass" +} diff --git a/contribution/generator/tests/TestGeneration/Group/GroupTest.php b/contribution/generator/tests/TestGeneration/Group/GroupTest.php new file mode 100644 index 000000000..524ec053d --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/GroupTest.php @@ -0,0 +1,108 @@ +subjectFor('empty-cases'); + + $this->assertInstanceOf(Item::class, $subject); + } + + #[Test] + #[TestDox('When given $_dataName, then returns null')] + #[DataProvider('nonRenderingScenarios')] + public function testNonRenderingScenario( + mixed $rawData, + ): void { + $subject = Group::from($rawData); + + $this->assertNull($subject); + } + + public static function nonRenderingScenarios(): array + { + // All possible types in JSON, but not object + // Any object without "cases" + return [ + 'an array' => [ [] ], + 'a bool' => [ true ], + 'a string' => [ 'some string' ], + 'an int' => [ 0 ], + 'a float' => [ 0.0 ], + 'null' => [ null ], + 'an empty object' => [ (object)[] ], + 'an object without "cases"' => [ (object)['some-property' => 'is not cases'] ], + ]; + } + + #[Test] + #[TestDox('$_dataName')] + #[DataProvider('renderingScenarios')] + public function testRenderingScenario( + string $scenario, + ): void { + $subject = $this->subjectFor($scenario); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsAllOfScenario($scenario, $actual); + } + + public static function renderingScenarios(): array + { + return [ + 'When given an object with empty "cases" list, then renders an empty folding section' + => [ 'empty-cases' ], + // As we use InnerGroup to render the list, one test case is enough + 'When given an object with "cases" containing a testcase, then renders the cases list into folding section' + => [ 'one-case-in-cases' ], + + 'When given "cases" and "description", then renders multiline comment with description above folding section' + => [ 'description' ], + 'When given "cases" and "comments", then renders multiline comment with comments above folding section' + => [ 'comments' ], + 'When given "cases", "description" and "comments", then renders multiline comment with description and comments above folding section' + => [ 'description-and-comments' ], + + 'When given "cases" and unknown keys, then renders multiline comment with JSON above tests into folding section' + => [ 'unknown' ], + ]; + } + + #[Test] + #[TestDox('When given "cases", "description" and "comments", then renders description before comments')] + public function renderingOrderOfDescriptionAndComments(): void + { + $subject = $this->subjectFor('description-and-comments'); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsStringBeforeString( + 'some title for this group of tests', + 'Some comments', + $actual, + ); + } + + private function subjectFor(string $scenario): ?Group + { + return Group::from($this->rawDataFor($scenario)); + } +} diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/comments/01-start-to-comments.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/01-start-to-comments.txt new file mode 100644 index 000000000..33662f554 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/01-start-to-comments.txt @@ -0,0 +1 @@ +/* diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/comments/02-comments-to-tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/02-comments-to-tests.txt new file mode 100644 index 000000000..c356ce6e5 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/02-comments-to-tests.txt @@ -0,0 +1,4 @@ + */ +// {{{ + +/** diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/comments/03-tests-to-end.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/03-tests-to-end.txt new file mode 100644 index 000000000..e9b1155be --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/03-tests-to-end.txt @@ -0,0 +1,3 @@ +} + +// }}} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/comments/comments.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/comments.txt new file mode 100644 index 000000000..712e1de0c --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/comments.txt @@ -0,0 +1,2 @@ + * Some comments + * on multiple lines diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/comments/input.json b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/input.json new file mode 100644 index 000000000..91fb51271 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/input.json @@ -0,0 +1,17 @@ +{ + "comments":[ + "Some comments", + "on multiple lines" + ], + "cases": [ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" + } + ] +} diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/comments/tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/tests.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/comments/tests.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/01-start-to-description.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/01-start-to-description.txt new file mode 100644 index 000000000..33662f554 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/01-start-to-description.txt @@ -0,0 +1 @@ +/* diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/02-description-to-comments.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/02-description-to-comments.txt new file mode 100644 index 000000000..b077d635b --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/02-description-to-comments.txt @@ -0,0 +1 @@ + * diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/03-comments-to-tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/03-comments-to-tests.txt new file mode 100644 index 000000000..c356ce6e5 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/03-comments-to-tests.txt @@ -0,0 +1,4 @@ + */ +// {{{ + +/** diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/04-tests-to-end.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/04-tests-to-end.txt new file mode 100644 index 000000000..e9b1155be --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/04-tests-to-end.txt @@ -0,0 +1,3 @@ +} + +// }}} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/comments.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/comments.txt new file mode 100644 index 000000000..712e1de0c --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/comments.txt @@ -0,0 +1,2 @@ + * Some comments + * on multiple lines diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/description.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/description.txt new file mode 100644 index 000000000..2084025ac --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/description.txt @@ -0,0 +1 @@ + * some title for this group of tests diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/input.json b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/input.json new file mode 100644 index 000000000..7b66a90c9 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/input.json @@ -0,0 +1,18 @@ +{ + "description": "some title for this group of tests", + "comments":[ + "Some comments", + "on multiple lines" + ], + "cases": [ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" + } + ] +} diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/tests.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description-and-comments/tests.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description/01-start-to-description.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description/01-start-to-description.txt new file mode 100644 index 000000000..33662f554 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description/01-start-to-description.txt @@ -0,0 +1 @@ +/* diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description/02-description-to-tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description/02-description-to-tests.txt new file mode 100644 index 000000000..c356ce6e5 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description/02-description-to-tests.txt @@ -0,0 +1,4 @@ + */ +// {{{ + +/** diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description/03-tests-to-end.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description/03-tests-to-end.txt new file mode 100644 index 000000000..e9b1155be --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description/03-tests-to-end.txt @@ -0,0 +1,3 @@ +} + +// }}} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description/description.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description/description.txt new file mode 100644 index 000000000..2084025ac --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description/description.txt @@ -0,0 +1 @@ + * some title for this group of tests diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description/input.json b/contribution/generator/tests/TestGeneration/Group/fixtures/description/input.json new file mode 100644 index 000000000..be3412448 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description/input.json @@ -0,0 +1,14 @@ +{ + "description": "some title for this group of tests", + "cases": [ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" + } + ] +} diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/description/tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/description/tests.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/description/tests.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/empty-cases/expected.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/empty-cases/expected.txt new file mode 100644 index 000000000..ff38d861a --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/empty-cases/expected.txt @@ -0,0 +1,2 @@ +// {{{ +// }}} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/empty-cases/input.json b/contribution/generator/tests/TestGeneration/Group/fixtures/empty-cases/input.json new file mode 100644 index 000000000..337aef584 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/empty-cases/input.json @@ -0,0 +1,3 @@ +{ + "cases": [] +} diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/01-start-to-tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/01-start-to-tests.txt new file mode 100644 index 000000000..114726e78 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/01-start-to-tests.txt @@ -0,0 +1,3 @@ +// {{{ + +/** diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/02-tests-to-end.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/02-tests-to-end.txt new file mode 100644 index 000000000..e9b1155be --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/02-tests-to-end.txt @@ -0,0 +1,3 @@ +} + +// }}} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/input.json b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/input.json new file mode 100644 index 000000000..0bf974d90 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/input.json @@ -0,0 +1,13 @@ +{ + "cases": [ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" + } + ] +} diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/tests.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/one-case-in-cases/tests.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/01-start-to-unknown.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/01-start-to-unknown.txt new file mode 100644 index 000000000..0c8f733fb --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/01-start-to-unknown.txt @@ -0,0 +1,4 @@ +// {{{ + +/* + * Unknown data: diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/02-unknown-to-tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/02-unknown-to-tests.txt new file mode 100644 index 000000000..2362dfda4 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/02-unknown-to-tests.txt @@ -0,0 +1,3 @@ + */ + +/** diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/03-tests-to-end.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/03-tests-to-end.txt new file mode 100644 index 000000000..e9b1155be --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/03-tests-to-end.txt @@ -0,0 +1,3 @@ +} + +// }}} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/input.json b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/input.json new file mode 100644 index 000000000..09067cde8 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/input.json @@ -0,0 +1,14 @@ +{ + "some-unknown-key": "with value", + "cases": [ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" + } + ] +} diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/tests.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/tests.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/tests.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/unknown.txt b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/unknown.txt new file mode 100644 index 000000000..77a2ec2a9 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Group/fixtures/unknown/unknown.txt @@ -0,0 +1 @@ + * {"some-unknown-key":"with value"} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/InnerGroupTest.php b/contribution/generator/tests/TestGeneration/InnerGroup/InnerGroupTest.php new file mode 100644 index 000000000..bdda9d9a4 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/InnerGroupTest.php @@ -0,0 +1,168 @@ +subjectFor('empty-list'); + + $this->assertInstanceOf(Item::class, $subject); + } + + #[Test] + #[TestDox('When given $_dataName, then returns null')] + #[DataProvider('nonRenderingScenarios')] + public function testNonRenderingScenario( + mixed $rawData, + ): void { + $subject = InnerGroup::from($rawData); + + $this->assertNull($subject); + } + + public static function nonRenderingScenarios(): array + { + // All possible types in JSON, but not array + return [ + 'an object' => [ (object)[] ], + 'a bool' => [ true ], + 'a string' => [ 'some string' ], + 'an int' => [ 0 ], + 'a float' => [ 0.0 ], + 'null' => [ null ], + ]; + } + + #[Test] + #[TestDox('$_dataName')] + #[DataProvider('renderingScenarios')] + public function testRenderingScenario( + string $scenario, + ): void { + $subject = $this->subjectFor($scenario); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsAllOfScenario($scenario, $actual); + } + + public static function renderingScenarios(): array + { + return [ + 'When given an empty list, then renders empty string' + => [ 'empty-list' ], + + 'When given one unknown item in list, then renders unknown item' + => [ 'one-unknown-case' ], + 'When given many unknown items in list, then renders all items' + => [ 'many-unknown-cases' ], + + 'When given one test case in list, then renders the test case' + => [ 'one-test-case' ], + 'When given many test cases in list, then renders all test cases' + => [ 'many-test-cases' ], + + 'When given one group in list, then renders the group' + => [ 'one-group' ], + + 'When given many mixed cases in list, then renders all cases' + => [ 'many-mixed-cases' ], + ]; + } + + #[Test] + #[TestDox('When given many unknown items in list, then renders the unknown items in order of input')] + public function testRenderingUnknownOrder(): void + { + $subject = $this->subjectFor('many-unknown-cases'); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsStringBeforeString( + '"an-unknown-item"', + '"another-unknown-item"', + $actual, + ); + $this->assertStringContainsStringBeforeString( + '"another-unknown-item"', + '"a-last-unknown-item"', + $actual, + ); + } + + #[Test] + #[TestDox('When given many test cases in list, then renders the test cases in order of input')] + public function testRenderingTestCaseOrder(): void + { + $subject = $this->subjectFor('many-test-cases'); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsStringBeforeString( + 'uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c', + 'uuid: 4f99b933-367b-404b-8c6d-36d5923ee476', + $actual, + ); + $this->assertStringContainsStringBeforeString( + 'uuid: 4f99b933-367b-404b-8c6d-36d5923ee476', + 'uuid: 91122d10-5ec7-47cb-b759-033756375869', + $actual, + ); + } + + #[Test] + #[TestDox('When given many mixed items in list, then renders the mixed items in order of input')] + public function testRenderingMixedOrder(): void + { + $subject = $this->subjectFor('many-mixed-cases'); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsStringBeforeString( + '"an-unknown-item"', + 'uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c', + $actual, + ); + $this->assertStringContainsStringBeforeString( + 'uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c', + 'section title', + $actual, + ); + $this->assertStringContainsStringBeforeString( + 'section title', + 'uuid: 4f99b933-367b-404b-8c6d-36d5923ee476', + $actual, + ); + $this->assertStringContainsStringBeforeString( + 'uuid: 4f99b933-367b-404b-8c6d-36d5923ee476', + '"also-unknown"', + $actual, + ); + $this->assertStringContainsStringBeforeString( + '"also-unknown"', + 'uuid: 91122d10-5ec7-47cb-b759-033756375869', + $actual, + ); + } + + private function subjectFor(string $scenario): ?InnerGroup + { + return InnerGroup::from($this->rawDataFor($scenario)); + } +} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/empty-list/expected.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/empty-list/expected.txt new file mode 100644 index 000000000..e69de29bb diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/empty-list/input.json b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/empty-list/input.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/empty-list/input.json @@ -0,0 +1 @@ +[] diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/01-start-to-first-case.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/01-start-to-first-case.txt new file mode 100644 index 000000000..2f5e9e367 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/01-start-to-first-case.txt @@ -0,0 +1,2 @@ + +/* diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/02-last-case-to-end.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/02-last-case-to-end.txt new file mode 100644 index 000000000..5c34318c2 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/02-last-case-to-end.txt @@ -0,0 +1 @@ +} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/first-test-case.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/first-test-case.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/first-test-case.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/first-unknown.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/first-unknown.txt new file mode 100644 index 000000000..669626510 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/first-unknown.txt @@ -0,0 +1 @@ + * {"an-unknown-item":"will render as multiline comment with JSON"} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/input.json b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/input.json new file mode 100644 index 000000000..722a66818 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/input.json @@ -0,0 +1,40 @@ +[ + { + "an-unknown-item": "will render as multiline comment with JSON" + }, + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "first description", + "property": "someProperty", + "input": { + "argumentName": [] + }, + "expected": true + }, + { + "description": "section title", + "cases": [ + { + "uuid": "4f99b933-367b-404b-8c6d-36d5923ee476", + "description": "second description", + "property": "otherProperty", + "input": { + "otherArgumentName": [[1, 1]] + }, + "expected": "some value" + }, + { + "also-unknown": "resulting in multiline comment with JSON" + } + ] + }, + { + "uuid": "91122d10-5ec7-47cb-b759-033756375869", + "description": "and a last one", + "property": "alsoProperty", + "input": { + "argumentName": [[1, 2]] + }, + "expected": [[1, 1, 3]] + } +] diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/last-test-case.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/last-test-case.txt new file mode 100644 index 000000000..ff6b76a81 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/last-test-case.txt @@ -0,0 +1 @@ + * uuid: 91122d10-5ec7-47cb-b759-033756375869 diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/second-test-case.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/second-test-case.txt new file mode 100644 index 000000000..9a88ba7a1 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/second-test-case.txt @@ -0,0 +1 @@ + * uuid: 4f99b933-367b-404b-8c6d-36d5923ee476 diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/second-unknown.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/second-unknown.txt new file mode 100644 index 000000000..2ab897d3e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-mixed-cases/second-unknown.txt @@ -0,0 +1 @@ + * {"also-unknown":"resulting in multiline comment with JSON"} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/01-start-to-first-test-case.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/01-start-to-first-test-case.txt new file mode 100644 index 000000000..5a8823e92 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/01-start-to-first-test-case.txt @@ -0,0 +1,2 @@ + +/** diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/02-last-test-case-to-end.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/02-last-test-case-to-end.txt new file mode 100644 index 000000000..5c34318c2 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/02-last-test-case-to-end.txt @@ -0,0 +1 @@ +} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/first-test.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/first-test.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/first-test.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/input.json b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/input.json new file mode 100644 index 000000000..ddc996642 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/input.json @@ -0,0 +1,29 @@ +[ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "first description", + "property": "someProperty", + "input": { + "argumentName": [] + }, + "expected": true + }, + { + "uuid": "4f99b933-367b-404b-8c6d-36d5923ee476", + "description": "second description", + "property": "otherProperty", + "input": { + "otherArgumentName": [[1, 1]] + }, + "expected": "some value" + }, + { + "uuid": "91122d10-5ec7-47cb-b759-033756375869", + "description": "and a last one", + "property": "alsoProperty", + "input": { + "argumentName": [[1, 2]] + }, + "expected": [[1, 1, 3]] + } +] diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/last-test.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/last-test.txt new file mode 100644 index 000000000..ff6b76a81 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/last-test.txt @@ -0,0 +1 @@ + * uuid: 91122d10-5ec7-47cb-b759-033756375869 diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/second-test.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/second-test.txt new file mode 100644 index 000000000..9a88ba7a1 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-test-cases/second-test.txt @@ -0,0 +1 @@ + * uuid: 4f99b933-367b-404b-8c6d-36d5923ee476 diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/01-start-to-first-item.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/01-start-to-first-item.txt new file mode 100644 index 000000000..2f5e9e367 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/01-start-to-first-item.txt @@ -0,0 +1,2 @@ + +/* diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/02-last-item-to-end.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/02-last-item-to-end.txt new file mode 100644 index 000000000..6e0dccf75 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/02-last-item-to-end.txt @@ -0,0 +1 @@ + */ diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/first-item.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/first-item.txt new file mode 100644 index 000000000..669626510 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/first-item.txt @@ -0,0 +1 @@ + * {"an-unknown-item":"will render as multiline comment with JSON"} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/input.json b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/input.json new file mode 100644 index 000000000..8eb830169 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/input.json @@ -0,0 +1,5 @@ +[ + { "an-unknown-item": "will render as multiline comment with JSON" }, + { "another-unknown-item": "will render as multiline comment with JSON" }, + { "a-last-unknown-item": "will render as multiline comment with JSON" } +] diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/last-item.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/last-item.txt new file mode 100644 index 000000000..69ff3b2e8 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/last-item.txt @@ -0,0 +1 @@ + * {"a-last-unknown-item":"will render as multiline comment with JSON"} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/second-item.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/second-item.txt new file mode 100644 index 000000000..efc81dd3f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/many-unknown-cases/second-item.txt @@ -0,0 +1 @@ + * {"another-unknown-item":"will render as multiline comment with JSON"} diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-group/expected.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-group/expected.txt new file mode 100644 index 000000000..1edb96682 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-group/expected.txt @@ -0,0 +1,3 @@ + +// {{{ +// }}} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-group/input.json b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-group/input.json new file mode 100644 index 000000000..5a69df785 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-group/input.json @@ -0,0 +1,6 @@ +[ + { + "description": "some description", + "cases": [] + } +] diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-test-case/expected.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-test-case/expected.txt new file mode 100644 index 000000000..13b552a5f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-test-case/expected.txt @@ -0,0 +1 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-test-case/input.json b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-test-case/input.json new file mode 100644 index 000000000..be200127b --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-test-case/input.json @@ -0,0 +1,11 @@ +[ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" + } +] diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-unknown-case/expected.txt b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-unknown-case/expected.txt new file mode 100644 index 000000000..45c874c25 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-unknown-case/expected.txt @@ -0,0 +1,5 @@ + +/* + * Unknown data: + * {"an-unknown-item":"will render as multiline comment with JSON"} + */ \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-unknown-case/input.json b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-unknown-case/input.json new file mode 100644 index 000000000..bee25c67e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/InnerGroup/fixtures/one-unknown-case/input.json @@ -0,0 +1,3 @@ +[ + { "an-unknown-item": "will render as multiline comment with JSON" } +] diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/ItemFactoryTest.php b/contribution/generator/tests/TestGeneration/ItemFactory/ItemFactoryTest.php new file mode 100644 index 000000000..7033fc624 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/ItemFactoryTest.php @@ -0,0 +1,62 @@ +rawDataFor($scenario); + + $subject = new ItemFactory(); + $actual = $subject->from($input); + + $this->assertInstanceOf(Item::class, $actual); + $this->assertInstanceOf($fqcn, $actual); + } + + public static function scenarios(): array + { + return [ + 'When given a minimal valid canonical data object, then produces CanonicalData' + => [ 'canonical-data-object-minimal', CanonicalData::class ], + 'When given a maximal valid canonical data object, then produces CanonicalData' + => [ 'canonical-data-object-maximal', CanonicalData::class ], + 'When given a minimal valid group object, then produces Group' + => [ 'group-object-minimal', Group::class ], + 'When given a maximal valid group object, then produces Group' + => [ 'group-object-maximal', Group::class ], + 'When given an empty array, then produces InnerGroup' + => [ 'empty-array', InnerGroup::class ], + 'When given a non-empty array, then produces InnerGroup' + => [ 'non-empty-array', InnerGroup::class ], + 'When given a minimal valid test case object, then produces TestCase' + => [ 'test-case-object-minimal', TestCase::class ], + 'When given a maximal valid test case object, then produces TestCase' + => [ 'test-case-object-maximal', TestCase::class ], + 'When given an empty object, then produces Unknown' + => [ 'empty-object', Unknown::class ], + ]; + } +} diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/canonical-data-object-maximal/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/canonical-data-object-maximal/input.json new file mode 100644 index 000000000..f828ce0ef --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/canonical-data-object-maximal/input.json @@ -0,0 +1,13 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass", + "exercise": "some-exercise-slug", + "comments": [ + "One line in comments" + ], + "cases": [ + { "some-unknown-item": "some value" } + ], + "some-unknown-key": "some value" +} diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/canonical-data-object-minimal/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/canonical-data-object-minimal/input.json new file mode 100644 index 000000000..30a4e5419 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/canonical-data-object-minimal/input.json @@ -0,0 +1,5 @@ +{ + "testClassName": "SomeTestClass", + "solutionFileName": "SomeSolutionFile.ext", + "solutionClassName": "SomeSolutionClass" +} diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/empty-array/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/empty-array/input.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/empty-array/input.json @@ -0,0 +1 @@ +[] diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/empty-object/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/empty-object/input.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/empty-object/input.json @@ -0,0 +1 @@ +{} diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/group-object-maximal/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/group-object-maximal/input.json new file mode 100644 index 000000000..ef07c475c --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/group-object-maximal/input.json @@ -0,0 +1,19 @@ +{ + "some-unknown-key": "with value", + "description": "some title for this group of tests", + "comments":[ + "Some comments", + "on multiple lines" + ], + "cases": [ + { + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" + } + ] +} diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/group-object-minimal/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/group-object-minimal/input.json new file mode 100644 index 000000000..337aef584 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/group-object-minimal/input.json @@ -0,0 +1,3 @@ +{ + "cases": [] +} diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/non-empty-array/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/non-empty-array/input.json new file mode 100644 index 000000000..e82af46d6 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/non-empty-array/input.json @@ -0,0 +1,3 @@ +[ + {} +] diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/test-case-object-maximal/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/test-case-object-maximal/input.json new file mode 100644 index 000000000..110ea7a6f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/test-case-object-maximal/input.json @@ -0,0 +1,10 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type", + "some-unknown-property": "This shall show up in DocBlock." +} diff --git a/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/test-case-object-minimal/input.json b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/test-case-object-minimal/input.json new file mode 100644 index 000000000..15ed7d677 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ItemFactory/fixtures/test-case-object-minimal/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/ScenarioFixture.php b/contribution/generator/tests/TestGeneration/ScenarioFixture.php new file mode 100644 index 000000000..9674ca65d --- /dev/null +++ b/contribution/generator/tests/TestGeneration/ScenarioFixture.php @@ -0,0 +1,56 @@ +pathToScenarioFixtures($scenario) . '/input.json'; + + if (!\file_exists($file)) { + $this->fail('Input fixture file of scenario not found: ' . $file); + } + + return \json_decode( + json: \file_get_contents($file), + flags: JSON_THROW_ON_ERROR + ); + } + + private function assertStringContainsAllOfScenario( + string $scenario, + string $actual, + string $message = '', + ):void { + $scenarioExpectations = \glob( + $this->pathToScenarioFixtures($scenario) . '/*.txt' + ); + + if (empty($scenarioExpectations)) { + $this->fail('Scenario ' . $scenario . ' contains no expectation files *.txt'); + } + + foreach ($scenarioExpectations as $scenarioExpectation) { + $this->assertStringContainsString( + \file_get_contents($scenarioExpectation), + $actual, + $message, + ); + } + } + + private function pathToScenarioFixtures(string $scenario): string + { + return $this->pathToFixtures() . '/' . $scenario; + } + + private function pathToFixtures(): string + { + $classReflector = new ReflectionClass($this); + + return \dirname($classReflector->getFileName()) . '/fixtures'; + } +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/TestCaseTest.php b/contribution/generator/tests/TestGeneration/TestCase/TestCaseTest.php new file mode 100644 index 000000000..573c41351 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/TestCaseTest.php @@ -0,0 +1,121 @@ +subjectFor('non-varying-parts'); + + $this->assertInstanceOf(Item::class, $subject); + } + + #[Test] + #[TestDox('$_dataName')] + #[DataProvider('nonRenderingScenarios')] + public function testNonRenderingScenario( + string $scenario, + ): void { + $subject = $this->subjectFor($scenario); + + $this->assertNull($subject); + } + + public static function nonRenderingScenarios(): array + { + return [ + 'When given no object, then returns null' + => [ 'no-object' ], + 'When given an empty object, then returns null' + => [ 'empty-object' ], + 'When given object without "uuid", then returns null' + => [ 'no-uuid' ], + 'When given object without "description", then returns null' + => [ 'no-description' ], + 'When given object without "property", then returns null' + => [ 'no-property' ], + 'When given object without "input", then returns null' + => [ 'no-input' ], + 'When given object without "expected", then returns null' + => [ 'no-expected' ], + ]; + } + + #[Test] + #[TestDox('$_dataName')] + #[DataProvider('renderingScenarios')] + public function testRenderingScenario( + string $scenario, + ): void { + $subject = $this->subjectFor($scenario); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsAllOfScenario($scenario, $actual); + } + + public static function renderingScenarios(): array + { + return [ + // This scenario asserts on the constant parts and their position in relation to the varying part(s) + 'When given a valid object and an unknown key, then renders all non-varying parts where they belong' + => [ 'non-varying-parts' ], + // These scenarios assert on the varying part(s) + 'When given a valid object, then renders uuid' + => [ 'uuid' ], + 'When given a valid object, then renders description as @testdox and method name' + => [ 'description' ], + 'When given a valid object with problematic chars in description, then renders @testdox with and method name without those' + => [ 'description-with-problematic-chars' ], + 'When given a valid object, then renders input object as PHP literal value' + => [ 'input' ], + 'When given no "error" in "expected", then renders "expected" as PHP literal value and asserts on it' + => [ 'expect-returned-value' ], + 'When given "error" in "expected", then renders assertion on expected Exception' + => [ 'expect-exception-thrown' ], + 'When given a different message in "error", then renders that message' + => [ 'expect-different-exception-message' ], + 'When given a valid object, then renders property as method call on subject' + => [ 'property' ], + 'When given a valid object and an unknown key, then renders unknown key as JSON' + => [ 'unknown' ], + 'When given a valid object and no unknown key, then renders no JSON' + => [ 'no-unknown' ], + ]; + } + + #[Test] + #[TestDox('When given "error" in "expected", then renders the exception expectation before the invocation')] + public function renderingExceptionExpectationOrder(): void + { + $subject = $this->subjectFor('expect-exception-thrown'); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsStringBeforeString( + '$this->expectException', + '$this->subject->', + $actual, + ); + } + + private function subjectFor(string $scenario): ?TestCase + { + return TestCase::from($this->rawDataFor($scenario)); + } +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/input.json new file mode 100644 index 000000000..82878e1fb --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "*+^-#.,'`\"<>= so_me *+-,'^`\"#.<>= descri*,'`\"\\+-#.<>=ption *^+-#.with trailing space!<>=,'`\" ", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/method-name.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/method-name.txt new file mode 100644 index 000000000..7edc5408d --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/method-name.txt @@ -0,0 +1 @@ +function so_meDescriPtionWithTrailingSpace( \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/testdox.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/testdox.txt new file mode 100644 index 000000000..7d7ac72de --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description-with-problematic-chars/testdox.txt @@ -0,0 +1 @@ +@testdox *+^-#.,'`"<>= so_me *+-,'^`"#.<>= descri*,'`"\+-#.<>=ption *^+-#.with trailing space!<>=,'`" diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/input.json new file mode 100644 index 000000000..15ed7d677 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/method-name.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/method-name.txt new file mode 100644 index 000000000..fde2b385e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/method-name.txt @@ -0,0 +1 @@ +function someDescription( \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/testdox.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/testdox.txt new file mode 100644 index 000000000..6cdd7f4e6 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/description/testdox.txt @@ -0,0 +1 @@ +@testdox Some description \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/empty-object/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/empty-object/input.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/empty-object/input.json @@ -0,0 +1 @@ +{} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-different-exception-message/exception-message.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-different-exception-message/exception-message.txt new file mode 100644 index 000000000..f3a55cf93 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-different-exception-message/exception-message.txt @@ -0,0 +1 @@ +'another exception message to expect' \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-different-exception-message/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-different-exception-message/input.json new file mode 100644 index 000000000..9965973e7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-different-exception-message/input.json @@ -0,0 +1,11 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": { + "error": "another exception message to expect" + } +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/01-property-to-end.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/01-property-to-end.txt new file mode 100644 index 000000000..fbe86480a --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/01-property-to-end.txt @@ -0,0 +1,2 @@ + $actual = $this->subject->camelCasedProperty(...$input); +} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/assertion-on-exception.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/assertion-on-exception.txt new file mode 100644 index 000000000..c41cd1664 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/assertion-on-exception.txt @@ -0,0 +1,2 @@ + $this->expectException(\Exception::class); + $this->expectExceptionMessage('some expected exception message'); diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/input.json new file mode 100644 index 000000000..8bbb8854a --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-exception-thrown/input.json @@ -0,0 +1,11 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": { + "error": "some expected exception message" + } +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/assertion-on-expected.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/assertion-on-expected.txt new file mode 100644 index 000000000..399a68e8d --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/assertion-on-expected.txt @@ -0,0 +1,2 @@ + + $this->assertSame($expected, $actual); diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/assign-expected.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/assign-expected.txt new file mode 100644 index 000000000..4a856aba7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/assign-expected.txt @@ -0,0 +1 @@ +$expected = 'maybe any type'; \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/input.json new file mode 100644 index 000000000..15ed7d677 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/expect-returned-value/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/input/expected.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/input/expected.txt new file mode 100644 index 000000000..dd14ac63d --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/input/expected.txt @@ -0,0 +1,3 @@ +input = array ( + 'camelCasedArgumentName' => 'maybe any type', + ); \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/input/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/input/input.json new file mode 100644 index 000000000..15ed7d677 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/input/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-description/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-description/input.json new file mode 100644 index 000000000..443b0e42a --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-description/input.json @@ -0,0 +1,8 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-expected/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-expected/input.json new file mode 100644 index 000000000..6815443f5 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-expected/input.json @@ -0,0 +1,8 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description, containing =,-,<,> (unwanted chars)", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + } +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-input/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-input/input.json new file mode 100644 index 000000000..214d50b26 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-input/input.json @@ -0,0 +1,6 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description, containing =,-,<,> (unwanted chars)", + "property": "camelCasedProperty", + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-object/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-object/input.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-object/input.json @@ -0,0 +1 @@ +[] diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-property/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-property/input.json new file mode 100644 index 000000000..e6b93e685 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-property/input.json @@ -0,0 +1,8 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description, containing =,-,<,> (unwanted chars)", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-unknown/01-start-to-uuid.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-unknown/01-start-to-uuid.txt new file mode 100644 index 000000000..861b5922e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-unknown/01-start-to-uuid.txt @@ -0,0 +1,2 @@ +/** + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-unknown/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-unknown/input.json new file mode 100644 index 000000000..15ed7d677 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-unknown/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-uuid/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-uuid/input.json new file mode 100644 index 000000000..7ce624b7e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/no-uuid/input.json @@ -0,0 +1,8 @@ +{ + "description": "some description, containing =,-,<,> (unwanted chars)", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/01-start-to-unknown.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/01-start-to-unknown.txt new file mode 100644 index 000000000..ce61be1fd --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/01-start-to-unknown.txt @@ -0,0 +1,3 @@ +/** + * Unknown data: + * {"some-unknown-property":"This shall show up in DocBlock."} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/02-unknown-to-uuid.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/02-unknown-to-uuid.txt new file mode 100644 index 000000000..f8b62d078 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/02-unknown-to-uuid.txt @@ -0,0 +1,3 @@ + * {"some-unknown-property":"This shall show up in DocBlock."} + * + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/03-uuid-to-testdox.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/03-uuid-to-testdox.txt new file mode 100644 index 000000000..a999944cd --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/03-uuid-to-testdox.txt @@ -0,0 +1,2 @@ + * uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c + * @testdox Some description \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/04-testdox-to-method-name.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/04-testdox-to-method-name.txt new file mode 100644 index 000000000..763600a42 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/04-testdox-to-method-name.txt @@ -0,0 +1,4 @@ + * @testdox Some description + * @test + */ +public function someDescription(): void \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/05-method-name-to-input.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/05-method-name-to-input.txt new file mode 100644 index 000000000..596c88b30 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/05-method-name-to-input.txt @@ -0,0 +1,5 @@ +public function someDescription(): void +{ + $this->markTestSkipped('This test has not been verified yet.'); + + $input = array ( \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/06-input-to-expected.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/06-input-to-expected.txt new file mode 100644 index 000000000..e0f96f70b --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/06-input-to-expected.txt @@ -0,0 +1,4 @@ + $input = array ( + 'camelCasedArgumentName' => 'maybe any type', + ); + $expected = 'maybe any type'; \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/07-expected-to-property.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/07-expected-to-property.txt new file mode 100644 index 000000000..2f7f3e1d4 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/07-expected-to-property.txt @@ -0,0 +1,3 @@ + $expected = 'maybe any type'; + + $actual = $this->subject->camelCasedProperty(...$input); \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/08-property-to-assertion.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/08-property-to-assertion.txt new file mode 100644 index 000000000..40eefe708 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/08-property-to-assertion.txt @@ -0,0 +1,3 @@ + $actual = $this->subject->camelCasedProperty(...$input); + + $this->assertSame($expected, $actual); diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/09-assertion-to-end.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/09-assertion-to-end.txt new file mode 100644 index 000000000..c1f5e5460 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/09-assertion-to-end.txt @@ -0,0 +1,2 @@ + $this->assertSame($expected, $actual); +} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/input.json new file mode 100644 index 000000000..110ea7a6f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/non-varying-parts/input.json @@ -0,0 +1,10 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type", + "some-unknown-property": "This shall show up in DocBlock." +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/property/expected.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/property/expected.txt new file mode 100644 index 000000000..2a5a98c4e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/property/expected.txt @@ -0,0 +1 @@ +subject->camelCasedProperty( \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/property/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/property/input.json new file mode 100644 index 000000000..15ed7d677 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/property/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/unknown/expected.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/unknown/expected.txt new file mode 100644 index 000000000..eaf01902c --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/unknown/expected.txt @@ -0,0 +1 @@ +* {"some-unknown-property":"This shall show up in DocBlock."} \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/unknown/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/unknown/input.json new file mode 100644 index 000000000..110ea7a6f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/unknown/input.json @@ -0,0 +1,10 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type", + "some-unknown-property": "This shall show up in DocBlock." +} diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/uuid/expected.txt b/contribution/generator/tests/TestGeneration/TestCase/fixtures/uuid/expected.txt new file mode 100644 index 000000000..7eb65deee --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/uuid/expected.txt @@ -0,0 +1 @@ +uuid: 31a673f2-5e54-49fe-bd79-1c1dae476c9c \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/TestCase/fixtures/uuid/input.json b/contribution/generator/tests/TestGeneration/TestCase/fixtures/uuid/input.json new file mode 100644 index 000000000..15ed7d677 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/TestCase/fixtures/uuid/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "some description", + "property": "camelCasedProperty", + "input": { + "camelCasedArgumentName": "maybe any type" + }, + "expected": "maybe any type" +} diff --git a/contribution/generator/tests/TestGeneration/Unknown/UnknownTest.php b/contribution/generator/tests/TestGeneration/Unknown/UnknownTest.php new file mode 100644 index 000000000..00ddeb52d --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/UnknownTest.php @@ -0,0 +1,67 @@ +assertInstanceOf(Item::class, Unknown::from((object)[])); + } + + #[Test] + #[TestDox('$_dataName')] + #[DataProvider('renderingScenarios')] + public function testRenderingScenario( + string $scenario, + ): void { + $subject = $this->subjectFor($scenario); + + $actual = $subject->renderPhpCode(); + + $this->assertStringContainsAllOfScenario($scenario, $actual); + } + + public static function renderingScenarios(): array + { + return [ + // This scenario asserts on the constant parts and their position in relation to the varying part(s) + 'When given an empty object, then renders multiline comment with JSON' + => [ 'non-varying-parts' ], + // These scenarios assert on the varying part(s) + 'When given an empty object, then renders it as JSON for a multiline comment' + => [ 'empty-object' ], + 'When given any object, then renders it as JSON for a multiline comment' + => [ 'any-object' ], + 'When given an array, then renders it as JSON for a multiline comment' + => [ 'array' ], + 'When given a bool, then renders it as JSON for a multiline comment' + => [ 'bool' ], + 'When given a string, then renders it as JSON for a multiline comment' + => [ 'string' ], + 'When given an int, then renders it as JSON for a multiline comment' + => [ 'int' ], + 'When given a float, then renders it as JSON for a multiline comment' + => [ 'float' ], + 'When given null, then renders it as JSON for a multiline comment' + => [ 'null' ], + ]; + } + + private function subjectFor(string $scenario): Unknown + { + return Unknown::from($this->rawDataFor($scenario)); + } +} diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/any-object/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/any-object/expected.txt new file mode 100644 index 000000000..3387a2cb5 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/any-object/expected.txt @@ -0,0 +1,2 @@ + * {"uuid":"31a673f2-5e54-49fe-bd79-1c1dae476c9c","description":"empty input = empty output","property":"canChain","input":{"dominoes":[]},"expected":true} + \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/any-object/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/any-object/input.json new file mode 100644 index 000000000..751d08b1f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/any-object/input.json @@ -0,0 +1,9 @@ +{ + "uuid": "31a673f2-5e54-49fe-bd79-1c1dae476c9c", + "description": "empty input = empty output", + "property": "canChain", + "input": { + "dominoes": [] + }, + "expected": true +} diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/array/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/array/expected.txt new file mode 100644 index 000000000..33e483145 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/array/expected.txt @@ -0,0 +1 @@ + * [] diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/array/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/array/input.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/array/input.json @@ -0,0 +1 @@ +[] diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/bool/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/bool/expected.txt new file mode 100644 index 000000000..b308e0342 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/bool/expected.txt @@ -0,0 +1 @@ + * true diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/bool/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/bool/input.json new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/bool/input.json @@ -0,0 +1 @@ +true diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/empty-object/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/empty-object/expected.txt new file mode 100644 index 000000000..f28ec5208 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/empty-object/expected.txt @@ -0,0 +1 @@ + * {} diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/empty-object/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/empty-object/input.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/empty-object/input.json @@ -0,0 +1 @@ +{} diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/float/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/float/expected.txt new file mode 100644 index 000000000..72c653086 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/float/expected.txt @@ -0,0 +1 @@ + * 1.1 diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/float/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/float/input.json new file mode 100644 index 000000000..9459d4ba2 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/float/input.json @@ -0,0 +1 @@ +1.1 diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/int/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/int/expected.txt new file mode 100644 index 000000000..72e37e595 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/int/expected.txt @@ -0,0 +1 @@ + * 1 diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/int/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/int/input.json new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/int/input.json @@ -0,0 +1 @@ +1 diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/01-start-to-json.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/01-start-to-json.txt new file mode 100644 index 000000000..90026c03f --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/01-start-to-json.txt @@ -0,0 +1,4 @@ + +/* + * Unknown data: + * {} diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/02-json-to-end.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/02-json-to-end.txt new file mode 100644 index 000000000..7811d8b7e --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/02-json-to-end.txt @@ -0,0 +1,2 @@ + * {} + */ \ No newline at end of file diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/input.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/non-varying-parts/input.json @@ -0,0 +1 @@ +{} diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/null/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/null/expected.txt new file mode 100644 index 000000000..5ca012f72 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/null/expected.txt @@ -0,0 +1 @@ + * null diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/null/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/null/input.json new file mode 100644 index 000000000..19765bd50 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/null/input.json @@ -0,0 +1 @@ +null diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/string/expected.txt b/contribution/generator/tests/TestGeneration/Unknown/fixtures/string/expected.txt new file mode 100644 index 000000000..c02c15760 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/string/expected.txt @@ -0,0 +1 @@ + * "some string" diff --git a/contribution/generator/tests/TestGeneration/Unknown/fixtures/string/input.json b/contribution/generator/tests/TestGeneration/Unknown/fixtures/string/input.json new file mode 100644 index 000000000..e39067c85 --- /dev/null +++ b/contribution/generator/tests/TestGeneration/Unknown/fixtures/string/input.json @@ -0,0 +1 @@ +"some string"