Merge pull request 'Fix admin messages never auto-dismissing (root cause)' (#66) from fix/admin-messages-auto-dismiss-v3 into main
Some checks failed
CI / ci (push) Has been skipped
CI / pr-e2e (push) Has been skipped
CI / deploy (push) Has been skipped
CI / nightly-e2e (push) Failing after 3m57s

Reviewed-on: #66
This commit was merged in pull request #66.
This commit is contained in:
2026-03-19 10:59:41 +00:00
2 changed files with 77 additions and 33 deletions

View File

@@ -15,7 +15,6 @@ from apps.core.middleware import AdminMessageGuardMiddleware
def admin_message_test_view(request): def admin_message_test_view(request):
messages.success(request, "Page 'Test page' has been updated.")
messages.success(request, "Page 'Test page' has been updated.") messages.success(request, "Page 'Test page' has been updated.")
messages.success(request, "Page 'Test page' has been published.") messages.success(request, "Page 'Test page' has been published.")
return render(request, "wagtailadmin/base.html", {}) return render(request, "wagtailadmin/base.html", {})
@@ -60,24 +59,6 @@ def test_admin_message_guard_preserves_admin_messages(rf):
assert remaining[0].message == "Page 'Test page' has been updated." assert remaining[0].message == "Page 'Test page' has been updated."
@pytest.mark.django_db
@override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling")
def test_wagtail_admin_template_deduplicates_consecutive_messages(client, django_user_model):
admin = django_user_model.objects.create_superuser(
username="admin-messages",
email="admin-messages@example.com",
password="admin-pass",
)
client.force_login(admin)
response = client.get("/cms/__tests__/admin-messages/")
content = response.content.decode()
assert response.status_code == 200
assert content.count("has been updated.") == 1
assert content.count("has been published.") == 1
@pytest.mark.django_db @pytest.mark.django_db
@override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling") @override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling")
def test_admin_messages_have_auto_clear(client, django_user_model): def test_admin_messages_have_auto_clear(client, django_user_model):
@@ -94,3 +75,46 @@ def test_admin_messages_have_auto_clear(client, django_user_model):
assert response.status_code == 200 assert response.status_code == 200
assert "data-w-messages-auto-clear-value" in content assert "data-w-messages-auto-clear-value" in content
@pytest.mark.django_db
@override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling")
def test_server_rendered_messages_have_auto_dismiss_script(client, django_user_model):
"""Server-rendered messages must include an inline script that removes them
after a timeout, because the w-messages Stimulus controller only auto-clears
messages added via JavaScript — not ones already in the HTML."""
admin = django_user_model.objects.create_superuser(
username="admin-dismiss",
email="admin-dismiss@example.com",
password="admin-pass",
)
client.force_login(admin)
response = client.get("/cms/__tests__/admin-messages/")
content = response.content.decode()
assert response.status_code == 200
# Messages are rendered with the data-server-rendered marker
assert "data-server-rendered" in content
# The auto-dismiss script targets those markers
assert "querySelectorAll" in content
assert "[data-server-rendered]" in content
@pytest.mark.django_db
@override_settings(ROOT_URLCONF="apps.core.tests.test_message_handling")
def test_admin_messages_render_all_messages(client, django_user_model):
"""All messages should be rendered (no de-duplication filtering)."""
admin = django_user_model.objects.create_superuser(
username="admin-render",
email="admin-render@example.com",
password="admin-pass",
)
client.force_login(admin)
response = client.get("/cms/__tests__/admin-messages/")
content = response.content.decode()
assert response.status_code == 200
assert "has been updated." in content
assert "has been published." in content

View File

@@ -8,13 +8,13 @@
{% keyboard_shortcuts_dialog %} {% keyboard_shortcuts_dialog %}
<main class="content-wrapper w-overflow-x-hidden" id="main"> <main class="content-wrapper w-overflow-x-hidden" id="main">
<div class="content"> <div class="content">
{# Always show messages div so it can be appended to by JS #}
<div class="messages" role="status" data-controller="w-messages" data-action="w-messages:add@document->w-messages#add" data-w-messages-added-class="new" data-w-messages-show-class="appear" data-w-messages-show-delay-value="100" data-w-messages-auto-clear-value="8000"> <div class="messages" role="status" data-controller="w-messages" data-action="w-messages:add@document->w-messages#add" data-w-messages-added-class="new" data-w-messages-show-class="appear" data-w-messages-show-delay-value="100" data-w-messages-auto-clear-value="8000">
<ul data-w-messages-target="container"> <ul data-w-messages-target="container">
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
{% message_level_tag message as level_tag %} {% message_level_tag message as level_tag %}
{% ifchanged level_tag message.extra_tags message %} <li class="{% message_tags message %}" data-server-rendered>
<li class="{% message_tags message %}">
{% if level_tag == "error" %} {% if level_tag == "error" %}
{% icon name="warning" classname="messages-icon" %} {% icon name="warning" classname="messages-icon" %}
{% elif message.extra_tags == "lock" %} {% elif message.extra_tags == "lock" %}
@@ -26,7 +26,6 @@
{% endif %} {% endif %}
{{ message }} {{ message }}
</li> </li>
{% endifchanged %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</ul> </ul>
@@ -41,6 +40,27 @@
</template> </template>
</div> </div>
{% comment %}
Wagtail's w-messages Stimulus controller only auto-clears messages
added dynamically via JavaScript (the add() method). Server-rendered
messages — the <li> elements above — have no connect() handler and
sit in the DOM forever. This script schedules their removal so they
auto-dismiss after the same timeout used for dynamic messages.
{% endcomment %}
<script>
(function () {
var items = document.querySelectorAll('[data-server-rendered]');
if (!items.length) return;
setTimeout(function () {
items.forEach(function (el) { el.remove(); });
var ul = document.querySelector('[data-w-messages-target="container"]');
if (ul && !ul.children.length) {
document.body.classList.remove('has-messages');
}
}, 8000);
})();
</script>
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
</main> </main>