diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 5038851edefdb6bf029dab4d469d94f252d61a85..00246943f5471105e4d67b4456b21d3ce9422332 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -509,11 +509,17 @@ protected function doRender(&$elements, $is_root_call = FALSE) { // We store the resulting output in $elements['#markup'], to be consistent // with how render cached output gets stored. This ensures that placeholder // replacement logic gets the same data to work with, no matter if #cache is - // disabled, #cache is enabled, there is a cache hit or miss. - $prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : ''; - $suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : ''; - - $elements['#markup'] = Markup::create($prefix . $elements['#children'] . $suffix); + // disabled, #cache is enabled, there is a cache hit or miss. If + // #render_children is set the #prefix and #suffix will have already been + // added. + if (isset($elements['#render_children'])) { + $elements['#markup'] = Markup::create($elements['#children']); + } + else { + $prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : ''; + $suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : ''; + $elements['#markup'] = Markup::create($prefix . $elements['#children'] . $suffix); + } // We've rendered this element (and its subtree!), now update the context. $context->update($elements); diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php index 4698185c4f6a82b4264280ec42e4970e98eb3def..be96f3c587eed0556e21a75c3df784535cf69216 100644 --- a/core/modules/file/src/Tests/FileFieldValidateTest.php +++ b/core/modules/file/src/Tests/FileFieldValidateTest.php @@ -187,4 +187,25 @@ public function testFileRemoval() { $this->assertText('Article ' . $node->getTitle() . ' has been updated.'); } + /** + * Test the validation message is displayed only once for ajax uploads. + */ + public function testAJAXValidationMessage() { + $field_name = strtolower($this->randomMachineName()); + $this->createFileField($field_name, 'node', 'article'); + + $this->drupalGet('node/add/article'); + /** @var \Drupal\file\FileInterface $image_file */ + $image_file = $this->getTestFile('image'); + $edit = [ + 'files[' . $field_name . '_0]' => $this->container->get('file_system')->realpath($image_file->getFileUri()), + 'title[0][value]' => $this->randomMachineName(), + ]; + $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_0_upload_button'); + $elements = $this->xpath('//div[contains(@class, :class)]', [ + ':class' => 'messages--error', + ]); + $this->assertEqual(count($elements), 1, 'Ajax validation messages are displayed once.'); + } + } diff --git a/core/modules/image/src/Tests/ImageFieldValidateTest.php b/core/modules/image/src/Tests/ImageFieldValidateTest.php index f630a24f7cfbea70b1b4d4389a4aa3316dbd20a4..98210d642199d92a4295865857cf5d11bc4d8ee9 100644 --- a/core/modules/image/src/Tests/ImageFieldValidateTest.php +++ b/core/modules/image/src/Tests/ImageFieldValidateTest.php @@ -161,4 +161,26 @@ protected function getFieldSettings($min_resolution, $max_resolution) { ]; } + /** + * Test the validation message is displayed only once for ajax uploads. + */ + public function testAJAXValidationMessage() { + $field_name = strtolower($this->randomMachineName()); + $this->createImageField($field_name, 'article', ['cardinality' => -1]); + + $this->drupalGet('node/add/article'); + /** @var \Drupal\file\FileInterface[] $text_files */ + $text_files = $this->drupalGetTestFiles('text'); + $text_file = reset($text_files); + $edit = [ + 'files[' . $field_name . '_0][]' => $this->container->get('file_system')->realpath($text_file->uri), + 'title[0][value]' => $this->randomMachineName(), + ]; + $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_0_upload_button'); + $elements = $this->xpath('//div[contains(@class, :class)]', [ + ':class' => 'messages--error', + ]); + $this->assertEqual(count($elements), 1, 'Ajax validation messages are displayed once.'); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Render/RenderTest.php b/core/tests/Drupal/KernelTests/Core/Render/RenderTest.php index a64b553a16dd581cfbbfe20b47dc7d866e962a9b..2acaefb873b62b80272d495704b5297c99234b36 100644 --- a/core/tests/Drupal/KernelTests/Core/Render/RenderTest.php +++ b/core/tests/Drupal/KernelTests/Core/Render/RenderTest.php @@ -16,7 +16,7 @@ class RenderTest extends KernelTestBase { * * @var array */ - public static $modules = ['system', 'common_test']; + public static $modules = ['system', 'common_test', 'theme_test']; /** * Tests theme preprocess functions being able to attach assets. @@ -43,6 +43,23 @@ public function testDrupalRenderThemePreprocessAttached() { \Drupal::state()->set('theme_preprocess_attached_test', FALSE); } + /** + * Ensures that render array children are processed correctly. + */ + public function testRenderChildren() { + // Ensure that #prefix and #suffix is only being printed once since that is + // the behaviour the caller code expects. + $build = [ + '#type' => 'container', + '#theme' => 'theme_test_render_element_children', + '#prefix' => 'kangaroo', + '#suffix' => 'kitten', + ]; + $this->render($build); + $this->removeWhiteSpace(); + $this->assertNoRaw('