diff --git a/packages/framework/src/Framework/Features/Publications/Models/PublicationField.php b/packages/framework/src/Framework/Features/Publications/Models/PublicationField.php index dba5f247cff..cdd191bde3d 100644 --- a/packages/framework/src/Framework/Features/Publications/Models/PublicationField.php +++ b/packages/framework/src/Framework/Features/Publications/Models/PublicationField.php @@ -10,6 +10,7 @@ use Hyde\Framework\Features\Publications\PublicationService; use Hyde\Support\Concerns\Serializable; use Hyde\Support\Contracts\SerializableContract; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Str; use Rgasch\Collection\Collection; use function strtolower; @@ -27,17 +28,19 @@ class PublicationField implements SerializableContract public readonly PublicationFieldTypes $type; public readonly string $name; public readonly ?string $tagGroup; + public readonly array $rules; public static function fromArray(array $array): static { return new static(...$array); } - public function __construct(PublicationFieldTypes|string $type, string $name, ?string $tagGroup = null) + public function __construct(PublicationFieldTypes|string $type, string $name, ?string $tagGroup = null, array $rules = []) { $this->type = $type instanceof PublicationFieldTypes ? $type : PublicationFieldTypes::from(strtolower($type)); $this->name = Str::kebab($name); $this->tagGroup = $tagGroup; + $this->rules = $rules; } public function toArray(): array @@ -46,35 +49,26 @@ public function toArray(): array 'type' => $this->type->value, 'name' => $this->name, 'tagGroup' => $this->tagGroup, + 'rules' => $this->rules, ]); } /** * @param \Hyde\Framework\Features\Publications\Models\PublicationType|null $publicationType Required only when using the 'image' type. * - * @see \Hyde\Framework\Testing\Unit\PublicationFieldTypeValidationRulesTest * @see https://laravel.com/docs/9.x/validation#available-validation-rules */ public function getValidationRules(?PublicationType $publicationType = null): Collection { - $defaultRules = Collection::create(PublicationFieldTypes::values()); - $fieldRules = Collection::create($defaultRules->get($this->type->value)); + $fieldRules = Collection::create(PublicationFieldTypes::getRules($this->type)); - switch ($this->type->value) { - case 'array': - $fieldRules->add('array'); - break; - case 'datetime': - $fieldRules->add('date'); + // Here we could check for a "strict" mode type of thing and add 'required' to the rules if we wanted to. - break; - case 'float': - $fieldRules->add('numeric'); - break; - case 'integer': - case 'string': - case 'text': - break; + // Apply any field rules. + $fieldRules->push(...$this->rules); + + // Apply any dynamic rules. + switch ($this->type->value) { case 'image': $mediaFiles = PublicationService::getMediaForPubType($publicationType); $valueList = $mediaFiles->implode(','); @@ -85,23 +79,21 @@ public function getValidationRules(?PublicationType $publicationType = null): Co $valueList = $tagValues->implode(','); $fieldRules->add("in:$valueList"); break; - case 'url': - $fieldRules->add('url'); - break; } return $fieldRules; } /** @param \Hyde\Framework\Features\Publications\Models\PublicationType|null $publicationType Required only when using the 'image' type. */ - public function validate(mixed $input = null, Collection $fieldRules = null, ?PublicationType $publicationType = null): array + public function validate(mixed $input = null, Arrayable|array|null $fieldRules = null, ?PublicationType $publicationType = null): array { - if (! $fieldRules) { - $fieldRules = $this->getValidationRules($publicationType); - } + $rules = $this->evaluateArrayable($fieldRules ?? $this->getValidationRules($publicationType)); - $validator = validator([$this->name => $input], [$this->name => $fieldRules->toArray()]); + return validator([$this->name => $input], [$this->name => $rules])->validate(); + } - return $validator->validate(); + protected function evaluateArrayable(array|Arrayable $array): array + { + return $array instanceof Arrayable ? $array->toArray() : $array; } } diff --git a/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php b/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php index 605d43e9ed6..41323c2d6c8 100644 --- a/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php +++ b/packages/framework/src/Framework/Features/Publications/PublicationFieldTypes.php @@ -4,7 +4,6 @@ namespace Hyde\Framework\Features\Publications; -use BadMethodCallException; use Illuminate\Support\Collection; /** @@ -45,16 +44,16 @@ public static function getRules(self $type): array { /** @noinspection PhpDuplicateMatchArmBodyInspection */ return match ($type) { - self::String => ['required', 'string', 'between'], - self::Boolean => ['required', 'boolean'], - self::Integer => ['required', 'integer', 'between'], - self::Float => ['required', 'numeric', 'between'], - self::Datetime => ['required', 'datetime', 'between'], - self::Url => ['required', 'url'], - self::Text => ['required', 'string', 'between'], - self::Array => throw new BadMethodCallException('This type has no validation rules'), - self::Image => throw new BadMethodCallException('This type has no validation rules'), - self::Tag => throw new BadMethodCallException('This type has no validation rules'), + self::String => ['string'], + self::Boolean => ['boolean'], + self::Integer => ['integer', 'numeric'], + self::Float => ['numeric'], + self::Datetime => ['date'], + self::Url => ['url'], + self::Text => ['string'], + self::Array => ['array'], + self::Image => [], + self::Tag => [], }; } } diff --git a/packages/framework/tests/Feature/PublicationFieldTest.php b/packages/framework/tests/Feature/PublicationFieldTest.php index 09fd93be487..cb25990fef5 100644 --- a/packages/framework/tests/Feature/PublicationFieldTest.php +++ b/packages/framework/tests/Feature/PublicationFieldTest.php @@ -5,8 +5,10 @@ namespace Hyde\Framework\Testing\Feature; use Hyde\Framework\Features\Publications\Models\PublicationField; +use Hyde\Framework\Features\Publications\Models\PublicationType; use Hyde\Framework\Features\Publications\PublicationFieldTypes; use Hyde\Testing\TestCase; +use Illuminate\Validation\ValidationException; use ValueError; /** @@ -44,13 +46,14 @@ public function test_can_get_field_as_array() ], (new PublicationField('string', 'test'))->toArray()); } - public function test_can_get_field_with_tag_group_as_array() + public function test_can_get_field_with_optional_properties_as_array() { $this->assertSame([ 'type' => 'string', 'name' => 'test', 'tagGroup' => 'foo', - ], (new PublicationField('string', 'test', 'foo'))->toArray()); + 'rules' => ['required'], + ], (new PublicationField('string', 'test', 'foo', ['required']))->toArray()); } public function test_can_encode_field_as_json() @@ -58,9 +61,9 @@ public function test_can_encode_field_as_json() $this->assertSame('{"type":"string","name":"test"}', json_encode(new PublicationField('string', 'test'))); } - public function test_can_encode_field_with_tag_group_as_json() + public function test_can_get_field_with_optional_properties_as_json() { - $this->assertSame('{"type":"string","name":"test","tagGroup":"foo"}', json_encode(new PublicationField('string', 'test', 'foo'))); + $this->assertSame('{"type":"string","name":"test","tagGroup":"foo","rules":["required"]}', json_encode(new PublicationField('string', 'test', 'foo', ['required']))); } public function test_can_construct_type_using_enum_case() @@ -94,8 +97,147 @@ public function test_name_gets_stored_as_kebab_case() $this->assertSame('test-field', $field->name); } - public function test_validate_input_against_rules() + public function testValidate() { - $this->markTestIncomplete('TODO: Implement this method.'); + $validated = (new PublicationField('string', 'myString'))->validate('foo'); + $this->assertSame(['my-string' => 'foo'], $validated); + + $this->expectValidationException('The my-string must be a string.'); + (new PublicationField('string', 'myString'))->validate(1); + } + + public function testValidateWithCustomTypeRules() + { + $validated = (new PublicationField('string', 'myString', rules: ['min:3']))->validate('foo'); + $this->assertSame(['my-string' => 'foo'], $validated); + + $this->expectValidationException('The my-string must be at least 5 characters.'); + (new PublicationField('string', 'myString', rules: ['min:5']))->validate('foo'); + } + + public function testValidateWithCustomRuleCollection() + { + $validated = (new PublicationField('string', 'myString'))->validate('foo', ['min:3']); + $this->assertSame(['my-string' => 'foo'], $validated); + + $this->expectValidationException('The my-string must be at least 5 characters.'); + (new PublicationField('string', 'myString'))->validate('foo', ['min:5']); + } + + public function testValidateWithCustomRuleCollectionOverridesDefaultRules() + { + $this->expectValidationException('The my-string must be a number.'); + (new PublicationField('string', 'myString'))->validate('foo', ['numeric']); + } + + public function testValidateMethodAcceptsArrayOfRules() + { + $validated = (new PublicationField('string', 'myString'))->validate('foo', ['min:3']); + $this->assertSame(['my-string' => 'foo'], $validated); + } + + public function testValidateMethodAcceptsArrayableOfRules() + { + $validated = (new PublicationField('string', 'myString'))->validate('foo', collect(['min:3'])); + $this->assertSame(['my-string' => 'foo'], $validated); + } + + public function testGetRules() + { + $rules = (new PublicationField('string', 'myString'))->getValidationRules(); + $this->assertSame(['string'], $rules->toArray()); + } + + public function testGetRulesWithCustomTypeRules() + { + $rules = (new PublicationField('string', 'myString', rules: ['foo', 'bar']))->getValidationRules(); + $this->assertSame(['string', 'foo', 'bar'], $rules->toArray()); + } + + public function testGetRulesForArray() + { + $rules = (new PublicationField('array', 'myArray'))->getValidationRules(); + $this->assertSame(['array'], $rules->toArray()); + } + + public function testValidateArrayPasses() + { + $validated = (new PublicationField('array', 'myArray'))->validate(['foo', 'bar', 'baz']); + $this->assertSame(['my-array' => ['foo', 'bar', 'baz']], $validated); + } + + public function testValidateArrayFails() + { + $this->expectValidationException('The my-array must be an array.'); + (new PublicationField('array', 'myArray'))->validate('foo'); + } + + public function testGetRulesForDatetime() + { + $rules = (new PublicationField('datetime', 'myDatetime'))->getValidationRules(); + $this->assertSame(['date'], $rules->toArray()); + } + + public function testValidateDatetimePasses() + { + $validated = (new PublicationField('datetime', 'myDatetime'))->validate('2021-01-01'); + $this->assertSame(['my-datetime' => '2021-01-01'], $validated); + } + + public function testValidateDatetimeFailsForInvalidType() + { + $this->expectValidationException('The my-datetime is not a valid date.'); + (new PublicationField('datetime', 'myDatetime'))->validate('string'); + } + + public function testGetRulesForFloat() + { + $rules = (new PublicationField('float', 'myFloat'))->getValidationRules(); + $this->assertSame(['numeric'], $rules->toArray()); + } + + public function testGetRulesForInteger() + { + $rules = (new PublicationField('integer', 'myInteger'))->getValidationRules(); + $this->assertSame(['integer', 'numeric'], $rules->toArray()); + } + + public function testGetRulesForString() + { + $rules = (new PublicationField('string', 'myString'))->getValidationRules(); + $this->assertSame(['string'], $rules->toArray()); + } + + public function testGetRulesForText() + { + $rules = (new PublicationField('text', 'myText'))->getValidationRules(); + $this->assertSame(['string'], $rules->toArray()); + } + + public function testGetRulesForImage() + { + $this->directory('_media/foo'); + $this->file('_media/foo/bar.jpg'); + $this->file('_media/foo/baz.png'); + $rules = (new PublicationField('image', 'myImage'))->getValidationRules(publicationType: new PublicationType('foo')); + $this->assertSame(['in:_media/foo/bar.jpg,_media/foo/baz.png'], $rules->toArray()); + } + + public function testGetRulesForTag() + { + $rules = (new PublicationField('tag', 'myTag', tagGroup: 'foo'))->getValidationRules(); + $this->assertSame(['in:'], $rules->toArray()); + } + + public function testGetRulesForUrl() + { + $rules = (new PublicationField('url', 'myUrl'))->getValidationRules(); + $this->assertSame(['url'], $rules->toArray()); + } + + protected function expectValidationException(string $message): void + { + $this->expectException(ValidationException::class); + $this->expectExceptionMessage($message); } } diff --git a/packages/framework/tests/Feature/PublicationFieldTypesEnumTest.php b/packages/framework/tests/Feature/PublicationFieldTypesEnumTest.php index fe5b248de49..5c0e2a51908 100644 --- a/packages/framework/tests/Feature/PublicationFieldTypesEnumTest.php +++ b/packages/framework/tests/Feature/PublicationFieldTypesEnumTest.php @@ -4,7 +4,6 @@ namespace Hyde\Framework\Testing\Feature; -use BadMethodCallException; use Hyde\Framework\Features\Publications\PublicationFieldTypes; use Hyde\Testing\TestCase; @@ -29,13 +28,18 @@ public function testCases() $this->assertSame('tag', PublicationFieldTypes::Tag->value); } - public function testCanGetRulesForEnum() + public function testGetRules() { - $this->assertSame([ - 'required', - 'string', - 'between', - ], PublicationFieldTypes::String->rules()); + $this->assertSame(['string'], PublicationFieldTypes::String->rules()); + $this->assertSame(['boolean'], PublicationFieldTypes::Boolean->rules()); + $this->assertSame(['integer', 'numeric'], PublicationFieldTypes::Integer->rules()); + $this->assertSame(['numeric'], PublicationFieldTypes::Float->rules()); + $this->assertSame(['date'], PublicationFieldTypes::Datetime->rules()); + $this->assertSame(['url'], PublicationFieldTypes::Url->rules()); + $this->assertSame(['string'], PublicationFieldTypes::Text->rules()); + $this->assertSame(['array'], PublicationFieldTypes::Array->rules()); + $this->assertSame([], PublicationFieldTypes::Image->rules()); + $this->assertSame([], PublicationFieldTypes::Tag->rules()); } public function testCollectCreatesCollectionOfCases() @@ -58,11 +62,4 @@ public function testValuesReturnsArrayOfCaseValues() 9 => 'tag', ], PublicationFieldTypes::values()); } - - public function testCanGetRulesForEnumWithNoRules() - { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessage('This type has no validation rules'); - PublicationFieldTypes::Tag->rules(); - } } diff --git a/packages/framework/tests/Feature/PublicationTypeTest.php b/packages/framework/tests/Feature/PublicationTypeTest.php index c40a25be672..e37d7c80583 100644 --- a/packages/framework/tests/Feature/PublicationTypeTest.php +++ b/packages/framework/tests/Feature/PublicationTypeTest.php @@ -169,30 +169,45 @@ public function test_get_field_rules() { $publicationType = new PublicationType(...$this->getTestData()); $this->assertEquals([ - 'title' => [], + 'title' => ['string'], ], $publicationType->getFieldRules()->toArray()); } - protected function getTestData(): array + public function test_get_field_rules_with_custom_type_rules() { - return [ - 'name' => 'Test Publication', + $publicationType = new PublicationType(...$this->getTestData(['fields' => [ + 'title' => [ + 'name' => 'title', + 'type' => 'string', + 'rules' => ['required', 'foo'], + ], + ]])); + + $this->assertEquals([ + 'title' => ['string', 'required', 'foo'], + ], $publicationType->getFieldRules()->toArray()); + } + + protected function getTestData(array $mergeData = []): array + { + return array_merge([ + 'name' => 'Test Publication', 'canonicalField' => 'title', 'detailTemplate' => 'test-publication_detail', - 'listTemplate' => 'test-publication_list', + 'listTemplate' => 'test-publication_list', 'pagination' => [ - 'sortField' => '__createdAt', - 'sortAscending' => true, - 'prevNextLinks' => true, - 'pageSize' => 25, + 'sortField' => '__createdAt', + 'sortAscending' => true, + 'prevNextLinks' => true, + 'pageSize' => 25, ], - 'fields' => [ + 'fields' => [ [ 'name' => 'title', 'type' => 'string', ], ], - ]; + ], $mergeData); } protected function getTestDataWithPathInformation(): array diff --git a/packages/framework/tests/Unit/PublicationFieldValidationRulesTest.php b/packages/framework/tests/Unit/PublicationFieldValidationRulesTest.php deleted file mode 100644 index 20425a0217a..00000000000 --- a/packages/framework/tests/Unit/PublicationFieldValidationRulesTest.php +++ /dev/null @@ -1,103 +0,0 @@ -getValidationRules(); - $this->assertSame(['array'], $rules->toArray()); - } - - public function testValidateArrayPasses() - { - $validated = (new PublicationField('array', 'myArray'))->validate(['foo', 'bar', 'baz']); - $this->assertSame(['my-array' => ['foo', 'bar', 'baz']], $validated); - } - - public function testValidateArrayFails() - { - $this->expectValidationException('The my-array must be an array.'); - (new PublicationField('array', 'myArray'))->validate('foo'); - } - - public function testGetRulesForDatetime() - { - $rules = (new PublicationField('datetime', 'myDatetime'))->getValidationRules(); - $this->assertSame(['date'], $rules->toArray()); - } - - public function testValidateDatetimePasses() - { - $validated = (new PublicationField('datetime', 'myDatetime'))->validate('2021-01-01'); - $this->assertSame(['my-datetime' => '2021-01-01'], $validated); - } - - public function testValidateDatetimeFailsForInvalidType() - { - $this->expectValidationException('The my-datetime is not a valid date.'); - (new PublicationField('datetime', 'myDatetime'))->validate('string'); - } - - public function testGetRulesForFloat() - { - $rules = (new PublicationField('float', 'myFloat'))->getValidationRules(); - $this->assertSame(['numeric'], $rules->toArray()); - } - - public function testGetRulesForInteger() - { - $rules = (new PublicationField('integer', 'myInteger'))->getValidationRules(); - $this->assertSame([], $rules->toArray()); - } - - public function testGetRulesForString() - { - $rules = (new PublicationField('string', 'myString'))->getValidationRules(); - $this->assertSame([], $rules->toArray()); - } - - public function testGetRulesForText() - { - $rules = (new PublicationField('text', 'myText'))->getValidationRules(); - $this->assertSame([], $rules->toArray()); - } - - public function testGetRulesForImage() - { - $this->directory('_media/foo'); - $this->file('_media/foo/bar.jpg'); - $this->file('_media/foo/baz.png'); - $rules = (new PublicationField('image', 'myImage'))->getValidationRules(publicationType: new PublicationType('foo')); - $this->assertSame(['in:_media/foo/bar.jpg,_media/foo/baz.png'], $rules->toArray()); - } - - public function testGetRulesForTag() - { - $rules = (new PublicationField('tag', 'myTag', tagGroup: 'foo'))->getValidationRules(); - $this->assertSame(['in:'], $rules->toArray()); - } - - public function testGetRulesForUrl() - { - $rules = (new PublicationField('url', 'myUrl'))->getValidationRules(); - $this->assertSame(['url'], $rules->toArray()); - } - - protected function expectValidationException(string $message): void - { - $this->expectException(ValidationException::class); - $this->expectExceptionMessage($message); - } -}