From 64bc402d356a8c0194138fec3fa8fc69d9957ea7 Mon Sep 17 00:00:00 2001 From: Christian Leucht Date: Tue, 27 Jun 2023 14:55:27 +0200 Subject: [PATCH] ChoiceList // refactor internal structure to support "disabled"-attribute for checkbox, radio and options and allow in future to add more settings like "help"-text. View // complete refactor of markup to move away from and be more flexible with styling elements. All elements on output will now have the "type" as modifier on the wrapper. Additionally the
"-wrapper. + // we don't want to nest those when rendering. if ($next instanceof Element\CollectionElement) { - $html .= $this->render($next); + $html .= $this->render($next); } else { $html .= $row->render($next); } @@ -66,12 +65,22 @@ function ($html, ElementInterface $next) use ($element, $row): string { '' ); + $attributes = $element->attributes(); + $attributes = $this->buildCssClasses($attributes, 'collection', $element); + // we do not want to get the "name" and "type" rendered as attribute on wrapper. + unset($attributes['name'], $attributes['type']); + $errors = $this->factory->create(Errors::class) ->render($element); - $class = $errors !== '' - ? 'form-table--has-errors' - : ''; + if ($errors !== '') { + $attributes['class'] .= ' form-collection--has-errors'; + } - return sprintf('%1$s
%3$s
', $errors, $class, $html); + return sprintf( + '%1$s
%3$s
', + $errors, + $this->attributesToString($attributes), + $html + ); } } diff --git a/src/View/Form.php b/src/View/Form.php index b9ca45d..ca4755f 100644 --- a/src/View/Form.php +++ b/src/View/Form.php @@ -16,7 +16,6 @@ */ class Form implements RenderableElementInterface { - use AttributeFormatterTrait; use AssertElementInstanceOfTrait; @@ -37,9 +36,9 @@ public function __construct(ViewFactory $factory = null) /** * @param ElementInterface|FormInterface $element * + * @return string * @throws InvalidClassException|UnknownTypeException * - * @return string */ public function render(ElementInterface $element): string { @@ -57,20 +56,25 @@ static function ($html, ElementInterface $next) use ($element, $row): string { '' ); + $attributes = $element->attributes(); + // Don't re-use the "type" as attribute on
-tag. + unset($attributes['type']); + $classes = (string) ($attributes['class'] ?? ''); + $classes .= 'form'; + $attributes['class'] = $classes; + $errors = $element instanceof ErrorAwareInterface ? $this->factory->create(Errors::class) ->render($element) : ''; - - $class = $errors !== '' - ? 'form-table--has-errors' - : ''; - - $html = sprintf('%1$s %3$s
', $errors, $class, $html); + if ($errors !== '') { + $attributes['class'] .= ' form--has-errors'; + } return sprintf( - '%s
', - $this->attributesToString($element->attributes()), + '
%2$s %3$s
', + $this->attributesToString($attributes), + $errors, $html ); } diff --git a/src/View/FormRow.php b/src/View/FormRow.php index d48bc49..5c4df21 100644 --- a/src/View/FormRow.php +++ b/src/View/FormRow.php @@ -55,39 +55,40 @@ public function render(ElementInterface $element): string : ''; $label = $element instanceof LabelAwareInterface + && !in_array( + $element->type(), + ['submit', 'button', 'reset'], + true + ) ? $this->factory->create(Label::class) ->render($element) : ''; $html = ($label !== '') ? sprintf( - '%1$s%2$s %3$s %4$s', + '%1$s %2$s %3$s %4$s', $label, $field, $description, $errors ) : sprintf( - '%1$s %2$s %3$s', + '%1$s %2$s %3$s', $field, $description, $errors ); - $classes = ['form-row', 'form-row__' . $element->name(), 'form-row--' . $element->type()]; + $rowAttributes = $this->buildCssClasses([], 'row', $element);; if ($errors !== '') { - $classes[] = 'form-row--has-errors'; + $rowAttributes['class'] .= ' form-row--has-errors'; } if ($element->type() === 'hidden') { - $classes[] = 'hidden'; + $rowAttributes['class'] .= ' hidden'; } - $rowAttributes = [ - 'class' => implode(' ', $classes), - ]; - return sprintf( - '%s', + '
%s
', $this->attributesToString($rowAttributes), $html ); diff --git a/src/View/Input.php b/src/View/Input.php index 4e820fd..5c3b7fd 100644 --- a/src/View/Input.php +++ b/src/View/Input.php @@ -3,6 +3,7 @@ namespace ChriCo\Fields\View; use ChriCo\Fields\Element\ElementInterface; +use ChriCo\Fields\Element\LabelAwareInterface; /** * Class Input @@ -11,7 +12,6 @@ */ class Input implements RenderableElementInterface { - use AttributeFormatterTrait; /** @@ -22,6 +22,7 @@ class Input implements RenderableElementInterface public function render(ElementInterface $element): string { $attributes = $element->attributes(); + $attributes = $this->buildCssClasses($attributes, 'element', $element); return sprintf( '', diff --git a/src/View/Label.php b/src/View/Label.php index a5cd04f..f848730 100644 --- a/src/View/Label.php +++ b/src/View/Label.php @@ -12,16 +12,15 @@ */ class Label implements RenderableElementInterface { - use AttributeFormatterTrait; use AssertElementInstanceOfTrait; /** * @param ElementInterface|LabelAwareInterface $element * + * @return string * @throws InvalidClassException * - * @return string */ public function render(ElementInterface $element): string { @@ -32,9 +31,10 @@ public function render(ElementInterface $element): string } $attributes = $element->labelAttributes(); - if (! isset($attributes['for'])) { + if (!isset($attributes['for'])) { $attributes['for'] = $element->id(); } + $attributes = $this->buildCssClasses($attributes, 'label', $element); return sprintf( '', diff --git a/src/View/Progress.php b/src/View/Progress.php index ad2b8d8..dcd3e90 100644 --- a/src/View/Progress.php +++ b/src/View/Progress.php @@ -21,6 +21,8 @@ public function render(ElementInterface $element): string { $attributes = $element->attributes(); + $attributes = $this->buildCssClasses($attributes, 'element', $element); + return sprintf( '%s', $this->attributesToString($attributes), diff --git a/src/View/Radio.php b/src/View/Radio.php index 879e55a..2858884 100644 --- a/src/View/Radio.php +++ b/src/View/Radio.php @@ -4,6 +4,7 @@ use ChriCo\Fields\Element\ChoiceElementInterface; use ChriCo\Fields\Element\ElementInterface; +use ChriCo\Fields\Element\LabelAwareInterface; use ChriCo\Fields\Exception\InvalidClassException; /** @@ -33,16 +34,17 @@ public function render(ElementInterface $element): string $html = []; - foreach ($choices as $key => $name) { + foreach ($choices as $key => $choice) { $elementAttr = $attributes; $elementAttr['id'] .= '_'.$key; $elementAttr['value'] = $key; + $elementAttr['disabled'] = $choice['disabled']; $elementAttr['checked'] = isset($selected[$key]); $label = sprintf( '', $this->escapeAttribute($elementAttr['id']), - $this->escapeHtml($name) + $this->escapeHtml($choice['label']) ); $html[] = sprintf( diff --git a/src/View/Select.php b/src/View/Select.php index 8b0500b..cfb7f5e 100644 --- a/src/View/Select.php +++ b/src/View/Select.php @@ -12,22 +12,22 @@ */ class Select implements RenderableElementInterface { - use AttributeFormatterTrait; use AssertElementInstanceOfTrait; /** * @param ElementInterface|ChoiceElementInterface $element * + * @return string * @throws InvalidClassException * - * @return string */ public function render(ElementInterface $element): string { $this->assertElementIsInstanceOf($element, ChoiceElementInterface::class); $attributes = $element->attributes(); + $attributes = $this->buildCssClasses($attributes, 'element', $element); unset($attributes['type'], $attributes['value']); return sprintf( @@ -45,23 +45,24 @@ public function render(ElementInterface $element): string */ protected function renderChoices(ChoiceListInterface $list, $currentValue): string { - if (! is_array($currentValue)) { + if (!is_array($currentValue)) { $currentValue = [$currentValue]; } $selected = $list->choicesForValue($currentValue); $html = []; - foreach ($list->choices() as $key => $name) { + foreach ($list->choices() as $key => $choice) { $html[] = sprintf( '', $this->attributesToString( [ 'value' => $key, 'selected' => isset($selected[$key]), + 'disabled' => $choice['disabled'], ] ), - $this->escapeHtml($name) + $this->escapeHtml($choice['label']) ); } diff --git a/src/View/Textarea.php b/src/View/Textarea.php index 29b1f2e..edbe648 100644 --- a/src/View/Textarea.php +++ b/src/View/Textarea.php @@ -20,6 +20,7 @@ class Textarea implements RenderableElementInterface public function render(ElementInterface $element): string { $attributes = $element->attributes(); + $attributes = $this->buildCssClasses($attributes, 'element', $element); unset($attributes['value']); return sprintf( diff --git a/tests/phpunit/Unit/ChoiceList/ArrayChoiceListTest.php b/tests/phpunit/Unit/ChoiceList/ArrayChoiceListTest.php index f2f50e3..df3b167 100644 --- a/tests/phpunit/Unit/ChoiceList/ArrayChoiceListTest.php +++ b/tests/phpunit/Unit/ChoiceList/ArrayChoiceListTest.php @@ -29,8 +29,15 @@ public function testBasic(): void */ public function testGetChoices(): void { - $expected = ['foo' => 'bar']; - $testee = new ArrayChoiceList($expected); + $input = ['foo' => 'bar']; + $expected = [ + 'foo' => [ + 'value' => 'foo', + 'label' => 'bar', + 'disabled' => false + ] + ]; + $testee = new ArrayChoiceList($input); static::assertSame($expected, $testee->choices()); } @@ -49,8 +56,16 @@ public function testGetValues($input, $expected): void public function provideGetValues(): Generator { - yield 'string values' => [['foo' => 'bar'], ['foo']]; - yield 'int values' => [[0 => 'foo', 1 => 'bar'], ['0', '1']]; + yield 'string values' => [ + ['foo' => 'bar'], + ['foo'] + ]; + + + yield 'int values' => [ + [0 => 'foo', 1 => 'bar'], + ['0', '1'] + ]; } /** @@ -68,20 +83,31 @@ public function testGetChoicesForValue($list, $selected, $expected): void public function provideGetChoicesForValue(): Generator { + $option1 = [ + 'value' => 'foo', + 'label' => 'Foo', + 'disabled' => false, + ]; + $option2 = [ + 'value' => 'bar', + 'label' => 'Bar', + 'disabled' => false, + ]; + yield '1 selected' => [ - ['foo' => 'bar', 'baz' => 'bam'], + ['foo' => $option1, 'bar' => $option2], ['foo'], - ['foo' => 'bar'], + ['foo' => $option1], ]; yield 'multiple selected' => [ - ['foo' => 'bar', 'baz' => 'bam'], - ['foo', 'baz'], - ['foo' => 'bar', 'baz' => 'bam'], + ['foo' => $option1, 'bar' => $option2], + ['foo', 'bar'], + ['foo' => $option1, 'bar' => $option2], ]; yield 'nothing selected' => [ - ['foo' => 'bar', 'baz' => 'bam'], + ['foo' => $option1, 'bar' => $option2], [], [], ]; @@ -94,7 +120,7 @@ public function provideGetChoicesForValue(): Generator yield 'empty list' => [ [], - ['foo' => 'bar'], + ['foo'], [], ]; } diff --git a/tests/phpunit/Unit/ChoiceList/CallbackChoiceListTest.php b/tests/phpunit/Unit/ChoiceList/CallbackChoiceListTest.php index 045ede2..84a404d 100644 --- a/tests/phpunit/Unit/ChoiceList/CallbackChoiceListTest.php +++ b/tests/phpunit/Unit/ChoiceList/CallbackChoiceListTest.php @@ -30,7 +30,13 @@ public function testBasic(): void */ public function testGetChoices(): void { - $expected = ['foo' => 'bar']; + $expected = [ + 'foo' => [ + 'value' => 'foo', + 'label' => 'Foo', + 'disabled' => false, + ], + ]; $callback = function () use ($expected) { return $expected; diff --git a/tests/phpunit/Unit/Element/CollectionElementTest.php b/tests/phpunit/Unit/Element/CollectionElementTest.php index 5161a40..a1269a7 100644 --- a/tests/phpunit/Unit/Element/CollectionElementTest.php +++ b/tests/phpunit/Unit/Element/CollectionElementTest.php @@ -105,7 +105,8 @@ public function testAddErrorsDeletaged(): void /** @var ErrorAwareInterface $element */ $element = $testee->element($expected_element_name); - static::assertSame($expected_error, $element->errors()); + static::assertCount(1, $element->errors()); + static::assertSame(array_values($expected_error), array_values($element->errors())); } /** diff --git a/tests/phpunit/Unit/ElementFactoryTest.php b/tests/phpunit/Unit/ElementFactoryTest.php index 2ab1791..71b2a34 100644 --- a/tests/phpunit/Unit/ElementFactoryTest.php +++ b/tests/phpunit/Unit/ElementFactoryTest.php @@ -249,21 +249,31 @@ public function testCreateWithChoices($choices, array $expected, string $instanc public function provideCreateWithChoices(): Generator { + $choices = [ + 'foo' => [ + 'value' => 'foo', + 'label' => 'bar', + 'disabled' => false, + ], + 'baz' => [ + 'value' => 'baz', + 'label' => 'bam', + 'disabled' => false, + ], + ]; + // normal choice list yield 'choice array' => [ ['foo' => 'bar', 'baz' => 'bam'], - ['foo' => 'bar', 'baz' => 'bam'], + $choices, ArrayChoiceList::class, ]; - // choice list with callback - $expected = ['foo' => 'bar', 'baz' => 'bam']; - yield 'choices via callable' => [ - function () use ($expected) { - return $expected; + function () use ($choices) { + return $choices; }, - $expected, + $choices, CallbackChoiceList::class, ]; } diff --git a/tests/phpunit/Unit/View/ButtonTest.php b/tests/phpunit/Unit/View/ButtonTest.php new file mode 100644 index 0000000..653e18c --- /dev/null +++ b/tests/phpunit/Unit/View/ButtonTest.php @@ -0,0 +1,59 @@ +withAttribute('type', 'button'); + $element->withLabel($expectedLabel); + + $output = (new Button())->render($element); + static::assertStringContainsString('', $output); + } + + /** + * @test + */ + public function testRenderTypeReset(): void + { + $expectedLabel = 'some label'; + + $element = new Element('foo'); + $element->withAttribute('type', 'reset'); + $element->withLabel($expectedLabel); + + $output = (new Button())->render($element); + static::assertStringContainsString('', $output); + } +} diff --git a/tests/phpunit/Unit/View/FormRowTest.php b/tests/phpunit/Unit/View/FormRowTest.php index 25e150b..1e91120 100644 --- a/tests/phpunit/Unit/View/FormRowTest.php +++ b/tests/phpunit/Unit/View/FormRowTest.php @@ -30,10 +30,9 @@ public function testRender(): void $element->withAttribute('type', 'text'); $output = (new FormRow())->render($element); - static::assertStringContainsString('', $output); - static::assertStringContainsString('', $output); + static::assertStringContainsString('', $output); } /** @@ -50,10 +49,10 @@ public function testRenderWithLabel(): void $element->withLabel($expected_label); $output = (new FormRow())->render($element); - static::assertStringContainsString('