From 9269a61b6828b784f7f9de7a9b07b89fac54b498 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 26 Jan 2024 18:47:16 +0530 Subject: [PATCH] refactor: break up xblock status component --- .../section-card/SectionCard.jsx | 8 +- .../section-card/SectionCard.scss | 26 +- .../subsection-card/SubsectionCard.jsx | 2 +- src/course-outline/unit-card/UnitCard.jsx | 2 +- .../xblock-status/XBlockStatus.jsx | 455 ++++++++++++------ .../xblock-status/XBlockStatus.test.jsx | 24 +- 6 files changed, 329 insertions(+), 188 deletions(-) diff --git a/src/course-outline/section-card/SectionCard.jsx b/src/course-outline/section-card/SectionCard.jsx index 47bafc29bf..4e81318d49 100644 --- a/src/course-outline/section-card/SectionCard.jsx +++ b/src/course-outline/section-card/SectionCard.jsx @@ -179,16 +179,18 @@ const SectionCard = ({
diff --git a/src/course-outline/section-card/SectionCard.scss b/src/course-outline/section-card/SectionCard.scss index 01e7209c96..25e3c688c2 100644 --- a/src/course-outline/section-card/SectionCard.scss +++ b/src/course-outline/section-card/SectionCard.scss @@ -13,28 +13,12 @@ color: $headings-color; } - .section-card__highlights { - display: flex; - align-items: center; - gap: .5rem; - padding: 0; - background: transparent; - font-size: 1rem; - - &::before { - display: none; - } - .highlights-badge { - display: flex; - justify-content: center; - align-items: center; - width: 1.5rem; - height: 1.5rem; - border-radius: 1.375rem; - font-size: 1rem; - font-weight: 700; - } + .highlights-badge { + width: 1.5rem; + height: 1.5rem; + border-radius: 1.375rem; + font-size: 1rem; } .section-card__content { diff --git a/src/course-outline/subsection-card/SubsectionCard.jsx b/src/course-outline/subsection-card/SubsectionCard.jsx index de542fc18e..f081c9ea4f 100644 --- a/src/course-outline/subsection-card/SubsectionCard.jsx +++ b/src/course-outline/subsection-card/SubsectionCard.jsx @@ -164,7 +164,7 @@ const SubsectionCard = ({ diff --git a/src/course-outline/unit-card/UnitCard.jsx b/src/course-outline/unit-card/UnitCard.jsx index e7745770a3..1601b54e4f 100644 --- a/src/course-outline/unit-card/UnitCard.jsx +++ b/src/course-outline/unit-card/UnitCard.jsx @@ -153,7 +153,7 @@ const UnitCard = ({ diff --git a/src/course-outline/xblock-status/XBlockStatus.jsx b/src/course-outline/xblock-status/XBlockStatus.jsx index c493e0f82e..e2a262d965 100644 --- a/src/course-outline/xblock-status/XBlockStatus.jsx +++ b/src/course-outline/xblock-status/XBlockStatus.jsx @@ -15,38 +15,19 @@ import { import messages from './messages'; import { COURSE_BLOCK_NAMES } from '../constants'; -const XBlockStatus = ({ - isSelfPaced, - isCustomRelativeDatesActive, - item, +const ReleaseStatus = ({ + isInstructorPaced, + explanatoryMessage, + releaseDate, + releasedToStudents, }) => { const intl = useIntl(); - const { - category, - explanatoryMessage, - releasedToStudents, - releaseDate, - isProctoredExam, - isOnboardingExam, - isPracticeExam, - prereq, - prereqs, - staffOnlyMessage, - userPartitionInfo, - hasPartitionGroupComponents, - format: gradingType, - dueDate, - relativeWeeksDue, - isTimeLimited, - graded, - courseGraders, - hideAfterDue, - } = item; - - const isInstructorPaced = !isSelfPaced; - const isVertical = category === COURSE_BLOCK_NAMES.vertical.id; - const statusMessages = []; + const explanatoryMessageDiv = () => ( + + {explanatoryMessage} + + ); let releaseLabel = messages.unscheduledLabel; if (releasedToStudents) { @@ -55,6 +36,55 @@ const XBlockStatus = ({ releaseLabel = messages.scheduledLabel; } + const releaseStatusDiv = () => ( +
+ + {intl.formatMessage(messages.releaseStatusScreenReaderTitle)} + + + {intl.formatMessage(releaseLabel)} + {releaseDate && releaseDate} +
+ ); + + if (explanatoryMessage) { + return explanatoryMessageDiv(); + } + + if (isInstructorPaced) { + return releaseStatusDiv(); + } + + return null; +}; + +ReleaseStatus.defaultProps = { + explanatoryMessage: '', +}; + +ReleaseStatus.propTypes = { + isInstructorPaced: PropTypes.bool.isRequired, + explanatoryMessage: PropTypes.string, + releaseDate: PropTypes.string.isRequired, + releasedToStudents: PropTypes.bool.isRequired, +}; + +const GradingTypeAndDueDate = ({ + isSelfPaced, + isInstructorPaced, + isCustomRelativeDatesActive, + isTimeLimited, + isProctoredExam, + isOnboardingExam, + isPracticeExam, + graded, + gradingType, + dueDate, + relativeWeeksDue, +}) => { + const intl = useIntl(); + const showRelativeWeeks = isSelfPaced && isCustomRelativeDatesActive && relativeWeeksDue; + let examValue = ''; if (isProctoredExam) { if (isOnboardingExam) { @@ -68,45 +98,6 @@ const XBlockStatus = ({ examValue = messages.timedExam; } - if (prereq) { - let prereqDisplayName = ''; - prereqs.forEach((block) => { - if (block.blockUsageKey === prereq) { - prereqDisplayName = block.blockDisplayName; - } - }); - statusMessages.push({ - icon: LockIcon, - text: intl.formatMessage(messages.prerequisiteLabel, { prereqDisplayName }), - }); - } - - if (!staffOnlyMessage && isVertical) { - const { selectedPartitionIndex, selectedGroupsLabel } = userPartitionInfo; - if (selectedPartitionIndex !== -1 && !Number.isNaN(selectedPartitionIndex)) { - statusMessages.push({ - icon: GroupsIcon, - text: intl.formatMessage(messages.restrictedUnitAccess, { selectedGroupsLabel }), - }); - } else if (hasPartitionGroupComponents) { - statusMessages.push({ - icon: GroupsIcon, - text: intl.formatMessage(messages.restrictedUnitAccessToSomeContent), - }); - } - } - - const releaseStatusDiv = () => ( -
- - {intl.formatMessage(messages.releaseStatusScreenReaderTitle)} - - - {intl.formatMessage(releaseLabel)} - {releaseDate && releaseDate} -
- ); - const gradingTypeDiv = () => (
@@ -139,50 +130,70 @@ const XBlockStatus = ({
); - const explanatoryMessageDiv = () => ( - - {explanatoryMessage} - - ); - - const renderGradingTypeAndDueDate = () => { - const showRelativeWeeks = isSelfPaced && isCustomRelativeDatesActive && relativeWeeksDue; - if (isTimeLimited) { - return ( - <> -
- {gradingTypeDiv()} - - {intl.formatMessage(examValue)} - - {intl.formatMessage(examValue)} - - {dueDateDiv()} -
- {showRelativeWeeks && (selfPacedRelativeDueWeeksDiv())} - - ); - } if ((dueDate && !isSelfPaced) || graded) { - return ( - <> -
- {gradingTypeDiv()} - {dueDateDiv()} -
- {showRelativeWeeks && (selfPacedRelativeDueWeeksDiv())} - - ); - } if (showRelativeWeeks) { - return ( - <> + if (isTimeLimited) { + return ( + <> +
+ {gradingTypeDiv()} - + {intl.formatMessage(examValue)} + + {intl.formatMessage(examValue)} + + {dueDateDiv()} +
+ {showRelativeWeeks && (selfPacedRelativeDueWeeksDiv())} + + ); + } if ((dueDate && !isSelfPaced) || graded) { + return ( + <> +
{gradingTypeDiv()} - {selfPacedRelativeDueWeeksDiv()} - - ); - } - return null; - }; + {dueDateDiv()} +
+ {showRelativeWeeks && (selfPacedRelativeDueWeeksDiv())} + + ); + } if (showRelativeWeeks) { + return ( + <> + {gradingTypeDiv()} + {selfPacedRelativeDueWeeksDiv()} + + ); + } + return null; +}; + +GradingTypeAndDueDate.defaultProps = { + isCustomRelativeDatesActive: false, + isTimeLimited: false, + isProctoredExam: false, + isOnboardingExam: false, + isPracticeExam: false, + graded: false, + gradingType: '', + dueDate: '', + relativeWeeksDue: null, +}; + +GradingTypeAndDueDate.propTypes = { + isInstructorPaced: PropTypes.bool.isRequired, + isSelfPaced: PropTypes.bool.isRequired, + isCustomRelativeDatesActive: PropTypes.bool, + isTimeLimited: PropTypes.bool, + isProctoredExam: PropTypes.bool, + isOnboardingExam: PropTypes.bool, + isPracticeExam: PropTypes.bool, + graded: PropTypes.bool, + gradingType: PropTypes.string, + dueDate: PropTypes.string, + relativeWeeksDue: PropTypes.number, +}; - const hideAfterDueMessage = () => ( +const HideAfterDueMessage = ({ isSelfPaced }) => { + const intl = useIntl(); + return (
@@ -192,56 +203,200 @@ const XBlockStatus = ({
); +}; + +HideAfterDueMessage.propTypes = { + isSelfPaced: PropTypes.bool.isRequired, +}; - const renderGradingPolicyAlert = () => { - let gradingPolicyMismatch = false; - if (graded) { - if (gradingType) { - gradingPolicyMismatch = ( - courseGraders.filter((cg) => cg.toLowerCase() === gradingType.toLowerCase()) - ).length === 0; +const StatusMessages = ({ + isVertical, + staffOnlyMessage, + prereq, + prereqs, + userPartitionInfo, + hasPartitionGroupComponents, +}) => { + const intl = useIntl(); + const statusMessages = []; + + if (prereq) { + let prereqDisplayName = ''; + prereqs.forEach((block) => { + if (block.blockUsageKey === prereq) { + prereqDisplayName = block.blockDisplayName; } - } + }); + statusMessages.push({ + icon: LockIcon, + text: intl.formatMessage(messages.prerequisiteLabel, { prereqDisplayName }), + }); + } - if (gradingPolicyMismatch) { - return ( -
- - {intl.formatMessage(messages.gradingPolicyMismatchText, { gradingType })} -
- ); + if (!staffOnlyMessage && isVertical) { + const { selectedPartitionIndex, selectedGroupsLabel } = userPartitionInfo; + if (selectedPartitionIndex !== -1 && !Number.isNaN(selectedPartitionIndex)) { + statusMessages.push({ + icon: GroupsIcon, + text: intl.formatMessage(messages.restrictedUnitAccess, { selectedGroupsLabel }), + }); + } else if (hasPartitionGroupComponents) { + statusMessages.push({ + icon: GroupsIcon, + text: intl.formatMessage(messages.restrictedUnitAccessToSomeContent), + }); } - return null; - }; + } - const renderStatusMessages = () => { - if (statusMessages.length > 0) { - return ( -
- {statusMessages.map(({ icon, text }) => ( -
- - {text} -
- ))} -
- ); + if (statusMessages.length > 0) { + return ( +
+ {statusMessages.map(({ icon, text }) => ( +
+ + {text} +
+ ))} +
+ ); + } + return null; +}; + +StatusMessages.defaultProps = { + staffOnlyMessage: false, + prereq: '', + prereqs: {}, + userPartitionInfo: {}, +}; + +StatusMessages.propTypes = { + isVertical: PropTypes.bool.isRequired, + staffOnlyMessage: PropTypes.bool, + prereq: PropTypes.string, + prereqs: PropTypes.arrayOf(PropTypes.shape({ + blockUsageKey: PropTypes.string.isRequired, + blockDisplayName: PropTypes.string.isRequired, + })), + userPartitionInfo: PropTypes.shape({ + selectedPartitionIndex: PropTypes.number.isRequired, + selectedGroupsLabel: PropTypes.string.isRequired, + }), + hasPartitionGroupComponents: PropTypes.bool.isRequired, +}; + +const GradingPolicyAlert = ({ + graded, + gradingType, + courseGraders, +}) => { + const intl = useIntl(); + + let gradingPolicyMismatch = false; + if (graded) { + if (gradingType) { + gradingPolicyMismatch = ( + courseGraders.filter((cg) => cg.toLowerCase() === gradingType.toLowerCase()) + ).length === 0; } - return null; - }; + } + + if (gradingPolicyMismatch) { + return ( +
+ + {intl.formatMessage(messages.gradingPolicyMismatchText, { gradingType })} +
+ ); + } + return null; +}; + +GradingPolicyAlert.defaultProps = { + graded: false, + gradingType: '', +}; + +GradingPolicyAlert.propTypes = { + graded: PropTypes.bool, + gradingType: PropTypes.string, + courseGraders: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired, +}; + +const XBlockStatus = ({ + isSelfPaced, + isCustomRelativeDatesActive, + blockData, +}) => { + const { + category, + explanatoryMessage, + releasedToStudents, + releaseDate, + isProctoredExam, + isOnboardingExam, + isPracticeExam, + prereq, + prereqs, + staffOnlyMessage, + userPartitionInfo, + hasPartitionGroupComponents, + format: gradingType, + dueDate, + relativeWeeksDue, + isTimeLimited, + graded, + courseGraders, + hideAfterDue, + } = blockData; + + const isInstructorPaced = !isSelfPaced; + const isVertical = category === COURSE_BLOCK_NAMES.vertical.id; return (
{!isVertical && ( - explanatoryMessage ? explanatoryMessageDiv() : isInstructorPaced && releaseStatusDiv() + + )} + {!isVertical && ( + + )} + {hideAfterDue && ( + )} - {!isVertical && renderGradingTypeAndDueDate()} - {hideAfterDue && hideAfterDueMessage()} - {renderStatusMessages()} - {renderGradingPolicyAlert()} + +
); }; @@ -253,7 +408,7 @@ XBlockStatus.defaultProps = { XBlockStatus.propTypes = { isSelfPaced: PropTypes.bool.isRequired, isCustomRelativeDatesActive: PropTypes.bool, - item: PropTypes.shape({ + blockData: PropTypes.shape({ category: PropTypes.string.isRequired, explanatoryMessage: PropTypes.string, releasedToStudents: PropTypes.bool.isRequired, diff --git a/src/course-outline/xblock-status/XBlockStatus.test.jsx b/src/course-outline/xblock-status/XBlockStatus.test.jsx index 1cc232e6d3..e3b5ab9340 100644 --- a/src/course-outline/xblock-status/XBlockStatus.test.jsx +++ b/src/course-outline/xblock-status/XBlockStatus.test.jsx @@ -78,7 +78,7 @@ describe(' for Instructor paced Section', () => { it('render XBlockStatus with explanatoryMessage', () => { const { queryByTestId } = renderComponent({ - item: { + blockData: { ...section, explanatoryMessage: 'some explanatory message', }, @@ -90,7 +90,7 @@ describe(' for Instructor paced Section', () => { }); it('renders XBlockStatus with release status, grading type, due date etc.', () => { - const { queryByTestId } = renderComponent({ item: section }); + const { queryByTestId } = renderComponent({ blockData: section }); expect(queryByTestId('explanatory-message-span')).not.toBeInTheDocument(); // when explanatory message is not displayed, release date should be visible @@ -148,7 +148,7 @@ describe(' for self paced Section', () => { const { queryByTestId } = renderComponent({ isSelfPaced: true, isCustomRelativeDatesActive: true, - item: { + blockData: { ...section, relativeWeeksDue: 2, }, @@ -182,7 +182,7 @@ describe(' for self paced Section', () => { it('renders XBlockStatus with grading mismatch alert', () => { const { queryByTestId } = renderComponent({ - item: { + blockData: { ...section, format: 'Fun', }, @@ -250,7 +250,7 @@ describe(' for Instructor paced Subsection', () => { }); it('renders XBlockStatus with release status, grading type, due date etc.', () => { - const { queryByTestId } = renderComponent({ item: subsection }); + const { queryByTestId } = renderComponent({ blockData: subsection }); expect(queryByTestId('explanatory-message-span')).not.toBeInTheDocument(); // when explanatory message is not displayed, release date should be visible @@ -291,7 +291,7 @@ describe(' for Instructor paced Subsection', () => { it('renders XBlockStatus with proctored exam info', () => { const { queryByTestId } = renderComponent({ - item: { + blockData: { ...subsection, isProctoredExam: true, isOnboardingExam: false, @@ -307,7 +307,7 @@ describe(' for Instructor paced Subsection', () => { it('renders XBlockStatus with practice proctored exam info', () => { const { queryByTestId } = renderComponent({ - item: { + blockData: { ...subsection, isProctoredExam: true, isOnboardingExam: false, @@ -323,7 +323,7 @@ describe(' for Instructor paced Subsection', () => { it('renders XBlockStatus with onboarding exam info', () => { const { queryByTestId } = renderComponent({ - item: { + blockData: { ...subsection, isProctoredExam: true, isOnboardingExam: true, @@ -339,7 +339,7 @@ describe(' for Instructor paced Subsection', () => { it('renders XBlockStatus correctly for graded but not time limited subsection', () => { const { queryByTestId } = renderComponent({ - item: { + blockData: { ...subsection, isTimeLimited: false, graded: true, @@ -383,7 +383,7 @@ describe(' for self paced Subsection', () => { const { queryByTestId } = renderComponent({ isSelfPaced: true, isCustomRelativeDatesActive: true, - item: { + blockData: { ...subsection, relativeWeeksDue: 2, }, @@ -461,7 +461,7 @@ describe(' for unit', () => { }); it('renders XBlockStatus with status messages', () => { - const { queryByTestId } = renderComponent({ item: unit }); + const { queryByTestId } = renderComponent({ blockData: unit }); expect(queryByTestId('explanatory-message-span')).not.toBeInTheDocument(); expect(queryByTestId('release-status-div')).not.toBeInTheDocument(); @@ -486,7 +486,7 @@ describe(' for unit', () => { it('renders XBlockStatus with status messages', () => { const { queryByTestId } = renderComponent({ - item: { + blockData: { ...unit, hasPartitionGroupComponents: true, userPartitionInfo: {