Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/generic/modal-dropzone/useModalDropzone.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const useModalDropzone = ({
const handleUpload = async () => {
if (!selectedFile) { return; }

onSavingStatus(RequestStatus.PENDING);
onSavingStatus({ status: RequestStatus.PENDING });

const onUploadProgress = (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
Expand Down
3 changes: 0 additions & 3 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { reducer as genericReducer } from './generic/data/slice';
import { reducer as videosReducer } from './files-and-videos/videos-page/data/slice';
import { reducer as courseOutlineReducer } from './course-outline/data/slice';
import { reducer as courseUnitReducer } from './course-unit/data/slice';
import { reducer as textbooksReducer } from './textbooks/data/slice';
import { reducer as certificatesReducer } from './certificates/data/slice';

type InferState<ReducerType> = ReducerType extends Reducer<infer T> ? T : never;
Expand Down Expand Up @@ -52,7 +51,6 @@ export interface DeprecatedReduxState {
componentMode: (typeof MODE_STATES)[keyof typeof MODE_STATES];
certificatesData: any;
};
textbooks: Record<string, any>;
}

export default function initializeStore(preloadedState: Partial<DeprecatedReduxState> | undefined = undefined) {
Expand All @@ -73,7 +71,6 @@ export default function initializeStore(preloadedState: Partial<DeprecatedReduxS
courseOutline: courseOutlineReducer,
courseUnit: courseUnitReducer,
certificates: certificatesReducer,
textbooks: textbooksReducer,
},
preloadedState: preloadedState as DeprecatedReduxState | undefined,
});
Expand Down
94 changes: 0 additions & 94 deletions src/textbooks/Textbook.test.jsx

This file was deleted.

74 changes: 74 additions & 0 deletions src/textbooks/Textbook.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import userEvent from '@testing-library/user-event';

import { CourseAuthoringProvider } from '@src/CourseAuthoringContext';
import {
initializeMocks,
render,
screen,
} from '@src/testUtils';
import { getTextbooksApiUrl } from './data/api';
import { textbooksMock } from './__mocks__';
import { Textbooks } from '.';
import messages from './messages';

let axiosMock;
const courseId = 'course-v1:org+101+101';
const emptyTextbooksMock = { textbooks: [] };

const renderComponent = () =>
render(
<CourseAuthoringProvider courseId={courseId}>
<Textbooks />
</CourseAuthoringProvider>,
);

describe('<Textbooks />', () => {
beforeEach(async () => {
const mocks = initializeMocks();

axiosMock = mocks.axiosMock;
axiosMock
.onGet(getTextbooksApiUrl(courseId))
.reply(200, textbooksMock);
});

it('renders Textbooks component correctly', async () => {
renderComponent();

expect(await screen.findByText(messages.headingTitle.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.breadcrumbContent.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.breadcrumbPagesAndResources.defaultMessage)).toBeInTheDocument();
expect(screen.getByRole('button', { name: messages.newTextbookButton.defaultMessage })).toBeInTheDocument();
expect(screen.getAllByTestId('textbook-card')).toHaveLength(2);
expect(screen.queryByTestId('textbooks-empty-placeholder')).not.toBeInTheDocument();
});

it('renders textbooks form when "New textbooks" button is clicked', async () => {
const user = userEvent.setup();
renderComponent();

const newTextbookButton = await screen.findByRole('button', { name: messages.newTextbookButton.defaultMessage });
await user.click(newTextbookButton);
expect(screen.getByTestId('textbook-form')).toBeInTheDocument();
});

it('renders Textbooks component with empty placeholder correctly', async () => {
axiosMock
.onGet(getTextbooksApiUrl(courseId))
.reply(200, emptyTextbooksMock);

renderComponent();

expect(await screen.findByTestId('textbooks-empty-placeholder')).toBeInTheDocument();
expect(screen.queryAllByTestId('textbook-card')).toHaveLength(0);
});

it('displays an alert when API responds with 403', async () => {
axiosMock
.onGet(getTextbooksApiUrl(courseId))
.reply(403);
renderComponent();

expect(await screen.findByTestId('connectionErrorAlert')).toBeInTheDocument();
});
});
29 changes: 11 additions & 18 deletions src/textbooks/Textbooks.jsx → src/textbooks/Textbooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import { Add as AddIcon } from '@openedx/paragon/icons';
import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom';
import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
import { RequestStatus } from '@src/data/constants';
import { SavingErrorAlert } from '@src/generic/saving-error-alert';
import { LoadingSpinner } from '@src/generic/Loading';
import SubHeader from '@src/generic/sub-header/SubHeader';

import { useWaffleFlags } from '../data/apiHooks';
import { SavingErrorAlert } from '../generic/saving-error-alert';
import { LoadingSpinner } from '../generic/Loading';
import SubHeader from '../generic/sub-header/SubHeader';
import ConnectionErrorAlert from '../generic/ConnectionErrorAlert';
import ConnectionErrorAlert from '@src/generic/ConnectionErrorAlert';
import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder';
import TextbookCard from './textbook-card/TextbooksCard';
import TextbookSidebar from './textbook-sidebar/TextbookSidebar';
Expand All @@ -27,24 +25,22 @@ import messages from './messages';

const Textbooks = () => {
const intl = useIntl();
const { courseId, courseDetails } = useCourseAuthoringContext();
const waffleFlags = useWaffleFlags(courseId);
const { courseDetails } = useCourseAuthoringContext();

const {
textbooks,
isLoading,
isLoadingFailed,
breadcrumbs,
errorMessage,
savingStatus,
mutationErrorMessage,
anyMutationFailed,
isTextbookFormOpen,
openTextbookForm,
closeTextbookForm,
handleTextbookFormSubmit,
handleSavingStatusDispatch,
handleTextbookEditFormSubmit,
handleTextbookDeleteSubmit,
} = useTextbooks(courseId, waffleFlags);
} = useTextbooks();

if (isLoadingFailed) {
return (
Expand Down Expand Up @@ -106,8 +102,6 @@ const Textbooks = () => {
<TextbookCard
key={textbook.id}
textbook={textbook}
courseId={courseId}
handleSavingStatusDispatch={handleSavingStatusDispatch}
onEditSubmit={handleTextbookEditFormSubmit}
onDeleteSubmit={handleTextbookDeleteSubmit}
textbookIndex={index}
Expand All @@ -121,23 +115,22 @@ const Textbooks = () => {
closeTextbookForm={closeTextbookForm}
initialFormValues={getTextbookFormInitialValues()}
onSubmit={handleTextbookFormSubmit}
onSavingStatus={handleSavingStatusDispatch}
/>
)}
</div>
</section>
</article>
</Layout.Element>
<Layout.Element>
<TextbookSidebar courseId={courseId} />
<TextbookSidebar />
</Layout.Element>
</Layout>
</section>
</Container>
<div className="alert-toast">
<SavingErrorAlert
isQueryFailed={savingStatus === RequestStatus.FAILED}
errorMessage={errorMessage}
isQueryFailed={anyMutationFailed}
errorMessage={mutationErrorMessage}
/>
</div>
</>
Expand Down
27 changes: 6 additions & 21 deletions src/textbooks/data/api.test.js → src/textbooks/data/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform';
import { initializeMocks } from '@src/testUtils';

import { textbooksMock } from 'CourseAuthoring/textbooks/__mocks__';
import {
Expand All @@ -18,25 +16,14 @@ const courseId = 'course-v1:org+101+101';

describe('getTextbooks', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
const mocks = initializeMocks();

axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock = mocks.axiosMock;
axiosMock
.onGet(getTextbooksApiUrl(courseId))
.reply(200, textbooksMock);
});

afterEach(() => {
axiosMock.reset();
});

it('should fetch textbooks for a course', async () => {
const textbooksData = [{ id: 1, title: 'Textbook 1' }, { id: 2, title: 'Textbook 2' }];
axiosMock.onGet(getTextbooksApiUrl(courseId)).reply(200, textbooksData);
Expand All @@ -49,7 +36,7 @@ describe('getTextbooks', () => {

describe('createTextbook', () => {
it('should create a new textbook for a course', async () => {
const textbookData = { title: 'New Textbook', chapters: [] };
const textbookData = { tabTitle: 'New Textbook', chapters: [] };
axiosMock.onPost(getUpdateTextbooksApiUrl(courseId)).reply(200, textbookData);

const result = await createTextbook(courseId, textbookData);
Expand All @@ -61,7 +48,7 @@ describe('createTextbook', () => {
describe('editTextbook', () => {
it('should edit an existing textbook for a course', async () => {
const textbookId = '1';
const editedTextbookData = { id: '1', title: 'Edited Textbook', chapters: [] };
const editedTextbookData = { id: '1', tabTitle: 'Edited Textbook', chapters: [] };
axiosMock.onPut(getEditTextbooksApiUrl(courseId, textbookId)).reply(200, editedTextbookData);

const result = await editTextbook(courseId, editedTextbookData);
Expand All @@ -75,8 +62,6 @@ describe('deleteTextbook', () => {
const textbookId = '1';
axiosMock.onDelete(getEditTextbooksApiUrl(courseId, textbookId)).reply(200, {});

const result = await deleteTextbook(courseId, textbookId);

expect(result).toEqual({});
await expect(deleteTextbook(courseId, textbookId)).resolves.toBeUndefined();
});
});
Loading