diff --git a/src/journal/tests/test_journal_frontend.py b/src/journal/tests/test_journal_frontend.py index 6b94cbe600..63e3a85041 100644 --- a/src/journal/tests/test_journal_frontend.py +++ b/src/journal/tests/test_journal_frontend.py @@ -1,3 +1,8 @@ +import os +import shutil + +from django.conf import settings +from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone @@ -8,6 +13,7 @@ from press.models import Press from utils.testing import helpers from utils import setting_handler +from core import files as core_files from core import models as core_models from submission import models as submission_models @@ -270,3 +276,85 @@ def test_search_excludes_artucle(self): response, self.article_title, ) + + +class TestTableModalKeyboardDismiss(TestCase): + """ + Regression tests for #5387: the enlarged-table modal must be keyboard + dismissable. Without tabindex="-1" on the modal, Bootstrap cannot move + focus into it, so keyboard users can never reach the close button. + """ + + @classmethod + def setUpTestData(cls): + cls.press = helpers.create_press() + cls.journal_domain = "tablemodal.janeway.systems" + cls.journal = make_test_journal( + code="tablemodal", + domain=cls.journal_domain, + ) + setting_handler.save_setting( + "general", + "journal_theme", + cls.journal, + "clean", + ) + helpers.create_roles(["Author"]) + cls.owner = helpers.create_user( + "table_modal_owner@janeway.systems", + ["author"], + cls.journal, + ) + cls.article = helpers.create_article( + journal=cls.journal, + title="An article with a table", + stage=submission_models.STAGE_PUBLISHED, + date_published=timezone.now(), + owner=cls.owner, + ) + galley_html = ( + '
' + "" + "
Table 1
Cell
" + "
" + ) + uploaded_file = SimpleUploadedFile( + "tables.html", + galley_html.encode("utf-8"), + content_type="text/html", + ) + file_obj = core_files.save_file_to_article( + uploaded_file, + cls.article, + cls.owner, + label="HTML", + is_galley=True, + ) + cls.galley = helpers.create_galley( + cls.article, + file_obj=file_obj, + label="HTML", + type="html", + ) + cls.addClassCleanup( + shutil.rmtree, + os.path.join(settings.BASE_DIR, "files", "articles", str(cls.article.pk)), + ignore_errors=True, + ) + + def test_table_modal_is_focusable_for_keyboard_dismissal(self): + response = self.client.get( + reverse( + "article_view", + kwargs={ + "identifier_type": "id", + "identifier": self.article.pk, + }, + ), + SERVER_NAME=self.journal_domain, + ) + self.assertEqual(response.status_code, 200) + self.assertContains( + response, + 'id="table-T1" tabindex="-1"', + ) diff --git a/src/static/common/js/reversable-links.js b/src/static/common/js/reversable-links.js index a236897e7b..28bb8cdeae 100644 --- a/src/static/common/js/reversable-links.js +++ b/src/static/common/js/reversable-links.js @@ -47,15 +47,26 @@ function drawUserAttention(targetElement){ element.tabIndex = "-1" element.focus(); + const restoreTabIndex = () => { + if (oldTabIndex === null) { + element.removeAttribute('tabIndex'); + } else { + element.tabIndex = oldTabIndex; + } + }; + const timeout = setTimeout(() => { element.classList.remove('draw-attention'); if (element.classList.length === 0) { element.removeAttribute('class'); } - if (oldTabIndex === null) { - element.removeAttribute('tabIndex'); + if (document.activeElement === element) { + // Removing tabindex while the element is focused sends focus + // back to , so tabbing would restart from the top of + // the page. Wait until focus moves on before restoring. + element.addEventListener('blur', restoreTabIndex, { once: true }); } else { - element.tabIndex = oldTabIndex; + restoreTabIndex(); } attentionTimeouts.delete(element); }, 2000); diff --git a/src/templates/common/elements/journal/article_authors_full.html b/src/templates/common/elements/journal/article_authors_full.html index 02e8d88c66..fd4d103911 100644 --- a/src/templates/common/elements/journal/article_authors_full.html +++ b/src/templates/common/elements/journal/article_authors_full.html @@ -1,4 +1,4 @@ -