diff --git a/demos/form-control/lookup.php b/demos/form-control/lookup.php index 79d470ce8c..36a6df2092 100644 --- a/demos/form-control/lookup.php +++ b/demos/form-control/lookup.php @@ -48,16 +48,13 @@ ]); $form->onSubmit(function (Form $form) { - $str = $form->model->ref('country1')->get(Country::hinting()->fieldName()->name) - . '; ' - . $form->model->ref('country2')->get(Country::hinting()->fieldName()->name) - . '; ' - . (new Country($form->getApp()->db))->load($form->model->get('country3')) - ->get(Country::hinting()->fieldName()->name); - $view = new Message('Select:'); // need in behat test. $view->invokeInit(); - $view->text->addParagraph($str); + $view->text->addParagraph($form->model->ref('country1')->get(Country::hinting()->fieldName()->name) ?? 'null'); + $view->text->addParagraph($form->model->ref('country2')->get(Country::hinting()->fieldName()->name) ?? 'null'); + $view->text->addParagraph($form->model->get('country3') !== '' // related with https://github.com/atk4/ui/pull/1805 + ? (new Country($form->getApp()->db))->load($form->model->get('country3'))->get(Country::hinting()->fieldName()->name) + : 'null'); return $view; }); diff --git a/src/Form.php b/src/Form.php index 4a101156af..8353ab3d83 100644 --- a/src/Form.php +++ b/src/Form.php @@ -259,6 +259,10 @@ public function onSubmit(\Closure $callback) } catch (ValidationException $e) { $response = []; foreach ($e->errors as $field => $error) { + if (!isset($this->controls[$field])) { + throw $e; + } + $response[] = $this->error($field, $error); } diff --git a/tests/FormTest.php b/tests/FormTest.php index 792bd1aab6..5b3fedd1f0 100644 --- a/tests/FormTest.php +++ b/tests/FormTest.php @@ -6,9 +6,11 @@ use Atk4\Core\Phpunit\TestCase; use Atk4\Data\Model; +use Atk4\Data\ValidationException; use Atk4\Ui\App; use Atk4\Ui\Callback; use Atk4\Ui\Exception; +use Atk4\Ui\Exception\UnhandledCallbackExceptionError; use Atk4\Ui\Form; use Mvorisek\Atk4\Hintable\Phpstan\PhpstanUtil; @@ -115,11 +117,6 @@ public function testTextarea(): void }); } - public function assertSubmitError(array $post, \Closure $checkExpectedErrorsFx): void - { - $this->assertSubmit($post, null, $checkExpectedErrorsFx); - } - public function assertFormControlError(string $field, string $error): void { $n = preg_match_all('~form\("add prompt", "([^"]*)", "([^"]*)"\)~', $this->formError, $matchesAll, \PREG_SET_ORDER); @@ -162,7 +159,7 @@ public function testSubmitError(): void $m = $m->createEntity(); $this->form->setModel($m); - $this->assertSubmitError(['opt1' => '2', 'opt3_z' => '0', 'opt4' => '', 'opt4_z' => '0'], function (string $formError) { + $this->assertSubmit(['opt1' => '2', 'opt3_z' => '0', 'opt4' => '', 'opt4_z' => '0'], null, function (string $formError) { // dropdown validates to make sure option is proper $this->assertFormControlError('opt1', 'not one of the allowed values'); @@ -176,6 +173,38 @@ public function testSubmitError(): void $this->assertFormControlError('opt4_z', 'Must not be empty'); }); } + + public function testSubmitNonFormFieldError(): void + { + $m = new Model(); + $m->addField('foo', ['nullable' => false]); + $m->addField('bar', ['nullable' => false]); + + $m = $m->createEntity(); + $this->form->setModel($m, ['foo']); + + $submitReached = false; + $catchReached = false; + try { + try { + $this->assertSubmit(['foo' => 'x'], function (Model $model) use (&$submitReached) { + $submitReached = true; + $model->set('bar', null); + }); + } catch (UnhandledCallbackExceptionError $e) { + $catchReached = true; + static::assertSame('bar', $e->getPrevious()->getParams()['field']->shortName); // @phpstan-ignore-line + + $this->expectException(ValidationException::class); + $this->expectExceptionMessage('Must not be null'); + + throw $e->getPrevious(); + } + } finally { + static::assertTrue($submitReached); + static::assertTrue($catchReached); + } + } } class AppFormTestMock extends App