From 9dc45e192da0fda8e45b058aad3643ef92dd2d1a Mon Sep 17 00:00:00 2001
From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com>
Date: Fri, 31 Jan 2025 14:18:28 -0500
Subject: [PATCH] fix: accessibility issues on outline and unit pages (#1580)
This PR fixes the following accessibility issues:
1. Header used for screenreader only text
2. Element focus when expanding and dismissing welcome message
3. Bookmark button using wrong ARIA attributing while processing bookmark status
---
src/course-home/outline-tab/OutlineTab.jsx | 19 +++---
.../outline-tab/OutlineTab.test.jsx | 12 ++++
.../outline-tab/widgets/WelcomeMessage.jsx | 61 +++++++++++--------
.../course/bookmark/BookmarkButton.jsx | 20 +++---
.../course/bookmark/BookmarkFilledIcon.jsx | 7 ---
.../course/bookmark/BookmarkOutlineIcon.jsx | 7 ---
src/courseware/course/bookmark/index.js | 2 -
.../Unit/__snapshots__/index.test.jsx.snap | 4 +-
src/courseware/course/sequence/Unit/index.jsx | 2 +-
.../sequence-navigation/UnitButton.jsx | 10 +--
.../sequence-navigation/UnitButton.test.jsx | 10 +--
11 files changed, 81 insertions(+), 73 deletions(-)
delete mode 100644 src/courseware/course/bookmark/BookmarkFilledIcon.jsx
delete mode 100644 src/courseware/course/bookmark/BookmarkOutlineIcon.jsx
diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx
index c0fcfab9..3fddf35a 100644
--- a/src/course-home/outline-tab/OutlineTab.jsx
+++ b/src/course-home/outline-tab/OutlineTab.jsx
@@ -1,9 +1,9 @@
-import React, { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
-import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { useIntl } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { AlertList } from '../../generic/user-messages';
@@ -29,7 +29,8 @@ import WelcomeMessage from './widgets/WelcomeMessage';
import ProctoringInfoPanel from './widgets/ProctoringInfoPanel';
import AccountActivationAlert from '../../alerts/logistration-alert/AccountActivationAlert';
-const OutlineTab = ({ intl }) => {
+const OutlineTab = () => {
+ const intl = useIntl();
const {
courseId,
proctoringPanelStatus,
@@ -42,6 +43,8 @@ const OutlineTab = ({ intl }) => {
userTimezone,
} = useModel('courseHomeMeta', courseId);
+ const expandButtonRef = useRef();
+
const {
accessExpiration,
courseBlocks: {
@@ -159,12 +162,12 @@ const OutlineTab = ({ intl }) => {
>
)}
-
+
{rootCourseId && (
<>
-
@@ -225,8 +228,4 @@ const OutlineTab = ({ intl }) => {
);
};
-OutlineTab.propTypes = {
- intl: intlShape.isRequired,
-};
-
-export default injectIntl(OutlineTab);
+export default OutlineTab;
diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx
index aace0578..5db8688a 100644
--- a/src/course-home/outline-tab/OutlineTab.test.jsx
+++ b/src/course-home/outline-tab/OutlineTab.test.jsx
@@ -292,6 +292,18 @@ describe('Outline Tab', () => {
showMoreButton = screen.getByRole('button', { name: 'Show More' });
expect(showMoreButton).toBeInTheDocument();
});
+
+ fit('dismisses message', async () => {
+ expect(screen.getByTestId('alert-container-welcome')).toBeInTheDocument();
+ const dismissButton = screen.queryByRole('button', { name: 'Dismiss' });
+ const expandButton = screen.queryByRole('button', { name: 'Expand all' });
+
+ fireEvent.click(dismissButton);
+
+ expect(expandButton).toHaveFocus();
+
+ expect(screen.queryByText('Welcome Message')).toBeNull();
+ });
});
it('ignores comments and misformatted HTML', async () => {
diff --git a/src/course-home/outline-tab/widgets/WelcomeMessage.jsx b/src/course-home/outline-tab/widgets/WelcomeMessage.jsx
index cca757d6..40bac852 100644
--- a/src/course-home/outline-tab/widgets/WelcomeMessage.jsx
+++ b/src/course-home/outline-tab/widgets/WelcomeMessage.jsx
@@ -1,7 +1,7 @@
-import React, { useState, useMemo } from 'react';
+import { useState, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
-import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { useIntl } from '@edx/frontend-platform/i18n';
import { Alert, Button, TransitionReplace } from '@openedx/paragon';
import truncate from 'truncate-html';
@@ -11,11 +11,13 @@ import messages from '../messages';
import { useModel } from '../../../generic/model-store';
import { dismissWelcomeMessage } from '../../data/thunks';
-const WelcomeMessage = ({ courseId, intl }) => {
+const WelcomeMessage = ({ courseId, nextElementRef }) => {
+ const intl = useIntl();
const {
welcomeMessageHtml,
} = useModel('outline', courseId);
+ const messageBodyRef = useRef();
const [display, setDisplay] = useState(true);
// welcomeMessageHtml can contain comments or malformatted HTML which can impact the length that determines
@@ -49,13 +51,20 @@ const WelcomeMessage = ({ courseId, intl }) => {
dismissible
show={display}
onClose={() => {
+ nextElementRef.current?.focus();
setDisplay(false);
dispatch(dismissWelcomeMessage(courseId));
}}
className="raised-card"
actions={messageCanBeShortened ? [
setShowShortMessage(!showShortMessage)}
+ onClick={() => {
+ if (showShortMessage) {
+ messageBodyRef.current?.focus();
+ }
+
+ setShowShortMessage(!showShortMessage);
+ }}
variant="outline-primary"
>
{showShortMessage ? intl.formatMessage(messages.welcomeMessageShowMoreButton)
@@ -63,32 +72,34 @@ const WelcomeMessage = ({ courseId, intl }) => {
,
] : []}
>
-
- {showShortMessage ? (
-
- ) : (
-
- )}
-
+
+
+ {showShortMessage ? (
+
+ ) : (
+
+ )}
+
+
);
};
WelcomeMessage.propTypes = {
courseId: PropTypes.string.isRequired,
- intl: intlShape.isRequired,
+ nextElementRef: PropTypes.shape({ current: PropTypes.instanceOf(HTMLInputElement) }),
};
-export default injectIntl(WelcomeMessage);
+export default WelcomeMessage;
diff --git a/src/courseware/course/bookmark/BookmarkButton.jsx b/src/courseware/course/bookmark/BookmarkButton.jsx
index 142f5503..54439da1 100644
--- a/src/courseware/course/bookmark/BookmarkButton.jsx
+++ b/src/courseware/course/bookmark/BookmarkButton.jsx
@@ -1,10 +1,9 @@
-import React, { useCallback } from 'react';
+import { useCallback } from 'react';
import PropTypes from 'prop-types';
-import { StatefulButton } from '@openedx/paragon';
+import { Icon, StatefulButton } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { useDispatch } from 'react-redux';
-import BookmarkOutlineIcon from './BookmarkOutlineIcon';
-import BookmarkFilledIcon from './BookmarkFilledIcon';
+import { Bookmark, BookmarkBorder } from '@openedx/paragon/icons';
import { removeBookmark, addBookmark } from './data/thunks';
const addBookmarkLabel = (
@@ -42,10 +41,11 @@ const BookmarkButton = ({
return (
,
- defaultProcessing:
,
- bookmarked:
,
- bookmarkedProcessing:
,
+ default:
,
+ defaultProcessing:
,
+ bookmarked:
,
+ bookmarkedProcessing:
,
}}
/>
);
diff --git a/src/courseware/course/bookmark/BookmarkFilledIcon.jsx b/src/courseware/course/bookmark/BookmarkFilledIcon.jsx
deleted file mode 100644
index af6efe73..00000000
--- a/src/courseware/course/bookmark/BookmarkFilledIcon.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from 'react';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faBookmark } from '@fortawesome/free-solid-svg-icons';
-
-const BookmarkFilledIcon = (props) =>
;
-
-export default BookmarkFilledIcon;
diff --git a/src/courseware/course/bookmark/BookmarkOutlineIcon.jsx b/src/courseware/course/bookmark/BookmarkOutlineIcon.jsx
deleted file mode 100644
index 21536257..00000000
--- a/src/courseware/course/bookmark/BookmarkOutlineIcon.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from 'react';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faBookmark } from '@fortawesome/free-regular-svg-icons';
-
-const BookmarkOutlineIcon = (props) =>
;
-
-export default BookmarkOutlineIcon;
diff --git a/src/courseware/course/bookmark/index.js b/src/courseware/course/bookmark/index.js
index 23353466..7348a4e7 100644
--- a/src/courseware/course/bookmark/index.js
+++ b/src/courseware/course/bookmark/index.js
@@ -1,3 +1 @@
export { default as BookmarkButton } from './BookmarkButton';
-export { default as BookmarkFilledIcon } from './BookmarkFilledIcon';
-export { default as BookmarkOutlineIcon } from './BookmarkFilledIcon';
diff --git a/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap b/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap
index c82b9fa4..9fa03431 100644
--- a/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap
+++ b/src/courseware/course/sequence/Unit/__snapshots__/index.test.jsx.snap
@@ -34,11 +34,11 @@ exports[`Unit component output snapshot: not bookmarked, do not show content 1`]
unitTitle="unit-title"
/>
-