Files
frontend-app-authoring/src/course-updates/CourseUpdates.test.jsx
Peter Kulko 0706a09acb fix: error handling (#1079)
Added alert message if 403 error occurs.
2024-11-18 11:27:36 -03:00

394 lines
14 KiB
JavaScript

import {
render, waitFor, fireEvent,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeMockApp } from '@edx/frontend-platform';
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import {
getCourseUpdatesApiUrl,
getCourseHandoutApiUrl,
updateCourseUpdatesApiUrl,
} from './data/api';
import {
createCourseUpdateQuery,
deleteCourseUpdateQuery,
editCourseHandoutsQuery,
editCourseUpdateQuery,
} from './data/thunk';
import initializeStore from '../store';
import { executeThunk } from '../utils';
import { RequestStatus } from '../data/constants';
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: () => <div>Widget</div>,
prepareEditorRef: jest.fn(() => ({
refReady: true,
setEditorRef: jest.fn().mockName('prepareEditorRef.setEditorRef'),
})),
}));
const RootWrapper = () => (
<AppProvider store={store}>
<IntlProvider locale="es">
<CourseUpdates courseId={courseId} />
</IntlProvider>
</AppProvider>
);
describe('<CourseUpdates />', () => {
describe('Successful API responses', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock
.onGet(getCourseUpdatesApiUrl(courseId))
.reply(200, courseUpdatesMock);
axiosMock
.onGet(getCourseHandoutApiUrl(courseId))
.reply(200, courseHandoutsMock);
});
it('render CourseUpdates component correctly', async () => {
const {
getByText, getAllByTestId, getByTestId, getByRole,
} = render(<RootWrapper />);
await waitFor(() => {
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.sectionInfo.defaultMessage)).toBeInTheDocument();
expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeInTheDocument();
expect(getAllByTestId('course-update')).toHaveLength(3);
expect(getByTestId('course-handouts')).toBeInTheDocument();
});
});
it('should create course update', async () => {
const { getByText } = render(<RootWrapper />);
const data = {
content: '<p>Some text</p>',
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(<RootWrapper />);
const data = {
id: courseUpdatesMock[0].id,
content: '<p>Some text</p>',
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(<RootWrapper />);
axiosMock
.onDelete(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id))
.reply(200);
await executeThunk(deleteCourseUpdateQuery(courseId, courseUpdatesMock[0].id), store.dispatch);
expect(queryByText(courseUpdatesMock[0].date)).not.toBeInTheDocument();
expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument();
});
it('should edit course handouts', async () => {
const { getByText, queryByText } = render(<RootWrapper />);
const data = {
...courseHandoutsMock,
data: '<p>Some handouts 1</p>',
};
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(<RootWrapper />);
await waitFor(() => {
const editUpdateButtons = getAllByTestId('course-update-edit-button');
const deleteButtons = getAllByTestId('course-update-delete-button');
const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
const newUpdateButton = getByRole('button', { name: messages.newUpdateButton.defaultMessage });
fireEvent.click(newUpdateButton);
expect(newUpdateButton).toBeDisabled();
editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
deleteButtons.forEach((button) => expect(button).toBeDisabled());
expect(getByText('Add new update')).toBeInTheDocument();
});
});
it('Edit handouts form is visible after clicking "Edit" button', async () => {
const { getByText, getByRole, getAllByTestId } = render(<RootWrapper />);
await waitFor(() => {
const editUpdateButtons = getAllByTestId('course-update-edit-button');
const deleteButtons = getAllByTestId('course-update-delete-button');
const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
const editHandoutsButton = editHandoutsButtons[0];
fireEvent.click(editHandoutsButton);
expect(editHandoutsButton).toBeDisabled();
expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeDisabled();
editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
deleteButtons.forEach((button) => expect(button).toBeDisabled());
expect(getByText('Edit handouts')).toBeInTheDocument();
});
});
it('Edit update form is visible after clicking "Edit" button', async () => {
const {
getByText, getByRole, getAllByTestId, queryByText,
} = render(<RootWrapper />);
await waitFor(() => {
const editUpdateButtons = getAllByTestId('course-update-edit-button');
const deleteButtons = getAllByTestId('course-update-delete-button');
const editHandoutsButtons = getAllByTestId('course-handouts-edit-button');
const editUpdateFirstButton = editUpdateButtons[0];
fireEvent.click(editUpdateFirstButton);
expect(getByText('Edit update')).toBeInTheDocument();
expect(getByRole('button', { name: messages.newUpdateButton.defaultMessage })).toBeDisabled();
editUpdateButtons.forEach((button) => expect(button).toBeDisabled());
editHandoutsButtons.forEach((button) => expect(button).toBeDisabled());
deleteButtons.forEach((button) => expect(button).toBeDisabled());
expect(queryByText(courseUpdatesMock[0].content)).not.toBeInTheDocument();
});
});
});
describe('page load failure API responses', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
});
it('Course updates fetch should show updates loading error', async () => {
axiosMock
.onGet(getCourseUpdatesApiUrl(courseId))
.reply(404);
axiosMock
.onGet(getCourseHandoutApiUrl(courseId))
.reply(200, courseHandoutsMock);
const {
getByText, queryByTestId, getByRole,
} = render(<RootWrapper />);
await waitFor(() => {
const newButton = getByRole('button', { name: messages.newUpdateButton.defaultMessage });
expect(getByText(messages.loadingUpdatesErrorTitle.defaultMessage));
expect(newButton).toBeDisabled();
expect(getByText(messages.noCourseUpdates.defaultMessage)).toBeVisible();
expect(queryByTestId('course-update')).toBeNull();
});
});
it('Course handouts fetch should show handouts loading error', async () => {
axiosMock
.onGet(getCourseUpdatesApiUrl(courseId))
.reply(200, courseUpdatesMock);
axiosMock
.onGet(getCourseHandoutApiUrl(courseId))
.reply(404);
const {
getByText, getByTestId,
} = render(<RootWrapper />);
await waitFor(() => {
expect(getByText(messages.loadingHandoutsErrorTitle.defaultMessage));
expect(getByTestId('course-handouts-edit-button')).toBeDisabled();
});
});
it('displays an alert and sets status to DENIED when API responds with 403', async () => {
axiosMock
.onGet(getCourseUpdatesApiUrl(courseId))
.reply(403, courseUpdatesMock);
axiosMock
.onGet(getCourseHandoutApiUrl(courseId))
.reply(403);
const { getByTestId } = render(<RootWrapper />);
await waitFor(() => {
expect(getByTestId('connectionErrorAlert')).toBeInTheDocument();
const { loadingStatuses } = store.getState().courseUpdates;
Object.values(loadingStatuses)
.some(status => expect(status).toEqual(RequestStatus.DENIED));
});
});
});
describe('saving failure API responses', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock
.onGet(getCourseUpdatesApiUrl(courseId))
.reply(200, courseUpdatesMock);
axiosMock
.onGet(getCourseHandoutApiUrl(courseId))
.reply(200, courseHandoutsMock);
});
it('creating new update should show saving error alert', async () => {
const { getByText, queryByText } = render(<RootWrapper />);
const data = {
content: '<p>Some text</p>',
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(<RootWrapper />);
const data = {
id: courseUpdatesMock[0].id,
content: '<p>Some text</p>',
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(<RootWrapper />);
axiosMock
.onDelete(updateCourseUpdatesApiUrl(courseId, courseUpdatesMock[0].id))
.reply(404);
await executeThunk(deleteCourseUpdateQuery(courseId, courseUpdatesMock[0].id), store.dispatch);
expect(getByText(courseUpdatesMock[0].date)).toBeVisible();
expect(getByText(courseUpdatesMock[0].content)).toBeVisible();
expect(getByText(messages.deletingUpdatesErrorDescription.defaultMessage)).toBeVisible();
});
it('editing course handouts should show saving error alert', async () => {
const { getByText, queryByText } = render(<RootWrapper />);
const data = {
...courseHandoutsMock,
data: '<p>Some handouts 1</p>',
};
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));
});
});
});