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');