diff --git a/README.md b/README.md index ba14dc26..4febffd5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ We would like to extend our thanks to the following sponsors for funding Pest de ### Platinum Sponsors - **[Advent](https://advent.dev)** -- **[Localazy](https://localazy.com)** +- **[Forge](https://forge.laravel.com)** - **[Spatie](https://spatie.be)** - **[Worksome](https://www.worksome.com/)** @@ -36,6 +36,7 @@ We would like to extend our thanks to the following sponsors for funding Pest de - [Auth0](https://auth0.com) - [Codecourse](https://codecourse.com/) - [Laracasts](https://laracasts.com/) +- [Localazy](https://localazy.com) - [Hyvor](https://hyvor.com/) - [Fathom Analytics](https://usefathom.com/) - [Meema](https://meema.io) diff --git a/composer.json b/composer.json index 8fa5a4a8..31b8bdb7 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "require": { "php": "^8.1.0", "nunomaduro/collision": "^7.0.0", - "nunomaduro/termwind": "^1.14.0", + "nunomaduro/termwind": "^1.14.2", "pestphp/pest-plugin": "^2.0.0", "phpunit/phpunit": "10.0.x-dev" }, diff --git a/overrides/Runner/Filter/NameFilterIterator.php b/overrides/Runner/Filter/NameFilterIterator.php index be5151aa..4ff7b6f7 100644 --- a/overrides/Runner/Filter/NameFilterIterator.php +++ b/overrides/Runner/Filter/NameFilterIterator.php @@ -156,7 +156,7 @@ final class NameFilterIterator extends RecursiveFilterIterator if ($test instanceof HasPrintableTestCaseName) { return [ $test::getPrintableTestCaseName(), - $test::getPrintableTestCaseMethodName(), + $test->getPrintableTestCaseMethodName(), ]; } diff --git a/phpstan.neon b/phpstan.neon index 0a1cba5c..d8b81f41 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,6 +12,7 @@ parameters: reportUnmatchedIgnoredErrors: true ignoreErrors: + - '#Cannot instantiate interface PHPUnit\\Util\\Exception#' - "#with a nullable type declaration#" - "#type mixed is not subtype of native#" - "#is not allowed to extend#" diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index e63c9a8e..36d47e38 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -22,7 +22,12 @@ trait Testable /** * Test method description. */ - private static string $__description; + private string $__description; + + /** + * Test "latest" method description. + */ + private static string $__latestDescription; /** * The Test Case "test" closure. @@ -69,7 +74,7 @@ trait Testable if ($test->hasMethod($name)) { $method = $test->getMethod($name); - self::$__description = $method->description; + $this->__description = self::$__latestDescription = $method->description; $this->__test = $method->getClosure($this); } } @@ -169,7 +174,7 @@ trait Testable */ protected function setUp(): void { - self::$__description = $this->name(); + $this->__description = self::$__latestDescription = $this->name(); TestSuite::getInstance()->test = $this; @@ -221,7 +226,7 @@ trait Testable { $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); - self::$__description = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description; + $this->__description = self::$__latestDescription = $this->dataName() ? $method->description.' with '.$this->dataName() : $method->description; if (count($arguments) !== 1) { return $arguments; @@ -258,7 +263,7 @@ trait Testable } /** - * Gets the Test Case name that should be used by printers. + * The printable test case name. */ public static function getPrintableTestCaseName(): string { @@ -266,10 +271,18 @@ trait Testable } /** - * Gets the Test Case name that should be used by printers. + * The printable test case method name. */ - public static function getPrintableTestCaseMethodName(): string + public function getPrintableTestCaseMethodName(): string { - return self::$__description; + return $this->__description; + } + + /** + * The latest printable test case method name. + */ + public static function getLatestPrintableTestCaseMethodName(): string + { + return self::$__latestDescription; } } diff --git a/src/Exceptions/TestDescriptionMissing.php b/src/Exceptions/TestDescriptionMissing.php new file mode 100644 index 00000000..67f75930 --- /dev/null +++ b/src/Exceptions/TestDescriptionMissing.php @@ -0,0 +1,24 @@ +shouldReset ? $this->original->value : $this->expectation->value; } diff --git a/src/Factories/TestCaseFactory.php b/src/Factories/TestCaseFactory.php index 0036be01..4591d9f1 100644 --- a/src/Factories/TestCaseFactory.php +++ b/src/Factories/TestCaseFactory.php @@ -10,6 +10,7 @@ use Pest\Contracts\HasPrintableTestCaseName; use Pest\Exceptions\DatasetMissing; use Pest\Exceptions\ShouldNotHappen; use Pest\Exceptions\TestAlreadyExist; +use Pest\Exceptions\TestDescriptionMissing; use Pest\Factories\Concerns\HigherOrderable; use Pest\Plugins\Environment; use Pest\Support\Reflection; @@ -122,6 +123,8 @@ final class TestCaseFactory $rootPath = TestSuite::getInstance()->rootPath; $relativePath = str_replace($rootPath.DIRECTORY_SEPARATOR, '', $filename); + $relativePath = ltrim($relativePath, DIRECTORY_SEPARATOR); + $basename = basename($relativePath, '.php'); $dotPos = strpos($basename, '.'); @@ -208,7 +211,11 @@ final class TestCaseFactory eval($classCode); } catch (ParseError $caught) { - throw new RuntimeException(sprintf('Unable to create test case for test file at %s', $filename), 1, $caught); + throw new RuntimeException(sprintf( + "Unable to create test case for test file at %s. \n %s", + $filename, + $classCode + ), 1, $caught); } } @@ -218,7 +225,7 @@ final class TestCaseFactory public function addMethod(TestCaseMethodFactory $method): void { if ($method->description === null) { - throw ShouldNotHappen::fromMessage('The test description may not be empty.'); + throw new TestDescriptionMissing($method->filename); } if (array_key_exists($method->description, $this->methods)) { diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index 0714d9a3..886ffab2 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -524,7 +524,6 @@ final class Expectation { Assert::assertIsString($this->value, $message); - // @phpstan-ignore-next-line Assert::assertJson($this->value, $message); return $this; diff --git a/src/Repositories/DatasetsRepository.php b/src/Repositories/DatasetsRepository.php index 26de6dd2..3c3f5fc1 100644 --- a/src/Repositories/DatasetsRepository.php +++ b/src/Repositories/DatasetsRepository.php @@ -156,7 +156,7 @@ final class DatasetsRepository } if ($datasets[$index] instanceof Traversable) { - $datasets[$index] = iterator_to_array($datasets[$index]); + $datasets[$index] = iterator_to_array($datasets[$index], false); } // @phpstan-ignore-next-line diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index dce67e78..ff302cf7 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -84,6 +84,9 @@ final class TestRepository } } + /** + * Gets the test case factory from the given filename. + */ public function get(string $filename): TestCaseFactory { return $this->testCases[$filename]; diff --git a/src/Support/Backtrace.php b/src/Support/Backtrace.php index beccedac..5077fb5a 100644 --- a/src/Support/Backtrace.php +++ b/src/Support/Backtrace.php @@ -28,7 +28,9 @@ final class Backtrace foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) { assert(array_key_exists(self::FILE, $trace)); - if (Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) { + $traceFile = str_replace(DIRECTORY_SEPARATOR, '/', $trace[self::FILE]); + + if (Str::endsWith($traceFile, 'overrides/Runner/TestSuiteLoader.php')) { break; } diff --git a/src/Support/Coverage.php b/src/Support/Coverage.php index 3d409ceb..743c73ab 100644 --- a/src/Support/Coverage.php +++ b/src/Support/Coverage.php @@ -85,7 +85,7 @@ final class Coverage $totalWidth = (new Terminal())->getWidth(); - $dottedLineLength = $totalWidth <= 70 ? $totalWidth : 70; + $dottedLineLength = $totalWidth - 6; $totalCoverage = $codeCoverage->getReport()->percentageOfExecutedLines(); diff --git a/tests/.snapshots/help-command.txt b/tests/.snapshots/help-command.txt index 9fd71c08..f6caacbc 100644 --- a/tests/.snapshots/help-command.txt +++ b/tests/.snapshots/help-command.txt @@ -60,6 +60,9 @@ --columns .................... Number of columns to use for progress output --columns max ............ Use maximum number of columns for progress output --stderr ................................. Write to STDERR instead of STDOUT + --no-progress .................... Disable output of test execution progress + --no-results ................................ Disable output of test results + --no-output ............................................. Disable all output --display-incomplete .................. Display details for incomplete tests --display-skipped ........................ Display details for skipped tests --display-deprecations . Display details for deprecations triggered by tests @@ -68,8 +71,7 @@ --display-warnings ......... Display details for warnings triggered by tests --reverse-list .............................. Print defects in reverse order --teamcity ............... Report test execution progress in TeamCity format - --testdox ................. Report test execution progress in TestDox format - --no-interaction ........................ Disable TestDox progress animation + --testdox ............................ Report test results in TestDox format LOGGING OPTIONS: --log-junit ................ Log test execution in JUnit XML format to file diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index f8819365..af0bfcef 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -29,7 +29,7 @@ ✓ it does not append CoversNothing to other methods ✓ it throws exception if no class nor method has been found - PASS Tests\Features\DatasetsTests + PASS Tests\Features\Datasets ✓ it throws exception if dataset does not exist ✓ it throws exception if dataset already exist ✓ it sets closures @@ -102,6 +102,11 @@ ✓ more than two datasets with (2) / (4) / (5) ✓ more than two datasets with (2) / (4) / (6) ✓ more than two datasets did the job right + ✓ eager registered wrapped datasets with Generator functions with (1) + ✓ eager registered wrapped datasets with Generator functions with (2) + ✓ eager registered wrapped datasets with Generator functions with (3) + ✓ eager registered wrapped datasets with Generator functions with (4) + ✓ eager registered wrapped datasets with Generator functions did the job right ✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #1 ✓ it can resolve a dataset after the test case is available with (Closure Object (...)) #2 ✓ it can resolve a dataset after the test case is available with shared yield sets with (Closure Object (...)) #1 @@ -116,7 +121,6 @@ ✓ it will not resolve a closure if it is type hinted as a callable with (Closure Object (...)) #2 ✓ it can correctly resolve a bound dataset that returns an array with (Closure Object (...)) ✓ it can correctly resolve a bound dataset that returns an array but wants to be spread with (Closure Object (...)) - ↓ forbids to define tests in Datasets dirs and Datasets.php files PASS Tests\Features\Depends ✓ first @@ -664,47 +668,6 @@ ✓ get 'foo' → get 'bar' → expect true → toBeTrue ✓ get 'foo' → expect true → toBeTrue - PASS Tests\Features\ScopedDatasets\Directory\NestedDirectory1\TestFileInNestedDirectoryWithDatasetsFile - ✓ uses dataset with (1) - ✓ uses dataset with (2) - ✓ uses dataset with (3) - ✓ uses dataset with (4) - ✓ uses dataset with (5) - ✓ uses dataset with ('ScopedDatasets/NestedDirector...ts.php') - ✓ the right dataset is taken - - PASS Tests\Features\ScopedDatasets\Directory\NestedDirectory2\TestFileInNestedDirectory - ✓ uses dataset with (1) - ✓ uses dataset with (2) - ✓ uses dataset with (3) - ✓ uses dataset with (4) - ✓ uses dataset with (5) - ✓ uses dataset with ('ScopedDatasets/Datasets/Scoped.php') - ✓ the right dataset is taken - - PASS Tests\Features\ScopedDatasets\Directory\TestFileWithLocallyDefinedDataset - ✓ uses dataset with (1) - ✓ uses dataset with (2) - ✓ uses dataset with (3) - ✓ uses dataset with (4) - ✓ uses dataset with (5) - ✓ uses dataset with ('ScopedDatasets/ScopedDatasets.php') - ✓ the right dataset is taken - - PASS Tests\Features\ScopedDatasets\Directory\TestFileWithScopedDataset - ✓ uses dataset with (1) - ✓ uses dataset with (2) - ✓ uses dataset with (3) - ✓ uses dataset with (4) - ✓ uses dataset with (5) - ✓ uses dataset with ('ScopedDatasets/Datasets/Scoped.php') - ✓ the right dataset is taken - - PASS Tests\Features\ScopedDatasets\TestFileOutOfScope - ✓ uses dataset with (1) - ✓ uses dataset with (2) - ✓ the right dataset is taken - WARN Tests\Features\Skip ✓ it do not skips - it skips with truthy → 1 @@ -803,7 +766,7 @@ PASS Tests\Unit\Console\Help ✓ it outputs the help information when --help is used - PASS Tests\Unit\DatasetsTests + PASS Tests\Unit\Datasets ✓ it show only the names of named datasets in their description ✓ it show the actual dataset of non-named datasets in their description ✓ it show only the names of multiple named datasets in their description @@ -829,26 +792,6 @@ ✓ it can resolve builtin value types ✓ it cannot resolve a parameter without type - PASS Tests\Unit\Support\DatasetInfo - ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datase...rs.php', true) - ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Datasets.php', false) - ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur...rs.php', true) - ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur...rs.php', false) - ✓ it can check if dataset is defined inside a Datasets directory with ('/var/www/project/tests/Featur...ts.php', false) - ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Datase...rs.php', false) - ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Datasets.php', true) - ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur...rs.php', false) #1 - ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur...rs.php', false) #2 - ✓ it can check if dataset is defined inside a Datasets.php file with ('/var/www/project/tests/Featur...ts.php', true) - ✓ it computes the dataset scope with ('/var/www/project/tests/Datase...rs.php', '/var/www/project/tests') - ✓ it computes the dataset scope with ('/var/www/project/tests/Datasets.php', '/var/www/project/tests') - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Features') - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Featur...rs.php') #1 - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur...ts.php', '/var/www/project/tests/Features') - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Featur...ollers') - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur...rs.php', '/var/www/project/tests/Featur...rs.php') #2 - ✓ it computes the dataset scope with ('/var/www/project/tests/Featur...ts.php', '/var/www/project/tests/Featur...ollers') - PASS Tests\Unit\Support\Reflection ✓ it gets file name from closure ✓ it gets property values @@ -880,4 +823,4 @@ PASS Tests\Visual\Version ✓ visual snapshot of help command output - Tests: 4 incomplete, 2 todos, 18 skipped, 611 passed (1521 assertions) \ No newline at end of file + Tests: 4 incomplete, 1 todo, 18 skipped, 567 passed (1465 assertions) \ No newline at end of file diff --git a/tests/Datasets/Numbers.php b/tests/Datasets/Numbers.php index 2b718699..fc2d81b7 100644 --- a/tests/Datasets/Numbers.php +++ b/tests/Datasets/Numbers.php @@ -13,3 +13,20 @@ dataset('numbers.closure.wrapped', function () { dataset('numbers.array', [[1], [2]]); dataset('numbers.array.wrapped', [1, 2]); + +dataset('numbers.generators.wrapped', function () { + yield from firstSetOfNumbers(); + yield from secondSetOfNumbers(); +}); + +function firstSetOfNumbers(): Generator +{ + yield 1; + yield 2; +} + +function secondSetOfNumbers(): Generator +{ + yield 3; + yield 4; +} diff --git a/tests/Features/DatasetsTests.php b/tests/Features/DatasetsTests.php index 65a12651..1698824b 100644 --- a/tests/Features/DatasetsTests.php +++ b/tests/Features/DatasetsTests.php @@ -230,6 +230,25 @@ test('more than two datasets did the job right', function () use ($state) { expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324135136145146235236245246'); }); +$wrapped_generator_state = new stdClass(); +$wrapped_generator_state->text = ''; +$wrapped_generator_function_datasets = [1, 2, 3, 4]; + +test( + 'eager registered wrapped datasets with Generator functions', + function (int $text) use ( + $wrapped_generator_state, + $wrapped_generator_function_datasets + ) { + $wrapped_generator_state->text .= $text; + expect(in_array($text, $wrapped_generator_function_datasets))->toBe(true); + } +)->with('numbers.generators.wrapped'); + +test('eager registered wrapped datasets with Generator functions did the job right', function () use ($wrapped_generator_state) { + expect($wrapped_generator_state->text)->toBe('1234'); +}); + it('can resolve a dataset after the test case is available', function ($result) { expect($result)->toBe('bar'); })->with([