diff --git a/packages/framework/src/Console/Commands/Helpers/InputStreamHandler.php b/packages/framework/src/Console/Commands/Helpers/InputStreamHandler.php index ea830600d61..a5ae964f994 100644 --- a/packages/framework/src/Console/Commands/Helpers/InputStreamHandler.php +++ b/packages/framework/src/Console/Commands/Helpers/InputStreamHandler.php @@ -54,10 +54,4 @@ public static function mockInput(string $input): void { self::$mockedStreamBuffer = explode("\n", $input); } - - /** Format a consistent message that can be output to the console */ - public static function formatMessage(string $name, string $type = 'values'): string - { - return "Enter $type for field [$name] (end with an empty line)"; - } } diff --git a/packages/framework/src/Console/Commands/MakePublicationCommand.php b/packages/framework/src/Console/Commands/MakePublicationCommand.php index 3cccaa32d31..547b652e576 100644 --- a/packages/framework/src/Console/Commands/MakePublicationCommand.php +++ b/packages/framework/src/Console/Commands/MakePublicationCommand.php @@ -18,6 +18,7 @@ use Hyde\Framework\Features\Publications\PublicationFieldTypes; use Hyde\Framework\Features\Publications\PublicationService; use Illuminate\Support\Collection; +use Illuminate\Support\Str; use function implode; use function in_array; use InvalidArgumentException; @@ -42,15 +43,19 @@ class MakePublicationCommand extends ValidatingCommand protected PublicationType $publicationType; + /** @var \Illuminate\Support\Collection */ + protected Collection $fieldData; + public function safeHandle(): int { $this->title('Creating a new publication!'); $this->publicationType = $this->getPublicationTypeSelection(); + $this->fieldData = new Collection(); - $fieldData = $this->collectFieldData(); + $this->collectFieldData(); - $creator = new CreatesNewPublicationPage($this->publicationType, $fieldData, (bool) $this->option('force')); + $creator = new CreatesNewPublicationPage($this->publicationType, $this->fieldData, (bool) $this->option('force')); if ($creator->hasFileConflict()) { $this->error('Error: A publication already exists with the same canonical field value'); if ($this->confirm('Do you wish to overwrite the existing file?')) { @@ -63,7 +68,7 @@ public function safeHandle(): int } $creator->create(); - $this->info("Created file {$creator->getOutputPath()}"); + $this->infoComment('All done! Created file', $creator->getOutputPath()); return Command::SUCCESS; } @@ -71,12 +76,10 @@ public function safeHandle(): int protected function getPublicationTypeSelection(): PublicationType { $publicationTypes = $this->getPublicationTypes(); - - $publicationTypeSelection = $this->argument('publicationType') ?? $publicationTypes->keys()->get( - (int) $this->choice( - 'Which publication type would you like to create a publication item for?', - $publicationTypes->keys()->toArray() - ) + $publicationTypeSelection = $this->argument('publicationType') ?? + $this->choice( + 'Which publication type would you like to create a publication item for?', + $publicationTypes->keys()->toArray() ); if ($publicationTypes->has($publicationTypeSelection)) { @@ -88,7 +91,6 @@ protected function getPublicationTypeSelection(): PublicationType throw new InvalidArgumentException("Unable to locate publication type [$publicationTypeSelection]"); } - /** @return \Illuminate\Support\Collection */ protected function getPublicationTypes(): Collection { $publicationTypes = PublicationService::getPublicationTypes(); @@ -99,57 +101,50 @@ protected function getPublicationTypes(): Collection return $publicationTypes; } - /** @return \Illuminate\Support\Collection */ - protected function collectFieldData(): Collection + protected function collectFieldData(): void { $this->newLine(); $this->info('Now please enter the field data:'); - $data = new Collection(); /** @var PublicationField $field */ foreach ($this->publicationType->getFields() as $field) { if (str_starts_with($field->name, '__')) { continue; } + $this->newLine(); $fieldInput = $this->captureFieldInput($field); - if ($fieldInput !== null) { - $data->put($field->name, $fieldInput); + if (empty($fieldInput)) { + $this->line(" > Skipping field $field->name"); + } else { + $this->fieldData->put($field->name, $fieldInput); } } - return $data; + $this->newLine(); } protected function captureFieldInput(PublicationField $field): ?PublicationFieldValue { - $selection = match ($field->type) { + return match ($field->type) { PublicationFieldTypes::Text => $this->captureTextFieldInput($field), PublicationFieldTypes::Array => $this->captureArrayFieldInput($field), PublicationFieldTypes::Image => $this->captureImageFieldInput($field), PublicationFieldTypes::Tag => $this->captureTagFieldInput($field), - default => new ($field->type->fieldClass())($this->askWithValidation($field->name, "Enter data for field [$field->name]", $field->getValidationRules()->toArray())), + default => $this->captureOtherFieldInput($field), }; - - if (empty($selection)) { - $this->line(" > Skipping field $field->name"); - - return null; - } - - return $selection; } protected function captureTextFieldInput(PublicationField $field): TextField { - $this->line(InputStreamHandler::formatMessage($field->name, 'lines')); + $this->infoComment('Enter lines for field', $field->name, '(end with an empty line)'); return new TextField(implode("\n", InputStreamHandler::call())); } protected function captureArrayFieldInput(PublicationField $field): ArrayField { - $this->line(InputStreamHandler::formatMessage($field->name)); + $this->infoComment('Enter values for field', $field->name, '(end with an empty line)'); return new ArrayField(InputStreamHandler::call()); } @@ -163,10 +158,7 @@ protected function captureImageFieldInput(PublicationField $field): ?ImageField return $this->handleEmptyOptionsCollection($field, 'media file', "No media files found in directory _media/{$this->publicationType->getIdentifier()}/"); } - $filesArray = $mediaFiles->toArray(); - $selection = (int) $this->choice('Which file would you like to use?', $filesArray); - - return new ImageField($filesArray[$selection]); + return new ImageField($this->choice('Which file would you like to use?', $mediaFiles->toArray())); } protected function captureTagFieldInput(PublicationField $field): ?TagField @@ -189,6 +181,24 @@ protected function captureTagFieldInput(PublicationField $field): ?TagField return new TagField($choice); } + protected function captureOtherFieldInput(PublicationField $field): ?PublicationFieldValue + { + $selection = $this->askForFieldData($field->name, $field->getValidationRules()->toArray()); + if (empty($selection)) { + return null; + } + + $namespace = Str::beforeLast(PublicationFieldValue::class, '\\'); + $className = "$namespace\\{$field->type->name}Field"; + + return new $className($selection); + } + + protected function askForFieldData(string $name, array $rules): string + { + return $this->askWithValidation($name, "Enter data for field [$name]", $rules); + } + /** @return null */ protected function handleEmptyOptionsCollection(PublicationField $field, string $type, string $message) { @@ -197,7 +207,7 @@ protected function handleEmptyOptionsCollection(PublicationField $field, string } $this->newLine(); - $this->warn(" Warning: $message"); + $this->warn("Warning: $message"); if ($this->confirm('Would you like to skip this field?', true)) { return null; } else { diff --git a/packages/framework/src/Framework/Actions/CreatesNewPublicationPage.php b/packages/framework/src/Framework/Actions/CreatesNewPublicationPage.php index f624387fa51..9a71181ec88 100644 --- a/packages/framework/src/Framework/Actions/CreatesNewPublicationPage.php +++ b/packages/framework/src/Framework/Actions/CreatesNewPublicationPage.php @@ -34,7 +34,7 @@ class CreatesNewPublicationPage extends CreateAction implements CreateActionCont */ public function __construct(PublicationType $pubType, Collection $fieldData, bool $force = false) { - $fieldData->prepend(new DatetimeField(Carbon::now()->format('Y-m-d H:i:s')), '__createdAt'); + $fieldData->prepend(new DatetimeField((string) Carbon::now()), '__createdAt'); $this->pubType = $pubType; $this->fieldData = $fieldData; @@ -56,11 +56,11 @@ protected function getCanonicalValue(): string { $canonicalFieldName = $this->pubType->canonicalField; if ($canonicalFieldName === '__createdAt') { - return $this->getFieldValue('__createdAt')->getValue()->format('Y-m-d H:i:s'); + return $this->getFieldFromCollection('__createdAt')->getValue()->format('Y-m-d H:i:s'); } if ($this->fieldData->has($canonicalFieldName)) { - $field = $this->getFieldValue($canonicalFieldName); + $field = $this->getFieldFromCollection($canonicalFieldName); return (string) $field->getValue(); // TODO here we can check if field has interface allowing it to be canonical, else throw exception } else { @@ -87,7 +87,7 @@ protected function normalizeData(Collection $data): array })->toArray(); } - protected function getFieldValue(string $key): PublicationFieldValue + protected function getFieldFromCollection(string $key): PublicationFieldValue { return $this->fieldData->get($key); } diff --git a/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php b/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php index 8e82d50a9d4..4b439793dbb 100644 --- a/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php +++ b/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php @@ -77,9 +77,4 @@ public static function canonicable(): array self::Text, ]; } - - public function fieldClass(): string - { - return "Hyde\\Framework\\Features\\Publications\\Models\\PublicationFieldValues\\{$this->name}Field"; - } } diff --git a/packages/framework/tests/Feature/Commands/MakePublicationCommandTest.php b/packages/framework/tests/Feature/Commands/MakePublicationCommandTest.php index 6dd092dd44e..0332633c9f7 100644 --- a/packages/framework/tests/Feature/Commands/MakePublicationCommandTest.php +++ b/packages/framework/tests/Feature/Commands/MakePublicationCommandTest.php @@ -42,10 +42,10 @@ public function test_command_creates_publication() $this->artisan('make:publication') ->expectsOutputToContain('Creating a new publication!') - ->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication']) + ->expectsChoice('Which publication type would you like to create a publication item for?', 'test-publication', ['test-publication']) ->expectsOutput('Creating a new publication of type [test-publication]') ->expectsQuestion('Enter data for field [title]', 'Hello World') - ->expectsOutput('Created file test-publication/hello-world.md') + ->expectsOutput('All done! Created file [test-publication/hello-world.md]') ->assertExitCode(0); $this->assertFileExists(Hyde::path('test-publication/hello-world.md')); @@ -61,6 +61,51 @@ public function test_command_with_no_publication_types() ->assertExitCode(1); } + public function test_command_selects_the_right_publication_using_the_names() + { + $this->makeSchemaFile([ + 'canonicalField' => '__createdAt', + 'fields' => [], + ]); + $this->directory('second-publication'); + file_put_contents( + Hyde::path('second-publication/schema.json'), + json_encode([ + 'name' => 'Second Publication', + 'canonicalField' => '__createdAt', + 'detailTemplate' => 'detail', + 'listTemplate' => 'list', + 'pagination' => [ + 'pageSize' => 10, + 'prevNextLinks' => true, + 'sortField' => '__createdAt', + 'sortAscending' => true, + ], + 'fields' => [], + ]) + ); + + $this->artisan('make:publication') + ->expectsOutputToContain('Creating a new publication!') + ->expectsChoice('Which publication type would you like to create a publication item for?', 'test-publication', [ + 'second-publication', + 'test-publication', + ], true) + ->expectsOutput('Creating a new publication of type [test-publication]') + ->expectsOutput('All done! Created file [test-publication/2022-01-01-000000.md]') + ->assertExitCode(0); + + $this->artisan('make:publication') + ->expectsOutputToContain('Creating a new publication!') + ->expectsChoice('Which publication type would you like to create a publication item for?', 'second-publication', [ + 'second-publication', + 'test-publication', + ], true) + ->expectsOutput('Creating a new publication of type [second-publication]') + ->expectsOutput('All done! Created file [second-publication/2022-01-01-000000.md]') + ->assertExitCode(0); + } + public function test_command_with_existing_publication() { $this->makeSchemaFile(); @@ -68,7 +113,7 @@ public function test_command_with_existing_publication() $this->artisan('make:publication') ->expectsOutputToContain('Creating a new publication!') - ->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication']) + ->expectsChoice('Which publication type would you like to create a publication item for?', 'test-publication', ['test-publication']) ->expectsQuestion('Enter data for field [title]', 'Hello World') ->expectsOutput('Error: A publication already exists with the same canonical field value') ->expectsConfirmation('Do you wish to overwrite the existing file?') @@ -86,7 +131,7 @@ public function test_command_with_existing_publication_and_overwrite() $this->artisan('make:publication') ->expectsOutputToContain('Creating a new publication!') - ->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication']) + ->expectsChoice('Which publication type would you like to create a publication item for?', 'test-publication', ['test-publication']) ->expectsQuestion('Enter data for field [title]', 'Hello World') ->expectsOutput('Error: A publication already exists with the same canonical field value') ->expectsConfirmation('Do you wish to overwrite the existing file?', 'yes') @@ -102,7 +147,7 @@ public function test_can_overwrite_existing_publication_by_passing_force_flag() $this->artisan('make:publication', ['--force' => true]) ->expectsOutputToContain('Creating a new publication!') - ->expectsChoice('Which publication type would you like to create a publication item for?', 0, ['test-publication']) + ->expectsChoice('Which publication type would you like to create a publication item for?', 'test-publication', ['test-publication']) ->expectsQuestion('Enter data for field [title]', 'Hello World') ->assertExitCode(0); @@ -116,7 +161,7 @@ public function test_command_with_publication_type_passed_as_argument() $this->artisan('make:publication test-publication') ->expectsOutput('Creating a new publication of type [test-publication]') ->expectsQuestion('Enter data for field [title]', 'Hello World') - ->expectsOutput('Created file test-publication/hello-world.md') + ->expectsOutput('All done! Created file [test-publication/hello-world.md]') ->assertExitCode(0); $this->assertFileExists(Hyde::path('test-publication/hello-world.md')); @@ -258,6 +303,30 @@ public function test_command_with_image_input() $this->assertCreatedPublicationMatterEquals('image: _media/test-publication/image.jpg'); } + public function test_image_input_selects_the_right_file() + { + $this->directory('_media/test-publication'); + $this->file('_media/test-publication/foo.jpg'); + $this->file('_media/test-publication/bar.png'); + $this->file('_media/test-publication/baz.svg'); + + $this->makeSchemaFile([ + 'canonicalField' => '__createdAt', + 'fields' => [[ + 'type' => 'image', + 'name' => 'image', + ], + ], + ]); + + $this->artisan('make:publication test-publication') + ->expectsQuestion('Which file would you like to use?', '_media/test-publication/bar.png') + ->assertExitCode(0); + + $this->assertDatedPublicationExists(); + $this->assertCreatedPublicationMatterEquals('image: _media/test-publication/bar.png'); + } + public function test_command_with_single_tag_input() { $this->file('tags.json', json_encode([ @@ -319,7 +388,7 @@ public function test_image_input_with_no_images() ]); $this->artisan('make:publication test-publication') - ->expectsOutput(' Warning: No media files found in directory _media/test-publication/') + ->expectsOutput('Warning: No media files found in directory _media/test-publication/') ->expectsConfirmation('Would you like to skip this field?') ->expectsOutput('Error: Unable to locate any media files for this publication type') ->assertExitCode(1); @@ -339,7 +408,7 @@ public function test_image_input_with_no_images_but_skips() ]); $this->artisan('make:publication test-publication') - ->expectsOutput(' Warning: No media files found in directory _media/test-publication/') + ->expectsOutput('Warning: No media files found in directory _media/test-publication/') ->expectsConfirmation('Would you like to skip this field?', 'yes') ->doesntExpectOutput('Error: Unable to locate any media files for this publication type') ->assertExitCode(0); @@ -370,7 +439,7 @@ public function test_tag_input_with_no_tags() ]); $this->artisan('make:publication test-publication') - ->expectsOutput(' Warning: No tags for this publication type found in tags.json') + ->expectsOutput('Warning: No tags for this publication type found in tags.json') ->expectsConfirmation('Would you like to skip this field?') ->expectsOutput('Error: Unable to locate any tags for this publication type') ->assertExitCode(1); @@ -390,7 +459,7 @@ public function test_tag_input_with_no_tags_but_skips() ]); $this->artisan('make:publication test-publication') - ->expectsOutput(' Warning: No tags for this publication type found in tags.json') + ->expectsOutput('Warning: No tags for this publication type found in tags.json') ->expectsConfirmation('Would you like to skip this field?', 'yes') ->doesntExpectOutput('Error: Unable to locate any tags for this publication type') ->assertExitCode(0); @@ -422,7 +491,7 @@ public function test_handleEmptyOptionsCollection_for_required_field() ]); $this->artisan('make:publication test-publication') - ->doesntExpectOutput(' Warning: No tags for this publication type found in tags.json') + ->doesntExpectOutput('Warning: No tags for this publication type found in tags.json') ->expectsOutput('Error: Unable to create publication: No tags for this publication type found in tags.json') ->assertExitCode(1); } @@ -451,6 +520,35 @@ public function test_with_custom_validation_rules() $this->assertCreatedPublicationMatterEquals('integer: 5'); } + public function test_with_skipping_inputs() + { + $this->makeSchemaFile([ + 'canonicalField' => '__createdAt', + 'fields' => [[ + 'type' => 'string', + 'name' => 'string', + ], + ], + ]); + + $this->artisan('make:publication test-publication') + ->expectsQuestion('Enter data for field [string]', '') + ->expectsOutput(' > Skipping field string') + ->assertExitCode(0); + + $this->assertDatedPublicationExists(); + $this->assertEquals( + <<<'MARKDOWN' + --- + __createdAt: 2022-01-01T00:00:00+00:00 + --- + + ## Write something awesome. + + + MARKDOWN, $this->getDatedPublicationContents()); + } + protected function makeSchemaFile(array $merge = []): void { file_put_contents( diff --git a/packages/framework/tests/Feature/PublicationFieldValueObjectsTest.php b/packages/framework/tests/Feature/PublicationFieldValueObjectsTest.php index 55faaed703f..11a139da299 100644 --- a/packages/framework/tests/Feature/PublicationFieldValueObjectsTest.php +++ b/packages/framework/tests/Feature/PublicationFieldValueObjectsTest.php @@ -19,6 +19,7 @@ use Hyde\Framework\Features\Publications\Models\PublicationFieldValues\UrlField; use Hyde\Framework\Features\Publications\PublicationFieldTypes; use Hyde\Testing\TestCase; +use Illuminate\Support\Str; use InvalidArgumentException; use Symfony\Component\Yaml\Yaml; @@ -469,9 +470,10 @@ public function testTagFieldParsingOptions() public function testAllTypesHaveAValueClass() { + $namespace = Str::beforeLast(PublicationFieldValue::class, '\\'); foreach (PublicationFieldTypes::names() as $type) { $this->assertTrue( - class_exists("Hyde\\Framework\\Features\\Publications\\Models\\PublicationFieldValues\\{$type}Field"), + class_exists("$namespace\\{$type}Field"), "Missing value class for type $type" ); } diff --git a/packages/framework/tests/Unit/InputStreamHandlerTest.php b/packages/framework/tests/Unit/InputStreamHandlerTest.php index eee5e74fb66..80d34366883 100644 --- a/packages/framework/tests/Unit/InputStreamHandlerTest.php +++ b/packages/framework/tests/Unit/InputStreamHandlerTest.php @@ -43,11 +43,6 @@ public function testCanTerminateWithUnixEndings() $this->assertSame(0, $this->makeCommand(['foo', 'bar', 'baz'])->handle()); } - public function testFormatMessageHelperMethod() - { - $this->assertSame('Enter values for field [foo] (end with an empty line)', InputStreamHandler::formatMessage('foo')); - } - protected function makeCommand(array $expected): TestCommand { $command = new TestCommand;