chore: [FC-0070] Some tests refactoring (#1518)

This commit is contained in:
Peter Kulko
2024-11-21 20:24:31 +02:00
committed by GitHub
parent b5419acd74
commit bc8d59b0eb
13 changed files with 258 additions and 297 deletions

View File

@@ -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();

View File

@@ -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,
}),
);
});
});
});

View File

@@ -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';

View File

@@ -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, {

View 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]);
});
});

View File

@@ -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,

View File

@@ -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 () => {

View File

@@ -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 () => {

View File

@@ -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);
});
});
});

View File

@@ -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);
});
});
});
});

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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(),