diff --git a/core/misc/message.js b/core/misc/message.js index e7c13b3..9505ad3 100644 --- a/core/misc/message.js +++ b/core/misc/message.js @@ -9,6 +9,7 @@ var messages = []; var messagesElement; var debouncedProcessMessages; + var messageWrapperSelector = '[data-drupal-messages]'; /** * Builds a div element with the aria-live attribute and attaches it to the DOM. @@ -16,49 +17,47 @@ Drupal.behaviors.drupalMessage = { attach: function () { if (!messagesElement) { - if (!$('[data-drupal-messages]').length) { + var $messagesWrapper = $(messageWrapperSelector); + if (!$messagesWrapper.length) { throw new Error(Drupal.t('There is no element with a [data-drupal-messages] attribute')); } - messagesElement = $('[data-drupal-messages]')[0]; + messagesElement = $messagesWrapper[0]; } } }; + // Debounce the function in case Drupal.message() is used in a loop. + debouncedProcessMessages = debounce(processMessages, 100); + /** * Displays all queued messages in one pass instead of one after the other. * @see debouncedProcessMessages */ function processMessages() { var text = []; - var message; - var messagesIndex; - var messagesLength; - if (messages.length) { - for (messagesIndex = 0, messagesLength = messages.length; messagesIndex < messagesLength; messagesIndex++) { - message = messages.pop(); - text.unshift(Drupal.theme('message', message)); - } - if (text.length) { - messagesElement.innerHTML += text.join(''); - } + messages.reverse().forEach(function (message) { + text.unshift(Drupal.theme('message', message)); + }); + if (text.length) { + messagesElement.innerHTML += text.join(''); } - } + // Reset the messages array after the messages are processed. + messages = []; - // Debounce the function in case Drupal.message() is used in a loop. - debouncedProcessMessages = debounce(processMessages, 100); + } Drupal.message = {}; /** * Displays a message on the page. * - * @param {String} message + * @param {string} message * The message to display - * @param {String} type + * @param {string} type * Message type, can be either 'status', 'error' or 'warning'. * Default to 'status' - * @param {String} context + * @param {string} context * The context of the message, used for removing messages again. */ Drupal.message.add = function (message, type, context) { @@ -77,9 +76,9 @@ /** * Removes messages from the page. * - * @param {String} context + * @param {string} context * The message context to remove - * @param {String} type + * @param {string} type * Message type, can be either 'status', 'error' or 'warning'. * Default to 'status' */ @@ -92,14 +91,18 @@ /** * Theme function for a message. * - * @param {Object} message - * An object with the following keys: - * - {String} text: The message text - * - {String} type: The message type - * @return {String} + * @param {object} message + * The message object. + * @param {string} message.text + * The message text. + * @param {string} message.type + * The message type. + * @param {string} message.context + * The message context. + * @return {string} * A string representing a DOM fragment. */ - Drupal.theme.message = function (message, context) { + Drupal.theme.message = function (message) { return '
' + message.text + '
'; }; diff --git a/core/modules/system/tests/modules/js_message_test/js/js_message_test.js b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js index 4f54544..576916f 100644 --- a/core/modules/system/tests/modules/js_message_test/js/js_message_test.js +++ b/core/modules/system/tests/modules/js_message_test/js/js_message_test.js @@ -27,6 +27,13 @@ var context = e.currentTarget.getAttribute('data-context'); message.remove(context, type); }); + $('.show-multiple').once('show-msg').on('click', function (e) { + e.preventDefault(); + for (var i = 0; i < 10; i++) { + message.add('Msg-' + i, 'default', 'context-' + i); + } + + }); } }; diff --git a/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml index 33c49e0..53da0f9 100644 --- a/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml +++ b/core/modules/system/tests/modules/js_message_test/js_message_test.libraries.yml @@ -4,3 +4,4 @@ show_message: js/js_message_test.js: {} dependencies: - core/drupal.message + - core/jquery.once diff --git a/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php index 839ef4f..ce25516 100644 --- a/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php +++ b/core/modules/system/tests/modules/js_message_test/src/Controller/JSMessageTestController.php @@ -40,8 +40,14 @@ public function messageLinks() { ], ]; } - } + $links['show-multi'] = [ + 'title' => "Show Multiple", + 'url' => Url::fromRoute('js_message_test.links'), + 'attributes' => [ + 'class' => ['show-multiple'], + ], + ]; return [ '#theme' => 'links', '#links' => $links, diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php index fc8746f..1893879 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/JsMessageTest.php @@ -16,22 +16,79 @@ class JsMessageTest extends JavascriptTestBase { */ public static $modules = ['js_message_test']; + /** + * The possible message contexts. + * + * @var array + * + * @see \Drupal\js_message_test\Controller\JSMessageTestController::messageLinks + */ + protected $msgContexts = ['context1', 'context2']; + + /** + * The possible message types. + * + * @var array + * + * @see \Drupal\js_message_test\Controller\JSMessageTestController::messageLinks + */ + protected $msgTypes = ['status', 'error', 'warning']; - public function testJSMessages() { + /** + * Test click on links to show messages and remove messages. + */ + public function testAddRemoveMessages() { $web_assert = $this->assertSession(); $this->drupalGet('js_message_test_link'); $web_assert->elementExists('css', '[data-drupal-messages]'); - $contexts = ['context1', 'context2']; - $types = ['status', 'error', 'warning']; - foreach ($contexts as $context) { - foreach ($types as $type) { + + foreach ($this->msgContexts as $context) { + foreach ($this->msgTypes as $type) { $this->clickLink("Show-$context-$type"); - $this->getSession()->wait('200'); $selector = ".messages.messages--$type.js-messages.js-messages-context--$context"; $msg_element = $web_assert->waitForElementVisible('css', $selector); $this->assertNotEmpty($msg_element, "Message element visible: $selector"); $web_assert->elementContains('css', $selector, "Msg-$type-$context"); - // @todo Test clicking remove links. + // Click all remove links except the one that will remove the message. + $this->clickAllRemoveLinksExcept($context, $type); + // Confirm the message was not removed. + $web_assert->elementContains('css', $selector, "Msg-$type-$context"); + // Click the remove links that should remove the message. + $this->clickLink("Remove-$context-$type"); + $web_assert->elementNotExists('css', $selector); + } + } + + // Test adding multiple messages at once. + // @see processMessages() + $this->clickLink('Show Multiple'); + $message_number = 0; + $last_msg_element = $web_assert->waitForElementVisible('css', '.messages.messages--default.js-messages.js-messages-context--context-9'); + $this->assertNotEmpty($last_msg_element, "Last message element visible."); + $message_divs = $this->getSession()->getPage()->findAll('css', '.messages'); + $this->assertEquals(10, count($message_divs), 'All messages shown'); + + foreach ($message_divs as $message_div) { + /** @var \Behat\Mink\Element\NodeElement $message_div */ + $this->assertEquals("Msg-$message_number", $message_div->getText()); + $message_number++; + } + } + + /** + * Clicks all remove message links except for combination specified. + * + * @param string $exclude_context + * The message context to exclude. + * @param string $exclude_type + * The message type to exclude. + */ + protected function clickAllRemoveLinksExcept($exclude_context, $exclude_type) { + foreach ($this->msgContexts as $context) { + foreach ($this->msgTypes as $type) { + if ($context !== $exclude_context || $type !== $exclude_type) { + $this->clickLink("Remove-$context-$type"); + } } } }