From 5b7f76b43d1ef3200d7e2d7bcf381f59aab4a555 Mon Sep 17 00:00:00 2001 From: KristinAoki Date: Wed, 26 Mar 2025 16:29:07 -0400 Subject: [PATCH] fix: breadcrumb preview link --- .../course/CourseBreadcrumbs.test.jsx | 134 ------------ .../course/breadcrumbs/BreadcrumbItem.tsx | 103 ++++++++++ .../{ => breadcrumbs}/CourseBreadcrumbs.jsx | 112 +---------- .../breadcrumbs/CourseBreadcrumbs.test.jsx | 190 ++++++++++++++++++ src/courseware/course/breadcrumbs/index.js | 3 + .../CourseBreadcrumbsSlot/index.tsx | 2 +- 6 files changed, 308 insertions(+), 236 deletions(-) delete mode 100644 src/courseware/course/CourseBreadcrumbs.test.jsx create mode 100644 src/courseware/course/breadcrumbs/BreadcrumbItem.tsx rename src/courseware/course/{ => breadcrumbs}/CourseBreadcrumbs.jsx (50%) create mode 100644 src/courseware/course/breadcrumbs/CourseBreadcrumbs.test.jsx create mode 100644 src/courseware/course/breadcrumbs/index.js diff --git a/src/courseware/course/CourseBreadcrumbs.test.jsx b/src/courseware/course/CourseBreadcrumbs.test.jsx deleted file mode 100644 index f51ead34..00000000 --- a/src/courseware/course/CourseBreadcrumbs.test.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import { screen, render } from '@testing-library/react'; -import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; -import { getConfig } from '@edx/frontend-platform'; -import { BrowserRouter } from 'react-router-dom'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { useModel, useModels } from '../../generic/model-store'; -import CourseBreadcrumbs from './CourseBreadcrumbs'; - -jest.mock('@edx/frontend-platform'); -jest.mock('@edx/frontend-platform/analytics'); - -// Remove When Fully rolled out>>> -jest.mock('../../generic/model-store'); -jest.mock('@edx/frontend-platform/auth'); -getConfig.mockImplementation(() => ({ ENABLE_JUMPNAV: 'true' })); -getAuthenticatedUser.mockImplementation(() => ({ administrator: true })); -// ^^^^Remove When Fully rolled out - -jest.mock('react-redux', () => ({ - connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({ - mapStateToProps, - mapDispatchToProps, - ReactComponent, - }), - Provider: ({ children }) => children, - useSelector: () => 'loaded', -})); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - Link: jest.fn().mockImplementation(({ to, children }) => ( - {children} - )), -})); - -useModels.mockImplementation((name) => { - if (name === 'sections') { - return [ - { - courseId: 'course-v1:edX+DemoX+Demo_Course', - id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b', - sequenceIds: ['block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction'], - title: 'Introduction', - }, - { - courseId: 'course-v1:edX+DemoX+Demo_Course', - id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations', - sequenceIds: ['block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5', - 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions'], - title: 'Example Week 1: Getting Started', - }, - ]; - } - return [ - { - id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5', - sectionId: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations', - title: 'Lesson 1 - Getting Started', - unitIds: [ - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4f6c1b4e316a419ab5b6bf30e6c708e9', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@3dc16db8d14842e38324e95d4030b8a0', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@e3601c0abee6427d8c17e6d6f8fdddd1', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@134df56c516a4a0dbb24dd5facef746e', - ], - }, - { - id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions', - sectionId: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations', - title: 'Homework - Question Styles', - unitIds: [ - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2152d4a4aadc4cb0af5256394a3d1fc7', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@47dbd5f836544e61877a483c0b75606c', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@54bb9b142c6c4c22afc62bcb628f0e68', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0c92347a5c00', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_1fef54c2b23b', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2889db1677a549abb15eb4d886f95d1c', - 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@e8a5cc2aed424838853defab7be45e42', - ], - }, - ]; -}); -useModel.mockImplementation(() => ({ - sectionIds: ['block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b', - 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations'], -})); - -describe('CourseBreadcrumbs', () => { - jest.spyOn(React, 'useMemo').mockImplementation(() => [ - [ - { - default: false, - id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@d8a6192ade314473a78242dfeedfbf5b', - label: 'Introduction', - url: 'http://localhost:2000/course/course-v1:edX+DemoX+Demo_Course/block-v1:edX+DemoX+Demo_Course+type@sequential+block@edx_introduction', - }, - { - default: true, - id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations', - label: 'Example Week 1: Getting Started', - url: 'http://localhost:2000/course/course-v1:edX+DemoX+Demo_Course/block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5', - }, - ], - [ - { - id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@simulations', label: "Lesson 2 - Let's Get Interactive!", default: true, url: 'http://localhost:2000/course/course-v1:edX+DemoX+D…e@vertical+block@d0d804e8863c4a95a659c04d8a2b2bc0', - }, - { - id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@175e76c4951144a29d46211361266e0e', label: 'Homework - Essays', default: false, url: 'http://localhost:2000/course/course-v1:edX+DemoX+D…e@vertical+block@fb79dcbad35b466a8c6364f8ffee9050', - }, - ], - ]); - render( - - - - , - , - ); - it('renders course breadcrumbs as expected', async () => { - 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.queryAllByTestId('breadcrumb-item')).toHaveLength(2); - }); -}); diff --git a/src/courseware/course/breadcrumbs/BreadcrumbItem.tsx b/src/courseware/course/breadcrumbs/BreadcrumbItem.tsx new file mode 100644 index 00000000..9533797e --- /dev/null +++ b/src/courseware/course/breadcrumbs/BreadcrumbItem.tsx @@ -0,0 +1,103 @@ +import React, { useState } from 'react'; +import { getConfig } from '@edx/frontend-platform'; +import { + useToggle, + ModalPopup, + Menu, +} from '@openedx/paragon'; +import { Link, useLocation } from 'react-router-dom'; +import JumpNavMenuItem from '../JumpNavMenuItem'; + +interface Props { + content: { + default: boolean, + id: string, + label: string, + sequences: { + id: string, + }[], + } []; + withSeparator: boolean | false, + separator: string | ''; + courseId: string; + sequenceId: string | ''; + unitId: string | ''; + isStaff: boolean | false; +} + +const BreadcrumbItem: React.FC = ({ + content, + withSeparator, + separator, + courseId, + sequenceId, + unitId, + isStaff, +}) => { + const defaultContent = content.filter( + (destination: { default: boolean }) => destination.default, + )[0] || { id: courseId, label: '', sequences: [] }; + + const showRegularLink = getConfig().ENABLE_JUMPNAV !== 'true' || content.length < 2 || !isStaff; + const [isOpen, open, close] = useToggle(false); + const [target, setTarget] = useState(null); + + const { pathname } = useLocation(); + const isPreview = pathname.startsWith('/preview'); + const baseUrl = defaultContent.sequences.length + ? `/course/${courseId}/${defaultContent.sequences[0].id}` + : `/course/${courseId}/${defaultContent.id}`; + const link = isPreview ? `/preview${baseUrl}` : baseUrl; + return ( + <> + {withSeparator && separator && ( +
  • {separator}
  • + )} + +
  • + {showRegularLink ? ( + + {defaultContent.label} + + ) : ( + <> + { + // @ts-ignore + + {defaultContent.label} + + } + + + {content.map((item) => ( + + ))} + + + + )} +
  • + + ); +}; + +export default BreadcrumbItem; \ No newline at end of file diff --git a/src/courseware/course/CourseBreadcrumbs.jsx b/src/courseware/course/breadcrumbs/CourseBreadcrumbs.jsx similarity index 50% rename from src/courseware/course/CourseBreadcrumbs.jsx rename to src/courseware/course/breadcrumbs/CourseBreadcrumbs.jsx index ab9d031d..d94ada18 100644 --- a/src/courseware/course/CourseBreadcrumbs.jsx +++ b/src/courseware/course/breadcrumbs/CourseBreadcrumbs.jsx @@ -1,107 +1,12 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { getConfig } from '@edx/frontend-platform'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faHome } from '@fortawesome/free-solid-svg-icons'; import { useSelector } from 'react-redux'; -import { useToggle, ModalPopup, Menu } from '@openedx/paragon'; import { Link } from 'react-router-dom'; -import { useModel, useModels } from '../../generic/model-store'; -import JumpNavMenuItem from './JumpNavMenuItem'; - -const CourseBreadcrumb = ({ - content, - withSeparator, - courseId, - sequenceId, - unitId, - isStaff, -}) => { - const defaultContent = content.filter( - (destination) => destination.default, - )[0] || { id: courseId, label: '', sequences: [] }; - - const showRegularLink = getConfig().ENABLE_JUMPNAV !== 'true' || content.length < 2 || !isStaff; - const [isOpen, open, close] = useToggle(false); - const [target, setTarget] = useState(null); - return ( - <> - {withSeparator && ( -
  • /
  • - )} - -
  • - {showRegularLink ? ( - - {defaultContent.label} - - ) : ( - <> - { - // eslint-disable-next-line - - {defaultContent.label} - - } - - - {content.map((item) => ( - - ))} - - - - )} -
  • - - ); -}; -CourseBreadcrumb.propTypes = { - content: PropTypes.arrayOf( - PropTypes.shape({ - default: PropTypes.bool, - id: PropTypes.string, - label: PropTypes.string, - }), - ).isRequired, - sequenceId: PropTypes.string, - unitId: PropTypes.string, - withSeparator: PropTypes.bool, - courseId: PropTypes.string, - isStaff: PropTypes.bool, -}; - -CourseBreadcrumb.defaultProps = { - withSeparator: false, - sequenceId: null, - unitId: null, - courseId: null, - isStaff: null, -}; +import { useModel, useModels } from '../../../generic/model-store'; +import BreadcrumbItem from './BreadcrumbItem'; const CourseBreadcrumbs = ({ courseId, @@ -110,14 +15,16 @@ const CourseBreadcrumbs = ({ unitId, isStaff, }) => { - const course = useModel('coursewareMeta', courseId); + const course = useModel('coursewareMeta', courseId); const courseStatus = useSelector((state) => state.courseware.courseStatus); const sequenceStatus = useSelector( (state) => state.courseware.sequenceStatus, ); + console.log( useModels('sections', course.sectionIds)); + const allSequencesInSections = Object.fromEntries( - useModels('sections', course.sectionIds).map((section) => [ + useModels('sections', course.sectionIds)?.map((section) => [ section.id, { default: section.id === sectionId, @@ -152,6 +59,8 @@ const CourseBreadcrumbs = ({ } return [chapters, sequentials]; }, [courseStatus, sequenceStatus, allSequencesInSections]); + console.log(links); + return (