From 29bd939f7d12e153c5c69ac9a1b1116f342933ea Mon Sep 17 00:00:00 2001 From: Jack McDade Date: Wed, 28 Oct 2026 15:52:49 -0400 Subject: [PATCH 1/5] Add fieldtype inspection commands --- src/Console/Commands/FieldtypesList.php | 42 +++++ src/Console/Commands/FieldtypesShow.php | 54 ++++++ src/Fields/FieldtypeInspector.php | 103 +++++++++++ src/Providers/ConsoleServiceProvider.php | 2 + tests/Console/Commands/FieldtypesTest.php | 215 ++++++++++++++++++++++ 5 files changed, 416 insertions(+) create mode 100644 src/Console/Commands/FieldtypesList.php create mode 100644 src/Console/Commands/FieldtypesShow.php create mode 100644 src/Fields/FieldtypeInspector.php create mode 100644 tests/Console/Commands/FieldtypesTest.php diff --git a/src/Console/Commands/FieldtypesList.php b/src/Console/Commands/FieldtypesList.php new file mode 100644 index 00000000000..5a92f281ad4 --- /dev/null +++ b/src/Console/Commands/FieldtypesList.php @@ -0,0 +1,42 @@ +all(); + + if ($this->option('json')) { + $this->output->writeln( + json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), + OutputInterface::OUTPUT_RAW + ); + + return self::SUCCESS; + } + + $this->components->info('Statamic '.$data['statamic_version']); + + $this->table(['Handle', 'Title', 'Selectable', 'Class'], array_map(fn ($fieldtype) => [ + $fieldtype['handle'], + $fieldtype['title'], + $fieldtype['selectable'] ? 'Yes' : 'No', + $fieldtype['class'], + ], $data['fieldtypes'])); + + return self::SUCCESS; + } +} diff --git a/src/Console/Commands/FieldtypesShow.php b/src/Console/Commands/FieldtypesShow.php new file mode 100644 index 00000000000..2cf20494a5c --- /dev/null +++ b/src/Console/Commands/FieldtypesShow.php @@ -0,0 +1,54 @@ +show($this->argument('handle')); + } catch (FieldtypeNotFoundException $exception) { + if ($this->option('json')) { + $this->output->writeln( + json_encode(['error' => $exception->getMessage()], JSON_THROW_ON_ERROR), + OutputInterface::OUTPUT_RAW + ); + } else { + $this->components->error($exception->getMessage()); + } + + return self::FAILURE; + } + + $this->output->writeln( + $this->option('json') + ? json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) + : Yaml::dump($data, 10, 2), + OutputInterface::OUTPUT_RAW + ); + + return self::SUCCESS; + } +} diff --git a/src/Fields/FieldtypeInspector.php b/src/Fields/FieldtypeInspector.php new file mode 100644 index 00000000000..d82c178d246 --- /dev/null +++ b/src/Fields/FieldtypeInspector.php @@ -0,0 +1,103 @@ + Statamic::version(), + 'fieldtypes' => $this->fieldtypes->classes() + ->sortKeys() + ->map(fn ($class, $handle) => $this->metadata($handle, app($class))) + ->values()->all(), + ]; + } + + public function show(string $handle): array + { + $fieldtype = $this->fieldtypes->find($handle); + + return [ + 'statamic_version' => Statamic::version(), + 'fieldtype' => $this->metadata($handle, $fieldtype), + 'config' => $this->definitions($fieldtype->configFields()), + 'common' => $this->definitions(Field::commonFieldOptions()), + ]; + } + + private function metadata(string $handle, Fieldtype $fieldtype): array + { + return [ + 'handle' => $handle, + 'class' => $fieldtype::class, + 'title' => $fieldtype->title(), + 'selectable' => $fieldtype->selectable(), + 'selectable_in_forms' => $fieldtype->selectableInForms(), + 'localizable' => $fieldtype->localizable(), + 'validatable' => $fieldtype->validatable(), + 'defaultable' => $fieldtype->defaultable(), + ]; + } + + private function definitions(Fields $fields): array + { + return $fields->all() + ->map(fn (Field $field) => $this->definition($field)) + ->all(); + } + + private function definition(Field $field): array + { + $definition = $field->config(); + $editorType = $definition['type'] ?? null; + unset($definition['type']); + + $definition = ['editor_type' => $editorType] + $definition; + + if ( + ! array_key_exists('options', $definition) + && in_array($editorType, ['bard_buttons_setting', 'markdown_buttons_setting'], true) + ) { + $definition['options'] = [ + 'status' => 'unresolved', + 'source' => 'editor', + ]; + } + + return $this->normalize($definition); + } + + private function normalize(mixed $value): mixed + { + if ($value instanceof BackedEnum) { + return $value->value; + } + + if ($value instanceof Collection) { + $value = $value->all(); + } + + if (is_array($value)) { + return array_map(fn ($item) => $this->normalize($item), $value); + } + + if (is_object($value) || is_resource($value)) { + return [ + 'status' => 'unresolved', + 'php_type' => get_debug_type($value), + ]; + } + + return $value; + } +} diff --git a/src/Providers/ConsoleServiceProvider.php b/src/Providers/ConsoleServiceProvider.php index 8d058c405ed..a7cdbc8fb70 100644 --- a/src/Providers/ConsoleServiceProvider.php +++ b/src/Providers/ConsoleServiceProvider.php @@ -15,6 +15,8 @@ class ConsoleServiceProvider extends ServiceProvider Commands\AssetsGeneratePresets::class, Commands\AssetsMeta::class, Commands\AssetsMetaClean::class, + Commands\FieldtypesList::class, + Commands\FieldtypesShow::class, Commands\GlideClear::class, Commands\Install::class, Commands\InstallCollaboration::class, diff --git a/tests/Console/Commands/FieldtypesTest.php b/tests/Console/Commands/FieldtypesTest.php new file mode 100644 index 00000000000..9125502d7b8 --- /dev/null +++ b/tests/Console/Commands/FieldtypesTest.php @@ -0,0 +1,215 @@ +put('test_inspection', InspectionFieldtype::class); + app('statamic.fieldtypes')->put('test_unconfigured', UnconfiguredInspectionFieldtype::class); + + $data = $this->commandJson('statamic:fieldtypes:list'); + $handles = array_column($data['fieldtypes'], 'handle'); + + $this->assertSame('3.0.0-testing', $data['statamic_version']); + $this->assertContains('bard', $handles); + $this->assertContains('markdown', $handles); + $this->assertContains('test_inspection', $handles); + $this->assertContains('test_unconfigured', $handles); + + $sorted = $handles; + sort($sorted); + $this->assertSame($sorted, $handles); + + $fieldtype = collect($data['fieldtypes'])->firstWhere('handle', 'test_inspection'); + $this->assertSame(InspectionFieldtype::class, $fieldtype['class']); + $this->assertSame('Inspection', $fieldtype['title']); + $this->assertFalse($fieldtype['selectable']); + } + + #[Test] + public function it_shows_native_options_defaults_and_shared_fields() + { + $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'grid']); + + $this->assertSame('3.0.0-testing', $data['statamic_version']); + $this->assertSame('grid', $data['fieldtype']['handle']); + $this->assertSame('Statamic\\Fieldtypes\\Grid', $data['fieldtype']['class']); + $this->assertSame('table', $data['config']['mode']['default']); + $this->assertSame(['table' => 'Table', 'stacked' => 'Stacked'], $data['config']['mode']['options']); + $this->assertSame('select', $data['config']['mode']['editor_type']); + $this->assertArrayNotHasKey('type', $data['config']['mode']); + $this->assertArrayNotHasKey('notes', $data); + $this->assertArrayHasKey('fields', $data['config']); + $this->assertSame('visible', $data['common']['visibility']['default']); + $this->assertArrayHasKey('instructions', $data['common']); + $this->assertArrayNotHasKey('mode', $data['common']); + } + + #[Test] + public function it_includes_bard_toolbar_defaults_and_conditional_configuration() + { + $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'bard']); + + $this->assertContains('bold', $data['config']['buttons']['default']); + $this->assertSame('bard_buttons_setting', $data['config']['buttons']['editor_type']); + $this->assertSame(['status' => 'unresolved', 'source' => 'editor'], $data['config']['buttons']['options']); + $this->assertSame(['buttons' => 'contains_any anchor, image'], $data['config']['container']['if']); + $this->assertSame('fixed', $data['config']['toolbar_mode']['default']); + + $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'markdown']); + + $this->assertSame('markdown_buttons_setting', $data['config']['buttons']['editor_type']); + $this->assertSame(['status' => 'unresolved', 'source' => 'editor'], $data['config']['buttons']['options']); + } + + #[Test] + public function it_includes_addon_configuration_and_marks_unresolved_values_without_running_them() + { + app('statamic.fieldtypes')->put('test_inspection', InspectionFieldtype::class); + InspectionFieldtype::appendConfigField('extra', ['type' => 'toggle', 'default' => false]); + + $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'test_inspection']); + + $this->assertSame(['editor_type' => 'toggle', 'default' => false], $data['config']['extra']); + $this->assertSame(['enabled' => 'Enabled'], $data['config']['mode']['options']); + $this->assertSame(['mode' => 'enabled'], $data['config']['limit']['if']); + $this->assertSame(0, $data['config']['limit']['default']); + $this->assertNull($data['config']['optional']['default']); + $this->assertSame(['status' => 'unresolved', 'php_type' => 'Closure'], $data['config']['dynamic']['options']); + $this->assertSame(['status' => 'unresolved', 'php_type' => 'stdClass'], $data['config']['nested']['default']['value']); + $this->assertSame('Inspection', $data['fieldtype']['title']); + $this->assertSame('text', $data['config']['nested']['editor_type']); + $this->assertSame('content_block', $data['config']['nested']['default']['type']); + } + + #[Test] + public function it_preserves_registered_aliases() + { + app('statamic.fieldtypes')->put('test_alias', InspectionFieldtype::class); + + $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'test_alias']); + + $this->assertSame('test_alias', $data['fieldtype']['handle']); + $this->assertSame(InspectionFieldtype::class, $data['fieldtype']['class']); + } + + #[Test] + public function it_can_inspect_every_registered_core_fieldtype() + { + $inspector = app(FieldtypeInspector::class); + + foreach (app('statamic.fieldtypes') as $handle => $class) { + if (! str_starts_with($class, 'Statamic\\Fieldtypes\\')) { + continue; + } + + $data = $inspector->show($handle); + + $this->assertSame($handle, $data['fieldtype']['handle']); + $this->assertJson(json_encode($data, JSON_THROW_ON_ERROR)); + } + } + + #[Test] + public function an_unknown_fieldtype_returns_a_nonzero_exit_code_and_json_error() + { + $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'does_not_exist'], 1); + + $this->assertSame(['error' => 'Fieldtype [does_not_exist] not found'], $data); + } + + #[Test] + public function it_can_show_human_readable_configuration() + { + $this->assertSame(0, Artisan::call('statamic:fieldtypes:show', ['handle' => 'grid'])); + + $data = Yaml::parse(Artisan::output()); + + $this->assertSame('grid', $data['fieldtype']['handle']); + $this->assertSame('table', $data['config']['mode']['default']); + } + + #[Test] + public function it_can_list_fieldtypes_without_json() + { + $this->artisan('statamic:fieldtypes:list') + ->expectsOutputToContain('bard') + ->expectsOutputToContain('markdown') + ->assertSuccessful(); + } + + #[Test] + public function it_can_run_both_commands_through_please() + { + require_once __DIR__.'/../Kernel.php'; + + $kernel = app(\Statamic\Console\Please\Kernel::class); + + $this->assertSame(0, $kernel->call('fieldtypes:list', ['--json' => true])); + $this->assertNotEmpty(json_decode($kernel->output(), true, 512, JSON_THROW_ON_ERROR)['fieldtypes']); + + $this->assertSame(0, $kernel->call('fieldtypes:show', ['handle' => 'grid', '--json' => true])); + $this->assertSame('grid', json_decode($kernel->output(), true, 512, JSON_THROW_ON_ERROR)['fieldtype']['handle']); + } + + private function commandJson(string $command, array $arguments = [], int $exitCode = 0): array + { + $this->assertSame($exitCode, Artisan::call($command, $arguments + ['--json' => true])); + + return json_decode(Artisan::output(), true, 512, JSON_THROW_ON_ERROR); + } +} + +class InspectionFieldtype extends Fieldtype +{ + protected static $title = 'Inspection'; + + protected $selectable = false; + + protected function configFieldItems(): array + { + return [ + 'mode' => [ + 'type' => 'select', + 'options' => collect(['enabled' => 'Enabled']), + ], + 'limit' => [ + 'type' => 'integer', + 'default' => 0, + 'if' => ['mode' => 'enabled'], + ], + 'optional' => [ + 'type' => 'text', + 'default' => null, + ], + 'dynamic' => [ + 'type' => 'select', + 'options' => function () { + throw new \RuntimeException('This callback must not run during inspection.'); + }, + ], + 'nested' => [ + 'type' => 'text', + 'default' => ['type' => 'content_block', 'value' => new \stdClass], + ], + ]; + } +} + +class UnconfiguredInspectionFieldtype extends Fieldtype +{ + protected function configFieldItems(): array + { + throw new \RuntimeException('Listing must not evaluate configuration.'); + } +} From e6f4d6b7dab4049c515d8b643af707c6e15ab31c Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 29 Oct 2026 18:24:36 +0000 Subject: [PATCH 2/5] mark `FieldtypeInspector` as internal --- src/Fields/FieldtypeInspector.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Fields/FieldtypeInspector.php b/src/Fields/FieldtypeInspector.php index d82c178d246..ceb20f5b550 100644 --- a/src/Fields/FieldtypeInspector.php +++ b/src/Fields/FieldtypeInspector.php @@ -6,6 +6,9 @@ use Illuminate\Support\Collection; use Statamic\Statamic; +/** + * @internal + */ class FieldtypeInspector { public function __construct(private FieldtypeRepository $fieldtypes) From f5283c71cb60498a9ec7259c95898f48211a10b5 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 29 Oct 2026 18:24:36 +0000 Subject: [PATCH 3/5] report unresolved values in a separate `unresolved` list instead of inline markers --- src/Fields/FieldtypeInspector.php | 37 +++++++++++++---------- tests/Console/Commands/FieldtypesTest.php | 15 ++++++--- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Fields/FieldtypeInspector.php b/src/Fields/FieldtypeInspector.php index ceb20f5b550..2ba8978f736 100644 --- a/src/Fields/FieldtypeInspector.php +++ b/src/Fields/FieldtypeInspector.php @@ -11,6 +11,8 @@ */ class FieldtypeInspector { + private array $unresolved = []; + public function __construct(private FieldtypeRepository $fieldtypes) { } @@ -30,11 +32,16 @@ public function show(string $handle): array { $fieldtype = $this->fieldtypes->find($handle); + $this->unresolved = []; + $config = $this->definitions($fieldtype->configFields(), ['config']); + $common = $this->definitions(Field::commonFieldOptions(), ['common']); + return [ 'statamic_version' => Statamic::version(), 'fieldtype' => $this->metadata($handle, $fieldtype), - 'config' => $this->definitions($fieldtype->configFields()), - 'common' => $this->definitions(Field::commonFieldOptions()), + 'config' => $config, + 'common' => $common, + 'unresolved' => $this->unresolved, ]; } @@ -52,14 +59,14 @@ private function metadata(string $handle, Fieldtype $fieldtype): array ]; } - private function definitions(Fields $fields): array + private function definitions(Fields $fields, array $path): array { return $fields->all() - ->map(fn (Field $field) => $this->definition($field)) + ->map(fn (Field $field, $handle) => $this->definition($field, [...$path, $handle])) ->all(); } - private function definition(Field $field): array + private function definition(Field $field, array $path): array { $definition = $field->config(); $editorType = $definition['type'] ?? null; @@ -71,16 +78,13 @@ private function definition(Field $field): array ! array_key_exists('options', $definition) && in_array($editorType, ['bard_buttons_setting', 'markdown_buttons_setting'], true) ) { - $definition['options'] = [ - 'status' => 'unresolved', - 'source' => 'editor', - ]; + $this->unresolved[] = ['path' => [...$path, 'options'], 'source' => 'editor']; } - return $this->normalize($definition); + return $this->normalize($definition, $path); } - private function normalize(mixed $value): mixed + private function normalize(mixed $value, array $path): mixed { if ($value instanceof BackedEnum) { return $value->value; @@ -91,14 +95,15 @@ private function normalize(mixed $value): mixed } if (is_array($value)) { - return array_map(fn ($item) => $this->normalize($item), $value); + return collect($value) + ->map(fn ($item, $key) => $this->normalize($item, [...$path, $key])) + ->all(); } if (is_object($value) || is_resource($value)) { - return [ - 'status' => 'unresolved', - 'php_type' => get_debug_type($value), - ]; + $this->unresolved[] = ['path' => $path, 'source' => 'php', 'php_type' => get_debug_type($value)]; + + return null; } return $value; diff --git a/tests/Console/Commands/FieldtypesTest.php b/tests/Console/Commands/FieldtypesTest.php index 9125502d7b8..4f75d170ccc 100644 --- a/tests/Console/Commands/FieldtypesTest.php +++ b/tests/Console/Commands/FieldtypesTest.php @@ -53,6 +53,9 @@ public function it_shows_native_options_defaults_and_shared_fields() $this->assertSame('visible', $data['common']['visibility']['default']); $this->assertArrayHasKey('instructions', $data['common']); $this->assertArrayNotHasKey('mode', $data['common']); + $this->assertSame([ + ['path' => ['common', 'handle', 'validate', 1], 'source' => 'php', 'php_type' => 'Statamic\\Rules\\Handle'], + ], $data['unresolved']); } #[Test] @@ -62,14 +65,16 @@ public function it_includes_bard_toolbar_defaults_and_conditional_configuration( $this->assertContains('bold', $data['config']['buttons']['default']); $this->assertSame('bard_buttons_setting', $data['config']['buttons']['editor_type']); - $this->assertSame(['status' => 'unresolved', 'source' => 'editor'], $data['config']['buttons']['options']); + $this->assertArrayNotHasKey('options', $data['config']['buttons']); + $this->assertContains(['path' => ['config', 'buttons', 'options'], 'source' => 'editor'], $data['unresolved']); $this->assertSame(['buttons' => 'contains_any anchor, image'], $data['config']['container']['if']); $this->assertSame('fixed', $data['config']['toolbar_mode']['default']); $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'markdown']); $this->assertSame('markdown_buttons_setting', $data['config']['buttons']['editor_type']); - $this->assertSame(['status' => 'unresolved', 'source' => 'editor'], $data['config']['buttons']['options']); + $this->assertArrayNotHasKey('options', $data['config']['buttons']); + $this->assertContains(['path' => ['config', 'buttons', 'options'], 'source' => 'editor'], $data['unresolved']); } #[Test] @@ -85,11 +90,13 @@ public function it_includes_addon_configuration_and_marks_unresolved_values_with $this->assertSame(['mode' => 'enabled'], $data['config']['limit']['if']); $this->assertSame(0, $data['config']['limit']['default']); $this->assertNull($data['config']['optional']['default']); - $this->assertSame(['status' => 'unresolved', 'php_type' => 'Closure'], $data['config']['dynamic']['options']); - $this->assertSame(['status' => 'unresolved', 'php_type' => 'stdClass'], $data['config']['nested']['default']['value']); + $this->assertNull($data['config']['dynamic']['options']); + $this->assertNull($data['config']['nested']['default']['value']); $this->assertSame('Inspection', $data['fieldtype']['title']); $this->assertSame('text', $data['config']['nested']['editor_type']); $this->assertSame('content_block', $data['config']['nested']['default']['type']); + $this->assertContains(['path' => ['config', 'dynamic', 'options'], 'source' => 'php', 'php_type' => 'Closure'], $data['unresolved']); + $this->assertContains(['path' => ['config', 'nested', 'default', 'value'], 'source' => 'php', 'php_type' => 'stdClass'], $data['unresolved']); } #[Test] From da63e1450b15169169f66b08beb886d6f88b8af6 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 29 Oct 2026 18:24:36 +0000 Subject: [PATCH 4/5] split fieldtype command tests into one file per command --- tests/Console/Commands/FieldtypesListTest.php | 73 +++++++++++++++++ ...ldtypesTest.php => FieldtypesShowTest.php} | 81 ++++--------------- 2 files changed, 90 insertions(+), 64 deletions(-) create mode 100644 tests/Console/Commands/FieldtypesListTest.php rename tests/Console/Commands/{FieldtypesTest.php => FieldtypesShowTest.php} (71%) diff --git a/tests/Console/Commands/FieldtypesListTest.php b/tests/Console/Commands/FieldtypesListTest.php new file mode 100644 index 00000000000..064a6c11fc5 --- /dev/null +++ b/tests/Console/Commands/FieldtypesListTest.php @@ -0,0 +1,73 @@ +put('test_listed', ListedFieldtype::class); + app('statamic.fieldtypes')->put('test_unconfigured', UnconfiguredFieldtype::class); + + $this->assertSame(0, Artisan::call('statamic:fieldtypes:list', ['--json' => true])); + + $data = json_decode(Artisan::output(), true, 512, JSON_THROW_ON_ERROR); + $handles = array_column($data['fieldtypes'], 'handle'); + + $this->assertSame('3.0.0-testing', $data['statamic_version']); + $this->assertContains('bard', $handles); + $this->assertContains('markdown', $handles); + $this->assertContains('test_listed', $handles); + $this->assertContains('test_unconfigured', $handles); + + $sorted = $handles; + sort($sorted); + $this->assertSame($sorted, $handles); + + $fieldtype = collect($data['fieldtypes'])->firstWhere('handle', 'test_listed'); + $this->assertSame(ListedFieldtype::class, $fieldtype['class']); + $this->assertSame('Listed', $fieldtype['title']); + $this->assertFalse($fieldtype['selectable']); + } + + #[Test] + public function it_can_list_fieldtypes_without_json() + { + $this->artisan('statamic:fieldtypes:list') + ->expectsOutputToContain('bard') + ->expectsOutputToContain('markdown') + ->assertSuccessful(); + } + + #[Test] + public function it_can_run_through_please() + { + require_once __DIR__.'/../Kernel.php'; + + $kernel = app(\Statamic\Console\Please\Kernel::class); + + $this->assertSame(0, $kernel->call('fieldtypes:list', ['--json' => true])); + $this->assertNotEmpty(json_decode($kernel->output(), true, 512, JSON_THROW_ON_ERROR)['fieldtypes']); + } +} + +class ListedFieldtype extends Fieldtype +{ + protected static $title = 'Listed'; + + protected $selectable = false; +} + +class UnconfiguredFieldtype extends Fieldtype +{ + protected function configFieldItems(): array + { + throw new \RuntimeException('Listing must not evaluate configuration.'); + } +} diff --git a/tests/Console/Commands/FieldtypesTest.php b/tests/Console/Commands/FieldtypesShowTest.php similarity index 71% rename from tests/Console/Commands/FieldtypesTest.php rename to tests/Console/Commands/FieldtypesShowTest.php index 4f75d170ccc..5553d94702c 100644 --- a/tests/Console/Commands/FieldtypesTest.php +++ b/tests/Console/Commands/FieldtypesShowTest.php @@ -9,37 +9,12 @@ use Symfony\Component\Yaml\Yaml; use Tests\TestCase; -class FieldtypesTest extends TestCase +class FieldtypesShowTest extends TestCase { - #[Test] - public function it_lists_registered_fieldtypes_without_evaluating_their_configuration() - { - app('statamic.fieldtypes')->put('test_inspection', InspectionFieldtype::class); - app('statamic.fieldtypes')->put('test_unconfigured', UnconfiguredInspectionFieldtype::class); - - $data = $this->commandJson('statamic:fieldtypes:list'); - $handles = array_column($data['fieldtypes'], 'handle'); - - $this->assertSame('3.0.0-testing', $data['statamic_version']); - $this->assertContains('bard', $handles); - $this->assertContains('markdown', $handles); - $this->assertContains('test_inspection', $handles); - $this->assertContains('test_unconfigured', $handles); - - $sorted = $handles; - sort($sorted); - $this->assertSame($sorted, $handles); - - $fieldtype = collect($data['fieldtypes'])->firstWhere('handle', 'test_inspection'); - $this->assertSame(InspectionFieldtype::class, $fieldtype['class']); - $this->assertSame('Inspection', $fieldtype['title']); - $this->assertFalse($fieldtype['selectable']); - } - #[Test] public function it_shows_native_options_defaults_and_shared_fields() { - $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'grid']); + $data = $this->commandJson(['handle' => 'grid']); $this->assertSame('3.0.0-testing', $data['statamic_version']); $this->assertSame('grid', $data['fieldtype']['handle']); @@ -61,7 +36,7 @@ public function it_shows_native_options_defaults_and_shared_fields() #[Test] public function it_includes_bard_toolbar_defaults_and_conditional_configuration() { - $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'bard']); + $data = $this->commandJson(['handle' => 'bard']); $this->assertContains('bold', $data['config']['buttons']['default']); $this->assertSame('bard_buttons_setting', $data['config']['buttons']['editor_type']); @@ -70,7 +45,7 @@ public function it_includes_bard_toolbar_defaults_and_conditional_configuration( $this->assertSame(['buttons' => 'contains_any anchor, image'], $data['config']['container']['if']); $this->assertSame('fixed', $data['config']['toolbar_mode']['default']); - $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'markdown']); + $data = $this->commandJson(['handle' => 'markdown']); $this->assertSame('markdown_buttons_setting', $data['config']['buttons']['editor_type']); $this->assertArrayNotHasKey('options', $data['config']['buttons']); @@ -83,7 +58,7 @@ public function it_includes_addon_configuration_and_marks_unresolved_values_with app('statamic.fieldtypes')->put('test_inspection', InspectionFieldtype::class); InspectionFieldtype::appendConfigField('extra', ['type' => 'toggle', 'default' => false]); - $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'test_inspection']); + $data = $this->commandJson(['handle' => 'test_inspection']); $this->assertSame(['editor_type' => 'toggle', 'default' => false], $data['config']['extra']); $this->assertSame(['enabled' => 'Enabled'], $data['config']['mode']['options']); @@ -104,7 +79,7 @@ public function it_preserves_registered_aliases() { app('statamic.fieldtypes')->put('test_alias', InspectionFieldtype::class); - $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'test_alias']); + $data = $this->commandJson(['handle' => 'test_alias']); $this->assertSame('test_alias', $data['fieldtype']['handle']); $this->assertSame(InspectionFieldtype::class, $data['fieldtype']['class']); @@ -127,14 +102,6 @@ public function it_can_inspect_every_registered_core_fieldtype() } } - #[Test] - public function an_unknown_fieldtype_returns_a_nonzero_exit_code_and_json_error() - { - $data = $this->commandJson('statamic:fieldtypes:show', ['handle' => 'does_not_exist'], 1); - - $this->assertSame(['error' => 'Fieldtype [does_not_exist] not found'], $data); - } - #[Test] public function it_can_show_human_readable_configuration() { @@ -147,31 +114,27 @@ public function it_can_show_human_readable_configuration() } #[Test] - public function it_can_list_fieldtypes_without_json() - { - $this->artisan('statamic:fieldtypes:list') - ->expectsOutputToContain('bard') - ->expectsOutputToContain('markdown') - ->assertSuccessful(); - } - - #[Test] - public function it_can_run_both_commands_through_please() + public function it_can_run_through_please() { require_once __DIR__.'/../Kernel.php'; $kernel = app(\Statamic\Console\Please\Kernel::class); - $this->assertSame(0, $kernel->call('fieldtypes:list', ['--json' => true])); - $this->assertNotEmpty(json_decode($kernel->output(), true, 512, JSON_THROW_ON_ERROR)['fieldtypes']); - $this->assertSame(0, $kernel->call('fieldtypes:show', ['handle' => 'grid', '--json' => true])); $this->assertSame('grid', json_decode($kernel->output(), true, 512, JSON_THROW_ON_ERROR)['fieldtype']['handle']); } - private function commandJson(string $command, array $arguments = [], int $exitCode = 0): array + #[Test] + public function an_unknown_fieldtype_returns_a_nonzero_exit_code_and_json_error() { - $this->assertSame($exitCode, Artisan::call($command, $arguments + ['--json' => true])); + $data = $this->commandJson(['handle' => 'does_not_exist'], 1); + + $this->assertSame(['error' => 'Fieldtype [does_not_exist] not found'], $data); + } + + private function commandJson(array $arguments, int $exitCode = 0): array + { + $this->assertSame($exitCode, Artisan::call('statamic:fieldtypes:show', $arguments + ['--json' => true])); return json_decode(Artisan::output(), true, 512, JSON_THROW_ON_ERROR); } @@ -181,8 +144,6 @@ class InspectionFieldtype extends Fieldtype { protected static $title = 'Inspection'; - protected $selectable = false; - protected function configFieldItems(): array { return [ @@ -212,11 +173,3 @@ protected function configFieldItems(): array ]; } } - -class UnconfiguredInspectionFieldtype extends Fieldtype -{ - protected function configFieldItems(): array - { - throw new \RuntimeException('Listing must not evaluate configuration.'); - } -} From 8791b9db71233cde29d6d3b4a4a8a325146a6e51 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 29 Oct 2026 18:27:00 +0000 Subject: [PATCH 5/5] remove redundant `please` tests from fieldtype command tests --- tests/Console/Commands/FieldtypesListTest.php | 11 ----------- tests/Console/Commands/FieldtypesShowTest.php | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/tests/Console/Commands/FieldtypesListTest.php b/tests/Console/Commands/FieldtypesListTest.php index 064a6c11fc5..eba4c62c865 100644 --- a/tests/Console/Commands/FieldtypesListTest.php +++ b/tests/Console/Commands/FieldtypesListTest.php @@ -44,17 +44,6 @@ public function it_can_list_fieldtypes_without_json() ->expectsOutputToContain('markdown') ->assertSuccessful(); } - - #[Test] - public function it_can_run_through_please() - { - require_once __DIR__.'/../Kernel.php'; - - $kernel = app(\Statamic\Console\Please\Kernel::class); - - $this->assertSame(0, $kernel->call('fieldtypes:list', ['--json' => true])); - $this->assertNotEmpty(json_decode($kernel->output(), true, 512, JSON_THROW_ON_ERROR)['fieldtypes']); - } } class ListedFieldtype extends Fieldtype diff --git a/tests/Console/Commands/FieldtypesShowTest.php b/tests/Console/Commands/FieldtypesShowTest.php index 5553d94702c..9af024dbbcd 100644 --- a/tests/Console/Commands/FieldtypesShowTest.php +++ b/tests/Console/Commands/FieldtypesShowTest.php @@ -113,17 +113,6 @@ public function it_can_show_human_readable_configuration() $this->assertSame('table', $data['config']['mode']['default']); } - #[Test] - public function it_can_run_through_please() - { - require_once __DIR__.'/../Kernel.php'; - - $kernel = app(\Statamic\Console\Please\Kernel::class); - - $this->assertSame(0, $kernel->call('fieldtypes:show', ['handle' => 'grid', '--json' => true])); - $this->assertSame('grid', json_decode($kernel->output(), true, 512, JSON_THROW_ON_ERROR)['fieldtype']['handle']); - } - #[Test] public function an_unknown_fieldtype_returns_a_nonzero_exit_code_and_json_error() {