diff --git a/.github/workflows/analyse.yaml b/.github/workflows/analyse.yaml index d29d2c93a..d1c8b9f17 100644 --- a/.github/workflows/analyse.yaml +++ b/.github/workflows/analyse.yaml @@ -20,7 +20,7 @@ jobs: experimental: - false - name: PHP${{ matrix.php }} PHPStan & Pint + name: PHP:${{ matrix.php }} PHPStan & Pint steps: - name: Checkout code @@ -50,6 +50,7 @@ jobs: os: - "ubuntu-latest" php: + - 8.2 - 8.3 dependencies: - "highest" diff --git a/CHANGELOG-8.x.md b/CHANGELOG-8.x.md index 1e94cbf0a..21b2dd9e3 100644 --- a/CHANGELOG-8.x.md +++ b/CHANGELOG-8.x.md @@ -2,6 +2,21 @@ This changelog references the relevant changes (bug and security fixes) done to `orchestra/testbench-core`. +## 8.27.0 + +Released: 2024-08-26 + +### Added + +* Added `artisan` binary to Laravel skeleton. +* Added `Orchestra\Testbench\join_paths()` function. +* Added `Orchestra\Testbench\Attributes\UsesVendor` attribute class. +* Added `defineStashRoutes()` method to register adhoc route for test. + +### Changes + +* Improvements to `Orchestra\Testbench\default_skeleton_path()`, `Orchestra\Testbench\package_path()`, and `Orchestra\Testbench\workbench_path()` usage based on new `Orchestra\Testbench\join_paths()` function. + ## 8.26.0 Released: 2024-08-14 diff --git a/CHANGELOG-9.x.md b/CHANGELOG-9.x.md index 05e8e02f2..7f7d922db 100644 --- a/CHANGELOG-9.x.md +++ b/CHANGELOG-9.x.md @@ -2,6 +2,21 @@ This changelog references the relevant changes (bug and security fixes) done to `orchestra/testbench-core`. +## 9.4.0 + +Released: 2024-08-26 + +### Added + +* Added `artisan` binary to Laravel skeleton. +* Added `Orchestra\Testbench\join_paths()` function. +* Added `Orchestra\Testbench\Attributes\UsesVendor` attribute class. +* Added `defineStashRoutes()` method to register adhoc route for test. + +### Changes + +* Improvements to `Orchestra\Testbench\default_skeleton_path()`, `Orchestra\Testbench\package_path()`, and `Orchestra\Testbench\workbench_path()` usage based on new `Orchestra\Testbench\join_paths()` function. + ## 9.3.0 Released: 2024-08-14 diff --git a/bin/sync b/bin/sync index a2d7510aa..9c0f9e9f4 100755 --- a/bin/sync +++ b/bin/sync @@ -33,6 +33,7 @@ Symfony\Component\Process\Process::fromShellCommandline( )->mustRun(); Illuminate\Support\Collection::make([ + 'artisan', '.env.example', 'database/.gitignore', 'database/migrations/0001_01_01_000000_create_users_table.php', diff --git a/composer.json b/composer.json index 3f2bef71d..16493330b 100644 --- a/composer.json +++ b/composer.json @@ -53,6 +53,7 @@ "conflict": { "brianium/paratest": "<7.3.0 || >=8.0.0", "laravel/framework": "<12.0.0 || >=13.0.0", + "laravel/serializable-closure": "<1.3.0 || >=2.0.0", "nunomaduro/collision": "<8.0.0 || >=9.0.0", "phpunit/phpunit": "<10.5.0 || 11.0.0 || >=11.4.0" }, diff --git a/laravel/artisan b/laravel/artisan new file mode 100644 index 000000000..8e04b4224 --- /dev/null +++ b/laravel/artisan @@ -0,0 +1,15 @@ +#!/usr/bin/env php +handleCommand(new ArgvInput); + +exit($status); diff --git a/laravel/bootstrap/app.php b/laravel/bootstrap/app.php index abc488017..309c96471 100644 --- a/laravel/bootstrap/app.php +++ b/laravel/bootstrap/app.php @@ -1,10 +1,11 @@ make('router'); - -collect(glob(join_paths(__DIR__, '..', 'routes', 'testbench-*.php'))) - ->each(static function ($routeFile) use ($app, $router) { - require $routeFile; - }); +(new SyncTestbenchCachedRoutes)->bootstrap($app); return $app; diff --git a/laravel/composer.json b/laravel/composer.json index 735f9c08e..7fb39bf56 100644 --- a/laravel/composer.json +++ b/laravel/composer.json @@ -5,12 +5,15 @@ "license": "MIT", "type": "project", "autoload": { - "classmap": [ - "database", - "tests/TestCase.php" - ], "psr-4": { - "App\\": "app/" + "App\\": "app/", + "Database\\Factories\\": "database/factories/", + "Database\\Seeders\\": "database/seeders/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" } }, "extra": { diff --git a/src/Attributes/UsesVendor.php b/src/Attributes/UsesVendor.php new file mode 100644 index 000000000..ea24bc5da --- /dev/null +++ b/src/Attributes/UsesVendor.php @@ -0,0 +1,49 @@ +basePath('vendor'); + + $laravel = Application::createVendorSymlink(base_path(), package_path('vendor')); + + $this->vendorSymlinkCreated = $laravel['TESTBENCH_VENDOR_SYMLINK'] ?? false; + } + + /** + * Handle the attribute. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + public function afterEach($app): void + { + $vendorPath = $app->basePath('vendor'); + + if (is_link($vendorPath) && $this->vendorSymlinkCreated === true) { + $app['files']->delete($vendorPath); + } + } +} diff --git a/src/Bootstrap/HandleExceptions.php b/src/Bootstrap/HandleExceptions.php index 952e77a2d..f179ceb91 100644 --- a/src/Bootstrap/HandleExceptions.php +++ b/src/Bootstrap/HandleExceptions.php @@ -6,7 +6,7 @@ use Orchestra\Testbench\Exceptions\DeprecatedException; use Orchestra\Testbench\Foundation\Env; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; /** * @internal diff --git a/src/Bootstrap/LoadConfigurationWithWorkbench.php b/src/Bootstrap/LoadConfigurationWithWorkbench.php index 10e423027..6305709ff 100644 --- a/src/Bootstrap/LoadConfigurationWithWorkbench.php +++ b/src/Bootstrap/LoadConfigurationWithWorkbench.php @@ -40,7 +40,7 @@ public function __construct() #[\Override] protected function resolveConfigurationFile(string $path, string $key): string { - $config = workbench_path(['config', "{$key}.php"]); + $config = workbench_path('config', "{$key}.php"); return $this->usesWorkbenchConfigFile === true && is_file($config) ? $config : $path; } diff --git a/src/Bootstrap/LoadEnvironmentVariables.php b/src/Bootstrap/LoadEnvironmentVariables.php index 5891b4288..3079396ea 100644 --- a/src/Bootstrap/LoadEnvironmentVariables.php +++ b/src/Bootstrap/LoadEnvironmentVariables.php @@ -5,7 +5,7 @@ use Dotenv\Dotenv; use Orchestra\Testbench\Foundation\Env; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; /** * @internal diff --git a/src/Concerns/HandlesRoutes.php b/src/Concerns/HandlesRoutes.php index d1e819fd8..96fa6cf20 100644 --- a/src/Concerns/HandlesRoutes.php +++ b/src/Concerns/HandlesRoutes.php @@ -2,18 +2,27 @@ namespace Orchestra\Testbench\Concerns; +use Closure; use Illuminate\Filesystem\Filesystem; use Illuminate\Foundation\Application as LaravelApplication; +use Laravel\SerializableClosure\SerializableClosure; use Orchestra\Testbench\Attributes\DefineRoute; use Orchestra\Testbench\Features\TestingFeature; -use Orchestra\Testbench\Foundation\Application; +use Orchestra\Testbench\Foundation\Bootstrap\SyncTestbenchCachedRoutes; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; use function Orchestra\Testbench\refresh_router_lookups; use function Orchestra\Testbench\remote; trait HandlesRoutes { + /** + * Indicates if we have made it through the requireApplicationCachedRoutes function. + * + * @var bool + */ + protected $requireApplicationCachedRoutesHasRun = false; + /** * Setup routes requirements. * @@ -79,15 +88,29 @@ protected function defineWebRoutes($router) // Define routes. } + /** + * Define stash routes setup. + * + * @api + * + * @param \Closure|string $route + * @return void + */ + protected function defineStashRoutes(Closure|string $route): void + { + $this->defineCacheRoutes($route, false); + } + /** * Define cache routes setup. * * @api * - * @param string $route + * @param \Closure|string $route + * @param bool $cached * @return void */ - protected function defineCacheRoutes(string $route) + protected function defineCacheRoutes(Closure|string $route, bool $cached = true): void { $files = new Filesystem; @@ -98,21 +121,29 @@ protected function defineCacheRoutes(string $route) ? join_paths($basePath, '.laravel') : join_paths($basePath, 'bootstrap'); + if ($route instanceof Closure) { + $cached = false; + /** @var string $serializeRoute */ + $serializeRoute = serialize(SerializableClosure::unsigned($route)); + $stub = $files->get(join_paths(__DIR__, 'stubs', 'routes.stub')); + $route = str_replace('{{routes}}', var_export($serializeRoute, true), $stub); + } + $files->put( join_paths($basePath, 'routes', "testbench-{$time}.php"), $route ); - remote('route:cache')->mustRun(); + if ($cached === true) { + remote('route:cache')->mustRun(); - $this->assertTrue( - $files->exists(join_paths($bootstrapPath, 'cache', 'routes-v7.php')) - ); + \assert($files->exists(join_paths($bootstrapPath, 'cache', 'routes-v7.php')) === true); + } if ($this->app instanceof LaravelApplication) { $this->reloadApplication(); } - $this->requireApplicationCachedRoutes($files); + $this->requireApplicationCachedRoutes($files, $cached); } /** @@ -123,11 +154,21 @@ protected function defineCacheRoutes(string $route) * @param \Illuminate\Filesystem\Filesystem $files * @return void */ - protected function requireApplicationCachedRoutes(Filesystem $files): void + protected function requireApplicationCachedRoutes(Filesystem $files, bool $cached): void { - $this->afterApplicationCreated(function () { - if ($this->app instanceof LaravelApplication) { - require $this->app->getCachedRoutesPath(); + if ($this->requireApplicationCachedRoutesHasRun === true) { + return; + } + + $this->afterApplicationCreated(function () use ($cached) { + $app = $this->app; + + if ($app instanceof LaravelApplication) { + if ($cached === true) { + require $app->getCachedRoutesPath(); + } else { + (new SyncTestbenchCachedRoutes)->bootstrap($app); + } } }); @@ -141,5 +182,7 @@ protected function requireApplicationCachedRoutes(Filesystem $files): void sleep(1); }); + + $this->requireApplicationCachedRoutesHasRun = true; } } diff --git a/src/Concerns/InteractsWithPublishedFiles.php b/src/Concerns/InteractsWithPublishedFiles.php index 23bc1e132..9ee68b6fa 100644 --- a/src/Concerns/InteractsWithPublishedFiles.php +++ b/src/Concerns/InteractsWithPublishedFiles.php @@ -4,7 +4,7 @@ use Illuminate\Support\Collection; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; /** * @internal diff --git a/src/Concerns/stubs/routes.stub b/src/Concerns/stubs/routes.stub new file mode 100644 index 000000000..b725e524d --- /dev/null +++ b/src/Concerns/stubs/routes.stub @@ -0,0 +1,9 @@ +getClosure(), function ($serializedClosure) use ($router) { + if ($serializedClosure instanceof Closure) { + value($serializedClosure, $router); + } +}); diff --git a/src/Console/Commander.php b/src/Console/Commander.php index 7dfd79b45..d53e3e39e 100644 --- a/src/Console/Commander.php +++ b/src/Console/Commander.php @@ -23,7 +23,7 @@ use Symfony\Component\Console\SignalRegistry\SignalRegistry; use Throwable; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; use function Orchestra\Testbench\transform_relative_path; /** diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index 61a0de652..cfe47692c 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -26,7 +26,7 @@ use Orchestra\Testbench\Contracts\Config as ConfigContract; use Orchestra\Testbench\Workbench\Workbench; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; /** * @api diff --git a/src/Foundation/Bootstrap/CreateVendorSymlink.php b/src/Foundation/Bootstrap/CreateVendorSymlink.php index 9d01ab892..7a78a3c48 100644 --- a/src/Foundation/Bootstrap/CreateVendorSymlink.php +++ b/src/Foundation/Bootstrap/CreateVendorSymlink.php @@ -6,7 +6,7 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Filesystem\Filesystem; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; /** * @internal @@ -50,8 +50,10 @@ public function bootstrap(Application $app): void try { $filesystem->link($this->workingPath, $appVendorPath); + + $app->instance('TESTBENCH_VENDOR_SYMLINK', true); } catch (ErrorException) { - // + $app->instance('TESTBENCH_VENDOR_SYMLINK', false); } } diff --git a/src/Foundation/Bootstrap/SyncTestbenchCachedRoutes.php b/src/Foundation/Bootstrap/SyncTestbenchCachedRoutes.php new file mode 100644 index 000000000..2086c17b0 --- /dev/null +++ b/src/Foundation/Bootstrap/SyncTestbenchCachedRoutes.php @@ -0,0 +1,29 @@ +make('router'); + + /** @phpstan-ignore argument.type */ + Collection::make(glob($app->basePath(join_paths('routes', 'testbench-*.php')))) + ->each(static function ($routeFile) use ($app, $router) { // @phpstan-ignore closure.unusedUse, closure.unusedUse + require $routeFile; + }); + } +} diff --git a/src/Foundation/Bootstrap/SyncTestbenchConfigurationFiles.php b/src/Foundation/Bootstrap/SyncTestbenchConfigurationFiles.php new file mode 100644 index 000000000..135af6ca0 --- /dev/null +++ b/src/Foundation/Bootstrap/SyncTestbenchConfigurationFiles.php @@ -0,0 +1,44 @@ +exists($app->basePath('testbench.yaml'))) { + $this->copyTestbenchConfigurationFile($app, $filesystem, package_path()); + } + + if (! $filesystem->exists($app->basePath('.env'))) { + $this->copyTestbenchDotEnvFile($app, $filesystem, package_path()); + } + + $app->terminating(function () { + $this->handleTerminatingConsole(); + }); + } +} diff --git a/src/Foundation/Config.php b/src/Foundation/Config.php index 265fbbc22..00854c017 100644 --- a/src/Foundation/Config.php +++ b/src/Foundation/Config.php @@ -8,7 +8,7 @@ use Orchestra\Testbench\Contracts\Config as ConfigContract; use Symfony\Component\Yaml\Yaml; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; use function Orchestra\Testbench\parse_environment_variables; use function Orchestra\Testbench\transform_relative_path; diff --git a/src/Foundation/Console/Actions/EnsureDirectoryExists.php b/src/Foundation/Console/Actions/EnsureDirectoryExists.php index c5df0bfa9..d0a622877 100644 --- a/src/Foundation/Console/Actions/EnsureDirectoryExists.php +++ b/src/Foundation/Console/Actions/EnsureDirectoryExists.php @@ -6,8 +6,8 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\LazyCollection; -use function Illuminate\Filesystem\join_paths; use function Laravel\Prompts\confirm; +use function Orchestra\Testbench\join_paths; class EnsureDirectoryExists extends Action { diff --git a/src/Foundation/Console/Actions/GeneratesFile.php b/src/Foundation/Console/Actions/GeneratesFile.php index 4025c7900..147c02318 100644 --- a/src/Foundation/Console/Actions/GeneratesFile.php +++ b/src/Foundation/Console/Actions/GeneratesFile.php @@ -5,8 +5,8 @@ use Illuminate\Console\View\Components\Factory as ComponentsFactory; use Illuminate\Filesystem\Filesystem; -use function Illuminate\Filesystem\join_paths; use function Laravel\Prompts\confirm; +use function Orchestra\Testbench\join_paths; class GeneratesFile extends Action { diff --git a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php index de63f1b52..7793c7750 100644 --- a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php +++ b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php @@ -6,7 +6,7 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\LazyCollection; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; trait CopyTestbenchFiles { @@ -26,11 +26,9 @@ protected function copyTestbenchConfigurationFile(Application $app, Filesystem $ yield 'testbench.yaml'; yield 'testbench.yaml.example'; yield 'testbench.yaml.dist'; - })->map(static function ($file) use ($workingPath) { - return "{$workingPath}/{$file}"; - })->filter(static function ($file) use ($filesystem) { - return $filesystem->exists($file); - })->first(); + })->map(static fn ($file) => join_paths($workingPath, $file)) + ->filter(static fn ($file) => $filesystem->exists($file)) + ->first(); $testbenchFile = $app->basePath('testbench.yaml'); @@ -73,8 +71,8 @@ protected function copyTestbenchDotEnvFile(Application $app, Filesystem $filesys yield $this->environmentFile; yield "{$this->environmentFile}.example"; yield "{$this->environmentFile}.dist"; - })->map(fn ($file) => join_paths($workingPath, $file)) - ->filter(fn ($file) => $filesystem->exists($file)) + })->map(static fn ($file) => join_paths($workingPath, $file)) + ->filter(static fn ($file) => $filesystem->exists($file)) ->first(); if (\is_null($configurationFile) && $filesystem->exists($app->basePath('.env.example'))) { diff --git a/src/Foundation/Console/CreateSqliteDbCommand.php b/src/Foundation/Console/CreateSqliteDbCommand.php index de510631f..3b2578872 100644 --- a/src/Foundation/Console/CreateSqliteDbCommand.php +++ b/src/Foundation/Console/CreateSqliteDbCommand.php @@ -6,7 +6,7 @@ use Illuminate\Filesystem\Filesystem; use Symfony\Component\Console\Attribute\AsCommand; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; #[AsCommand(name: 'package:create-sqlite-db', description: 'Create sqlite database file')] class CreateSqliteDbCommand extends Command diff --git a/src/Foundation/Console/PurgeSkeletonCommand.php b/src/Foundation/Console/PurgeSkeletonCommand.php index fafdccdef..9263a34ea 100644 --- a/src/Foundation/Console/PurgeSkeletonCommand.php +++ b/src/Foundation/Console/PurgeSkeletonCommand.php @@ -9,7 +9,7 @@ use Orchestra\Testbench\Contracts\Config as ConfigContract; use Symfony\Component\Console\Attribute\AsCommand; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; #[AsCommand(name: 'package:purge-skeleton', description: 'Purge skeleton folder to original state')] class PurgeSkeletonCommand extends Command diff --git a/src/Foundation/Console/TestCommand.php b/src/Foundation/Console/TestCommand.php index 8127a1c84..bad132d8e 100644 --- a/src/Foundation/Console/TestCommand.php +++ b/src/Foundation/Console/TestCommand.php @@ -74,9 +74,10 @@ public function phpUnitConfigurationFile() $configurationFile = str_replace('./', '', $this->option('configuration') ?? 'phpunit.xml'); return Collection::make([ - package_path(DIRECTORY_SEPARATOR.$configurationFile), - package_path(DIRECTORY_SEPARATOR.$configurationFile.'.dist'), - ])->filter(static fn ($path) => file_exists($path)) + package_path($configurationFile), + package_path("{$configurationFile}.dist"), + ])->transform(static fn ($path) => DIRECTORY_SEPARATOR.$path) + ->filter(static fn ($path) => file_exists($path)) ->first() ?? './'; } diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index 6b62f3da4..4c79f0680 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -91,7 +91,7 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ $app->booted(static function ($app) use ($discoversConfig, $healthCheckEnabled) { tap($app->make('router'), static function (Router $router) use ($discoversConfig, $healthCheckEnabled) { if (($discoversConfig['api'] ?? false) === true) { - if (file_exists($route = workbench_path(['routes', 'api.php']))) { + if (file_exists($route = workbench_path('routes', 'api.php'))) { $router->middleware('api')->group($route); } } @@ -101,13 +101,13 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ Event::dispatch(new DiagnosingHealth); return View::file( - package_path(['vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'health-up.blade.php']) + package_path('vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'health-up.blade.php') ); }); } if (($discoversConfig['web'] ?? false) === true) { - if (file_exists($route = workbench_path(['routes', 'web.php']))) { + if (file_exists($route = workbench_path('routes', 'web.php'))) { $router->middleware('web')->group($route); } } @@ -122,7 +122,7 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ /** @var \Illuminate\Contracts\Translation\Loader $translator */ $path = Collection::make([ workbench_path('lang'), - workbench_path(['resources', 'lang']), + workbench_path('resources', 'lang'), ])->filter(static fn ($path) => is_dir($path)) ->first(); @@ -135,7 +135,7 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ after_resolving($app, 'view', static function ($view, $app) use ($discoversConfig) { /** @var \Illuminate\Contracts\View\Factory|\Illuminate\View\Factory $view */ - if (! is_dir($path = workbench_path(['resources', 'views']))) { + if (! is_dir($path = workbench_path('resources', 'views'))) { return; } @@ -153,7 +153,7 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ after_resolving($app, 'blade.compiler', static function ($blade) use ($discoversConfig) { /** @var \Illuminate\View\Compilers\BladeCompiler $blade */ - if (($discoversConfig['components'] ?? false) === false && is_dir(workbench_path(['app', 'View', 'Components']))) { + if (($discoversConfig['components'] ?? false) === false && is_dir(workbench_path('app', 'View', 'Components'))) { $blade->componentNamespace('Workbench\\App\\View\\Components', 'workbench'); } }); @@ -201,17 +201,17 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ */ public static function discoverCommandsRoutes(ApplicationContract $app): void { - if (file_exists($console = workbench_path(['routes', 'console.php']))) { + if (file_exists($console = workbench_path('routes', 'console.php'))) { require $console; } - if (! is_dir(workbench_path(['app', 'Console', 'Commands']))) { + if (! is_dir(workbench_path('app', 'Console', 'Commands'))) { return; } $namespace = 'Workbench\App'; - foreach ((new Finder)->in([workbench_path(['app', 'Console', 'Commands'])])->files() as $command) { + foreach ((new Finder)->in([workbench_path('app', 'Console', 'Commands')])->files() as $command) { $command = $namespace.str_replace( ['/', '.php'], ['\\', ''], @@ -253,7 +253,7 @@ public static function configuration(): ConfigContract public static function applicationConsoleKernel(): ?string { if (! isset(static::$cachedCoreBindings['kernel']['console'])) { - static::$cachedCoreBindings['kernel']['console'] = file_exists(workbench_path(['app', 'Console', 'Kernel.php'])) + static::$cachedCoreBindings['kernel']['console'] = file_exists(workbench_path('app', 'Console', 'Kernel.php')) ? 'Workbench\App\Console\Kernel' : null; } @@ -269,7 +269,7 @@ public static function applicationConsoleKernel(): ?string public static function applicationHttpKernel(): ?string { if (! isset(static::$cachedCoreBindings['kernel']['http'])) { - static::$cachedCoreBindings['kernel']['http'] = file_exists(workbench_path(['app', 'Http', 'Kernel.php'])) + static::$cachedCoreBindings['kernel']['http'] = file_exists(workbench_path('app', 'Http', 'Kernel.php')) ? 'Workbench\App\Http\Kernel' : null; } @@ -285,7 +285,7 @@ public static function applicationHttpKernel(): ?string public static function applicationExceptionHandler(): ?string { if (! isset(static::$cachedCoreBindings['handler']['exception'])) { - static::$cachedCoreBindings['handler']['exception'] = file_exists(workbench_path(['app', 'Exceptions', 'Handler.php'])) + static::$cachedCoreBindings['handler']['exception'] = file_exists(workbench_path('app', 'Exceptions', 'Handler.php')) ? 'Workbench\App\Exceptions\Handler' : null; } diff --git a/src/functions.php b/src/functions.php index e39ce8202..a8dfc7b0a 100644 --- a/src/functions.php +++ b/src/functions.php @@ -20,8 +20,6 @@ use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; -use function Illuminate\Filesystem\join_paths; - /** * Create Laravel application instance. * @@ -85,7 +83,7 @@ function remote(array|string $command, array|string $env = []): Process $binary = \defined('TESTBENCH_DUSK') ? 'testbench-dusk' : 'testbench'; - $commander = is_file($vendorBin = package_path(['vendor', 'bin', $binary])) + $commander = is_file($vendorBin = package_path('vendor', 'bin', $binary)) ? ProcessUtils::escapeArgument((string) $vendorBin) : $binary; @@ -241,7 +239,7 @@ function transform_relative_path(string $path, string $workingPath): string */ function default_skeleton_path(array|string $path = ''): string { - return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...Arr::wrap($path))); + return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))); } /** @@ -254,11 +252,17 @@ function default_skeleton_path(array|string $path = ''): string */ function package_path(array|string $path = ''): string { + $argumentCount = \func_num_args(); + $workingPath = \defined('TESTBENCH_WORKING_PATH') ? TESTBENCH_WORKING_PATH : Env::get('TESTBENCH_WORKING_PATH', getcwd()); - $path = join_paths(...Arr::wrap($path)); + if ($argumentCount === 1 && \is_string($path) && str_starts_with($path, './')) { + return transform_relative_path($path, $workingPath); + } + + $path = join_paths(...Arr::wrap($argumentCount > 1 ? \func_get_args() : $path)); return str_starts_with($path, './') ? transform_relative_path($path, $workingPath) @@ -292,7 +296,7 @@ function workbench(): array */ function workbench_path(array|string $path = ''): string { - return package_path(join_paths('workbench', ...Arr::wrap($path))); + return package_path('workbench', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path)); } /** @@ -362,3 +366,23 @@ function phpunit_version_compare(string $version, ?string $operator = null): int return version_compare(Version::id(), $version, $operator); } + +/** + * Join the given paths together. + * + * @param string|null $basePath + * @param string ...$paths + * @return string + */ +function join_paths(?string $basePath, string ...$paths): string +{ + foreach ($paths as $index => $path) { + if (empty($path) && $path !== '0') { + unset($paths[$index]); + } else { + $paths[$index] = DIRECTORY_SEPARATOR.ltrim($path, DIRECTORY_SEPARATOR); + } + } + + return $basePath.implode('', $paths); +} diff --git a/tests/Attributes/UsesVendorTest.php b/tests/Attributes/UsesVendorTest.php new file mode 100644 index 000000000..2a67f947d --- /dev/null +++ b/tests/Attributes/UsesVendorTest.php @@ -0,0 +1,26 @@ +assertSame( + $filesystem->hash(base_path(join_paths('vendor', 'autoload.php'))), + $filesystem->hash(package_path('vendor', 'autoload.php')) + ); + } +} diff --git a/tests/Foundation/Console/Actions/EnsureDirectoryExistsTest.php b/tests/Foundation/Console/Actions/EnsureDirectoryExistsTest.php index 630336d29..58ca515ae 100644 --- a/tests/Foundation/Console/Actions/EnsureDirectoryExistsTest.php +++ b/tests/Foundation/Console/Actions/EnsureDirectoryExistsTest.php @@ -8,7 +8,7 @@ use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\Test; -use function Illuminate\Filesystem\join_paths; +use function Orchestra\Testbench\join_paths; class EnsureDirectoryExistsTest extends TestCase { diff --git a/tests/Helpers/PackagePathTest.php b/tests/Helpers/PackagePathTest.php index b14cf622b..e7becb6a9 100644 --- a/tests/Helpers/PackagePathTest.php +++ b/tests/Helpers/PackagePathTest.php @@ -3,21 +3,27 @@ namespace Orchestra\Testbench\Tests\Helpers; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; use function Illuminate\Filesystem\join_paths; use function Orchestra\Testbench\package_path; +#[Group('workbench')] class PackagePathTest extends TestCase { #[Test] - #[Group('workbench')] public function it_can_use_package_path() { $this->assertSame(realpath(__DIR__.'/../../'), package_path()); $this->assertSame(implode('', [realpath(__DIR__.'/../../'), DIRECTORY_SEPARATOR]), package_path(DIRECTORY_SEPARATOR)); + } + #[Test] + #[DataProvider(('pathDataProvider'))] + public function it_can_resolve_correct_package_path(string $path) + { $this->assertSame( realpath(join_paths(__DIR__, 'PackagePathTest.php')), package_path(join_paths('./tests', 'Helpers', 'PackagePathTest.php')) @@ -38,4 +44,16 @@ public function it_can_use_package_path() package_path(join_paths('tests', 'Helpers', 'PackagePathTest.php')) ); } + + public static function pathDataProvider() + { + yield [package_path('tests'.DIRECTORY_SEPARATOR.'Helpers'.DIRECTORY_SEPARATOR.'PackagePathTest.php')]; + yield [package_path('./tests'.DIRECTORY_SEPARATOR.'Helpers'.DIRECTORY_SEPARATOR.'PackagePathTest.php')]; + yield [package_path(DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'Helpers'.DIRECTORY_SEPARATOR.'PackagePathTest.php')]; + + yield [package_path('tests', 'Helpers', 'PackagePathTest.php')]; + yield [package_path(['tests', 'Helpers', 'PackagePathTest.php'])]; + yield [package_path('./tests', 'Helpers', 'PackagePathTest.php')]; + yield [package_path(['./tests', 'Helpers', 'PackagePathTest.php'])]; + } } diff --git a/tests/Integrations/ArtisanTest.php b/tests/Integrations/ArtisanTest.php new file mode 100644 index 000000000..00af58218 --- /dev/null +++ b/tests/Integrations/ArtisanTest.php @@ -0,0 +1,31 @@ +find(); + + $remote = remote('--version --no-ansi')->mustRun(); + + $artisan = (new Process( + command: [$phpBinary, 'artisan', '--version', '--no-ansi'], + cwd: package_path('laravel'), + ))->mustRun(); + + $this->assertSame(json_decode($artisan->getOutput(), true), json_decode($remote->getOutput(), true)); + } +} diff --git a/tests/Integrations/RequestTest.php b/tests/Integrations/RequestTest.php index 9b929682d..60e6272f1 100644 --- a/tests/Integrations/RequestTest.php +++ b/tests/Integrations/RequestTest.php @@ -10,12 +10,8 @@ #[WithConfig('app.key', 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF')] class RequestTest extends TestCase { - /** - * Define routes setup. - * - * @param \Illuminate\Routing\Router $router - * @return void - */ + /** {@inheritDoc} */ + #[\Override] protected function defineRoutes($router) { $router->get('hello', ['uses' => fn () => 'hello world']); diff --git a/tests/Integrations/StashRouteTest.php b/tests/Integrations/StashRouteTest.php new file mode 100644 index 000000000..76b8cfcf9 --- /dev/null +++ b/tests/Integrations/StashRouteTest.php @@ -0,0 +1,29 @@ +defineStashRoutes(function () { + Route::get('stubs-controller', 'Workbench\App\Http\Controllers\ExampleController@index'); + }); + + parent::setUp(); + } + + #[Test] + public function it_can_cache_route() + { + $this->get('stubs-controller') + ->assertOk() + ->assertSee('ExampleController@index'); + } +} diff --git a/tests/Workbench/HelpersTest.php b/tests/Workbench/HelpersTest.php index e239bf540..8704279e2 100644 --- a/tests/Workbench/HelpersTest.php +++ b/tests/Workbench/HelpersTest.php @@ -83,9 +83,21 @@ public function it_can_resolve_workbench_without_bound() #[Group('workbench')] public function it_can_resolve_workbench_path() { + $expected = realpath(package_path('workbench/database/migrations/2013_07_26_182750_create_testbench_users_table.php')); + $this->assertSame( realpath(package_path('workbench/database/migrations/2013_07_26_182750_create_testbench_users_table.php')), - workbench_path(join_paths('database', 'migrations', '2013_07_26_182750_create_testbench_users_table.php')) + workbench_path(join_paths('database'.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR.'2013_07_26_182750_create_testbench_users_table.php')) + ); + + $this->assertSame( + $expected, + workbench_path('database', 'migrations', '2013_07_26_182750_create_testbench_users_table.php') + ); + + $this->assertSame( + $expected, + workbench_path(['database', 'migrations', '2013_07_26_182750_create_testbench_users_table.php']) ); } }