diff --git a/src/Commands/FreshCommand.php b/src/Commands/FreshCommand.php index 7d17b23b..12d7f999 100644 --- a/src/Commands/FreshCommand.php +++ b/src/Commands/FreshCommand.php @@ -6,13 +6,23 @@ use Native\Desktop\NativeServiceProvider; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand( - name: 'native:migrate:fresh', - description: 'Drop all tables and re-run all migrations in the NativePHP development environment', -)] +#[AsCommand(name: 'native:migrate:fresh')] class FreshCommand extends BaseFreshCommand { - protected $name = 'native:migrate:fresh'; + // The parent declares its own signature, which wins over $name. + // Inheriting it would register this as a second migrate:fresh, + // so the name and options are spelled out here. + protected $signature = 'native:migrate:fresh + {--database= : The database connection to use} + {--drop-views : Drop all tables and views} + {--drop-types : Drop all tables and types (Postgres only)} + {--force : Force the operation to run when in production} + {--path=* : The path(s) to the migrations files to be executed} + {--realpath : Indicate any provided migration file paths are pre-resolved absolute paths} + {--schema-path= : The path to a schema dump file} + {--seed : Indicates if the seed task should be re-run} + {--seeder= : The class name of the root seeder} + {--step : Force the migrations to be run so they can be rolled back individually}'; protected $description = 'Drop all tables and re-run all migrations in the NativePHP development environment'; diff --git a/src/Commands/MigrateCommand.php b/src/Commands/MigrateCommand.php index 191f56cd..3d9ccc82 100644 --- a/src/Commands/MigrateCommand.php +++ b/src/Commands/MigrateCommand.php @@ -8,12 +8,11 @@ use Native\Desktop\NativeServiceProvider; use Symfony\Component\Console\Attribute\AsCommand; -#[AsCommand( - name: 'native:migrate', - description: 'Run the database migrations in the NativePHP development environment', -)] +#[AsCommand(name: 'native:migrate')] class MigrateCommand extends BaseMigrateCommand { + protected $description = 'Run the database migrations in the NativePHP development environment'; + public function __construct(Migrator $migrator, Dispatcher $dispatcher) { $this->signature = 'native:'.$this->signature; diff --git a/src/Commands/SeedDatabaseCommand.php b/src/Commands/SeedDatabaseCommand.php index f7432a85..f67db3ef 100644 --- a/src/Commands/SeedDatabaseCommand.php +++ b/src/Commands/SeedDatabaseCommand.php @@ -8,14 +8,13 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; -#[AsCommand( - name: 'native:seed', - description: 'Seed the database in the NativePHP development environment', -)] +#[AsCommand(name: 'native:seed')] class SeedDatabaseCommand extends BaseSeedCommand { protected $signature = 'native:seed'; + protected $description = 'Seed the database in the NativePHP development environment'; + protected function configure(): void { parent::configure(); diff --git a/src/Commands/WipeDatabaseCommand.php b/src/Commands/WipeDatabaseCommand.php index 1c71884c..8c637666 100644 --- a/src/Commands/WipeDatabaseCommand.php +++ b/src/Commands/WipeDatabaseCommand.php @@ -4,10 +4,19 @@ use Illuminate\Database\Console\WipeCommand as BaseWipeCommand; use Native\Desktop\NativeServiceProvider; +use Symfony\Component\Console\Attribute\AsCommand; +#[AsCommand(name: 'native:db:wipe')] class WipeDatabaseCommand extends BaseWipeCommand { - protected $name = 'native:db:wipe'; + // The parent declares its own signature, which wins over $name. + // Inheriting it would register this as a second db:wipe, + // so the name and options are spelled out here. + protected $signature = 'native:db:wipe + {--database= : The database connection to use} + {--drop-views : Drop all tables and views} + {--drop-types : Drop all tables and types (Postgres only)} + {--force : Force the operation to run when in production}'; protected $description = 'Wipe the database in the NativePHP development environment'; diff --git a/tests/Commands/CommandRegistrationTest.php b/tests/Commands/CommandRegistrationTest.php new file mode 100644 index 00000000..5cd9bd0b --- /dev/null +++ b/tests/Commands/CommandRegistrationTest.php @@ -0,0 +1,79 @@ + [MigrateCommand::class, BaseMigrateCommand::class], + 'native:migrate:fresh' => [FreshCommand::class, BaseFreshCommand::class], + 'native:seed' => [SeedDatabaseCommand::class, BaseSeedCommand::class], + 'native:db:wipe' => [WipeDatabaseCommand::class, BaseWipeCommand::class], +]; + +beforeEach(function () { + $this->commands = app(Kernel::class)->all(); +}); + +function attributeName(string $class): ?string +{ + $attribute = (new ReflectionClass($class))->getAttributes(AsCommand::class); + + return empty($attribute) ? null : $attribute[0]->newInstance()->name; +} + +function optionNames(Command $command): array +{ + return array_keys($command->getDefinition()->getOptions()); +} + +/* +|-------------------------------------------------------------------------- +| Tests +|-------------------------------------------------------------------------- +*/ +it('registers the native commands under their own names', function (string $name, string $class) { + expect($this->commands)->toHaveKey($name) + ->and($this->commands[$name])->toBeInstanceOf($class); +})->with(array_map(fn ($name, $classes) => [$name, $classes[0]], array_keys($nativeCommands), $nativeCommands)); + +it('leaves the commands they extend registered under the Laravel names', function () { + expect($this->commands['migrate:fresh'])->toBeInstanceOf(BaseFreshCommand::class) + ->not->toBeInstanceOf(FreshCommand::class) + ->and($this->commands['db:wipe'])->toBeInstanceOf(BaseWipeCommand::class) + ->not->toBeInstanceOf(WipeDatabaseCommand::class); +}); + +// Commands are registered lazily under the name in their attribute, but named +// from their signature once built. Any disagreement between the two +// makes Artisan fail with "registered under multiple names". +it('names each native command the same way in its attribute and its signature', function (string $name, string $class) { + expect(attributeName($class))->toBe($name) + ->and($this->commands[$name]->getName())->toBe($name); +})->with(array_map(fn ($name, $classes) => [$name, $classes[0]], array_keys($nativeCommands), $nativeCommands)); + +it('describes the native commands rather than inheriting the Laravel wording', function (string $name, string $parent) { + expect($this->commands[$name]->getDescription()) + ->not->toBe((new ReflectionClass($parent))->getDefaultProperties()['description']) + ->toContain('NativePHP development environment'); +})->with(array_map(fn ($name, $classes) => [$name, $classes[1]], array_keys($nativeCommands), $nativeCommands)); + +it('carries over every option the parent commands define', function () { + expect(optionNames($this->commands['native:migrate:fresh'])) + ->toContain(...optionNames(new BaseFreshCommand(app('migrator')))) + ->and(optionNames($this->commands['native:db:wipe'])) + ->toContain(...optionNames(new BaseWipeCommand)); +});