Skip to content

Commit cc83b8b

Browse files
authored
Added props validation (#61)
* Added validation for 'items' option in ListFieldTrait * Added validation for non-empty 'id' in LabelledChoiceInputTrait and corresponding test * Removed unused class * Added validation for non-empty 'name' option * Enhanced ChoiceInputLabel validation to disallow empty 'for' values * Enhanced ListField and ListFieldTrait with additional properties for checkbox and radio button items * Refactor ListFieldTraitTest to improve invalid item options testing * Refactor ListFieldTrait to use fluent interface for option definitions * Fixed namespace usage for stdClass
1 parent a7a2dfd commit cc83b8b

File tree

13 files changed

+283
-48
lines changed

13 files changed

+283
-48
lines changed

src/lib/Twig/Components/AbstractChoiceInput.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ public function validate(array $props): array
4444
$resolver
4545
->define('name')
4646
->required()
47-
->allowedTypes('string');
47+
->allowedTypes('string')
48+
->allowedValues(static function (string $value): bool {
49+
return trim($value) !== '';
50+
});
4851
$resolver
4952
->define('checked')
5053
->allowedTypes('bool')

src/lib/Twig/Components/AbstractChoiceInputField.php

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/lib/Twig/Components/Checkbox/ListField.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@
1515

1616
/**
1717
* @phpstan-type CheckboxItem array{
18+
* id: non-empty-string,
1819
* value: string|int,
1920
* label: string,
20-
* disabled?: bool
21+
* disabled?: bool,
22+
* name?: string,
23+
* required?: bool,
24+
* attributes?: array<string, mixed>,
25+
* label_attributes?: array<string, mixed>,
26+
* inputWrapperClassName?: string,
27+
* labelClassName?: string,
28+
* checked?: bool
2129
* }
2230
* @phpstan-type CheckboxItems list<CheckboxItem>
2331
*/

src/lib/Twig/Components/ChoiceInputLabel.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ public function validate(array $props): array
3636
$resolver
3737
->define('for')
3838
->required()
39-
->allowedTypes('string');
39+
->allowedTypes('string')
40+
->allowedValues(static function (string $value): bool {
41+
return trim($value) !== '';
42+
});
4043

4144
return $resolver->resolve($props) + $props;
4245
}

src/lib/Twig/Components/LabelledChoiceInputTrait.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ protected function validateLabelledProps(OptionsResolver $resolver): void
2626
$resolver
2727
->define('id')
2828
->required()
29-
->allowedTypes('string');
29+
->allowedTypes('string')
30+
->allowedValues(static function (string $value): bool {
31+
return trim($value) !== '';
32+
});
3033
$resolver
3134
->define('inputWrapperClassName')
3235
->allowedTypes('string')

src/lib/Twig/Components/ListFieldTrait.php

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@
1313

1414
/**
1515
* @phpstan-type ListItem array{
16+
* id: non-empty-string,
1617
* value: string|int,
1718
* label: string,
19+
* disabled?: bool,
20+
* name?: string,
21+
* required?: bool,
22+
* attributes?: array<string, mixed>,
23+
* label_attributes?: array<string, mixed>,
24+
* inputWrapperClassName?: string,
25+
* labelClassName?: string
1826
* }
1927
* @phpstan-type ListItems list<ListItem>
2028
*/
@@ -53,10 +61,57 @@ protected function modifyListItem(array $item): array
5361

5462
protected function validateListFieldProps(OptionsResolver $resolver): void
5563
{
56-
$resolver->setDefaults([
57-
'items' => [],
58-
]);
59-
$resolver->setAllowedTypes('items', 'array');
64+
$resolver
65+
->define('items')
66+
->default([])
67+
->allowedTypes('array');
68+
69+
$resolver->setOptions('items', static function (OptionsResolver $itemsResolver): void {
70+
$itemsResolver->setPrototype(true);
71+
$itemsResolver
72+
->define('id')
73+
->required()
74+
->allowedTypes('string')
75+
->allowedValues(static fn (string $value): bool => trim($value) !== '');
76+
77+
$itemsResolver
78+
->define('label')
79+
->required()
80+
->allowedTypes('string');
81+
82+
$itemsResolver
83+
->define('value')
84+
->required()
85+
->allowedTypes('string', 'int');
86+
87+
$itemsResolver
88+
->define('disabled')
89+
->allowedTypes('bool');
90+
91+
$itemsResolver
92+
->define('attributes')
93+
->allowedTypes('array');
94+
95+
$itemsResolver
96+
->define('label_attributes')
97+
->allowedTypes('array');
98+
99+
$itemsResolver
100+
->define('inputWrapperClassName')
101+
->allowedTypes('string');
102+
103+
$itemsResolver
104+
->define('labelClassName')
105+
->allowedTypes('string');
106+
107+
$itemsResolver
108+
->define('name')
109+
->allowedTypes('string');
110+
111+
$itemsResolver
112+
->define('required')
113+
->allowedTypes('bool');
114+
});
60115

61116
$resolver
62117
->define('direction')

src/lib/Twig/Components/RadioButton/ListField.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@
1515

1616
/**
1717
* @phpstan-type RadioButtonItem array{
18+
* id: non-empty-string,
1819
* value: string|int,
1920
* label: string,
20-
* disabled?: bool
21+
* disabled?: bool,
22+
* name?: string,
23+
* required?: bool,
24+
* attributes?: array<string, mixed>,
25+
* label_attributes?: array<string, mixed>,
26+
* inputWrapperClassName?: string,
27+
* labelClassName?: string
2128
* }
2229
* @phpstan-type RadioButtonItems list<RadioButtonItem>
2330
*/

tests/integration/Twig/Components/Checkbox/FieldTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,24 @@ public function testInvalidIndeterminateTypeCausesResolverErrorOnMount(): void
101101
]));
102102
}
103103

104+
public function testEmptyNameCausesResolverErrorOnMount(): void
105+
{
106+
$this->expectException(InvalidOptionsException::class);
107+
108+
$this->mountTwigComponent(Field::class, $this->baseProps([
109+
'name' => '',
110+
]));
111+
}
112+
113+
public function testEmptyIdCausesResolverErrorOnMount(): void
114+
{
115+
$this->expectException(InvalidOptionsException::class);
116+
117+
$this->mountTwigComponent(Field::class, $this->baseProps([
118+
'id' => '',
119+
]));
120+
}
121+
104122
public function testMissingRequiredOptionsCauseResolverErrorOnMount(): void
105123
{
106124
$this->expectException(MissingOptionsException::class);

tests/integration/Twig/Components/Checkbox/InputTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ public function testInvalidIndeterminateTypeCausesResolverErrorOnMount(): void
104104
]);
105105
}
106106

107+
public function testEmptyNameCausesResolverErrorOnMount(): void
108+
{
109+
$this->expectException(InvalidOptionsException::class);
110+
111+
$this->mountTwigComponent(Input::class, [
112+
'id' => 'agree',
113+
'name' => '',
114+
'value' => 'yes',
115+
]);
116+
}
117+
107118
public function testMissingRequiredOptionsCauseResolverErrorOnMount(): void
108119
{
109120
$this->expectException(MissingOptionsException::class);

tests/integration/Twig/Components/ChoiceInputLabelTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ public function testAttributesMergeClassAndData(): void
7171
self::assertSame('custom', $label->attr('data-custom'), 'Custom data attribute should be rendered on the label.');
7272
}
7373

74+
public function testEmptyForCausesResolverErrorOnMount(): void
75+
{
76+
$this->expectException(InvalidOptionsException::class);
77+
78+
$this->mountTwigComponent(ChoiceInputLabel::class, ['for' => '', 'content' => 'x']);
79+
}
80+
7481
public function testInvalidForTypeCausesResolverErrorOnMount(): void
7582
{
7683
$this->expectException(InvalidOptionsException::class);

0 commit comments

Comments
 (0)