From df532b36abc7d1128385906c31ec2bc02a3f2607 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Tue, 19 Dec 2023 13:06:28 +0530 Subject: [PATCH] test: improve coverage for course outline refactor: remove delete unit hook and thunk till unit list is implemented test: additional tests for sections test: additional tests for subsections test: replace query calls by button clicks --- src/course-outline/CourseOutline.test.jsx | 261 ++++++++++-------- .../__mocks__/courseOutlineIndex.js | 4 +- src/course-outline/card-header/CardHeader.jsx | 26 +- .../card-header/CardHeader.test.jsx | 6 +- src/course-outline/data/slice.js | 18 -- src/course-outline/data/thunk.js | 10 - .../delete-modal/DeleteModal.jsx | 1 + .../header-navigations/HeaderNavigations.jsx | 1 + src/course-outline/hooks.jsx | 7 +- .../publish-modal/PublishModal.jsx | 5 +- .../section-card/SectionCard.test.jsx | 75 ++++- src/course-outline/status-bar/StatusBar.jsx | 2 +- .../subsection-card/SubsectionCard.test.jsx | 33 ++- 13 files changed, 276 insertions(+), 173 deletions(-) diff --git a/src/course-outline/CourseOutline.test.jsx b/src/course-outline/CourseOutline.test.jsx index eac6fa5c84..0f4ee33e17 100644 --- a/src/course-outline/CourseOutline.test.jsx +++ b/src/course-outline/CourseOutline.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { - render, waitFor, cleanup, fireEvent, within, + act, render, waitFor, cleanup, fireEvent, within, } from '@testing-library/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; @@ -19,20 +19,10 @@ import { getXBlockBaseApiUrl, } from './data/api'; import { - addNewSectionQuery, - addNewSubsectionQuery, - deleteCourseSectionQuery, - deleteCourseSubsectionQuery, - duplicateSectionQuery, - duplicateSubsectionQuery, - editCourseItemQuery, - enableCourseHighlightsEmailsQuery, + configureCourseSectionQuery, fetchCourseBestPracticesQuery, fetchCourseLaunchQuery, fetchCourseOutlineIndexQuery, - fetchCourseReindexQuery, - fetchCourseSectionQuery, - publishCourseItemQuery, updateCourseSectionHighlightsQuery, setSectionOrderListQuery, } from './data/thunk'; @@ -50,6 +40,7 @@ import CourseOutline from './CourseOutline'; import messages from './messages'; import headerMessages from './header-navigations/messages'; import cardHeaderMessages from './card-header/messages'; +import enableHighlightsModalMessages from './enable-highlights-modal/messages'; let axiosMock; let store; @@ -111,24 +102,39 @@ describe('', () => { }); it('check reindex and render success alert is correctly', async () => { - const { getByText } = render(); + const { findByText, findByTestId } = render(); axiosMock .onGet(getCourseReindexApiUrl(courseOutlineIndexMock.reindexLink)) .reply(200); - await executeThunk(fetchCourseReindexQuery(courseId, courseOutlineIndexMock.reindexLink), store.dispatch); + const reindexButton = await findByTestId('course-reindex'); + fireEvent.click(reindexButton); - expect(getByText(messages.alertSuccessDescription.defaultMessage)).toBeInTheDocument(); + expect(await findByText(messages.alertSuccessDescription.defaultMessage)).toBeInTheDocument(); + }); + + it('render error alert after failed reindex correctly', async () => { + const { findByText, findByTestId } = render(); + + axiosMock + .onGet(getCourseReindexApiUrl(courseOutlineIndexMock.reindexLink)) + .reply(500); + const reindexButton = await findByTestId('course-reindex'); + await act(async () => { + fireEvent.click(reindexButton); + }); + + expect(await findByText(messages.alertErrorTitle.defaultMessage)).toBeInTheDocument(); }); it('adds new section correctly', async () => { - const { findAllByTestId } = render(); - let element = await findAllByTestId('section-card'); + const { findAllByTestId, findByTestId } = render(); + let elements = await findAllByTestId('section-card'); window.HTMLElement.prototype.getBoundingClientRect = jest.fn(() => ({ top: 0, bottom: 4000, })); - expect(element.length).toBe(4); + expect(elements.length).toBe(4); axiosMock .onPost(getXBlockBaseApiUrl()) @@ -138,16 +144,18 @@ describe('', () => { axiosMock .onGet(getXBlockApiUrl(courseSectionMock.id)) .reply(200, courseSectionMock); - await executeThunk(addNewSectionQuery(courseId), store.dispatch); + const newSectionButton = await findByTestId('new-section-button'); + await act(async () => { + fireEvent.click(newSectionButton); + }); - element = await findAllByTestId('section-card'); - expect(element.length).toBe(5); + elements = await findAllByTestId('section-card'); + expect(elements.length).toBe(5); expect(window.HTMLElement.prototype.scrollIntoView).toBeCalled(); }); it('adds new subsection correctly', async () => { const { findAllByTestId } = render(); - const sectionId = courseOutlineIndexMock.courseStructure.childInfo.children[0].id; const [section] = await findAllByTestId('section-card'); let subsections = await within(section).findAllByTestId('subsection-card'); expect(subsections.length).toBe(1); @@ -164,24 +172,16 @@ describe('', () => { axiosMock .onGet(getXBlockApiUrl(courseSubsectionMock.id)) .reply(200, courseSubsectionMock); - await executeThunk(addNewSubsectionQuery(sectionId), store.dispatch); + const newSubsectionButton = await within(section).findByTestId('new-subsection-button'); + await act(async () => { + fireEvent.click(newSubsectionButton); + }); subsections = await within(section).findAllByTestId('subsection-card'); expect(subsections.length).toBe(2); expect(window.HTMLElement.prototype.scrollIntoView).toBeCalled(); }); - it('render error alert after failed reindex correctly', async () => { - const { getByText } = render(); - - axiosMock - .onGet(getCourseReindexApiUrl('some link')) - .reply(500); - await executeThunk(fetchCourseReindexQuery(courseId, 'some link'), store.dispatch); - - expect(getByText(messages.alertErrorTitle.defaultMessage)).toBeInTheDocument(); - }); - it('render checklist value correctly', async () => { const { getByText } = render(); @@ -208,15 +208,9 @@ describe('', () => { }); it('check highlights are enabled after enable highlights query is successful', async () => { - const { findByTestId } = render(); - - axiosMock - .onGet(getCourseOutlineIndexApiUrl(courseId)) - .reply(200, { - ...courseOutlineIndexMock, - highlightsEnabledForMessaging: false, - }); + const { findByTestId, findByText } = render(); + axiosMock.reset(); axiosMock .onPost(getEnableHighlightsEmailsApiUrl(courseId), { publish: 'republish', @@ -225,8 +219,22 @@ describe('', () => { }, }) .reply(200); + axiosMock + .onGet(getCourseOutlineIndexApiUrl(courseId)) + .reply(200, { + ...courseOutlineIndexMock, + courseStructure: { + ...courseOutlineIndexMock.courseStructure, + highlightsEnabledForMessaging: true, + }, + }); - await executeThunk(enableCourseHighlightsEmailsQuery(courseId), store.dispatch); + const enableButton = await findByTestId('highlights-enable-button'); + fireEvent.click(enableButton); + const saveButton = await findByText(enableHighlightsModalMessages.submitButton.defaultMessage); + await act(async () => { + fireEvent.click(saveButton); + }); expect(await findByTestId('highlights-enabled-span')).toBeInTheDocument(); }); @@ -264,10 +272,10 @@ describe('', () => { }); it('check edit section when edit query is successfully', async () => { - const { getByText } = render(); + const { findAllByTestId, findByText } = render(); const newDisplayName = 'New section name'; - const section = courseOutlineIndexMock.courseStructure.childInfo.children[0]; + const [section] = courseOutlineIndexMock.courseStructure.childInfo.children; axiosMock .onPost(getCourseItemApiUrl(section.id, { @@ -275,45 +283,70 @@ describe('', () => { display_name: newDisplayName, }, })) - .reply(200); - - await executeThunk(editCourseItemQuery(section.id, section.id, newDisplayName), store.dispatch); - + .reply(200, { dummy: 'value' }); axiosMock .onGet(getXBlockApiUrl(section.id)) - .reply(200); - await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch); + .reply(200, { + ...section, + display_name: newDisplayName, + }); - await waitFor(() => { - expect(getByText(section.displayName)).toBeInTheDocument(); + const [sectionElement] = await findAllByTestId('section-card'); + const editButton = await within(sectionElement).findByTestId('section-edit-button'); + fireEvent.click(editButton); + const editField = await within(sectionElement).findByTestId('section-edit-field'); + fireEvent.change(editField, { target: { value: newDisplayName } }); + await act(async () => { + fireEvent.blur(editField); }); + + expect(await findByText(newDisplayName)).toBeInTheDocument(); }); - it('check whether section is deleted when delete query is successfully', async () => { - const { queryByText } = render(); - const section = courseOutlineIndexMock.courseStructure.childInfo.children[1]; + it('check whether section is deleted when delete button is clicked', async () => { + const { findAllByTestId, findByTestId, queryByText } = render(); + const [section] = courseOutlineIndexMock.courseStructure.childInfo.children; await waitFor(() => { expect(queryByText(section.displayName)).toBeInTheDocument(); }); axiosMock.onDelete(getCourseItemApiUrl(section.id)).reply(200); - await executeThunk(deleteCourseSectionQuery(section.id), store.dispatch); + + const [sectionElement] = await findAllByTestId('section-card'); + const menu = await within(sectionElement).findByTestId('section-card-header__menu-button'); + fireEvent.click(menu); + const deleteButton = await within(sectionElement).findByTestId('section-card-header__menu-delete-button'); + fireEvent.click(deleteButton); + const confirmButton = await findByTestId('delete-confirm-button'); + await act(async () => { + fireEvent.click(confirmButton); + }); await waitFor(() => { expect(queryByText(section.displayName)).not.toBeInTheDocument(); }); }); - it('check whether subsection is deleted when delete query is successfully', async () => { - const { queryByText } = render(); - const section = courseOutlineIndexMock.courseStructure.childInfo.children[1]; + it('check whether subsection is deleted when delete button is clicked', async () => { + const { findAllByTestId, findByTestId, queryByText } = render(); + const [section] = courseOutlineIndexMock.courseStructure.childInfo.children; const [subsection] = section.childInfo.children; await waitFor(() => { expect(queryByText(subsection.displayName)).toBeInTheDocument(); }); axiosMock.onDelete(getCourseItemApiUrl(subsection.id)).reply(200); - await executeThunk(deleteCourseSubsectionQuery(subsection.id, section.id), store.dispatch); + + const [sectionElement] = await findAllByTestId('section-card'); + const [subsectionElement] = await within(sectionElement).findAllByTestId('subsection-card'); + const menu = await within(subsectionElement).findByTestId('subsection-card-header__menu-button'); + fireEvent.click(menu); + const deleteButton = await within(subsectionElement).findByTestId('subsection-card-header__menu-delete-button'); + fireEvent.click(deleteButton); + const confirmButton = await findByTestId('delete-confirm-button'); + await act(async () => { + fireEvent.click(confirmButton); + }); await waitFor(() => { expect(queryByText(subsection.displayName)).not.toBeInTheDocument(); @@ -322,9 +355,7 @@ describe('', () => { it('check whether section is duplicated successfully', async () => { const { findAllByTestId } = render(); - const section = courseOutlineIndexMock.courseStructure.childInfo.children[0]; - const sectionId = section.id; - const courseBlockId = courseOutlineIndexMock.courseStructure.id; + const [section] = courseOutlineIndexMock.courseStructure.childInfo.children; expect(await findAllByTestId('section-card')).toHaveLength(4); axiosMock @@ -338,8 +369,14 @@ describe('', () => { .reply(200, { ...section, }); - await executeThunk(duplicateSectionQuery(sectionId, courseBlockId), store.dispatch); + const [sectionElement] = await findAllByTestId('section-card'); + const menu = await within(sectionElement).findByTestId('section-card-header__menu-button'); + fireEvent.click(menu); + const duplicateButton = await within(sectionElement).findByTestId('section-card-header__menu-duplicate-button'); + await act(async () => { + fireEvent.click(duplicateButton); + }); expect(await findAllByTestId('section-card')).toHaveLength(5); }); @@ -348,7 +385,6 @@ describe('', () => { const section = courseOutlineIndexMock.courseStructure.childInfo.children[0]; let [sectionElement] = await findAllByTestId('section-card'); const [subsection] = section.childInfo.children; - const subsectionId = subsection.id; let subsections = await within(sectionElement).findAllByTestId('subsection-card'); expect(subsections.length).toBe(1); @@ -365,41 +401,27 @@ describe('', () => { ...section, }); - await executeThunk(duplicateSubsectionQuery(subsectionId, section.id), store.dispatch); + const menu = await within(subsections[0]).findByTestId('subsection-card-header__menu-button'); + fireEvent.click(menu); + const duplicateButton = await within(subsections[0]).findByTestId('subsection-card-header__menu-duplicate-button'); + await act(async () => { + fireEvent.click(duplicateButton); + }); [sectionElement] = await findAllByTestId('section-card'); subsections = await within(sectionElement).findAllByTestId('subsection-card'); expect(subsections.length).toBe(2); }); - it('check publish section when publish query is successfully', async () => { - cleanup(); - const { getAllByTestId } = render(); - const section = courseOutlineIndexMock.courseStructure.childInfo.children[0]; - - axiosMock - .onGet(getCourseOutlineIndexApiUrl(courseId)) - .reply(200, { - courseOutlineIndexMock, - courseStructure: { - childInfo: { - children: [ - { - ...section, - published: false, - }, - ], - }, - }, - }); + it('check section is published when publish button is clicked', async () => { + const [section] = courseOutlineIndexMock.courseStructure.childInfo.children; + const { findAllByTestId, findByTestId } = render(); axiosMock .onPost(getCourseItemApiUrl(section.id), { publish: 'make_public', }) - .reply(200); - - await executeThunk(publishCourseItemQuery(section.id, section.id), store.dispatch); + .reply(200, { dummy: 'value' }); axiosMock .onGet(getXBlockApiUrl(section.id)) @@ -409,47 +431,42 @@ describe('', () => { releasedToStudents: false, }); - await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch); - - const firstSection = getAllByTestId('section-card')[0]; - expect(firstSection.querySelector('.item-card-header__badge-status')).toHaveTextContent('Published not live'); + const [sectionElement] = await findAllByTestId('section-card'); + const menu = await within(sectionElement).findByTestId('section-card-header__menu-button'); + fireEvent.click(menu); + const publishButton = await within(sectionElement).findByTestId('section-card-header__menu-publish-button'); + await act(async () => fireEvent.click(publishButton)); + const confirmButton = await findByTestId('publish-confirm-button'); + await act(async () => fireEvent.click(confirmButton)); + + expect( + sectionElement.querySelector('.item-card-header__badge-status'), + ).toHaveTextContent(cardHeaderMessages.statusBadgePublishedNotLive.defaultMessage); }); it('check configure section when configure query is successful', async () => { - cleanup(); - const { getAllByTestId, getByText, getByPlaceholderText } = render(); + const { findAllByTestId, findByPlaceholderText } = render(); const section = courseOutlineIndexMock.courseStructure.childInfo.children[0]; const newReleaseDate = '2025-08-10T10:00:00Z'; axiosMock .onPost(getCourseItemApiUrl(section.id), { - id: section.id, - data: null, + publish: 'republish', metadata: { - display_name: section.displayName, - start: newReleaseDate, visible_to_staff_only: true, + start: newReleaseDate, }, }) - .reply(200); + .reply(200, { dummy: 'value' }); axiosMock .onGet(getXBlockApiUrl(section.id)) .reply(200, section); - await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch); - - const firstSection = getAllByTestId('section-card')[0]; + const [firstSection] = await findAllByTestId('section-card'); - const sectionDropdownButton = firstSection.querySelector('#section-card-header__menu'); - expect(sectionDropdownButton).toBeInTheDocument(); + const sectionDropdownButton = await within(firstSection).findByTestId('section-card-header__menu-button'); fireEvent.click(sectionDropdownButton); - const configureBtn = getByText(cardHeaderMessages.menuConfigure.defaultMessage); - fireEvent.click(configureBtn); - - const datePicker = getByPlaceholderText('MM/DD/YYYY'); - fireEvent.change(datePicker, { target: { value: '08/10/2025' } }); - axiosMock .onGet(getXBlockApiUrl(section.id)) .reply(200, { @@ -457,10 +474,12 @@ describe('', () => { start: newReleaseDate, }); - fireEvent.click(getByText('Save')); + await executeThunk(configureCourseSectionQuery(section.id, true, newReleaseDate), store.dispatch); fireEvent.click(sectionDropdownButton); + const configureBtn = await within(firstSection).findByTestId('section-card-header__menu-configure-button'); fireEvent.click(configureBtn); + const datePicker = await findByPlaceholderText('MM/DD/YYYY'); expect(datePicker).toHaveValue('08/10/2025'); }); @@ -483,9 +502,7 @@ describe('', () => { highlights, }, }) - .reply(200); - - await executeThunk(updateCourseSectionHighlightsQuery(section.id, highlights), store.dispatch); + .reply(200, { dummy: 'value' }); axiosMock .onGet(getXBlockApiUrl(section.id)) @@ -494,9 +511,11 @@ describe('', () => { highlights, }); - await executeThunk(fetchCourseSectionQuery(section.id), store.dispatch); + await executeThunk(updateCourseSectionHighlightsQuery(section.id, highlights), store.dispatch); - expect(getByRole('button', { name: '5 Section highlights' })).toBeInTheDocument(); + await waitFor(() => { + expect(getByRole('button', { name: '5 Section highlights' })).toBeInTheDocument(); + }); }); it('check section list is ordered successfully', async () => { @@ -506,8 +525,8 @@ describe('', () => { children = children.splice(2, 0, children.splice(0, 1)[0]); axiosMock - .onPut(getEnableHighlightsEmailsApiUrl(courseBlockId), children) - .reply(200); + .onPut(getEnableHighlightsEmailsApiUrl(courseBlockId), { children }) + .reply(200, { dummy: 'value' }); await executeThunk(setSectionOrderListQuery(courseBlockId, children, () => {}), store.dispatch); @@ -527,7 +546,7 @@ describe('', () => { const newChildren = children.splice(2, 0, children.splice(0, 1)[0]); axiosMock - .onPut(getEnableHighlightsEmailsApiUrl(courseBlockId), undefined) + .onPut(getEnableHighlightsEmailsApiUrl(courseBlockId), { children }) .reply(500); await executeThunk(setSectionOrderListQuery(courseBlockId, undefined, () => children), store.dispatch); diff --git a/src/course-outline/__mocks__/courseOutlineIndex.js b/src/course-outline/__mocks__/courseOutlineIndex.js index b65784c9be..d7ed4ed35f 100644 --- a/src/course-outline/__mocks__/courseOutlineIndex.js +++ b/src/course-outline/__mocks__/courseOutlineIndex.js @@ -55,7 +55,7 @@ module.exports = { }, ], showCorrectness: 'always', - highlightsEnabledForMessaging: true, + highlightsEnabledForMessaging: false, highlightsEnabled: true, highlightsPreviewOnly: false, highlightsDocUrl: 'http://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_sections.html#set-section-highlights-for-weekly-course-highlight-messages', @@ -72,7 +72,7 @@ module.exports = { category: 'chapter', hasChildren: true, editedOn: 'Aug 23, 2023 at 12:35 UTC', - published: true, + published: false, publishedOn: 'Aug 23, 2023 at 12:35 UTC', studioUrl: '/course/course-v1:edX+DemoX+Demo_Course?show=block-v1%3AedX%2BDemoX%2BDemo_Course%2Btype%40chapter%2Bblock%40d8a6192ade314473a78242dfeedfbf5b', releasedToStudents: true, diff --git a/src/course-outline/card-header/CardHeader.jsx b/src/course-outline/card-header/CardHeader.jsx index 150d90c269..2c06e02220 100644 --- a/src/course-outline/card-header/CardHeader.jsx +++ b/src/course-outline/card-header/CardHeader.jsx @@ -62,7 +62,7 @@ const CardHeader = ({ {isFormOpen ? ( e && e.focus()} value={titleValue} name="displayName" @@ -115,7 +115,7 @@ const CardHeader = ({
{!isFormOpen && ( {intl.formatMessage(messages.menuPublish)} - {intl.formatMessage(messages.menuConfigure)} - {intl.formatMessage(messages.menuDuplicate)} - {intl.formatMessage(messages.menuDelete)} + + {intl.formatMessage(messages.menuConfigure)} + + + {intl.formatMessage(messages.menuDuplicate)} + + + {intl.formatMessage(messages.menuDelete)} +
diff --git a/src/course-outline/card-header/CardHeader.test.jsx b/src/course-outline/card-header/CardHeader.test.jsx index e9269531c9..704d69baad 100644 --- a/src/course-outline/card-header/CardHeader.test.jsx +++ b/src/course-outline/card-header/CardHeader.test.jsx @@ -146,7 +146,7 @@ describe('', () => { it('calls onClickEdit when the button is clicked', async () => { const { findByTestId } = renderComponent(); - const editButton = await findByTestId('edit-button'); + const editButton = await findByTestId('section-edit-button'); fireEvent.click(editButton); waitFor(() => { expect(onClickEditMock).toHaveBeenCalled(); @@ -159,7 +159,7 @@ describe('', () => { isFormOpen: true, }); - expect(await findByTestId('edit field')).toBeInTheDocument(); + expect(await findByTestId('section-edit-field')).toBeInTheDocument(); waitFor(() => { expect(queryByTestId('section-card-header__expanded-btn')).not.toBeInTheDocument(); expect(queryByTestId('edit-button')).not.toBeInTheDocument(); @@ -173,7 +173,7 @@ describe('', () => { isDisabledEditField: true, }); - expect(await findByTestId('edit field')).toBeDisabled(); + expect(await findByTestId('section-edit-field')).toBeDisabled(); }); it('calls onClickDelete when item is clicked', async () => { diff --git a/src/course-outline/data/slice.js b/src/course-outline/data/slice.js index edb8b1e4a5..bd408c958e 100644 --- a/src/course-outline/data/slice.js +++ b/src/course-outline/data/slice.js @@ -121,23 +121,6 @@ const slice = createSlice({ return section; }); }, - deleteUnit: (state, { payload }) => { - state.sectionsList = state.sectionsList.map((section) => { - if (section.id !== payload.sectionId) { - return section; - } - section.childInfo.children = section.childInfo.children.map((subsection) => { - if (subsection.id !== payload.subsectionId) { - return subsection; - } - subsection.childInfo.children = subsection.childInfo.children.filter( - ({ id }) => id !== payload.itemId, - ); - return subsection; - }); - return section; - }); - }, duplicateSection: (state, { payload }) => { state.sectionsList = state.sectionsList.reduce((result, currentValue) => { if (currentValue.id === payload.id) { @@ -166,7 +149,6 @@ export const { setCurrentSubsection, deleteSection, deleteSubsection, - deleteUnit, duplicateSection, reorderSectionList, } = slice.actions; diff --git a/src/course-outline/data/thunk.js b/src/course-outline/data/thunk.js index 81996791e2..50909f5a90 100644 --- a/src/course-outline/data/thunk.js +++ b/src/course-outline/data/thunk.js @@ -39,7 +39,6 @@ import { updateFetchSectionLoadingStatus, deleteSection, deleteSubsection, - deleteUnit, duplicateSection, reorderSectionList, } from './slice'; @@ -265,15 +264,6 @@ export function deleteCourseSubsectionQuery(subsectionId, sectionId) { }; } -export function deleteCourseUnitQuery(unitId, subsectionId, sectionId) { - return async (dispatch) => { - dispatch(deleteCourseItemQuery( - unitId, - () => deleteUnit({ itemId: unitId, subsectionId, sectionId }), - )); - }; -} - /** * Generic function to duplicate any course item. See wrapper functions below for specific implementations. * @param {string} itemId diff --git a/src/course-outline/delete-modal/DeleteModal.jsx b/src/course-outline/delete-modal/DeleteModal.jsx index a6c08fdeeb..4569ac3248 100644 --- a/src/course-outline/delete-modal/DeleteModal.jsx +++ b/src/course-outline/delete-modal/DeleteModal.jsx @@ -28,6 +28,7 @@ const DeleteModal = ({ isOpen, close, onDeleteSubmit }) => { {intl.formatMessage(messages.cancelButton)} diff --git a/src/course-outline/section-card/SectionCard.test.jsx b/src/course-outline/section-card/SectionCard.test.jsx index b1a65402d3..d391e48ffc 100644 --- a/src/course-outline/section-card/SectionCard.test.jsx +++ b/src/course-outline/section-card/SectionCard.test.jsx @@ -8,6 +8,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import initializeStore from '../../store'; import SectionCard from './SectionCard'; +import cardHeaderMessages from '../card-header/messages'; // eslint-disable-next-line no-unused-vars let axiosMock; @@ -25,6 +26,8 @@ const section = { highlights: ['highlight 1', 'highlight 2'], }; +const onEditSectionSubmit = jest.fn(); + const renderComponent = (props) => render( @@ -33,14 +36,11 @@ const renderComponent = (props) => render( onOpenPublishModal={jest.fn()} onOpenHighlightsModal={jest.fn()} onOpenDeleteModal={jest.fn()} - onEditClick={jest.fn()} savingStatus="" - onEditSectionSubmit={jest.fn()} + onEditSectionSubmit={onEditSectionSubmit} onDuplicateSubmit={jest.fn()} isSectionsExpanded - namePrefix="section" - connectDragSource={(el) => el} - isDragging + onNewSubsectionSubmit={jest.fn()} {...props} > children @@ -83,4 +83,69 @@ describe('', () => { expect(queryByTestId('section-card__subsections')).toBeInTheDocument(); expect(queryByTestId('new-subsection-button')).toBeInTheDocument(); }); + + it('title only updates if changed', async () => { + const { findByTestId } = renderComponent(); + + let editButton = await findByTestId('section-edit-button'); + fireEvent.click(editButton); + let editField = await findByTestId('section-edit-field'); + fireEvent.blur(editField); + + expect(onEditSectionSubmit).not.toHaveBeenCalled(); + + editButton = await findByTestId('section-edit-button'); + fireEvent.click(editButton); + editField = await findByTestId('section-edit-field'); + fireEvent.change(editField, { target: { value: 'some random value' } }); + fireEvent.blur(editField); + expect(onEditSectionSubmit).toHaveBeenCalled(); + }); + + it('renders live status', async () => { + const { findByText } = renderComponent(); + expect(await findByText(cardHeaderMessages.statusBadgeLive.defaultMessage)).toBeInTheDocument(); + }); + + it('renders published but live status', async () => { + const { findByText } = renderComponent({ + section: { + ...section, + published: true, + releasedToStudents: false, + visibleToStaffOnly: false, + visibilityState: 'visible', + staffOnlyMessage: false, + }, + }); + expect(await findByText(cardHeaderMessages.statusBadgePublishedNotLive.defaultMessage)).toBeInTheDocument(); + }); + + it('renders staff status', async () => { + const { findByText } = renderComponent({ + section: { + ...section, + published: false, + releasedToStudents: false, + visibleToStaffOnly: true, + visibilityState: 'staff_only', + staffOnlyMessage: true, + }, + }); + expect(await findByText(cardHeaderMessages.statusBadgeStaffOnly.defaultMessage)).toBeInTheDocument(); + }); + + it('renders draft status', async () => { + const { findByText } = renderComponent({ + section: { + ...section, + published: false, + releasedToStudents: false, + visibleToStaffOnly: false, + visibilityState: 'staff_only', + staffOnlyMessage: false, + }, + }); + expect(await findByText(cardHeaderMessages.statusBadgeDraft.defaultMessage)).toBeInTheDocument(); + }); }); diff --git a/src/course-outline/status-bar/StatusBar.jsx b/src/course-outline/status-bar/StatusBar.jsx index c843338ab1..3e08d5b414 100644 --- a/src/course-outline/status-bar/StatusBar.jsx +++ b/src/course-outline/status-bar/StatusBar.jsx @@ -81,7 +81,7 @@ const StatusBar = ({ {intl.formatMessage(messages.highlightEmailsEnabled)} ) : ( - )} diff --git a/src/course-outline/subsection-card/SubsectionCard.test.jsx b/src/course-outline/subsection-card/SubsectionCard.test.jsx index a0f8c688cf..080ae40da0 100644 --- a/src/course-outline/subsection-card/SubsectionCard.test.jsx +++ b/src/course-outline/subsection-card/SubsectionCard.test.jsx @@ -36,6 +36,8 @@ const subsection = { hasChanges: false, }; +const onEditSubectionSubmit = jest.fn(); + const renderComponent = (props) => render( @@ -47,7 +49,7 @@ const renderComponent = (props) => render( onOpenDeleteModal={jest.fn()} onEditClick={jest.fn()} savingStatus="" - onEditSubmit={jest.fn()} + onEditSubmit={onEditSubectionSubmit} onDuplicateSubmit={jest.fn()} namePrefix="subsection" {...props} @@ -91,4 +93,33 @@ describe('', () => { expect(queryByTestId('subsection-card__units')).not.toBeInTheDocument(); expect(queryByTestId('new-unit-button')).not.toBeInTheDocument(); }); + + it('updates current section, subsection and item', async () => { + const { findByTestId } = renderComponent(); + + const menu = await findByTestId('subsection-card-header__menu'); + fireEvent.click(menu); + const { currentSection, currentSubsection, currentItem } = store.getState().courseOutline; + expect(currentSection).toEqual(section); + expect(currentSubsection).toEqual(subsection); + expect(currentItem).toEqual(subsection); + }); + + it('title only updates if changed', async () => { + const { findByTestId } = renderComponent(); + + let editButton = await findByTestId('subsection-edit-button'); + fireEvent.click(editButton); + let editField = await findByTestId('subsection-edit-field'); + fireEvent.blur(editField); + + expect(onEditSubectionSubmit).not.toHaveBeenCalled(); + + editButton = await findByTestId('subsection-edit-button'); + fireEvent.click(editButton); + editField = await findByTestId('subsection-edit-field'); + fireEvent.change(editField, { target: { value: 'some random value' } }); + fireEvent.keyDown(editField, { key: 'Enter', keyCode: 13 }); + expect(onEditSubectionSubmit).toHaveBeenCalled(); + }); });