Some text
', - date: 'August 29, 2023', - }; - - axiosMock - .onPost(getCourseUpdatesApiUrl(courseId)) - .reply(200, data); - - await executeThunk(createCourseUpdateQuery(courseId, data), store.dispatch); - expect(getByText('Some text')).toBeInTheDocument(); - expect(getByText(data.date)).toBeInTheDocument(); - }); - - it('should edit course update', async () => { - const { getByText, queryByText } = render(Some text
', - date: 'August 29, 2023', - }; - - axiosMock - .onPut(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id)) - .reply(200, data); - - await executeThunk(editCourseUpdateQuery(courseId, data), store.dispatch); - expect(getByText('Some text')).toBeInTheDocument(); - expect(getByText(data.date)).toBeInTheDocument(); - expect(queryByText(courseUpdatesMock[0].date)).not.toBeInTheDocument(); - expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument(); - }); - - it('should delete course update', async () => { - const { queryByText } = render(Some handouts 1
', - }; - - axiosMock - .onPut(getCourseHandoutApiUrl(courseId)) - .reply(200, data); - - await executeThunk(editCourseHandoutsQuery(courseId, data), store.dispatch); - expect(getByText('Some handouts 1')).toBeInTheDocument(); - expect(queryByText(courseHandoutsMock.data)).not.toBeInTheDocument(); - }); - - it('Add new update form is visible after clicking "New update" button', async () => { - const { getByText, getByRole, getAllByTestId } = render(Some text
', - date: 'August 29, 2023', - }; - - axiosMock - .onPost(getCourseUpdatesApiUrl(courseId), data) - .reply(404); - - await executeThunk(createCourseUpdateQuery(courseId, data), store.dispatch); - expect(getByText(messages.savingNewUpdateErrorAlertDescription.defaultMessage)).toBeVisible(); - expect(queryByText('Some text')).toBeNull(); - expect(queryByText(data.date)).toBeNull(); - }); - - it('editing course update should show saving error alert', async () => { - const { getByText, queryByText } = render(Some text
', - date: 'August 29, 2023', - }; - - axiosMock - .onPut(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id)) - .reply(404); - - await executeThunk(editCourseUpdateQuery(courseId, data), store.dispatch); - expect(queryByText('Some text')).toBeNull(); - expect(queryByText(data.date)).toBeNull(); - expect(getByText(courseUpdatesMock[0].date)).toBeVisible(); - expect(getByText(courseUpdatesMock[0].content)).toBeVisible(); - expect(getByText(messages.savingUpdatesErrorDescription.defaultMessage)).toBeVisible(); - }); - - it('deleting course update should show delete saving error alert', async () => { - const { getByText } = render(Some handouts 1
', - }; - - axiosMock - .onPut(getCourseHandoutApiUrl(courseId)) - .reply(404); - - await executeThunk(editCourseHandoutsQuery(courseId, data), store.dispatch); - expect(queryByText('Some handouts 1')).toBeNull(); - expect(getByText(courseHandoutsMock.data)).toBeVisible(); - expect(getByText(messages.savingHandoutsErrorDescription.defaultMessage)); - }); - }); -}); diff --git a/src/course-updates/CourseUpdates.test.tsx b/src/course-updates/CourseUpdates.test.tsx new file mode 100644 index 000000000..0a84e3611 --- /dev/null +++ b/src/course-updates/CourseUpdates.test.tsx @@ -0,0 +1,345 @@ +import { CourseAuthoringProvider } from '@src/CourseAuthoringContext'; +import { executeThunk } from '@src/utils'; +import { RequestStatus } from '@src/data/constants'; +import { + initializeMocks, render, waitFor, fireEvent, screen, +} from '@src/testUtils'; + +import { + getCourseUpdatesApiUrl, + getCourseHandoutApiUrl, + updateCourseUpdatesApiUrl, +} from './data/api'; +import { + createCourseUpdateQuery, + deleteCourseUpdateQuery, + editCourseHandoutsQuery, + editCourseUpdateQuery, +} from './data/thunk'; +import { courseUpdatesMock, courseHandoutsMock } from './__mocks__'; +import CourseUpdates from './CourseUpdates'; +import messages from './messages'; + +let axiosMock; +let store; +const mockPathname = '/foo-bar'; +const courseId = '123'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ + pathname: mockPathname, + }), +})); + +jest.mock('@tinymce/tinymce-react', () => { + const originalModule = jest.requireActual('@tinymce/tinymce-react'); + return { + __esModule: true, + ...originalModule, + Editor: () => 'foo bar', + }; +}); + +jest.mock('../editors/sharedComponents/TinyMceWidget', () => ({ + __esModule: true, // Required to mock a default export + default: () =>Some text
', + date: 'August 29, 2023', + }; + + axiosMock + .onPost(getCourseUpdatesApiUrl(courseId)) + .reply(200, data); + + render(Some text
', + date: 'August 29, 2023', + }; + + axiosMock + .onPut(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id)) + .reply(200, data); + + render(Some handouts 1
', + }; + + axiosMock + .onPut(getCourseHandoutApiUrl(courseId)) + .reply(200, data); + + render(Some text
', + date: 'August 29, 2023', + }; + + axiosMock + .onPost(getCourseUpdatesApiUrl(courseId), data) + .reply(404); + + await executeThunk(createCourseUpdateQuery(courseId, data), store.dispatch); + expect(screen.getByText(messages.savingNewUpdateErrorAlertDescription.defaultMessage)).toBeVisible(); + expect(screen.queryByText('Some text')).toBeNull(); + expect(screen.queryByText(data.date)).toBeNull(); + }); + + it('editing course update should show saving error alert', async () => { + render(Some text
', + date: 'August 29, 2023', + }; + + axiosMock + .onPut(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id)) + .reply(404); + + await executeThunk(editCourseUpdateQuery(courseId, data), store.dispatch); + expect(screen.queryByText('Some text')).toBeNull(); + expect(screen.queryByText(data.date)).toBeNull(); + expect(screen.getByText(courseUpdatesMock[0].date)).toBeVisible(); + expect(screen.getByText(courseUpdatesMock[0].content)).toBeVisible(); + expect(screen.getByText(messages.savingUpdatesErrorDescription.defaultMessage)).toBeVisible(); + }); + + it('deleting course update should show delete saving error alert', async () => { + render(Some handouts 1
', + }; + + axiosMock + .onPut(getCourseHandoutApiUrl(courseId)) + .reply(404); + + await executeThunk(editCourseHandoutsQuery(courseId, data), store.dispatch); + expect(screen.queryByText('Some handouts 1')).toBeNull(); + expect(screen.getByText(courseHandoutsMock.data)).toBeVisible(); + expect(await screen.findByText(messages.savingHandoutsErrorDescription.defaultMessage)); + }); + }); +}); diff --git a/src/course-updates/CourseUpdates.jsx b/src/course-updates/CourseUpdates.tsx similarity index 91% rename from src/course-updates/CourseUpdates.jsx rename to src/course-updates/CourseUpdates.tsx index 0d82f3bf8..10745fdd9 100644 --- a/src/course-updates/CourseUpdates.jsx +++ b/src/course-updates/CourseUpdates.tsx @@ -1,5 +1,3 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { Helmet } from 'react-helmet'; import { useIntl } from '@edx/frontend-platform/i18n'; import { @@ -11,13 +9,13 @@ import { import { Add as AddIcon, ErrorOutline as ErrorIcon } from '@openedx/paragon/icons'; import { useSelector } from 'react-redux'; -import { useModel } from '../generic/model-store'; -import { getProcessingNotification } from '../generic/processing-notification/data/selectors'; -import ProcessingNotification from '../generic/processing-notification'; -import SubHeader from '../generic/sub-header/SubHeader'; -import InternetConnectionAlert from '../generic/internet-connection-alert'; -import ConnectionErrorAlert from '../generic/ConnectionErrorAlert'; -import { RequestStatus } from '../data/constants'; +import { getProcessingNotification } from '@src/generic/processing-notification/data/selectors'; +import ProcessingNotification from '@src/generic/processing-notification'; +import SubHeader from '@src/generic/sub-header/SubHeader'; +import InternetConnectionAlert from '@src/generic/internet-connection-alert'; +import ConnectionErrorAlert from '@src/generic/ConnectionErrorAlert'; +import { RequestStatus } from '@src/data/constants'; +import { useCourseAuthoringContext } from '@src/CourseAuthoringContext'; import CourseHandouts from './course-handouts/CourseHandouts'; import CourseUpdate from './course-update/CourseUpdate'; import DeleteModal from './delete-modal/DeleteModal'; @@ -34,9 +32,9 @@ import { matchesAnyStatus } from './utils'; import getPageHeadTitle from '../generic/utils'; import AlertMessage from '../generic/alert-message'; -const CourseUpdates = ({ courseId }) => { +const CourseUpdates = () => { const intl = useIntl(); - const courseDetails = useModel('courseDetails', courseId); + const { courseId, courseDetails } = useCourseAuthoringContext(); const { requestType, @@ -81,7 +79,7 @@ const CourseUpdates = ({ courseId }) => { <>