From c8806a02991f04233f859d2ef8870363172a2186 Mon Sep 17 00:00:00 2001
From: bnjmnm <benm@umich.edu>
Date: Wed, 7 Jun 2023 15:08:50 -0400
Subject: [PATCH] Issue #3364713 by lauriii, bnjmnm, andy-blum, Dave Reid,
 cosmicdreams: Claro: Messages can be malformed when JS creates messages and
 PHP messages already exist

---
 .../js_message_test.routing.yml               |   8 ++
 .../Controller/JSMessageTestController.php    |  16 +++
 .../modules/olivero_test/olivero_test.module  |  19 ----
 .../nightwatch_testing.info.yml               |   1 +
 .../Tests/Claro/claroDrupalMessageTest.js     | 101 ++++++++++++++++++
 .../Tests/Olivero/oliveroDrupalMessageTest.js |   4 +-
 core/themes/claro/js/messages.js              |   5 +-
 .../templates/misc/status-messages.html.twig  |  84 ++++++++-------
 8 files changed, 175 insertions(+), 63 deletions(-)
 create mode 100644 core/tests/Drupal/Nightwatch/Tests/Claro/claroDrupalMessageTest.js

diff --git a/core/modules/system/tests/modules/js_message_test/js_message_test.routing.yml b/core/modules/system/tests/modules/js_message_test/js_message_test.routing.yml
index 4c325e84611e..a3e5f44d009e 100644
--- a/core/modules/system/tests/modules/js_message_test/js_message_test.routing.yml
+++ b/core/modules/system/tests/modules/js_message_test/js_message_test.routing.yml
@@ -5,3 +5,11 @@ js_message_test.links:
     _title: 'JsMessageLinks'
   requirements:
     _access: 'TRUE'
+
+js_message_test.links_with_system_messages:
+  path: '/js_message_test_link_with_system_messages'
+  defaults:
+    _controller: '\Drupal\js_message_test\Controller\JSMessageTestController::messageLinksWithSystemMessages'
+    _title: 'JsMessageLinks'
+  requirements:
+    _access: 'TRUE'
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 c6cd8b4670f6..b242656daeba 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
@@ -29,6 +29,22 @@ public static function getMessagesSelectors() {
     return ['', '[data-drupal-messages-other]'];
   }
 
+  /**
+   * Displays links to show messages via JavaScript with messages from backend.
+   *
+   * @return array
+   *   Render array for links.
+   */
+  public function messageLinksWithSystemMessages() {
+    // Add PHP rendered messages to the page.
+    $messenger = \Drupal::messenger();
+    $messenger->addStatus('PHP Status');
+    $messenger->addWarning('PHP Warning');
+    $messenger->addError('PHP Error');
+
+    return $this->messageLinks();
+  }
+
   /**
    * Displays links to show messages via JavaScript.
    *
diff --git a/core/modules/system/tests/modules/olivero_test/olivero_test.module b/core/modules/system/tests/modules/olivero_test/olivero_test.module
index 2f6ccb442031..5c2c0c4a44e7 100644
--- a/core/modules/system/tests/modules/olivero_test/olivero_test.module
+++ b/core/modules/system/tests/modules/olivero_test/olivero_test.module
@@ -15,25 +15,6 @@ function olivero_test_preprocess_field_multiple_value_form(&$variables) {
   }
 }
 
-/**
- * Implements hook_preprocess().
- */
-function olivero_test_preprocess_page(&$variables) {
-  $route = \Drupal::routeMatch()->getRouteName();
-
-  switch ($route) {
-    case 'js_message_test.links':
-      $messenger = \Drupal::messenger();
-      $messenger->addStatus('PHP Status');
-      $messenger->addWarning('PHP Warning');
-      $messenger->addError('PHP Error');
-      break;
-
-    default:
-      break;
-  }
-}
-
 /**
  * Implements hook_preprocess_html().
  */
diff --git a/core/profiles/nightwatch_testing/nightwatch_testing.info.yml b/core/profiles/nightwatch_testing/nightwatch_testing.info.yml
index 6e3f05ad741c..c13d545635ba 100644
--- a/core/profiles/nightwatch_testing/nightwatch_testing.info.yml
+++ b/core/profiles/nightwatch_testing/nightwatch_testing.info.yml
@@ -5,3 +5,4 @@ version: VERSION
 hidden: true
 install:
   - js_testing_log_test
+  - nightwatch_theme_install_utility
diff --git a/core/tests/Drupal/Nightwatch/Tests/Claro/claroDrupalMessageTest.js b/core/tests/Drupal/Nightwatch/Tests/Claro/claroDrupalMessageTest.js
new file mode 100644
index 000000000000..e4366650142f
--- /dev/null
+++ b/core/tests/Drupal/Nightwatch/Tests/Claro/claroDrupalMessageTest.js
@@ -0,0 +1,101 @@
+const mainContent = '.region-content';
+const mainMessagesContainer =
+  '[data-drupal-messages] > .messages-list__wrapper';
+const secondaryMessagesContainer = '[data-drupal-messages-other]';
+
+const mainButtons = {
+  addStatus: '#add--status',
+  removeStatus: '#remove--status',
+  addError: '#add--error',
+  removeError: '#remove--error',
+  addWarning: '#add--warning',
+  removeWarning: '#remove--warning',
+  clearAll: '#clear-all',
+};
+
+const secondaryButtons = {
+  addStatus: '[id="add-[data-drupal-messages-other]-status"]',
+  removeStatus: '[id="remove-[data-drupal-messages-other]-status"]',
+  addError: '[id="add-[data-drupal-messages-other]-error"]',
+  removeError: '[id="remove-[data-drupal-messages-other]-error"]',
+  addWarning: '[id="add-[data-drupal-messages-other]-warning"]',
+  removeWarning: '[id="remove-[data-drupal-messages-other]-warning"]',
+};
+
+module.exports = {
+  '@tags': ['core', 'claro'],
+  before(browser) {
+    browser
+      .drupalInstall()
+      .drupalInstallModule('js_message_test')
+      .drupalEnableTheme('claro');
+  },
+  after(browser) {
+    browser.drupalUninstall();
+  },
+  'Verify default placement of javascript-created messages': (browser) => {
+    browser
+      .drupalRelativeURL('/js_message_test_link_with_system_messages')
+      .waitForElementVisible(mainContent)
+      .assert.elementPresent(mainMessagesContainer)
+
+      // We should load 3 messages on page load from \Drupal::messenger()
+      .assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 3)
+
+      // We should have one message of each type
+      .assert.elementCount(`${mainMessagesContainer} > .messages--status`, 1)
+      .assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 1)
+      .assert.elementCount(`${mainMessagesContainer} > .messages--error`, 1)
+
+      // Trigger new messages via javascript
+      .click(mainButtons.addStatus)
+      .click(mainButtons.addWarning)
+      .click(mainButtons.addError)
+
+      // We should have 6 total messages
+      .assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 6)
+
+      // We should have 2 messages of each type
+      .assert.elementCount(`${mainMessagesContainer} > .messages--status`, 2)
+      .assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 2)
+      .assert.elementCount(`${mainMessagesContainer} > .messages--error`, 2);
+  },
+
+  'Verify customized placement of javascript-created messages': (browser) => {
+    browser
+      .drupalRelativeURL('/js_message_test_link_with_system_messages')
+      .waitForElementVisible(mainContent)
+      .assert.elementPresent(secondaryMessagesContainer)
+
+      // We should load 3 messages on page load from \Drupal::messenger()
+      .assert.elementCount(
+        `${secondaryMessagesContainer} > .messages-list__item`,
+        0,
+      )
+
+      // Trigger new messages via javascript
+      .click(secondaryButtons.addStatus)
+      .click(secondaryButtons.addWarning)
+      .click(secondaryButtons.addError)
+
+      // We should have 6 total messages
+      .assert.elementCount(
+        `${secondaryMessagesContainer} > .messages-list__item`,
+        3,
+      )
+
+      // We should have 2 messages of each type
+      .assert.elementCount(
+        `${secondaryMessagesContainer} > .messages--status`,
+        1,
+      )
+      .assert.elementCount(
+        `${secondaryMessagesContainer} > .messages--warning`,
+        1,
+      )
+      .assert.elementCount(
+        `${secondaryMessagesContainer} > .messages--error`,
+        1,
+      );
+  },
+};
diff --git a/core/tests/Drupal/Nightwatch/Tests/Olivero/oliveroDrupalMessageTest.js b/core/tests/Drupal/Nightwatch/Tests/Olivero/oliveroDrupalMessageTest.js
index 958f33381f03..9e145eded55c 100644
--- a/core/tests/Drupal/Nightwatch/Tests/Olivero/oliveroDrupalMessageTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/Olivero/oliveroDrupalMessageTest.js
@@ -35,7 +35,7 @@ module.exports = {
   },
   'Verify default placement of javascript-created messages': (browser) => {
     browser
-      .drupalRelativeURL('/js_message_test_link')
+      .drupalRelativeURL('/js_message_test_link_with_system_messages')
       .waitForElementVisible(mainContent)
       .assert.elementPresent(mainMessagesContainer)
 
@@ -63,7 +63,7 @@ module.exports = {
 
   'Verify customized placement of javascript-created messages': (browser) => {
     browser
-      .drupalRelativeURL('/js_message_test_link')
+      .drupalRelativeURL('/js_message_test_link_with_system_messages')
       .waitForElementVisible(mainContent)
       .assert.elementPresent(secondaryMessagesContainer)
 
diff --git a/core/themes/claro/js/messages.js b/core/themes/claro/js/messages.js
index 196568742af7..26632ae6bce1 100644
--- a/core/themes/claro/js/messages.js
+++ b/core/themes/claro/js/messages.js
@@ -25,7 +25,10 @@
     const messagesTypes = Drupal.Message.getMessageTypeLabels();
     const messageWrapper = document.createElement('div');
 
-    messageWrapper.setAttribute('class', `messages messages--${type}`);
+    messageWrapper.setAttribute(
+      'class',
+      `messages messages--${type} messages-list__item`,
+    );
     messageWrapper.setAttribute(
       'role',
       type === 'error' || type === 'warning' ? 'alert' : 'status',
diff --git a/core/themes/claro/templates/misc/status-messages.html.twig b/core/themes/claro/templates/misc/status-messages.html.twig
index 91207a54ccd3..7708a745b2fc 100644
--- a/core/themes/claro/templates/misc/status-messages.html.twig
+++ b/core/themes/claro/templates/misc/status-messages.html.twig
@@ -23,50 +23,52 @@
  */
 #}
 <div data-drupal-messages class="messages-list">
-{% for type, messages in message_list %}
-  {%
-    set classes = [
-      'messages-list__item',
-      'messages',
-      'messages--' ~ type,
-    ]
-  %}
-  {%
-    set is_message_with_title = status_headings[type]
-  %}
-  {%
-    set is_message_with_icon = type in ['error', 'status', 'warning']
-  %}
+  <div class="messages-list__wrapper">
+    {% for type, messages in message_list %}
+      {%
+        set classes = [
+          'messages-list__item',
+          'messages',
+          'messages--' ~ type,
+        ]
+      %}
+      {%
+        set is_message_with_title = status_headings[type]
+      %}
+      {%
+        set is_message_with_icon = type in ['error', 'status', 'warning']
+      %}
 
-  <div role="contentinfo" aria-labelledby="{{ title_ids[type] }}"{{ attributes.addClass(classes)|without('role', 'aria-label') }}>
-    {% if type == 'error' %}
-      <div role="alert">
-    {% endif %}
-      {% if is_message_with_title or is_message_with_icon %}
-        <div class="messages__header">
-          {% if is_message_with_title %}
-            <h2 id="{{ title_ids[type] }}" class="messages__title">
-              {{ status_headings[type] }}
-            </h2>
+      <div role="contentinfo" aria-labelledby="{{ title_ids[type] }}"{{ attributes.addClass(classes)|without('role', 'aria-label') }}>
+        {% if type == 'error' %}
+          <div role="alert">
+        {% endif %}
+          {% if is_message_with_title or is_message_with_icon %}
+            <div class="messages__header">
+              {% if is_message_with_title %}
+                <h2 id="{{ title_ids[type] }}" class="messages__title">
+                  {{ status_headings[type] }}
+                </h2>
+              {% endif %}
+            </div>
           {% endif %}
-        </div>
-      {% endif %}
-      <div class="messages__content">
-        {% if messages|length > 1 %}
-          <ul class="messages__list">
-            {% for message in messages %}
-              <li class="messages__item">{{ message }}</li>
-            {% endfor %}
-          </ul>
-        {% else %}
-          {{ messages|first }}
+          <div class="messages__content">
+            {% if messages|length > 1 %}
+              <ul class="messages__list">
+                {% for message in messages %}
+                  <li class="messages__item">{{ message }}</li>
+                {% endfor %}
+              </ul>
+            {% else %}
+              {{ messages|first }}
+            {% endif %}
+          </div>
+        {% if type == 'error' %}
+          </div>
         {% endif %}
       </div>
-    {% if type == 'error' %}
-      </div>
-    {% endif %}
+      {# Remove type specific classes. #}
+      {% set attributes = attributes.removeClass(classes) %}
+    {% endfor %}
   </div>
-  {# Remove type specific classes. #}
-  {% set attributes = attributes.removeClass(classes) %}
-{% endfor %}
 </div>
-- 
GitLab