diff --git a/TODO.md b/TODO.md index 9f20487a..edb7104e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,6 @@ 1. TeamCity 2. Junit -3. Help display -4. Thanks display -5. Check all plugins -6. Parallel testing. -7. Finish Collision's todo +3. Thanks display +4. Check all plugins +5. Parallel testing. +6. Finish Collision's todo diff --git a/composer.json b/composer.json index 8f401627..f90104d9 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "require": { "php": "^8.1.0", "nunomaduro/collision": "dev-next", - "pestphp/pest-plugin": "^1.0.0", + "nunomaduro/termwind": "^1.14", + "pestphp/pest-plugin": "dev-master", "phpunit/phpunit": "10.0.x-dev" }, "autoload": { @@ -95,6 +96,7 @@ "Pest\\Plugins\\Coverage", "Pest\\Plugins\\Init", "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", "Pest\\Plugins\\Memory", "Pest\\Plugins\\Printer", "Pest\\Plugins\\Retry", diff --git a/resources/views/components/two-column-detail.php b/resources/views/components/two-column-detail.php new file mode 100644 index 00000000..152f20b5 --- /dev/null +++ b/resources/views/components/two-column-detail.php @@ -0,0 +1,12 @@ +
+ + + + + + + + + +
+ diff --git a/resources/views/usage.php b/resources/views/usage.php new file mode 100644 index 00000000..e52db575 --- /dev/null +++ b/resources/views/usage.php @@ -0,0 +1,4 @@ +
+ USAGE:pest') ?> [options] +
+ diff --git a/resources/views/version.php b/resources/views/version.php new file mode 100644 index 00000000..2457bc3c --- /dev/null +++ b/resources/views/version.php @@ -0,0 +1,3 @@ +
+ Pest Testing Framework. +
diff --git a/src/Bootstrappers/BootFiles.php b/src/Bootstrappers/BootFiles.php index f19aca40..765d0843 100644 --- a/src/Bootstrappers/BootFiles.php +++ b/src/Bootstrappers/BootFiles.php @@ -59,16 +59,18 @@ final class BootFiles } /** - * Loads the given filename, if possible. + * Loads, if possible, the given file. */ private function load(string $filename): void { if (! Str::endsWith($filename, '.php')) { return; } + if (! file_exists($filename)) { return; } + include_once $filename; } } diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index 5163b77c..815c7821 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -6,6 +6,7 @@ namespace Pest\Bootstrappers; use Pest\Subscribers; use PHPUnit\Event; +use PHPUnit\Event\Subscriber; /** * @internal @@ -15,7 +16,7 @@ final class BootSubscribers /** * The Kernel subscribers. * - * @var array> + * @var array> */ private const SUBSCRIBERS = [ Subscribers\EnsureConfigurationIsValid::class, diff --git a/src/Bootstrappers/BootView.php b/src/Bootstrappers/BootView.php new file mode 100644 index 00000000..226b7982 --- /dev/null +++ b/src/Bootstrappers/BootView.php @@ -0,0 +1,28 @@ +output); + } +} diff --git a/src/Contracts/Plugins/Bootable.php b/src/Contracts/Plugins/Bootable.php new file mode 100644 index 00000000..00cadbb6 --- /dev/null +++ b/src/Contracts/Plugins/Bootable.php @@ -0,0 +1,16 @@ +shutdown(); + }); } /** @@ -38,9 +49,11 @@ final class Kernel public static function boot(): self { foreach (self::BOOTSTRAPPERS as $bootstrapper) { - (new $bootstrapper())->__invoke(); + Container::getInstance()->get($bootstrapper)->__invoke(); } + (new CallsBoot())->__invoke(); + return new self(new Application()); } @@ -53,13 +66,13 @@ final class Kernel */ public function handle(array $argv): int { - $argv = (new Plugins\Actions\HandleArguments())->__invoke($argv); + $argv = (new Plugins\Actions\CallsHandleArguments())->__invoke($argv); $this->application->run( $argv, false, ); - return (new Plugins\Actions\AddsOutput())->__invoke( + return (new CallsAddsOutput())->__invoke( Result::exitCode(), ); } @@ -69,6 +82,6 @@ final class Kernel */ public function shutdown(): void { - // .. + (new CallsShutdown())->__invoke(); } } diff --git a/src/Plugins/Actions/AddsOutput.php b/src/Plugins/Actions/CallsAddsOutput.php similarity index 95% rename from src/Plugins/Actions/AddsOutput.php rename to src/Plugins/Actions/CallsAddsOutput.php index fefac940..a8b59f50 100644 --- a/src/Plugins/Actions/AddsOutput.php +++ b/src/Plugins/Actions/CallsAddsOutput.php @@ -10,7 +10,7 @@ use Pest\Plugin\Loader; /** * @internal */ -final class AddsOutput +final class CallsAddsOutput { /** * Executes the Plugin action. diff --git a/src/Plugins/Actions/CallsBoot.php b/src/Plugins/Actions/CallsBoot.php new file mode 100644 index 00000000..e498d945 --- /dev/null +++ b/src/Plugins/Actions/CallsBoot.php @@ -0,0 +1,29 @@ +boot(); + } + } +} diff --git a/src/Plugins/Actions/HandleArguments.php b/src/Plugins/Actions/CallsHandleArguments.php similarity index 95% rename from src/Plugins/Actions/HandleArguments.php rename to src/Plugins/Actions/CallsHandleArguments.php index 6b466e45..5d7326a7 100644 --- a/src/Plugins/Actions/HandleArguments.php +++ b/src/Plugins/Actions/CallsHandleArguments.php @@ -10,7 +10,7 @@ use Pest\Plugin\Loader; /** * @internal */ -final class HandleArguments +final class CallsHandleArguments { /** * Executes the Plugin action. diff --git a/src/Plugins/Actions/CallsShutdown.php b/src/Plugins/Actions/CallsShutdown.php new file mode 100644 index 00000000..4bbb7d8e --- /dev/null +++ b/src/Plugins/Actions/CallsShutdown.php @@ -0,0 +1,29 @@ +shutdown(); + } + } +} diff --git a/src/Plugins/Help.php b/src/Plugins/Help.php new file mode 100644 index 00000000..e8d814c5 --- /dev/null +++ b/src/Plugins/Help.php @@ -0,0 +1,105 @@ +hasArgument('--help', $arguments)) { + View::render('version', [ + 'version' => version(), + ]); + + View::render('usage'); + + foreach ($this->getContent() as $title => $options) { + if ($title === 'Usage') { + continue; + } + + $this->output->writeln([ + '', + sprintf(' %s:', mb_strtoupper($title)), + ]); + + foreach ($options as $option) { + if (! array_key_exists('arg', $option)) { + continue; + } + + [ + 'arg' => $argument, + 'desc' => $description, + ] = $option; + + View::render('components.two-column-detail', [ + 'left' => $argument, + 'right' => $description, + ]); + } + } + + $this->output->write('', true); + + exit(0); + } + + return $arguments; + } + + /** + * @return array> + */ + private function getContent(): array + { + // Access the PHPUnit help class's private const HELP + $helpReflection = new \ReflectionClass(PHPUnitHelp::class); + + /** @var array> $content */ + $content = $helpReflection->getConstant('HELP_TEXT'); + + $content['Configuration'] = [[ + 'arg' => '--init', + 'desc' => 'Initialise a standard Pest configuration', + ]] + $content['Configuration']; + + $content['Code Coverage'] = [ + [ + 'arg' => '--coverage ', + 'desc' => 'Generate code coverage report and output to standard output', + ], + [ + 'arg' => '--coverage --min', + 'desc' => 'Set the minimum required coverage percentage, and fail if not met', + ], + ] + $content['Code Coverage']; + + return $content; + } +} diff --git a/src/Plugins/Version.php b/src/Plugins/Version.php index 943909fb..8422cb0d 100644 --- a/src/Plugins/Version.php +++ b/src/Plugins/Version.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Pest\Plugins; use Pest\Contracts\Plugins\HandlesArguments; +use Pest\Support\View; use function Pest\version; -use Symfony\Component\Console\Output\OutputInterface; /** * @internal @@ -15,24 +15,17 @@ final class Version implements HandlesArguments { use Concerns\HandleArguments; - /** - * Creates a new Plugin instance. - */ - public function __construct( - private readonly OutputInterface $output - ) { - // .. - } - /** * {@inheritDoc} */ public function handleArguments(array $arguments): array { if ($this->hasArgument('--version', $arguments)) { - $this->output->writeln( - sprintf('Pest %s', version()), - ); + View::render('version', [ + 'version' => version(), + ]); + + exit(0); } return $arguments; diff --git a/src/Support/Container.php b/src/Support/Container.php index f5b41187..8f865ce9 100644 --- a/src/Support/Container.php +++ b/src/Support/Container.php @@ -35,16 +35,21 @@ final class Container /** * Gets a dependency from the container. * - * @param class-string $id - * @return mixed + * @template TObject of object + * + * @param class-string $id + * @return TObject */ - public function get(string $id) + public function get(string $id): mixed { if (! array_key_exists($id, $this->instances)) { $this->instances[$id] = $this->build($id); } - return $this->instances[$id]; + /** @var TObject $concrete */ + $concrete = $this->instances[$id]; + + return $concrete; } /** @@ -58,9 +63,12 @@ final class Container /** * Tries to build the given instance. * - * @param class-string $id + * @template TObject of object + * + * @param class-string $id + * @return TObject */ - private function build(string $id): object + private function build(string $id): mixed { $reflectionClass = new ReflectionClass($id); diff --git a/src/Support/View.php b/src/Support/View.php new file mode 100644 index 00000000..9d5823a8 --- /dev/null +++ b/src/Support/View.php @@ -0,0 +1,71 @@ + $data + */ + public static function render(string $path, array $data = []): void + { + $contents = self::compile($path, $data); + + $existing = Termwind::getRenderer(); + + renderUsing(self::$output); + + try { + render($contents); + } finally { + renderUsing($existing); + } + } + + /** + * Compiles the given view. + * + * @param array $data + */ + private static function compile(string $path, array $data): string + { + extract($data); + + ob_start(); + + $path = str_replace('.', '/', $path); + + include sprintf('%s/../../resources/views/%s.php', __DIR__, $path); + + $contents = ob_get_contents(); + + ob_clean(); + + return (string) $contents; + } +} diff --git a/tests/.snapshots/help-command.txt b/tests/.snapshots/help-command.txt index 203e8d07..e4d8166f 100644 --- a/tests/.snapshots/help-command.txt +++ b/tests/.snapshots/help-command.txt @@ -1,5 +1,103 @@ -Pest Options: - --init Initialise a standard Pest configuration - --coverage Enable coverage and output to standard output - --min= Set the minimum required coverage percentage (), and fail if not met - --group= Only runs tests from the specified group(s) + + Pest Testing Framework 2.x-dev. + + USAGE: pest [options] + + CONFIGURATION: + --init ............................ Initialise a standard Pest configuration + -c|--configuration ....................... Read configuration from XML file + --no-configuration ......... Ignore default configuration file (phpunit.xml) + --no-extensions ............................. Do not load PHPUnit extensions + --include-path ..... Prepend PHP's include_path with given path(s) + -d ...................................... Sets a php.ini value + --cache-directory ................................. Specify cache directory + --generate-configuration Generate configuration file with suggested settings + --migrate-configuration ....... Migrate configuration file to current format + + SELECTION: + --list-suites ................................... List available test suites + --testsuite ............... Only run tests from the specified test suite(s) + --exclude-testsuite ........ Exclude tests from the specified test suite(s) + --list-groups ................................... List available test groups + --group ........................ Only run tests from the specified group(s) + --exclude-group ................. Exclude tests from the specified group(s) + --covers ......................... Only run tests annotated with "@covers " + --uses ............................. Only run tests annotated with "@uses " + --list-tests .......................................... List available tests + --list-tests-xml ....................... List available tests in XML format + --filter ........................................ Filter which tests to run + --test-suffix Only search for test in files with specified suffix(es). Default: Test.php,.phpt + + EXECUTION: + --process-isolation ................ Run each test in a separate PHP process + --globals-backup ................. Backup and restore $GLOBALS for each test + --static-backup ......... Backup and restore static properties for each test + --strict-coverage . Be strict about code coverage attributes and annotations + --strict-global-state .............. Be strict about changes to global state + --disallow-test-output ................. Be strict about output during tests + --enforce-time-limit ................. Enforce time limit based on test size + --default-time-limit Timeout in seconds for tests that have no declared size + --dont-report-useless-tests .. Do not report tests that do not test anything + --stop-on-defect ................. Stop execution upon first not-passed test + --stop-on-error ............................ Stop execution upon first error + --stop-on-failure ............... Stop execution upon first error or failure + --stop-on-warning ........................ Stop execution upon first warning + --stop-on-risky ....................... Stop execution upon first risky test + --stop-on-skipped ................... Stop execution upon first skipped test + --stop-on-incomplete ............. Stop execution upon first incomplete test + --fail-on-incomplete .................... Treat incomplete tests as failures + --fail-on-risky .............................. Treat risky tests as failures + --fail-on-skipped .......................... Treat skipped tests as failures + --fail-on-warning .................... Treat tests with warnings as failures + --repeat ...................................... Runs the test(s) repeatedly + --cache-result ............................ Write test results to cache file + --do-not-cache-result .............. Do not write test results to cache file + --order-by Run tests in order: default|defects|duration|no-depends|random|reverse|size + --random-order-seed .......... Use a specific random seed for random order + + REPORTING: + --colors ............... Use colors in output ("never", "auto" or "always") + --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 + --display-incomplete .................. Display details for incomplete tests + --display-skipped ........................ Display details for skipped tests + --display-deprecations . Display details for deprecations triggered by tests + --display-errors ............. Display details for errors triggered by tests + --display-notices ........... Display details for notices triggered by tests + --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 + + LOGGING: + --log-junit ................ Log test execution in JUnit XML format to file + --log-teamcity .............. Log test execution in TeamCity format to file + --testdox-html ........... Write agile documentation in HTML format to file + --testdox-text ........... Write agile documentation in Text format to file + --testdox-xml ............. Write agile documentation in XML format to file + --log-events-text ..................... Stream events as plain text to file + --log-events-verbose-text Stream events as plain text to file (with telemetry information) + --no-logging .................................. Ignore logging configuration + + CODE COVERAGE: + --coverage ..... Generate code coverage report and output to standard output + --coverage --min Set the minimum required coverage percentage, and fail if not met + --coverage-crap4j ...... Generate code coverage report in Crap4J XML format + --coverage-html .............. Generate code coverage report in HTML format + --coverage-php ..................... Export PHP_CodeCoverage object to file + --coverage-text= Generate code coverage report in text format [default: standard output] + --coverage-xml ........ Generate code coverage report in PHPUnit XML format + --warm-coverage-cache ........................... Warm static analysis cache + --coverage-filter ...................... Include in code coverage analysis + --path-coverage ............................. Perform path coverage analysis + --disable-coverage-ignore Disable attributes and annotations for ignoring code coverage + --no-coverage ........................... Ignore code coverage configuration + + MISCELLANEOUS: + -h|--help .................................... Prints this usage information + --version ..................................... Prints the version and exits + --atleast-version ....... Checks that version is greater than min and exits + --check-version ................ Check whether PHPUnit is the latest version + diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index cad084d6..f54d21fa 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -729,10 +729,6 @@ PASS Tests\Unit\Plugins\Retry ✓ it retries if --retry argument is used - PASS Tests\Unit\Plugins\Version - ✓ it outputs the version when --version is used - ✓ it do not outputs version when --version is not used - PASS Tests\Unit\Support\Backtrace ✓ it gets file name from called file @@ -756,8 +752,8 @@ ✓ it can filter the test suite filenames to those with the only method ✓ it does not filter the test suite filenames to those with the only method when working in CI pipeline - WARN Tests\Visual\Help - - visual snapshot of help command output → Not supported yet. + PASS Tests\Visual\Help + ✓ visual snapshot of help command output WARN Tests\Visual\JUnit - it is can successfully call all public methods → Not supported yet. @@ -773,4 +769,7 @@ WARN Tests\Visual\TeamCity - it is can successfully call all public methods → Not supported yet. - Tests: 4 incomplete, 1 todo, 19 skipped, 514 passed (1289 assertions) + PASS Tests\Visual\Version + ✓ visual snapshot of help command output + + Tests: 4 incomplete, 1 todo, 18 skipped, 514 passed (1289 assertions) diff --git a/tests/.snapshots/version-command.txt b/tests/.snapshots/version-command.txt new file mode 100644 index 00000000..df653c1c --- /dev/null +++ b/tests/.snapshots/version-command.txt @@ -0,0 +1,3 @@ + + Pest Testing Framework 2.x-dev. + diff --git a/tests/Features/Coverage.php b/tests/Features/Coverage.php index 8f06d34b..06246a2e 100644 --- a/tests/Features/Coverage.php +++ b/tests/Features/Coverage.php @@ -11,19 +11,19 @@ it('adds coverage if --coverage exist', function () { expect($plugin->coverage)->toBeFalse(); $arguments = $plugin->handleArguments([]); - expect($arguments)->toEqual([]); - expect($plugin->coverage)->toBeFalse(); + expect($arguments)->toEqual([]) + ->and($plugin->coverage)->toBeFalse(); $arguments = $plugin->handleArguments(['--coverage']); - expect($arguments)->toEqual(['--coverage-php', Coverage::getPath()]); - expect($plugin->coverage)->toBeTrue(); + expect($arguments)->toEqual(['--coverage-php', Coverage::getPath()]) + ->and($plugin->coverage)->toBeTrue(); })->skip(! \Pest\Support\Coverage::isAvailable() || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available'); it('adds coverage if --min exist', function () { $plugin = new CoveragePlugin(new ConsoleOutput()); - expect($plugin->coverageMin)->toEqual(0.0); + expect($plugin->coverageMin)->toEqual(0.0) + ->and($plugin->coverage)->toBeFalse(); - expect($plugin->coverage)->toBeFalse(); $plugin->handleArguments([]); expect($plugin->coverageMin)->toEqual(0.0); diff --git a/tests/Unit/Plugins/Version.php b/tests/Unit/Plugins/Version.php deleted file mode 100644 index d79fe7c8..00000000 --- a/tests/Unit/Plugins/Version.php +++ /dev/null @@ -1,21 +0,0 @@ -handleArguments(['foo', '--version']); - expect($output->fetch())->toContain('Pest '.version()); -}); - -it('do not outputs version when --version is not used', function () { - $output = new BufferedOutput(); - $plugin = new Version($output); - - $plugin->handleArguments(['foo', 'bar']); - expect($output->fetch())->toBe(''); -}); diff --git a/tests/Visual/Help.php b/tests/Visual/Help.php index 3b0463f7..9066f730 100644 --- a/tests/Visual/Help.php +++ b/tests/Visual/Help.php @@ -1,20 +1,8 @@ fetch()); - } - $output = function () { $process = (new Symfony\Component\Process\Process(['php', 'bin/pest', '--help'], null, ['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'])); @@ -23,5 +11,11 @@ test('visual snapshot of help command output', function () { return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput()); }; + if (getenv('REBUILD_SNAPSHOTS')) { + file_put_contents($snapshot, $output()); + + $this->markTestSkipped('Snapshot rebuilt.'); + } + expect($output())->toContain(file_get_contents($snapshot)); -})->skip(PHP_OS_FAMILY === 'Windows')->skip('Not supported yet.'); +})->skip(PHP_OS_FAMILY === 'Windows'); diff --git a/tests/Visual/Version.php b/tests/Visual/Version.php new file mode 100644 index 00000000..bdc2f514 --- /dev/null +++ b/tests/Visual/Version.php @@ -0,0 +1,21 @@ + 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'])); + + $process->run(); + + return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput()); + }; + + if (getenv('REBUILD_SNAPSHOTS')) { + file_put_contents($snapshot, $output()); + + $this->markTestSkipped('Snapshot rebuilt.'); + } + + expect($output())->toContain(file_get_contents($snapshot)); +})->skip(PHP_OS_FAMILY === 'Windows');