chore: [FC-0070] Some tests refactoring (#1518)
This commit is contained in:
@@ -1,19 +1,12 @@
|
||||
import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { getConfig, initializeMockApp } from '@edx/frontend-platform';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import initializeStore from './store';
|
||||
import CourseAuthoringPage from './CourseAuthoringPage';
|
||||
import PagesAndResources from './pages-and-resources/PagesAndResources';
|
||||
import { executeThunk } from './utils';
|
||||
import { fetchCourseApps } from './pages-and-resources/data/thunks';
|
||||
import { fetchCourseDetail, fetchWaffleFlags } from './data/thunks';
|
||||
import { getApiWaffleFlagsUrl } from './data/api';
|
||||
import { initializeMocks, render } from './testUtils';
|
||||
|
||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||
let mockPathname = '/evilguy/';
|
||||
@@ -27,16 +20,9 @@ let axiosMock;
|
||||
let store;
|
||||
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
const mocks = initializeMocks();
|
||||
store = mocks.reduxStore;
|
||||
axiosMock = mocks.axiosMock;
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl(courseId))
|
||||
.reply(200, {});
|
||||
@@ -56,13 +42,9 @@ describe('Editor Pages Load no header', () => {
|
||||
mockPathname = '/editor/';
|
||||
await mockStoreSuccess();
|
||||
const wrapper = render(
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en">
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<PagesAndResources courseId={courseId} />
|
||||
</CourseAuthoringPage>
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<PagesAndResources courseId={courseId} />
|
||||
</CourseAuthoringPage>
|
||||
,
|
||||
);
|
||||
expect(wrapper.queryByRole('status')).not.toBeInTheDocument();
|
||||
@@ -71,13 +53,9 @@ describe('Editor Pages Load no header', () => {
|
||||
mockPathname = '/evilguy/';
|
||||
await mockStoreSuccess();
|
||||
const wrapper = render(
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en">
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<PagesAndResources courseId={courseId} />
|
||||
</CourseAuthoringPage>
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<PagesAndResources courseId={courseId} />
|
||||
</CourseAuthoringPage>
|
||||
,
|
||||
);
|
||||
expect(wrapper.queryByRole('status')).toBeInTheDocument();
|
||||
@@ -105,14 +83,7 @@ describe('Course authoring page', () => {
|
||||
};
|
||||
test('renders not found page on non-existent course key', async () => {
|
||||
await mockStoreNotFound();
|
||||
const wrapper = render(
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en">
|
||||
<CourseAuthoringPage courseId={courseId} />
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
,
|
||||
);
|
||||
const wrapper = render(<CourseAuthoringPage courseId={courseId} />);
|
||||
expect(await wrapper.findByTestId('notFoundAlert')).toBeInTheDocument();
|
||||
});
|
||||
test('does not render not found page on other kinds of error', async () => {
|
||||
@@ -123,13 +94,9 @@ describe('Course authoring page', () => {
|
||||
// found alert is not present.
|
||||
const contentTestId = 'courseAuthoringPageContent';
|
||||
const wrapper = render(
|
||||
<AppProvider store={store}>
|
||||
<IntlProvider locale="en">
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<div data-testid={contentTestId} />
|
||||
</CourseAuthoringPage>
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<div data-testid={contentTestId} />
|
||||
</CourseAuthoringPage>
|
||||
,
|
||||
);
|
||||
expect(await wrapper.findByTestId(contentTestId)).toBeInTheDocument();
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import React from 'react';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import CourseAuthoringRoutes from './CourseAuthoringRoutes';
|
||||
import initializeStore from './store';
|
||||
import { executeThunk } from './utils';
|
||||
import { getApiWaffleFlagsUrl } from './data/api';
|
||||
import { fetchWaffleFlags } from './data/thunks';
|
||||
import {
|
||||
screen, initializeMocks, render, waitFor,
|
||||
} from './testUtils';
|
||||
|
||||
const courseId = 'course-v1:edX+TestX+Test_Course';
|
||||
const pagesAndResourcesMockText = 'Pages And Resources';
|
||||
@@ -17,7 +12,6 @@ const editorContainerMockText = 'Editor Container';
|
||||
const videoSelectorContainerMockText = 'Video Selector Container';
|
||||
const customPagesMockText = 'Custom Pages';
|
||||
let store;
|
||||
let axiosMock;
|
||||
const mockComponentFn = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
@@ -57,72 +51,58 @@ jest.mock('./custom-pages/CustomPages', () => (props) => {
|
||||
|
||||
describe('<CourseAuthoringRoutes>', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
const { axiosMock, reduxStore } = initializeMocks();
|
||||
store = reduxStore;
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl(courseId))
|
||||
.reply(200, {});
|
||||
await executeThunk(fetchWaffleFlags(courseId), store.dispatch);
|
||||
});
|
||||
|
||||
fit('renders the PagesAndResources component when the pages and resources route is active', () => {
|
||||
it('renders the PagesAndResources component when the pages and resources route is active', async () => {
|
||||
render(
|
||||
<AppProvider store={store} wrapWithRouter={false}>
|
||||
<MemoryRouter initialEntries={['/pages-and-resources']}>
|
||||
<CourseAuthoringRoutes />
|
||||
</MemoryRouter>
|
||||
</AppProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(pagesAndResourcesMockText)).toBeVisible();
|
||||
expect(mockComponentFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
courseId,
|
||||
}),
|
||||
<CourseAuthoringRoutes />,
|
||||
{ routerProps: { initialEntries: ['/pages-and-resources'] } },
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(pagesAndResourcesMockText)).toBeVisible();
|
||||
expect(mockComponentFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
courseId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the EditorContainer component when the course editor route is active', () => {
|
||||
it('renders the EditorContainer component when the course editor route is active', async () => {
|
||||
render(
|
||||
<AppProvider store={store} wrapWithRouter={false}>
|
||||
<MemoryRouter initialEntries={['/editor/video/block-id']}>
|
||||
<CourseAuthoringRoutes />
|
||||
</MemoryRouter>
|
||||
</AppProvider>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText(editorContainerMockText)).toBeInTheDocument();
|
||||
expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument();
|
||||
expect(mockComponentFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
courseId,
|
||||
}),
|
||||
<CourseAuthoringRoutes />,
|
||||
{ routerProps: { initialEntries: ['/editor/video/block-id'] } },
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(editorContainerMockText)).toBeInTheDocument();
|
||||
expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument();
|
||||
expect(mockComponentFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
learningContextId: courseId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the VideoSelectorContainer component when the course videos route is active', () => {
|
||||
it('renders the VideoSelectorContainer component when the course videos route is active', async () => {
|
||||
render(
|
||||
<AppProvider store={store} wrapWithRouter={false}>
|
||||
<MemoryRouter initialEntries={['/editor/course-videos/block-id']}>
|
||||
<CourseAuthoringRoutes />
|
||||
</MemoryRouter>
|
||||
</AppProvider>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText(videoSelectorContainerMockText)).toBeInTheDocument();
|
||||
expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument();
|
||||
expect(mockComponentFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
courseId,
|
||||
}),
|
||||
<CourseAuthoringRoutes />,
|
||||
{ routerProps: { initialEntries: ['/editor/course-videos/block-id'] } },
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(videoSelectorContainerMockText)).toBeInTheDocument();
|
||||
expect(screen.queryByText(pagesAndResourcesMockText)).not.toBeInTheDocument();
|
||||
expect(mockComponentFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
courseId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { within, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { camelCaseObject } from '@edx/frontend-platform';
|
||||
|
||||
import { initializeMocks, render } from '../../testUtils';
|
||||
import {
|
||||
initializeMocks, render, screen, within,
|
||||
} from '../../testUtils';
|
||||
import { getApiWaffleFlagsUrl } from '../../data/api';
|
||||
import { fetchWaffleFlags } from '../../data/thunks';
|
||||
import { generateCourseLaunchData } from '../factories/mockApiResponses';
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import {
|
||||
render,
|
||||
act,
|
||||
fireEvent,
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import initializeStore from '../store';
|
||||
import {
|
||||
initializeMocks,
|
||||
fireEvent,
|
||||
screen,
|
||||
act,
|
||||
render,
|
||||
} from '../testUtils';
|
||||
import { executeThunk } from '../utils';
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import { getApiWaffleFlagsUrl } from '../data/api';
|
||||
@@ -23,7 +17,6 @@ import {
|
||||
generateNewPageApiResponse,
|
||||
getStatusValue,
|
||||
courseId,
|
||||
initialState,
|
||||
} from './factories/mockApiResponses';
|
||||
|
||||
import {
|
||||
@@ -39,13 +32,7 @@ let store;
|
||||
ReactDOM.createPortal = jest.fn(node => node);
|
||||
|
||||
const renderComponent = () => {
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<CustomPages courseId={courseId} />
|
||||
</AppProvider>
|
||||
</IntlProvider>,
|
||||
);
|
||||
render(<CustomPages courseId={courseId} />);
|
||||
};
|
||||
|
||||
const mockStore = async (status) => {
|
||||
@@ -64,16 +51,9 @@ const mockStore = async (status) => {
|
||||
|
||||
describe('CustomPages', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore(initialState);
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
const mocks = initializeMocks();
|
||||
store = mocks.reduxStore;
|
||||
axiosMock = mocks.axiosMock;
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl(courseId))
|
||||
.reply(200, {
|
||||
|
||||
55
src/grading-settings/grading-scale/react-ranger.test.js
Normal file
55
src/grading-settings/grading-scale/react-ranger.test.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
|
||||
import { useRanger } from './react-ranger';
|
||||
|
||||
describe('useRanger Hook', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
const mockOnDrag = jest.fn();
|
||||
|
||||
const defaultProps = {
|
||||
values: [20, 80],
|
||||
min: 0,
|
||||
max: 100,
|
||||
stepSize: 10,
|
||||
onChange: mockOnChange,
|
||||
onDrag: mockOnDrag,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('initializes with correct default properties', () => {
|
||||
const { result } = renderHook(() => useRanger(defaultProps));
|
||||
|
||||
expect(result.current.ticks).toBeDefined();
|
||||
expect(result.current.segments).toBeDefined();
|
||||
expect(result.current.handles).toHaveLength(2); // Two handles for two values
|
||||
expect(result.current.activeHandleIndex).toBeNull();
|
||||
});
|
||||
|
||||
it('calculates ticks based on min, max, and stepSize', () => {
|
||||
const { result } = renderHook(() => useRanger(defaultProps));
|
||||
|
||||
const tickValues = result.current.ticks.map((tick) => tick.value);
|
||||
expect(tickValues).toEqual([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]);
|
||||
});
|
||||
|
||||
it('resets active handle index after interaction', () => {
|
||||
const { result } = renderHook(() => useRanger(defaultProps));
|
||||
|
||||
act(() => {
|
||||
result.current.handles[0].getHandleProps().onMouseDown({ persist: jest.fn() }, 0);
|
||||
document.dispatchEvent(new MouseEvent('mouseup'));
|
||||
});
|
||||
|
||||
expect(result.current.activeHandleIndex).toBeNull();
|
||||
});
|
||||
|
||||
it('computes segments based on values', () => {
|
||||
const { result } = renderHook(() => useRanger(defaultProps));
|
||||
|
||||
const segmentValues = result.current.segments.map((segment) => segment.value);
|
||||
expect(segmentValues).toEqual([20, 80, 100]);
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,6 @@ import { getStudioHomeApiUrl } from '../studio-home/data/api';
|
||||
import { mockBroadcastChannel } from '../generic/data/api.mock';
|
||||
import { LibraryLayout } from '.';
|
||||
import { getLibraryCollectionsApiUrl } from './data/api';
|
||||
import { getApiWaffleFlagsUrl } from '../data/api';
|
||||
|
||||
mockGetCollectionMetadata.applyMock();
|
||||
mockContentSearchConfig.applyMock();
|
||||
@@ -56,7 +55,6 @@ describe('<LibraryAuthoringPage />', () => {
|
||||
beforeEach(async () => {
|
||||
const { axiosMock } = initializeMocks();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {});
|
||||
|
||||
// The Meilisearch client-side API uses fetch, not Axios.
|
||||
fetchMock.mockReset();
|
||||
@@ -681,7 +679,6 @@ describe('<LibraryAuthoringPage />', () => {
|
||||
|
||||
it('Shows an error if libraries V2 is disabled', async () => {
|
||||
const { axiosMock } = initializeMocks();
|
||||
axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {});
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, {
|
||||
...studioHomeMock,
|
||||
libraries_v2_enabled: false,
|
||||
|
||||
@@ -21,7 +21,6 @@ import { mockBroadcastChannel, mockClipboardEmpty } from '../../generic/data/api
|
||||
import { mockContentSearchConfig, mockSearchResult } from '../../search-manager/data/api.mock';
|
||||
import { studioHomeMock } from '../../studio-home/__mocks__';
|
||||
import { getStudioHomeApiUrl } from '../../studio-home/data/api';
|
||||
import { getApiWaffleFlagsUrl } from '../../data/api';
|
||||
import LibraryLayout from '../LibraryLayout';
|
||||
|
||||
mockContentSearchConfig.applyMock();
|
||||
@@ -52,7 +51,6 @@ describe('AddContentWorkflow test', () => {
|
||||
beforeEach(async () => {
|
||||
const { axiosMock } = initializeMocks();
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {});
|
||||
});
|
||||
|
||||
it('can create an HTML component', async () => {
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
mockGetCollectionMetadata,
|
||||
} from '../data/api.mocks';
|
||||
import { PickLibraryContentModal } from './PickLibraryContentModal';
|
||||
import { getApiWaffleFlagsUrl } from '../../data/api';
|
||||
|
||||
mockContentSearchConfig.applyMock();
|
||||
mockContentLibrary.applyMock();
|
||||
@@ -48,7 +47,6 @@ describe('<PickLibraryContentModal />', () => {
|
||||
const mocks = initializeMocks();
|
||||
mockShowToast = mocks.mockShowToast;
|
||||
mocks.axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
mocks.axiosMock.onGet(getApiWaffleFlagsUrl()).reply(200, {});
|
||||
});
|
||||
|
||||
it('can pick components from the modal', async () => {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import {
|
||||
initializeMocks,
|
||||
screen,
|
||||
render,
|
||||
queryAllByRole,
|
||||
} from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { initializeMockApp, getConfig } from '@edx/frontend-platform';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import initializeStore from '../../store';
|
||||
waitFor,
|
||||
} from '../../testUtils';
|
||||
import PageGrid from './PageGrid';
|
||||
import { executeThunk } from '../../utils';
|
||||
import { getApiWaffleFlagsUrl } from '../../data/api';
|
||||
@@ -42,34 +40,18 @@ const mockPageConfig = [
|
||||
|
||||
const renderComponent = () => {
|
||||
const wrapper = render(
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<PagesAndResourcesProvider courseId={courseId}>
|
||||
<PageGrid pages={mockPageConfig} />
|
||||
</PagesAndResourcesProvider>
|
||||
</AppProvider>
|
||||
</IntlProvider>,
|
||||
<PagesAndResourcesProvider courseId={courseId}>
|
||||
<PageGrid pages={mockPageConfig} />
|
||||
</PagesAndResourcesProvider>,
|
||||
);
|
||||
container = wrapper.container;
|
||||
};
|
||||
|
||||
describe('LiveSettings', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: false,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore({
|
||||
courseDetail: {
|
||||
courseId: 'id',
|
||||
status: 'sucessful',
|
||||
},
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
const mocks = initializeMocks();
|
||||
store = mocks.reduxStore;
|
||||
axiosMock = mocks.axiosMock;
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl(courseId))
|
||||
.reply(200, {
|
||||
@@ -83,13 +65,17 @@ describe('LiveSettings', () => {
|
||||
|
||||
it('should render three cards', async () => {
|
||||
renderComponent();
|
||||
expect(queryAllByRole(container, 'button')).toHaveLength(3);
|
||||
waitFor(() => {
|
||||
expect(screen.queryAllByRole(container, 'button')).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to legacyLink', async () => {
|
||||
renderComponent();
|
||||
const textbookPagePath = mockPageConfig[0][1];
|
||||
const textbookSettingsButton = queryAllByRole(container, 'link')[1];
|
||||
expect(textbookSettingsButton).toHaveAttribute('href', textbookPagePath);
|
||||
waitFor(() => {
|
||||
const textbookSettingsButton = screen.queryAllByRole(container, 'link')[1];
|
||||
expect(textbookSettingsButton).toHaveAttribute('href', textbookPagePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +1,22 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import {
|
||||
act, fireEvent, render, waitFor,
|
||||
} from '@testing-library/react';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
import initializeStore from '../store';
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import { COURSE_CREATOR_STATES } from '../constants';
|
||||
import { executeThunk } from '../utils';
|
||||
import { studioHomeMock } from './__mocks__';
|
||||
import { getStudioHomeApiUrl } from './data/api';
|
||||
import { fetchStudioHomeData } from './data/thunks';
|
||||
import { getApiWaffleFlagsUrl } from '../data/api';
|
||||
import { fetchWaffleFlags } from '../data/thunks';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
waitFor,
|
||||
initializeMocks,
|
||||
} from '../testUtils';
|
||||
import messages from './messages';
|
||||
import createNewCourseMessages from './create-new-course-form/messages';
|
||||
import createOrRerunCourseMessages from '../generic/create-or-rerun-course/messages';
|
||||
import { StudioHome } from '.';
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
const {
|
||||
studioShortName,
|
||||
studioRequestEmail,
|
||||
@@ -44,90 +34,60 @@ jest.mock('react-router-dom', () => ({
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const RootWrapper = () => (
|
||||
<AppProvider store={store} wrapWithRouter={false}>
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter initialEntries={['/home']}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/home"
|
||||
element={<StudioHome intl={injectIntl} />}
|
||||
/>
|
||||
<Route
|
||||
path="/libraries"
|
||||
element={<StudioHome intl={injectIntl} />}
|
||||
/>
|
||||
<Route
|
||||
path="/libraries-v1"
|
||||
element={<StudioHome intl={injectIntl} />}
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
</IntlProvider>
|
||||
</AppProvider>
|
||||
<MemoryRouter initialEntries={['/home']}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/home"
|
||||
element={<StudioHome />}
|
||||
/>
|
||||
<Route
|
||||
path="/libraries"
|
||||
element={<StudioHome />}
|
||||
/>
|
||||
<Route
|
||||
path="/libraries-v1"
|
||||
element={<StudioHome />}
|
||||
/>
|
||||
</Routes>,
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
describe('<StudioHome />', () => {
|
||||
describe('api fetch fails', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(404);
|
||||
await executeThunk(fetchStudioHomeData(), store.dispatch);
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl())
|
||||
.reply(200, {});
|
||||
await executeThunk(fetchWaffleFlags(), store.dispatch);
|
||||
const mocks = initializeMocks();
|
||||
mocks.axiosMock.onGet(getStudioHomeApiUrl()).reply(404);
|
||||
useSelector.mockReturnValue({ studioHomeLoadingStatus: RequestStatus.FAILED });
|
||||
});
|
||||
|
||||
it('should render fetch error', () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText(messages.homePageLoadFailedMessage.defaultMessage)).toBeInTheDocument();
|
||||
waitFor(() => {
|
||||
expect(getByText(messages.homePageLoadFailedMessage.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render Studio home title', () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText('Studio home')).toBeInTheDocument();
|
||||
waitFor(() => {
|
||||
expect(getByText('Studio home')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('api fetch succeeds', () => {
|
||||
beforeEach(async () => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
store = initializeStore();
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
await executeThunk(fetchStudioHomeData(), store.dispatch);
|
||||
const mocks = initializeMocks();
|
||||
mocks.axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock);
|
||||
useSelector.mockReturnValue(studioHomeMock);
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl())
|
||||
.reply(200, {});
|
||||
await executeThunk(fetchWaffleFlags(), store.dispatch);
|
||||
});
|
||||
|
||||
it('should render page and page title correctly', () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText(`${studioShortName} home`)).toBeInTheDocument();
|
||||
waitFor(() => {
|
||||
expect(getByText(`${studioShortName} home`)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render email staff header button', async () => {
|
||||
@@ -137,8 +97,10 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
|
||||
const { getByRole } = render(<RootWrapper />);
|
||||
expect(getByRole('link', { name: messages.emailStaffBtnText.defaultMessage }))
|
||||
.toHaveAttribute('href', `mailto:${studioRequestEmail}`);
|
||||
waitFor(() => {
|
||||
expect(getByRole('link', { name: messages.emailStaffBtnText.defaultMessage }))
|
||||
.toHaveAttribute('href', `mailto:${studioRequestEmail}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render create new course button', async () => {
|
||||
@@ -148,7 +110,9 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
|
||||
const { getByRole } = render(<RootWrapper />);
|
||||
expect(getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage })).toBeInTheDocument();
|
||||
waitFor(() => {
|
||||
expect(getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show verify email layout if user inactive', () => {
|
||||
@@ -158,7 +122,9 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText('Thanks for signing up, abc123!', { exact: false })).toBeInTheDocument();
|
||||
waitFor(() => {
|
||||
expect(getByText('Thanks for signing up, abc123!', { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the spinner before the query is complete', async () => {
|
||||
@@ -169,8 +135,10 @@ describe('<StudioHome />', () => {
|
||||
|
||||
await act(async () => {
|
||||
const { getByRole } = render(<RootWrapper />);
|
||||
const spinner = getByRole('status');
|
||||
expect(spinner.textContent).toEqual('Loading...');
|
||||
waitFor(() => {
|
||||
const spinner = getByRole('status');
|
||||
expect(spinner.textContent).toEqual('Loading...');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -184,13 +152,15 @@ describe('<StudioHome />', () => {
|
||||
const studioBaseUrl = 'http://localhost:18010';
|
||||
|
||||
const { getByTestId } = render(<RootWrapper />);
|
||||
const createNewLibraryButton = getByTestId('new-library-button');
|
||||
waitFor(() => {
|
||||
const createNewLibraryButton = getByTestId('new-library-button');
|
||||
|
||||
const { open } = window;
|
||||
window.open = jest.fn();
|
||||
fireEvent.click(createNewLibraryButton);
|
||||
expect(window.open).toHaveBeenCalledWith(`${studioBaseUrl}/home_library`);
|
||||
window.open = open;
|
||||
const { open } = window;
|
||||
window.open = jest.fn();
|
||||
fireEvent.click(createNewLibraryButton);
|
||||
expect(window.open).toHaveBeenCalledWith(`${studioBaseUrl}/home_library`);
|
||||
window.open = open;
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to the library authoring page in course authoring', () => {
|
||||
@@ -199,11 +169,11 @@ describe('<StudioHome />', () => {
|
||||
librariesV1Enabled: false,
|
||||
});
|
||||
const { getByTestId } = render(<RootWrapper />);
|
||||
const createNewLibraryButton = getByTestId('new-library-button');
|
||||
|
||||
fireEvent.click(createNewLibraryButton);
|
||||
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/library/create');
|
||||
waitFor(() => {
|
||||
const createNewLibraryButton = getByTestId('new-library-button');
|
||||
fireEvent.click(createNewLibraryButton);
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/library/create');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,7 +194,9 @@ describe('<StudioHome />', () => {
|
||||
librariesV1Enabled: false,
|
||||
});
|
||||
const { queryByTestId } = render(<RootWrapper />);
|
||||
expect(queryByTestId('new-library-button')).toBeInTheDocument();
|
||||
waitFor(() => {
|
||||
expect(queryByTestId('new-library-button')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render create new course container', async () => {
|
||||
@@ -234,10 +206,12 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
|
||||
const { getByRole, getByText } = render(<RootWrapper />);
|
||||
const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage });
|
||||
waitFor(() => {
|
||||
const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage });
|
||||
|
||||
await act(() => fireEvent.click(createNewCourseButton));
|
||||
expect(getByText(createNewCourseMessages.createNewCourse.defaultMessage)).toBeInTheDocument();
|
||||
act(() => fireEvent.click(createNewCourseButton));
|
||||
expect(getByText(createNewCourseMessages.createNewCourse.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide create new course container', async () => {
|
||||
@@ -247,16 +221,16 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
|
||||
const { getByRole, queryByText, getByText } = render(<RootWrapper />);
|
||||
const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage });
|
||||
|
||||
fireEvent.click(createNewCourseButton);
|
||||
waitFor(() => {
|
||||
const createNewCourseButton = getByRole('button', { name: messages.addNewCourseBtnText.defaultMessage });
|
||||
fireEvent.click(createNewCourseButton);
|
||||
expect(getByText(createNewCourseMessages.createNewCourse.defaultMessage)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const cancelButton = getByRole('button', { name: createOrRerunCourseMessages.cancelButton.defaultMessage });
|
||||
fireEvent.click(cancelButton);
|
||||
waitFor(() => {
|
||||
const cancelButton = getByRole('button', { name: createOrRerunCourseMessages.cancelButton.defaultMessage });
|
||||
fireEvent.click(cancelButton);
|
||||
expect(queryByText(createNewCourseMessages.createNewCourse.defaultMessage)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -270,13 +244,15 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
const { getByText, queryByText } = render(<RootWrapper />);
|
||||
const defaultTitleMessage = messages.defaultSection_1_Title.defaultMessage;
|
||||
const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio');
|
||||
const administratorCardTitle = getByText(titleWithStudioName);
|
||||
waitFor(() => {
|
||||
const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio');
|
||||
const administratorCardTitle = getByText(titleWithStudioName);
|
||||
|
||||
expect(administratorCardTitle).toBeVisible();
|
||||
expect(administratorCardTitle).toBeVisible();
|
||||
|
||||
const addCourseButton = queryByText(messages.btnAddNewCourseText.defaultMessage);
|
||||
expect(addCourseButton).toBeNull();
|
||||
const addCourseButton = queryByText(messages.btnAddNewCourseText.defaultMessage);
|
||||
expect(addCourseButton).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show contact administrator card with add course buttons', () => {
|
||||
@@ -287,23 +263,27 @@ describe('<StudioHome />', () => {
|
||||
});
|
||||
const { getByText, getByTestId } = render(<RootWrapper />);
|
||||
const defaultTitleMessage = messages.defaultSection_1_Title.defaultMessage;
|
||||
const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio');
|
||||
const administratorCardTitle = getByText(titleWithStudioName);
|
||||
waitFor(() => {
|
||||
const titleWithStudioName = defaultTitleMessage.replace('{studioShortName}', 'Studio');
|
||||
const administratorCardTitle = getByText(titleWithStudioName);
|
||||
|
||||
expect(administratorCardTitle).toBeVisible();
|
||||
expect(administratorCardTitle).toBeVisible();
|
||||
|
||||
const addCourseButton = getByTestId('contact-admin-create-course');
|
||||
expect(addCourseButton).toBeVisible();
|
||||
const addCourseButton = getByTestId('contact-admin-create-course');
|
||||
expect(addCourseButton).toBeVisible();
|
||||
|
||||
fireEvent.click(addCourseButton);
|
||||
expect(getByTestId('create-course-form')).toBeVisible();
|
||||
fireEvent.click(addCourseButton);
|
||||
expect(getByTestId('create-course-form')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show footer', () => {
|
||||
const { getByText } = render(<RootWrapper />);
|
||||
expect(getByText('Looking for help with Studio?')).toBeInTheDocument();
|
||||
expect(getByText('LMS')).toHaveAttribute('href', process.env.LMS_BASE_URL);
|
||||
waitFor(() => {
|
||||
expect(getByText('Looking for help with Studio?')).toBeInTheDocument();
|
||||
expect(getByText('LMS')).toHaveAttribute('href', process.env.LMS_BASE_URL);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,6 +98,7 @@ const CollapsibleStateWithAction = ({ state, className }) => {
|
||||
<Collapsible.Advanced
|
||||
className={classNames('collapsible-card rounded-sm', className)}
|
||||
defaultOpen={[COURSE_CREATOR_STATES.denied, COURSE_CREATOR_STATES.pending].includes(state)}
|
||||
data-testid="collapsible-state-with-action"
|
||||
>
|
||||
<Collapsible.Trigger className="collapsible-trigger d-flex py-2.5 px-3.5 bg-gray-700">
|
||||
<span className="flex-grow-1 text-white small">{title}</span>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
|
||||
import { COURSE_CREATOR_STATES } from '../../../constants';
|
||||
import initializeStore from '../../../store';
|
||||
import { studioHomeMock } from '../../__mocks__';
|
||||
import { initialState } from '../../factories/mockApiResponses';
|
||||
@@ -127,4 +128,17 @@ describe('<CoursesTab />', () => {
|
||||
const alertCoursesNotFound = screen.queryByTestId('processing-courses-title');
|
||||
expect(alertCoursesNotFound).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render CollapsibleStateWithAction when courseCreatorStatus is true', () => {
|
||||
const props = { isShowProcessing: true, isEnabledPagination: false };
|
||||
const customStoreData = {
|
||||
studioHomeData: {
|
||||
inProcessCourseActions: [],
|
||||
courseCreatorStatus: COURSE_CREATOR_STATES.denied,
|
||||
},
|
||||
};
|
||||
renderComponent(props, customStoreData);
|
||||
const collapsibleStateWithAction = screen.queryByTestId('collapsible-state-with-action');
|
||||
expect(collapsibleStateWithAction).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
|
||||
import { ToastContext, type ToastContextData } from './generic/toast-context';
|
||||
import initializeReduxStore from './store';
|
||||
import { getApiWaffleFlagsUrl } from './data/api';
|
||||
|
||||
/** @deprecated Use React Query and/or regular React Context instead of redux */
|
||||
let reduxStore: Store;
|
||||
@@ -172,6 +173,10 @@ export function initializeMocks({ user = defaultUser, initialState = undefined }
|
||||
});
|
||||
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
|
||||
axiosMock
|
||||
.onGet(getApiWaffleFlagsUrl())
|
||||
.reply(200, {});
|
||||
|
||||
// Reset `mockToastContext` for this current test
|
||||
mockToastContext = {
|
||||
showToast: jest.fn(),
|
||||
|
||||
Reference in New Issue
Block a user