Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions src/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace ipl\Html;

use Generator;
use ipl\Html\Contract\DefaultFormElementDecoration;
use ipl\Html\Contract\FormDecoration;
use ipl\Html\Contract\FormElement;
use ipl\Html\Contract\FormSubmitElement;
use ipl\Html\Contract\MutableHtml;
use ipl\Html\FormDecoration\DecoratorChain;
use ipl\Html\FormDecoration\FormDecorationResult;
use ipl\Html\FormElement\FormElements;
Expand Down Expand Up @@ -354,9 +354,7 @@ public function validate()
*/
public function validatePartial()
{
$this->ensureAssembled();

foreach ($this->getElements() as $element) {
foreach ($this->yieldElementsRecursive($this) as $element) {
if ($element->hasValue()) {
$element->validate();
}
Expand Down Expand Up @@ -442,4 +440,28 @@ protected function beforeRender(): void

$this->applyDecoration();
}

/**
* Yield all sub form elements of $from by traversing the form elements tree recursively
*
* Recurses into nested {@see FormElements} instances instead of yielding them directly.
*
* @param Contract\FormElements $from Entry point for traversing
*
* @return Generator<FormElement>
*/
protected function yieldElementsRecursive(Contract\FormElements $from): Generator
{
if ($from instanceof HtmlDocument) {
$from->ensureAssembled();
}

foreach ($from->getElements() as $element) {
if ($element instanceof Contract\FormElements) {
yield from $this->yieldElementsRecursive($element);
} else {
yield $element;
}
}
}
}
41 changes: 41 additions & 0 deletions tests/FormTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use ipl\Html\Form;
use ipl\Html\FormElement\BaseFormElement;
use ipl\Html\FormElement\FieldsetElement;
use ipl\Html\Test\TestCase;
use Psr\Http\Message\ServerRequestInterface;

Expand Down Expand Up @@ -63,6 +64,46 @@ public function testEscapeReservedChars(): void
$this->assertSame(Form::escapeReservedChars('foo-bar123'), 'foo-bar123');
}

public function testValidatePartialOnlyValidatesFieldsetChildrenWithAValue(): void
{
$fieldset = (new FieldsetElement('set'))
->addElement('text', 'filled', ['required' => true])
->addElement('text', 'empty', ['required' => true]);
$fieldset->getElement('filled')->setValue('value');

$form = (new Form())
->addElement($fieldset)
->validatePartial();

$this->assertTrue(
$form->getElement('set')->getElement('filled')->isValid(),
'Fieldset child with a value is not validated during partial validation'
);
$this->assertEmpty(
$form->getElement('set')->getElement('empty')->getMessages(),
'Empty fieldset child produces a required error during partial validation'
);
}

public function testValidatePartialRecursesIntoNestedFieldsets(): void
{
$innerFieldset = (new FieldsetElement('inner'))
->addElement('text', 'deep', ['required' => true]);
$innerFieldset->getElement('deep')->setValue('value');

$outerFieldset = (new FieldsetElement('outer'))
->addElement($innerFieldset);

$form = (new Form())
->addElement($outerFieldset)
->validatePartial();

$this->assertTrue(
$form->getElement('outer')->getElement('inner')->getElement('deep')->isValid(),
'Nested fieldset child with a value is not validated during partial validation'
);
}

public function testFormElementsWithReservedCharsInName(): void
{
$form = new Form();
Expand Down
Loading