diff --git a/.github/workflows/parallel-tests.yaml b/.github/workflows/parallel-tests.yaml index c5245fa7b..c8f76bfe1 100644 --- a/.github/workflows/parallel-tests.yaml +++ b/.github/workflows/parallel-tests.yaml @@ -58,11 +58,3 @@ jobs: run: ./testbench package:test --parallel --exclude-group commander,without-parallel,database,session --no-coverage env: RAY_ENABLED: false - if: matrix.dependencies == 'highest' - - - name: Execute tests (without deprecations) - run: ./testbench package:test --parallel --exclude-group commander,without-parallel,database,session,deprecations --no-coverage - env: - RAY_ENABLED: false - TESTBENCH_CONVERT_DEPRECATIONS_TO_EXCEPTIONS: false - if: matrix.dependencies != 'highest' diff --git a/CHANGELOG-8.x.md b/CHANGELOG-8.x.md index 21b2dd9e3..323cfa49d 100644 --- a/CHANGELOG-8.x.md +++ b/CHANGELOG-8.x.md @@ -2,6 +2,43 @@ This changelog references the relevant changes (bug and security fixes) done to `orchestra/testbench-core`. +## 8.28.1 + +Released: 2024-09-24 + +### Fixes + +* Fixes compatibility with PHPUnit 10.3. + +## 8.28.0 + +Released: 2024-09-23 + +### Added + +* Added `markTestSkippedWhen()` and `markTestSkippedUnless()` assertion helper to conditionally handle `markTestSkipped()`. +* Added `Orchestra\Testbench\default_migration_path()` helper function. +* Added `Orchestra\Testbench\laravel_vendor_exists()` helper function. +* Allows TestCase to inherit Attributes defined on parent TestCase by @BlackLanzer in #233. + +### Changes + +* Allow Testbench to delete `vendor` symlink directory if it was created while running tests. + +### Fixes + +* Fixes `view.paths` configuration not being updated to include `workbench/resources/views` due to IoC booting sequence. + +### Deprecated + +* Deprecated `Orchestra\Testbench\laravel_migration_path()`, use `default_migration_path()` instead. + + + ## 8.27.0 Released: 2024-08-26 diff --git a/CHANGELOG-9.x.md b/CHANGELOG-9.x.md index 7f7d922db..66a7964ca 100644 --- a/CHANGELOG-9.x.md +++ b/CHANGELOG-9.x.md @@ -2,6 +2,44 @@ This changelog references the relevant changes (bug and security fixes) done to `orchestra/testbench-core`. +## 9.5.0 + +Released: 2024-09-23 + +### Added + +* Added `Orchestra\Testbench\Attributes\RequiresDatabase` attribute class. +* Added `markTestSkippedWhen()` and `markTestSkippedUnless()` assertion helper to conditionally handle `markTestSkipped()`. +* Added `Orchestra\Testbench\default_migration_path()` helper function. +* Added `Orchestra\Testbench\laravel_vendor_exists()` helper function. +* Allows TestCase to inherit Attributes defined on parent TestCase by @BlackLanzer in #233. + +### Changes + +* Allow Testbench to delete `vendor` symlink directory if it was created while running tests. + +### Fixes + +* Fixes `view.paths` configuration not being updated to include `workbench/resources/views` due to IoC booting sequence. + +### Deprecated + +* Deprecated `Orchestra\Testbench\laravel_migration_path()`, use `default_migration_path()` instead. + + + +## 9.4.1 + +Released: 2024-09-12 + +### Changes + +* Add `concurrency.php` configuration based on Laravel Framework 11.23. + ## 9.4.0 Released: 2024-08-26 diff --git a/laravel/config/concurrency.php b/laravel/config/concurrency.php new file mode 100644 index 000000000..1d66b701f --- /dev/null +++ b/laravel/config/concurrency.php @@ -0,0 +1,20 @@ + env('CONCURRENCY_DRIVER', 'process'), + +]; diff --git a/src/Attributes/RequiresDatabase.php b/src/Attributes/RequiresDatabase.php new file mode 100644 index 000000000..df781c851 --- /dev/null +++ b/src/Attributes/RequiresDatabase.php @@ -0,0 +1,92 @@ +default = true; + } + + if (\is_array($driver) && $default === true) { + throw new InvalidArgumentException('Unable to validate default connection when given an array of database drivers'); + } + } + + /** + * Handle the attribute. + * + * @param \Illuminate\Foundation\Application $app + * @param \Closure(string, array):void $action + * @return void + */ + public function handle($app, Closure $action): void + { + $connection = DB::connection($this->connection); + + if ( + ($this->default ?? false) === true + && \is_string($this->driver) + && $connection->getDriverName() !== $this->driver + ) { + \call_user_func($action, 'markTestSkipped', [\sprintf('Requires %s to configured for "%s" database connection', $this->driver, $connection->getName())]); + + return; + } + + $drivers = Collection::make( + Arr::wrap($this->driver) + )->filter(fn ($driver) => $driver === $connection->getDriverName()); + + if ($drivers->isEmpty()) { + \call_user_func( + $action, + 'markTestSkipped', + [\sprintf('Requires [%s] to be configured for "%s" database connection', Arr::join(Arr::wrap($this->driver), '/'), $connection->getName())] + ); + + return; + } + + if ( + is_string($this->driver) + && ! \is_null($this->versionRequirement) + && preg_match('/(?P[<>=!]{0,2})\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m', $this->versionRequirement, $matches) + ) { + if (empty($matches['operator'])) { + $matches['operator'] = '>='; + } + + if (! version_compare($connection->getServerVersion(), $matches['version'], $matches['operator'])) { + \call_user_func( + $action, + 'markTestSkipped', + [\sprintf('Requires %s:%s to be configured for "%s" database connection', $this->driver, $this->versionRequirement, $connection->getName())] + ); + } + } + } +} diff --git a/src/Attributes/WithMigration.php b/src/Attributes/WithMigration.php index eacd5452f..10dc2da9c 100644 --- a/src/Attributes/WithMigration.php +++ b/src/Attributes/WithMigration.php @@ -6,7 +6,7 @@ use Illuminate\Support\Collection; use Orchestra\Testbench\Contracts\Attributes\Invokable as InvokableContract; -use function Orchestra\Testbench\laravel_migration_path; +use function Orchestra\Testbench\default_migration_path; use function Orchestra\Testbench\load_migration_paths; #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] @@ -38,7 +38,7 @@ public function __invoke($app): void { /** @var array $types */ $types = Collection::make($this->types) - ->transform(static fn ($type) => laravel_migration_path($type !== 'laravel' ? $type : null)) + ->transform(static fn ($type) => default_migration_path($type !== 'laravel' ? $type : null)) ->all(); load_migration_paths($app, $types); diff --git a/src/Concerns/HandlesAssertions.php b/src/Concerns/HandlesAssertions.php new file mode 100644 index 000000000..860d55884 --- /dev/null +++ b/src/Concerns/HandlesAssertions.php @@ -0,0 +1,36 @@ +markTestSkipped($message); + } + } + + /** + * Mark the test as skipped when condition is equivalent to true. + * + * @param (\Closure($this): bool)|bool|null $condition + * @param string $message + * @return void + */ + protected function markTestSkippedWhen($condition, string $message): void + { + /** @phpstan-ignore argument.type */ + if (value($condition)) { + $this->markTestSkipped($message); + } + } +} diff --git a/src/Concerns/HandlesDatabases.php b/src/Concerns/HandlesDatabases.php index 602d18d31..635d32d40 100644 --- a/src/Concerns/HandlesDatabases.php +++ b/src/Concerns/HandlesDatabases.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Database\Events\DatabaseRefreshed; use Orchestra\Testbench\Attributes\DefineDatabase; +use Orchestra\Testbench\Attributes\RequiresDatabase; use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Exceptions\ApplicationNotAvailableException; use Orchestra\Testbench\Features\TestingFeature; @@ -27,6 +28,11 @@ protected function setUpDatabaseRequirements(Closure $callback): void throw ApplicationNotAvailableException::make(__METHOD__); } + TestingFeature::run( + testCase: $this, + attribute: fn () => $this->parseTestMethodAttributes($app, RequiresDatabase::class), + ); + $app['events']->listen(DatabaseRefreshed::class, function () { $this->defineDatabaseMigrationsAfterDatabaseRefreshed(); }); diff --git a/src/Concerns/InteractsWithMigrations.php b/src/Concerns/InteractsWithMigrations.php index bb90b4e40..b3bf62f23 100644 --- a/src/Concerns/InteractsWithMigrations.php +++ b/src/Concerns/InteractsWithMigrations.php @@ -9,7 +9,7 @@ use Orchestra\Testbench\Database\MigrateProcessor; use Orchestra\Testbench\Exceptions\ApplicationNotAvailableException; -use function Orchestra\Testbench\laravel_migration_path; +use function Orchestra\Testbench\default_migration_path; use function Orchestra\Testbench\load_migration_paths; /** @@ -130,7 +130,7 @@ protected function loadLaravelMigrations(array|string $database = []): void } $options = $this->resolveLaravelMigrationsOptions($database); - $options['--path'] = laravel_migration_path(); + $options['--path'] = default_migration_path(); $options['--realpath'] = true; $migrator = new MigrateProcessor($this, $this->resolveLaravelMigrationsOptions($options)); diff --git a/src/Concerns/Testing.php b/src/Concerns/Testing.php index 40da1254b..ea937263c 100644 --- a/src/Concerns/Testing.php +++ b/src/Concerns/Testing.php @@ -23,6 +23,7 @@ trait Testing use ApplicationTestingHooks; use CreatesApplication; use HandlesAnnotations; + use HandlesAssertions; use HandlesAttributes; use HandlesDatabases; use HandlesRoutes; diff --git a/src/Concerns/WithLaravelMigrations.php b/src/Concerns/WithLaravelMigrations.php index a86523262..f9faadb43 100644 --- a/src/Concerns/WithLaravelMigrations.php +++ b/src/Concerns/WithLaravelMigrations.php @@ -5,7 +5,7 @@ use Illuminate\Foundation\Testing\RefreshDatabaseState; use function Orchestra\Testbench\after_resolving; -use function Orchestra\Testbench\laravel_migration_path; +use function Orchestra\Testbench\default_migration_path; trait WithLaravelMigrations { @@ -23,7 +23,7 @@ protected function setUpWithLaravelMigrations(): void /** @var bool $loadLaravelMigrations */ $loadLaravelMigrations = static::cachedConfigurationForWorkbench()->getWorkbenchAttributes()['install'] ?? false; - if (! ($loadLaravelMigrations && is_dir(laravel_migration_path()))) { + if (! ($loadLaravelMigrations && is_dir(default_migration_path()))) { return; } @@ -34,7 +34,7 @@ protected function setUpWithLaravelMigrations(): void ) { after_resolving($this->app, 'migrator', static function ($migrator, $app) { /** @var \Illuminate\Database\Migrations\Migrator $migrator */ - $migrator->path(laravel_migration_path()); + $migrator->path(default_migration_path()); }); } else { $this->loadLaravelMigrations(); diff --git a/src/Foundation/Bootstrap/CreateVendorSymlink.php b/src/Foundation/Bootstrap/CreateVendorSymlink.php index 7a78a3c48..6ab4b0dab 100644 --- a/src/Foundation/Bootstrap/CreateVendorSymlink.php +++ b/src/Foundation/Bootstrap/CreateVendorSymlink.php @@ -7,6 +7,7 @@ use Illuminate\Filesystem\Filesystem; use function Orchestra\Testbench\join_paths; +use function Orchestra\Testbench\laravel_vendor_exists; /** * @internal @@ -36,27 +37,45 @@ public function bootstrap(Application $app): void $appVendorPath = $app->basePath('vendor'); - if ( - ! $filesystem->isFile(join_paths($appVendorPath, 'autoload.php')) || - $filesystem->hash(join_paths($appVendorPath, 'autoload.php')) !== $filesystem->hash(join_paths($this->workingPath, 'autoload.php')) - ) { + $vendorLinkCreated = false; + + if (! laravel_vendor_exists($app, $this->workingPath)) { if ($filesystem->exists($app->bootstrapPath(join_paths('cache', 'packages.php')))) { $filesystem->delete($app->bootstrapPath(join_paths('cache', 'packages.php'))); } - if (is_link($appVendorPath)) { - $filesystem->delete($appVendorPath); - } + $this->deleteVendorSymlink($app); try { $filesystem->link($this->workingPath, $appVendorPath); - $app->instance('TESTBENCH_VENDOR_SYMLINK', true); + $vendorLinkCreated = true; } catch (ErrorException) { - $app->instance('TESTBENCH_VENDOR_SYMLINK', false); + // } } $app->flush(); + + $app->instance('TESTBENCH_VENDOR_SYMLINK', $vendorLinkCreated); + } + + /** + * Safely remove symlink for Unix & Windows environment. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + public function deleteVendorSymlink(Application $app): void + { + tap($app->basePath('vendor'), static function ($appVendorPath) { + if (windows_os() && is_dir($appVendorPath) && readlink($appVendorPath) !== $appVendorPath) { + @rmdir($appVendorPath); + } elseif (is_link($appVendorPath)) { + @unlink($appVendorPath); + } + + clearstatcache(false, \dirname($appVendorPath)); + }); } } diff --git a/src/Foundation/Bootstrap/LoadMigrationsFromArray.php b/src/Foundation/Bootstrap/LoadMigrationsFromArray.php index d48ff67fd..f816ef39a 100644 --- a/src/Foundation/Bootstrap/LoadMigrationsFromArray.php +++ b/src/Foundation/Bootstrap/LoadMigrationsFromArray.php @@ -10,7 +10,7 @@ use Illuminate\Support\Collection; use Orchestra\Testbench\Foundation\Env; -use function Orchestra\Testbench\laravel_migration_path; +use function Orchestra\Testbench\default_migration_path; use function Orchestra\Testbench\load_migration_paths; use function Orchestra\Testbench\transform_relative_path; use function Orchestra\Testbench\workbench; @@ -85,8 +85,10 @@ protected function bootstrapMigrations(Application $app): void { $paths = Collection::make( ! \is_bool($this->migrations) ? Arr::wrap($this->migrations) : [] - )->when($this->includesDefaultMigrations($app), static fn ($migrations) => $migrations->push(laravel_migration_path())) - ->filter(static fn ($migration) => \is_string($migration)) + )->when( + $this->includesDefaultMigrations($app), + static fn ($migrations) => $migrations->push(default_migration_path()), + )->filter(static fn ($migration) => \is_string($migration)) ->transform(static fn ($migration) => transform_relative_path($migration, $app->basePath())) ->all(); @@ -104,6 +106,6 @@ protected function includesDefaultMigrations($app): bool return workbench()['install'] === true && Env::get('TESTBENCH_WITHOUT_DEFAULT_MIGRATIONS') !== true - && rescue(static fn () => is_dir(laravel_migration_path()), false, false); + && rescue(static fn () => is_dir(default_migration_path()), false, false); } } diff --git a/src/PHPUnit/AttributeParser.php b/src/PHPUnit/AttributeParser.php index f3b3bfdbe..8c2f2635f 100644 --- a/src/PHPUnit/AttributeParser.php +++ b/src/PHPUnit/AttributeParser.php @@ -27,8 +27,9 @@ class AttributeParser public static function forClass(string $className): array { $attributes = []; + $reflection = new ReflectionClass($className); - foreach ((new ReflectionClass($className))->getAttributes() as $attribute) { + foreach ($reflection->getAttributes() as $attribute) { if (! static::validAttribute($attribute->getName())) { continue; } @@ -40,6 +41,12 @@ public static function forClass(string $className): array } } + $parent = $reflection->getParentClass(); + + if ($parent && $parent->isSubclassOf(TestCase::class)) { + $attributes = [...static::forClass($parent->getName()), ...$attributes]; + } + return $attributes; } diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index 4c79f0680..207b6665e 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -133,23 +133,28 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ $translator->addNamespace('workbench', $path); }); - 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'))) { - return; + if (is_dir($workbenchViewPath = workbench_path('resources', 'views'))) { + if (($discoversConfig['views'] ?? false) === true) { + $app->booted(static function () use ($app, $workbenchViewPath) { + tap($app->make('config'), function ($config) use ($workbenchViewPath) { + /** @var \Illuminate\Contracts\Config\Repository $config */ + $config->set('view.paths', array_merge( + $config->get('view.paths', []), + [$workbenchViewPath] + )); + }); + }); } - if (($discoversConfig['views'] ?? false) === true && method_exists($view, 'addLocation')) { - $view->addLocation($path); - - tap($app->make('config'), static fn ($config) => $config->set('view.paths', array_merge( - $config->get('view.paths', []), - [$path] - ))); - } + after_resolving($app, 'view', static function ($view, $app) use ($discoversConfig, $workbenchViewPath) { + /** @var \Illuminate\Contracts\View\Factory|\Illuminate\View\Factory $view */ + if (($discoversConfig['views'] ?? false) === true && method_exists($view, 'addLocation')) { + $view->addLocation($workbenchViewPath); + } - $view->addNamespace('workbench', $path); - }); + $view->addNamespace('workbench', $workbenchViewPath); + }); + } after_resolving($app, 'blade.compiler', static function ($blade) use ($discoversConfig) { /** @var \Illuminate\View\Compilers\BladeCompiler $blade */ diff --git a/src/functions.php b/src/functions.php index a8dfc7b0a..a670b23e2 100644 --- a/src/functions.php +++ b/src/functions.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Contracts\Console\Kernel as ConsoleKernel; use Illuminate\Contracts\Foundation\Application as ApplicationContract; +use Illuminate\Filesystem\Filesystem; use Illuminate\Foundation\Application; use Illuminate\Routing\Router; use Illuminate\Support\Arr; @@ -242,6 +243,29 @@ function default_skeleton_path(array|string $path = ''): string return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))); } +/** + * Get the migration path by type. + * + * @api + * + * @param string|null $type + * @return string + * + * @throws \InvalidArgumentException + */ +function default_migration_path(?string $type = null): string +{ + $path = realpath( + \is_null($type) ? base_path('migrations') : base_path(join_paths('migrations', $type)) + ); + + if ($path === false) { + throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel')); + } + + return $path; +} + /** * Get the path to the package folder. * @@ -308,18 +332,32 @@ function workbench_path(array|string $path = ''): string * @return string * * @throws \InvalidArgumentException + * + * @deprecated */ function laravel_migration_path(?string $type = null): string { - $path = realpath( - \is_null($type) ? base_path('migrations') : base_path(join_paths('migrations', $type)) - ); + return default_migration_path($type); +} - if ($path === false) { - throw new InvalidArgumentException(\sprintf('Unable to resolve migration path for type [%s]', $type ?? 'laravel')); - } +/** + * Determine if vendor symlink exists on the laravel application. + * + * @api + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @param string|null $workingPath + * @return bool + */ +function laravel_vendor_exists(ApplicationContract $app, ?string $workingPath = null): bool +{ + $filesystem = new Filesystem; - return $path; + $appVendorPath = $app->basePath('vendor'); + $workingPath ??= package_path('vendor'); + + return $filesystem->isFile(join_paths($appVendorPath, 'autoload.php')) && + $filesystem->hash(join_paths($appVendorPath, 'autoload.php')) === $filesystem->hash(join_paths($workingPath, 'autoload.php')); } /** diff --git a/tests/Attributes/AttributesInheritanceTest.php b/tests/Attributes/AttributesInheritanceTest.php new file mode 100644 index 000000000..399d41b53 --- /dev/null +++ b/tests/Attributes/AttributesInheritanceTest.php @@ -0,0 +1,42 @@ +assertSame(true, config('fake.parent_attribute')); + } + + #[Test] + public function it_can_override_parent_attributes() + { + $this->assertSame('child', config('fake.override_attribute')); + $this->assertSame('child', config('fake.override_attribute_2')); + } +} diff --git a/tests/Attributes/RequiresDatabaseTest.php b/tests/Attributes/RequiresDatabaseTest.php new file mode 100644 index 000000000..c359ea8fd --- /dev/null +++ b/tests/Attributes/RequiresDatabaseTest.php @@ -0,0 +1,61 @@ +handle($this->app, function () { + throw new \Exception; + }); + + $this->addToAssertionCount(1); + + $stub = new RequiresDatabase(['pgsql', 'sqlite']); + + $stub->handle($this->app, function () { + throw new \Exception; + }); + + $this->addToAssertionCount(1); + } + + #[Test] + public function it_can_invalidate_unmatched_database() + { + $stub = new RequiresDatabase('mysql'); + + $stub->handle($this->app, function ($method, $parameters) { + $this->assertSame('markTestSkipped', $method); + $this->assertSame(['Requires mysql to configured for "testing" database connection'], $parameters); + }); + + $stub = new RequiresDatabase(['mysql', 'mariadb']); + + $stub->handle($this->app, function ($method, $parameters) { + $this->assertSame('markTestSkipped', $method); + $this->assertSame(['Requires [mysql/mariadb] to be configured for "testing" database connection'], $parameters); + }); + } + + #[Test] + public function it_can_invalidate_unmatched_database_version() + { + $stub = new RequiresDatabase('sqlite', '<2.0.0'); + + $stub->handle($this->app, function ($method, $parameters) { + $this->assertSame('markTestSkipped', $method); + $this->assertSame(['Requires sqlite:<2.0.0 to be configured for "testing" database connection'], $parameters); + }); + } +} diff --git a/tests/Attributes/UsesTestingFeaturesTest.php b/tests/Attributes/UsesTestingFeaturesTest.php new file mode 100644 index 000000000..80f546d0e --- /dev/null +++ b/tests/Attributes/UsesTestingFeaturesTest.php @@ -0,0 +1,42 @@ +assertSame(true, config('fake.parent_attribute')); + } + + #[Test] + public function it_can_override_parent_attributes() + { + $this->assertSame('child', config('fake.override_attribute')); + $this->assertSame('child', config('fake.override_attribute_2')); + } +} diff --git a/tests/Concerns/HandlesAssertionsTest.php b/tests/Concerns/HandlesAssertionsTest.php new file mode 100644 index 000000000..51a5f153d --- /dev/null +++ b/tests/Concerns/HandlesAssertionsTest.php @@ -0,0 +1,48 @@ +markTestSkippedWhen(true, 'Successfully skipped current test'); + + $this->assertTrue(false, 'Test incorrectly executed.'); + } + + #[Test] + public function it_should_mark_the_tests_as_skipped_when_condition_is_false() + { + $this->markTestSkippedWhen(function () { + return false; + }, 'Failed skipped current test'); + + $this->assertTrue(true, 'Test is correctly executed.'); + } + + #[Test] + public function it_should_mark_the_tests_as_skipped_unless_condition_is_false() + { + $this->markTestSkippedUnless(false, 'Successfully skipped current test'); + + $this->assertTrue(false, 'Test incorrectly executed.'); + } + + #[Test] + public function it_should_mark_the_tests_as_skipped_unless_condition_is_true() + { + $this->markTestSkippedUnless(function () { + return true; + }, 'Failed skipped current test'); + + $this->assertTrue(true, 'Test is correctly executed.'); + } +} diff --git a/tests/Foundation/Bootstrap/CreateVendorSymlinkTest.php b/tests/Foundation/Bootstrap/CreateVendorSymlinkTest.php new file mode 100644 index 000000000..cf470eb0a --- /dev/null +++ b/tests/Foundation/Bootstrap/CreateVendorSymlinkTest.php @@ -0,0 +1,49 @@ +createApplication(); + + $stub = (new CreateVendorSymlink($workingPath)); + + if (laravel_vendor_exists($laravel, $workingPath)) { + $stub->deleteVendorSymlink($laravel); + } + + $stub->bootstrap($laravel); + + $this->assertTrue($laravel['TESTBENCH_VENDOR_SYMLINK']); + } + + #[Test] + public function it_can_skip_existing_vendor_symlink() + { + $workingPath = package_path('vendor'); + + $laravel = container()->createApplication(); + + if (! laravel_vendor_exists($laravel, $workingPath)) { + (new Filesystem)->link($workingPath, $laravel->basePath('vendor')); + } + + (new CreateVendorSymlink($workingPath))->bootstrap($laravel); + + $this->assertFalse($laravel['TESTBENCH_VENDOR_SYMLINK']); + } +} diff --git a/tests/Integrations/WithFakerTest.php b/tests/Integrations/WithFakerTest.php index f5d7d903c..7345f047b 100644 --- a/tests/Integrations/WithFakerTest.php +++ b/tests/Integrations/WithFakerTest.php @@ -4,6 +4,7 @@ use Faker\Generator; use Illuminate\Foundation\Testing\WithFaker; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\Test; @@ -16,4 +17,15 @@ public function it_can_use_faker() { $this->assertInstanceOf(Generator::class, $this->faker); } + + #[Test] + #[WithConfig('app.faker_locale', 'it_IT')] + public function it_can_override_faker_locale() + { + $providerNames = array_map(function ($p) { + return \get_class($p); + }, $this->faker()->getProviders()); + + $this->assertContains('Faker\Provider\it_IT\Person', $providerNames); + } } diff --git a/tests/Workbench/DiscoversTest.php b/tests/Workbench/DiscoversTest.php index dc885a738..db39dee92 100644 --- a/tests/Workbench/DiscoversTest.php +++ b/tests/Workbench/DiscoversTest.php @@ -62,6 +62,15 @@ public function it_can_resolve_views_from_discovers() ->assertSee('Notification Component'); } + #[Test] + public function it_can_resolve_errors_views_from_discovers() + { + $this->get('/root') + ->assertStatus(418) + ->assertSeeText('I\'m a teapot') + ->assertDontSeeText('412'); + } + #[Test] public function it_can_resolve_route_name_from_discovers() { diff --git a/workbench/resources/views/errors/418.blade.php b/workbench/resources/views/errors/418.blade.php new file mode 100644 index 000000000..8fee9665b --- /dev/null +++ b/workbench/resources/views/errors/418.blade.php @@ -0,0 +1 @@ +I'm a teapot diff --git a/workbench/routes/web.php b/workbench/routes/web.php index 2b56da100..58bf708c2 100644 --- a/workbench/routes/web.php +++ b/workbench/routes/web.php @@ -19,3 +19,6 @@ Route::view('/testbench', 'workbench::testbench')->name('testbench'); Route::text('/hello-world', 'Hello world'); +Route::get('/root', function () { + abort(418); +});