diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap index e6f2743fd5..9df06d19e6 100644 --- a/src/course-home/data/__snapshots__/redux.test.js.snap +++ b/src/course-home/data/__snapshots__/redux.test.js.snap @@ -499,6 +499,7 @@ Object { "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2": Object { "complete": false, "courseId": "course-v1:edX+DemoX+Demo_Course", + "hideFromTOC": undefined, "id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2", "resumeBlock": false, "sequenceIds": Array [ @@ -514,6 +515,7 @@ Object { "due": null, "effortActivities": 2, "effortTime": 15, + "hideFromTOC": undefined, "icon": null, "id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1", "sectionId": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2", diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js index 963bdefc76..7ce2525ffe 100644 --- a/src/course-home/data/api.js +++ b/src/course-home/data/api.js @@ -136,6 +136,7 @@ export function normalizeOutlineBlocks(courseId, blocks) { title: block.display_name, resumeBlock: block.resume_block, sequenceIds: block.children || [], + hideFromTOC: block.hide_from_toc, }; break; @@ -152,6 +153,7 @@ export function normalizeOutlineBlocks(courseId, blocks) { // link in the outline (even though we ignore the given url and use an internal to ourselves). showLink: !!block.lms_web_url, title: block.display_name, + hideFromTOC: block.hide_from_toc, }; break; diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx index bcb219d49e..ba536adaa0 100644 --- a/src/course-home/outline-tab/OutlineTab.test.jsx +++ b/src/course-home/outline-tab/OutlineTab.test.jsx @@ -1269,5 +1269,97 @@ describe('Outline Tab', () => { await waitFor(() => expect(axiosMock.history.post).toHaveLength(1)); expect(axiosMock.history.post[0].url).toEqual(resendEmailUrl); }); + + it('section should show hidden from toc message when hide_from_toc is true', async () => { + const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true }); + const courseBlocksIds = Object.keys(courseBlocks.blocks); + const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({ + ...blocks, + [blockId]: { + ...courseBlocks.blocks[blockId], + hide_from_toc: true, + }, + }), {}); + + setTabData({ + course_blocks: { blocks: newCourseBlocks }, + }); + await fetchAndRender(); + + const iconHiddenFromTocSectionNode = screen.getByTestId('hide-from-toc-section-icon'); + const textHiddenFromTocSectionNode = screen.getByTestId('hide-from-toc-section-text'); + expect(iconHiddenFromTocSectionNode).toBeInTheDocument(); + expect(textHiddenFromTocSectionNode).toBeInTheDocument(); + expect(textHiddenFromTocSectionNode.textContent).toBe('Hidden in Course Outline, accessible via link'); + }); + + it('section should not show hidden from toc message when hide_from_toc is false', async () => { + const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true }); + const courseBlocksIds = Object.keys(courseBlocks.blocks); + const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({ + ...blocks, + [blockId]: { + ...courseBlocks.blocks[blockId], + hide_from_toc: false, + }, + }), {}); + + setTabData({ + course_blocks: { blocks: newCourseBlocks }, + }); + await fetchAndRender(); + + const iconHiddenFromTocSectionNode = screen.queryByTestId('hide-from-toc-section-icon'); + const textHiddenFromTocSectionNode = screen.queryByTestId('hide-from-toc-section-text'); + + expect(iconHiddenFromTocSectionNode).not.toBeInTheDocument(); + expect(textHiddenFromTocSectionNode).not.toBeInTheDocument(); + }); + + it('sequence link should show hidden from toc message when hide_from_toc is true', async () => { + const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true }); + const courseBlocksIds = Object.keys(courseBlocks.blocks); + const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({ + ...blocks, + [blockId]: { + ...courseBlocks.blocks[blockId], + hide_from_toc: true, + }, + }), {}); + + setTabData({ + course_blocks: { blocks: newCourseBlocks }, + }); + await fetchAndRender(); + + const iconHiddenFromTocSequenceLinkNode = screen.getByTestId('hide-from-toc-sequence-link-icon'); + const textHiddenFromTocSequenceLink = screen.getByTestId('hide-from-toc-sequence-link-text'); + expect(iconHiddenFromTocSequenceLinkNode).toBeInTheDocument(); + expect(textHiddenFromTocSequenceLink).toBeInTheDocument(); + expect(textHiddenFromTocSequenceLink.textContent).toBe('Subsections are not navigable between each other, they can only be accessed through their link.'); + }); + + it('sequence link not show hidden from toc message when hide_from_toc is false', async () => { + const { courseBlocks } = await buildMinimalCourseBlocks(courseId, 'Title', { resumeBlock: true }); + const courseBlocksIds = Object.keys(courseBlocks.blocks); + const newCourseBlocks = courseBlocksIds.reduce((blocks, blockId) => ({ + ...blocks, + [blockId]: { + ...courseBlocks.blocks[blockId], + hide_from_toc: false, + }, + }), {}); + + setTabData({ + course_blocks: { blocks: newCourseBlocks }, + }); + await fetchAndRender(); + + const iconHiddenFromTocSequenceLink = screen.queryByTestId('hide-from-toc-sequence-link-icon'); + const textHiddenFromTocSequenceLink = screen.queryByTestId('hide-from-toc-sequence-link-text'); + + expect(iconHiddenFromTocSequenceLink).not.toBeInTheDocument(); + expect(textHiddenFromTocSequenceLink).not.toBeInTheDocument(); + }); }); }); diff --git a/src/course-home/outline-tab/Section.jsx b/src/course-home/outline-tab/Section.jsx index bd8b3d781f..4e013c73cb 100644 --- a/src/course-home/outline-tab/Section.jsx +++ b/src/course-home/outline-tab/Section.jsx @@ -1,11 +1,12 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Collapsible, IconButton } from '@openedx/paragon'; +import { Collapsible, IconButton, Icon } from '@openedx/paragon'; import { faCheckCircle as fasCheckCircle, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'; import { faCheckCircle as farCheckCircle } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { DisabledVisible } from '@openedx/paragon/icons'; import SequenceLink from './SequenceLink'; import { useModel } from '../../generic/model-store'; @@ -23,6 +24,7 @@ const Section = ({ complete, sequenceIds, title, + hideFromTOC, } = section; const { courseBlocks: { @@ -42,7 +44,7 @@ const Section = ({ }, []); const sectionTitle = ( -