diff --git a/core/modules/views/src/Controller/ViewAjaxController.php b/core/modules/views/src/Controller/ViewAjaxController.php index 6cd3cc85007a156b577b43786381b57fc1626c5a..1b0188982e58b4074d2880636d128a035cfe3d14 100644 --- a/core/modules/views/src/Controller/ViewAjaxController.php +++ b/core/modules/views/src/Controller/ViewAjaxController.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Ajax\PrependCommand; use Drupal\Core\Ajax\ReplaceCommand; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityStorageInterface; @@ -200,6 +201,7 @@ public function ajaxView(Request $request) { ->applyTo($preview); } $response->addCommand(new ReplaceCommand(".js-view-dom-id-$dom_id", $preview)); + $response->addCommand(new PrependCommand(".js-view-dom-id-$dom_id", ['#type' => 'status_messages'])); return $response; } diff --git a/core/modules/views/src/Form/ViewsExposedForm.php b/core/modules/views/src/Form/ViewsExposedForm.php index bbeb4db2766ed131e8368ed38b38b90f23db04ed..bd6a9edd93cf7be31cbab5dc672a39b9df4ce4cc 100644 --- a/core/modules/views/src/Form/ViewsExposedForm.php +++ b/core/modules/views/src/Form/ViewsExposedForm.php @@ -145,6 +145,9 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['#action'] = $form_action; $form['#theme'] = $view->buildThemeFunctions('views_exposed_form'); $form['#id'] = Html::cleanCssIdentifier('views_exposed_form-' . $view->storage->id() . '-' . $display['id']); + // Labels are built too late for inline form errors to work, resulting + // in duplicated messages. + $form['#disable_inline_form_errors'] = TRUE; /** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form_plugin */ $exposed_form_plugin = $view->display_handler->getPlugin('exposed_form'); diff --git a/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php index 24873e674f5c5450a9edc1fa7ec0641811e86e15..2e312269e4dbd2c0ec278347383b36796cb7de03 100644 --- a/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php +++ b/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php @@ -5,6 +5,7 @@ use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; +use Drupal\views\Tests\ViewTestData; /** * Tests the basic AJAX functionality of Views exposed forms. @@ -19,13 +20,25 @@ class ExposedFilterAJAXTest extends WebDriverTestBase { /** * {@inheritdoc} */ - protected static $modules = ['node', 'views', 'views_test_modal']; + protected static $modules = [ + 'node', + 'views', + 'views_test_modal', + 'user_test_views', + ]; /** * {@inheritdoc} */ protected $defaultTheme = 'stark'; + /** + * Views used by this test. + * + * @var array + */ + public static $testViews = ['test_user_name']; + /** * {@inheritdoc} */ @@ -37,6 +50,12 @@ protected function setUp(): void { ->set('display.default.display_options.use_ajax', TRUE) ->save(); + // Import user_test_views and set it to use ajax. + ViewTestData::createTestViews(get_class($this), ['user_test_views']); + \Drupal::configFactory()->getEditable('views.view.test_user_name') + ->set('display.default.display_options.use_ajax', TRUE) + ->save(); + // Create a Content type and two test nodes. $this->createContentType(['type' => 'page']); $this->createNode(['title' => 'Page One']); @@ -194,4 +213,25 @@ public function testExposedFilteringWithButtonElement() { $this->assertSame($ajax_views_before, $ajax_views_after); } + /** + * Tests that errors messages are displayed for exposed filters via ajax. + */ + public function testExposedFilterErrorMessages(): void { + $this->drupalGet('test_user_name'); + // Submit an invalid name, triggering validation errors. + $name = $this->randomMachineName(); + $this->submitForm(['uid' => $name], 'Apply'); + $this->assertSession()->waitForElement('css', 'div[aria-label="Error message"]'); + $this->assertSession()->pageTextContainsOnce(sprintf('There are no users matching "%s"', $name)); + + \Drupal::service('module_installer')->install(['inline_form_errors']); + + $this->drupalGet('test_user_name'); + // Submit an invalid name, triggering validation errors. + $name = $this->randomMachineName(); + $this->submitForm(['uid' => $name], 'Apply'); + $this->assertSession()->waitForElement('css', 'div[aria-label="Error message"]'); + $this->assertSession()->pageTextContainsOnce(sprintf('There are no users matching "%s"', $name)); + } + } diff --git a/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php b/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php index fdef9f93620c40f5e37cfb9bc48425b27ea27854..af6f8a135e904693dc6207e672654e234fbb7a92 100644 --- a/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php +++ b/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php @@ -74,7 +74,7 @@ protected function setUp(): void { ->getMock(); $this->renderer = $this->createMock('\Drupal\Core\Render\RendererInterface'); $this->renderer->expects($this->any()) - ->method('render') + ->method('renderRoot') ->willReturnCallback(function (array &$elements) { $elements['#attached'] = []; @@ -93,6 +93,10 @@ protected function setUp(): void { $this->viewAjaxController = new ViewAjaxController($this->viewStorage, $this->executableFactory, $this->renderer, $this->currentPath, $this->redirectDestination); $element_info_manager = $this->createMock('\Drupal\Core\Render\ElementInfoManagerInterface'); + $element_info_manager->expects($this->any()) + ->method('getInfo') + ->with('status_messages') + ->willReturn([]); $request_stack = new RequestStack(); $request_stack->push(new Request()); $this->renderer = new Renderer(