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:
committed by
Kristin Aoki
parent
b0cb53ab44
commit
df532b36ab
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -50,6 +50,7 @@ const HeaderNavigations = ({
|
||||
>
|
||||
<Button
|
||||
onClick={handleReIndex}
|
||||
data-testid="course-reindex"
|
||||
variant="outline-primary"
|
||||
disabled={isDisabledReindexButton}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user