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
This commit is contained in:
Navin Karkera
2023-12-19 13:06:28 +05:30
committed by Kristin Aoki
parent b0cb53ab44
commit df532b36ab
13 changed files with 275 additions and 172 deletions

View File

@@ -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('<CourseOutline />', () => {
});
it('check reindex and render success alert is correctly', async () => {
const { getByText } = render(<RootWrapper />);
const { findByText, findByTestId } = render(<RootWrapper />);
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(<RootWrapper />);
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(<RootWrapper />);
let element = await findAllByTestId('section-card');
const { findAllByTestId, findByTestId } = render(<RootWrapper />);
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('<CourseOutline />', () => {
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(<RootWrapper />);
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('<CourseOutline />', () => {
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(<RootWrapper />);
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(<RootWrapper />);
@@ -208,15 +208,9 @@ describe('<CourseOutline />', () => {
});
it('check highlights are enabled after enable highlights query is successful', async () => {
const { findByTestId } = render(<RootWrapper />);
axiosMock
.onGet(getCourseOutlineIndexApiUrl(courseId))
.reply(200, {
...courseOutlineIndexMock,
highlightsEnabledForMessaging: false,
});
const { findByTestId, findByText } = render(<RootWrapper />);
axiosMock.reset();
axiosMock
.onPost(getEnableHighlightsEmailsApiUrl(courseId), {
publish: 'republish',
@@ -225,8 +219,22 @@ describe('<CourseOutline />', () => {
},
})
.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('<CourseOutline />', () => {
});
it('check edit section when edit query is successfully', async () => {
const { getByText } = render(<RootWrapper />);
const { findAllByTestId, findByText } = render(<RootWrapper />);
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('<CourseOutline />', () => {
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(<RootWrapper />);
const section = courseOutlineIndexMock.courseStructure.childInfo.children[1];
it('check whether section is deleted when delete button is clicked', async () => {
const { findAllByTestId, findByTestId, queryByText } = render(<RootWrapper />);
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(<RootWrapper />);
const section = courseOutlineIndexMock.courseStructure.childInfo.children[1];
it('check whether subsection is deleted when delete button is clicked', async () => {
const { findAllByTestId, findByTestId, queryByText } = render(<RootWrapper />);
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('<CourseOutline />', () => {
it('check whether section is duplicated successfully', async () => {
const { findAllByTestId } = render(<RootWrapper />);
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('<CourseOutline />', () => {
.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('<CourseOutline />', () => {
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('<CourseOutline />', () => {
...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(<RootWrapper />);
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(<RootWrapper />);
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('<CourseOutline />', () => {
releasedToStudents: false,
});
await executeThunk(fetchCourseSectionQuery(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 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));
const firstSection = getAllByTestId('section-card')[0];
expect(firstSection.querySelector('.item-card-header__badge-status')).toHaveTextContent('Published not live');
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(<RootWrapper />);
const { findAllByTestId, findByPlaceholderText } = render(<RootWrapper />);
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] = await findAllByTestId('section-card');
const firstSection = getAllByTestId('section-card')[0];
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('<CourseOutline />', () => {
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('<CourseOutline />', () => {
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('<CourseOutline />', () => {
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('<CourseOutline />', () => {
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('<CourseOutline />', () => {
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);

View File

@@ -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,

View File

@@ -62,7 +62,7 @@ const CardHeader = ({
{isFormOpen ? (
<Form.Group className="m-0">
<Form.Control
data-testid="edit field"
data-testid={`${namePrefix}-edit-field`}
ref={(e) => e && e.focus()}
value={titleValue}
name="displayName"
@@ -115,7 +115,7 @@ const CardHeader = ({
<div className="ml-auto d-flex">
{!isFormOpen && (
<IconButton
data-testid="edit-button"
data-testid={`${namePrefix}-edit-button`}
alt={intl.formatMessage(messages.altButtonEdit)}
iconAs={EditIcon}
onClick={onClickEdit}
@@ -133,14 +133,30 @@ const CardHeader = ({
/>
<Dropdown.Menu>
<Dropdown.Item
data-testid={`${namePrefix}-card-header__menu-publish-button`}
disabled={isDisabledPublish}
onClick={onClickPublish}
>
{intl.formatMessage(messages.menuPublish)}
</Dropdown.Item>
<Dropdown.Item onClick={onClickConfigure}>{intl.formatMessage(messages.menuConfigure)}</Dropdown.Item>
<Dropdown.Item onClick={onClickDuplicate}>{intl.formatMessage(messages.menuDuplicate)}</Dropdown.Item>
<Dropdown.Item onClick={onClickDelete}>{intl.formatMessage(messages.menuDelete)}</Dropdown.Item>
<Dropdown.Item
data-testid={`${namePrefix}-card-header__menu-configure-button`}
onClick={onClickConfigure}
>
{intl.formatMessage(messages.menuConfigure)}
</Dropdown.Item>
<Dropdown.Item
data-testid={`${namePrefix}-card-header__menu-duplicate-button`}
onClick={onClickDuplicate}
>
{intl.formatMessage(messages.menuDuplicate)}
</Dropdown.Item>
<Dropdown.Item
data-testid={`${namePrefix}-card-header__menu-delete-button`}
onClick={onClickDelete}
>
{intl.formatMessage(messages.menuDelete)}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>

View File

@@ -146,7 +146,7 @@ describe('<CardHeader />', () => {
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('<CardHeader />', () => {
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('<CardHeader />', () => {
isDisabledEditField: true,
});
expect(await findByTestId('edit field')).toBeDisabled();
expect(await findByTestId('section-edit-field')).toBeDisabled();
});
it('calls onClickDelete when item is clicked', async () => {

View File

@@ -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;

View File

@@ -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

View File

@@ -28,6 +28,7 @@ const DeleteModal = ({ isOpen, close, onDeleteSubmit }) => {
{intl.formatMessage(messages.cancelButton)}
</Button>
<Button
data-testid="delete-confirm-button"
onClick={(e) => {
e.preventDefault();
onDeleteSubmit();

View File

@@ -50,6 +50,7 @@ const HeaderNavigations = ({
>
<Button
onClick={handleReIndex}
data-testid="course-reindex"
variant="outline-primary"
disabled={isDisabledReindexButton}
>

View File

@@ -24,7 +24,6 @@ import {
addNewSubsectionQuery,
deleteCourseSectionQuery,
deleteCourseSubsectionQuery,
deleteCourseUnitQuery,
editCourseItemQuery,
duplicateSectionQuery,
duplicateSubsectionQuery,
@@ -133,11 +132,7 @@ const useCourseOutline = ({ courseId }) => {
dispatch(deleteCourseSubsectionQuery(currentItem.id, currentSection.id));
break;
case COURSE_BLOCK_NAMES.vertical.id:
dispatch(deleteCourseUnitQuery(
currentItem.id,
currentSubsection.id,
currentSection.id,
));
// delete unit
break;
default:
return;

View File

@@ -67,7 +67,10 @@ const PublishModal = ({
<ModalDialog.CloseButton variant="tertiary">
{intl.formatMessage(messages.cancelButton)}
</ModalDialog.CloseButton>
<Button onClick={onPublishSubmit}>
<Button
data-testid="publish-confirm-button"
onClick={onPublishSubmit}
>
{intl.formatMessage(messages.publishButton)}
</Button>
</ActionRow>

View File

@@ -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(
<AppProvider store={store}>
<IntlProvider locale="en">
@@ -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}
>
<span>children</span>
@@ -83,4 +83,69 @@ describe('<SectionCard />', () => {
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();
});
});

View File

@@ -81,7 +81,7 @@ const StatusBar = ({
{intl.formatMessage(messages.highlightEmailsEnabled)}
</span>
) : (
<Button size="sm" onClick={openEnableHighlightsModal}>
<Button data-testid="highlights-enable-button" size="sm" onClick={openEnableHighlightsModal}>
{intl.formatMessage(messages.highlightEmailsButton)}
</Button>
)}

View File

@@ -36,6 +36,8 @@ const subsection = {
hasChanges: false,
};
const onEditSubectionSubmit = jest.fn();
const renderComponent = (props) => render(
<AppProvider store={store}>
<IntlProvider locale="en">
@@ -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('<SubsectionCard />', () => {
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();
});
});