diff --git a/.env b/.env
index 62cf2ad666..c8064b4664 100644
--- a/.env
+++ b/.env
@@ -13,6 +13,7 @@ DISCOVERY_API_BASE_URL=''
DISCUSSIONS_MFE_BASE_URL=''
ECOMMERCE_BASE_URL=''
ENABLE_JUMPNAV='true'
+ENABLE_LEGACY_NAV=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
EXAMS_BASE_URL=''
diff --git a/.env.development b/.env.development
index 94f8a19332..f0cae5b828 100644
--- a/.env.development
+++ b/.env.development
@@ -13,6 +13,7 @@ DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
+ENABLE_LEGACY_NAV=''
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
EXAMS_BASE_URL='http://localhost:18740'
diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx
index 072f0d04c6..c5260154c0 100644
--- a/src/course-home/outline-tab/OutlineTab.jsx
+++ b/src/course-home/outline-tab/OutlineTab.jsx
@@ -121,6 +121,13 @@ const OutlineTab = ({ intl }) => {
}
}, [location.search]);
+ // Remove the initial # sign.
+ const hashValue = location.hash.substring(1);
+ const selectedSectionId = courses[rootCourseId].sectionIds.find((sectionId) => (
+ // Section is selected or contains selected subsection.
+ (hashValue === sectionId) || (sections[sectionId].sequenceIds.includes(hashValue))
+ ));
+
return (
<>
@@ -171,7 +178,11 @@ const OutlineTab = ({ intl }) => {
diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx
index c6fc547a0c..45ec952e1a 100644
--- a/src/course-home/outline-tab/OutlineTab.test.jsx
+++ b/src/course-home/outline-tab/OutlineTab.test.jsx
@@ -107,6 +107,28 @@ describe('Outline Tab', () => {
expect(expandedSectionNode).toHaveAttribute('aria-expanded', 'true');
});
+ it('expands selected section', async () => {
+ const { courseBlocks, sectionBlocks } = await buildMinimalCourseBlocks(courseId, 'Title');
+ setTabData({
+ course_blocks: { blocks: courseBlocks.blocks },
+ });
+ window.location.hash = `#${sectionBlocks[0].id}`;
+ await fetchAndRender();
+ const expandedSectionNode = screen.getByRole('button', { name: /Title of Section/ });
+ expect(expandedSectionNode).toHaveAttribute('aria-expanded', 'true');
+ });
+
+ it('expands section that contains selected subsection', async () => {
+ const { courseBlocks, sequenceBlocks } = await buildMinimalCourseBlocks(courseId, 'Title');
+ setTabData({
+ course_blocks: { blocks: courseBlocks.blocks },
+ });
+ window.location.hash = `#${sequenceBlocks[0].id}`;
+ await fetchAndRender();
+ const expandedSectionNode = screen.getByRole('button', { name: /Title of Section/ });
+ expect(expandedSectionNode).toHaveAttribute('aria-expanded', 'true');
+ });
+
it('handles expand/collapse all button click', async () => {
await fetchAndRender();
// Button renders as "Expand All"
diff --git a/src/course-home/outline-tab/Section.jsx b/src/course-home/outline-tab/Section.jsx
index 3de888a89a..f723229320 100644
--- a/src/course-home/outline-tab/Section.jsx
+++ b/src/course-home/outline-tab/Section.jsx
@@ -1,5 +1,7 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { useLocation } from 'react-router-dom';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Collapsible, IconButton } from '@edx/paragon';
import { faCheckCircle as fasCheckCircle, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
@@ -29,6 +31,9 @@ const Section = ({
sequences,
},
} = useModel('outline', courseId);
+ // Remove the initial # sign.
+ const hashValue = useLocation().hash.substring(1);
+ const selected = hashValue === section.id;
const [open, setOpen] = useState(defaultOpen);
@@ -42,7 +47,7 @@ const Section = ({
}, []);
const sectionTitle = (
-
+
{complete ? (
))}
diff --git a/src/course-home/outline-tab/SequenceLink.jsx b/src/course-home/outline-tab/SequenceLink.jsx
index 0530d53e66..88f007125c 100644
--- a/src/course-home/outline-tab/SequenceLink.jsx
+++ b/src/course-home/outline-tab/SequenceLink.jsx
@@ -22,6 +22,7 @@ const SequenceLink = ({
courseId,
first,
sequence,
+ selected,
}) => {
const {
complete,
@@ -86,7 +87,7 @@ const SequenceLink = ({
return (
-
+
{complete ? (
{
const defaultContent = content.filter(destination => destination.default)[0] || { id: courseId, label: '', sequences: [] };
+ let defaultTo;
+ if (getConfig().ENABLE_LEGACY_NAV === 'true') {
+ defaultTo = `/course/${courseId}/home#${defaultContent.id}`;
+ } else if (defaultContent.sequences.length) {
+ defaultTo = `/course/${courseId}/${defaultContent.sequences[0].id}`;
+ } else {
+ defaultTo = `/course/${courseId}/${defaultContent.id}`;
+ }
return (
<>
{withSeparator && (
@@ -23,12 +31,7 @@ const CourseBreadcrumb = ({
{ getConfig().ENABLE_JUMPNAV !== 'true' || content.length < 2 || !isStaff
? (
-
+
{defaultContent.label}
)
diff --git a/src/courseware/course/CourseBreadcrumbs.test.jsx b/src/courseware/course/CourseBreadcrumbs.test.jsx
index 23285f1410..60b70ae617 100644
--- a/src/courseware/course/CourseBreadcrumbs.test.jsx
+++ b/src/courseware/course/CourseBreadcrumbs.test.jsx
@@ -106,23 +106,46 @@ describe('CourseBreadcrumbs', () => {
},
],
]);
- render(
-
-
-
- ,
- ,
- );
it('renders course breadcrumbs as expected', async () => {
+ await render(
+
+
+
+ ,
+ ,
+ );
expect(screen.queryAllByRole('link')).toHaveLength(1);
const courseHomeButtonDestination = screen.getAllByRole('link')[0].href;
expect(courseHomeButtonDestination).toBe('http://localhost/course/course-v1:edX+DemoX+Demo_Course/home');
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
expect(screen.queryAllByRole('button')).toHaveLength(2);
});
+ it('renders legacy navigation links as expected', async () => {
+ getConfig.mockImplementation(() => ({
+ ENABLE_JUMPNAV: 'false',
+ ENABLE_LEGACY_NAV: 'true',
+ }));
+ await render(
+
+
+
+ ,
+ ,
+ );
+ expect(screen.queryAllByRole('link')).toHaveLength(3);
+ const sectionButtonDestination = screen.getAllByRole('link')[1].href;
+ expect(sectionButtonDestination).toBe('http://localhost/course/course-v1:edX+DemoX+Demo_Course/home#block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations');
+ const sequenceButtonDestination = screen.getAllByRole('link')[2].href;
+ expect(sequenceButtonDestination).toBe('http://localhost/course/course-v1:edX+DemoX+Demo_Course/home#block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions');
+ expect(screen.queryAllByRole('button')).toHaveLength(0);
+ });
});
diff --git a/src/index.jsx b/src/index.jsx
index 7fec13b112..fe4dc124d2 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -123,6 +123,7 @@ initialize({
DISCUSSIONS_MFE_BASE_URL: process.env.DISCUSSIONS_MFE_BASE_URL || null,
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null,
ENABLE_JUMPNAV: process.env.ENABLE_JUMPNAV || null,
+ ENABLE_LEGACY_NAV: process.env.ENABLE_LEGACY_NAV || null,
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null,
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,