test: fix test code improperly using/missing 'await' (#2560)

This commit is contained in:
Braden MacDonald
2025-10-24 14:31:51 -07:00
committed by GitHub
parent c54c21e2b4
commit 157e2464aa
49 changed files with 286 additions and 297 deletions

View File

@@ -125,10 +125,13 @@ describe('ORASettings', () => {
});
it('Displays title, helper text and badge when flexible peer grading button is enabled', async () => {
renderComponent();
await mockStore({ apiStatus: 200, enabled: true });
renderComponent();
waitFor(() => {
const checkbox = await screen.getByRole('checkbox', { name: /Flex Peer Grading/ });
expect(checkbox).toBeChecked();
await waitFor(() => {
const label = screen.getByText(messages.enableFlexPeerGradeLabel.defaultMessage);
const enableBadge = screen.getByTestId('enable-badge');

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
@@ -22,7 +21,6 @@ jest.mock('react-textarea-autosize', () => jest.fn((props) => (
<textarea
{...props}
onFocus={() => {}}
onBlur={() => {}}
/>
)));
@@ -86,10 +84,10 @@ describe('<SettingCard />', () => {
await waitFor(() => {
expect(inputBox).toHaveValue('3, 2, 1');
});
await (async () => {
await user.tab(); // blur off of the input.
await waitFor(() => {
expect(setEdited).toHaveBeenCalled();
expect(handleBlur).toHaveBeenCalled();
});
fireEvent.focusOut(inputBox);
});
});

View File

@@ -30,7 +30,7 @@ const SettingCard = ({
const { deprecated, help, displayName } = settingData;
const initialValue = JSON.stringify(settingData.value, null, 4);
const [isOpen, open, close] = useToggle(false);
const [target, setTarget] = useState(null);
const [target, setTarget] = useState<HTMLButtonElement | null>(null);
const [newValue, setNewValue] = useState(initialValue);
const handleSettingChange = (e) => {
@@ -118,7 +118,7 @@ SettingCard.propTypes = {
deprecated: PropTypes.bool,
help: PropTypes.string,
displayName: PropTypes.string,
value: PropTypes.PropTypes.oneOfType([
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.number,

View File

@@ -65,7 +65,7 @@ describe('CertificateDetails', () => {
await user.type(input, newInputValue);
waitFor(() => {
await waitFor(() => {
expect(input.value).toBe(newInputValue);
});
});

View File

@@ -96,7 +96,7 @@ describe('CertificateSignatories', () => {
expect(useCreateSignatory().handleAddSignatory).toHaveBeenCalled();
});
it('calls remove for the correct signatory when delete icon is clicked', async () => {
it.skip('calls remove for the correct signatory when delete icon is clicked', async () => {
const user = userEvent.setup();
const { getAllByRole } = renderComponent(defaultProps);
@@ -105,7 +105,9 @@ describe('CertificateSignatories', () => {
await user.click(deleteIcons[0]);
waitFor(() => {
// FIXME: this isn't called because the whole 'useEditSignatory' hook
// which calls it is mocked out.
await waitFor(() => {
expect(mockArrayHelpers.remove).toHaveBeenCalledWith(0);
});
});

View File

@@ -1,4 +1,4 @@
import { render, waitFor } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
@@ -30,6 +30,7 @@ const initialState = {
};
const defaultProps = {
index: 0,
...signatoriesMock[0],
showDeleteButton: true,
isEdit: true,
@@ -62,15 +63,20 @@ describe('Signatory Component', () => {
it('handles input change', async () => {
const user = userEvent.setup();
const handleChange = jest.fn();
const { getByPlaceholderText } = renderSignatory({ ...defaultProps, handleChange });
const input = getByPlaceholderText(messages.namePlaceholder.defaultMessage);
renderSignatory({ ...defaultProps, handleChange });
const input = screen.getByPlaceholderText(messages.namePlaceholder.defaultMessage);
const newInputValue = 'Jane Doe';
await user.type(input, newInputValue, { name: 'signatories[0].name' });
expect(handleChange).not.toHaveBeenCalled();
expect(input.value).not.toBe(newInputValue);
await user.type(input, newInputValue);
waitFor(() => {
expect(handleChange).toHaveBeenCalledWith(expect.anything());
expect(input.value).toBe(newInputValue);
await waitFor(() => {
// This is not a great test; handleChange() gets called for each key press:
expect(handleChange).toHaveBeenCalledTimes(newInputValue.length);
// And the input value never actually changes because it's a controlled component
// and we pass the name in as a prop, which hasn't changed.
// expect(input.value).toBe(newInputValue);
});
});

View File

@@ -62,7 +62,7 @@ describe('HeaderButtons Component', () => {
const dropdownButton = getByRole('button', { name: certificatesDataMock.courseModes[0] });
await user.click(dropdownButton);
const verifiedMode = await getByRole('button', { name: certificatesDataMock.courseModes[1] });
const verifiedMode = getByRole('button', { name: certificatesDataMock.courseModes[1] });
await user.click(verifiedMode);
await waitFor(() => {

View File

@@ -114,7 +114,7 @@ describe('<CourseLibraries />', () => {
const dismissBtn = await screen.findByRole('button', { name: 'Dismiss' });
await user.click(dismissBtn);
expect(allTab).toHaveAttribute('aria-selected', 'true');
waitFor(() => expect(alert).not.toBeInTheDocument());
await waitFor(() => expect(alert).not.toBeInTheDocument());
// review updates button
const reviewActionBtn = await screen.findByRole('button', { name: 'Review Updates' });
await user.click(reviewActionBtn);

View File

@@ -33,10 +33,10 @@ jest.mock('react-redux', () => ({
expect(newBtn).toBeInTheDocument();
const useBtn = await screen.findByRole('button', { name: `Use ${containerType} from library` });
expect(useBtn).toBeInTheDocument();
userEvent.click(newBtn);
waitFor(() => expect(newClickHandler).toHaveBeenCalled());
userEvent.click(useBtn);
waitFor(() => expect(useFromLibClickHandler).toHaveBeenCalled());
await userEvent.click(newBtn);
await waitFor(() => expect(newClickHandler).toHaveBeenCalled());
await userEvent.click(useBtn);
await waitFor(() => expect(useFromLibClickHandler).toHaveBeenCalled());
});
});
});

View File

@@ -2218,7 +2218,7 @@ describe('<CourseUnit />', () => {
const currentSubSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name;
const helpLinkUrl = 'https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/developing_course/course_components.html#components-that-contain-other-components';
waitFor(() => {
await waitFor(() => {
const unitHeaderTitle = screen.getByTestId('unit-header-title');
expect(screen.getByText(unitDisplayName)).toBeInTheDocument();
expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonEdit.defaultMessage })).toBeInTheDocument();
@@ -2291,19 +2291,17 @@ describe('<CourseUnit />', () => {
});
it('renders and navigates to the new HTML XBlock editor after xblock duplicating', async () => {
render(<RootWrapper />);
const updatedCourseVerticalChildrenMock = JSON.parse(JSON.stringify(courseVerticalChildrenMock));
const targetBlockId = updatedCourseVerticalChildrenMock.children[1].block_id;
updatedCourseVerticalChildrenMock.children = updatedCourseVerticalChildrenMock.children
.map((child) => (child.block_id === targetBlockId
? { ...child, block_type: 'html' }
: child));
// Convert the second child from drag and drop to HTML:
const targetChild = updatedCourseVerticalChildrenMock.children[1];
targetChild.block_type = 'html';
targetChild.name = 'Test HTML Block';
targetChild.block_id = 'block-v1:OpenedX+L153+3T2023+type@html+block@test123original';
axiosMock
.onPost(postXBlockBaseApiUrl({
parent_locator: blockId,
duplicate_source_locator: courseVerticalChildrenMock.children[0].block_id,
duplicate_source_locator: targetChild.block_id,
}))
.replyOnce(200, { locator: '1234567890' });
@@ -2311,21 +2309,20 @@ describe('<CourseUnit />', () => {
.onGet(getCourseVerticalChildrenApiUrl(blockId))
.reply(200, updatedCourseVerticalChildrenMock);
render(<RootWrapper />);
await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch);
await waitFor(() => {
const iframe = screen.getByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage);
expect(iframe).toBeInTheDocument();
simulatePostMessageEvent(messageTypes.currentXBlockId, {
id: targetBlockId,
});
});
waitFor(() => {
simulatePostMessageEvent(messageTypes.duplicateXBlock, {});
simulatePostMessageEvent(messageTypes.newXBlockEditor, {});
expect(mockedUsedNavigate)
.toHaveBeenCalledWith(`/course/${courseId}/editor/html/${targetBlockId}`, { replace: true });
// After duplicating, the editor modal will open:
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
simulatePostMessageEvent(messageTypes.duplicateXBlock, { usageId: targetChild.block_id });
simulatePostMessageEvent(messageTypes.newXBlockEditor, { blockType: 'html', usageId: targetChild.block_id });
await waitFor(() => {
expect(screen.queryByRole('dialog')).toBeInTheDocument();
});
});

View File

@@ -100,7 +100,7 @@ describe('CustomPages', () => {
it('should update page order on drag', async () => {
renderComponent();
await mockStore(RequestStatus.SUCCESSFUL);
const buttons = await screen.queryAllByRole('button');
const buttons = screen.queryAllByRole('button');
const draggableButton = buttons[9];
expect(draggableButton).toBeVisible();
await act(async () => {

View File

@@ -48,15 +48,15 @@ describe('cms api', () => {
});
describe('apiMethods', () => {
describe('fetchBlockId', () => {
it('should call get with url.blocks', () => {
apiMethods.fetchBlockById({ blockId, studioEndpointUrl });
it('should call get with url.blocks', async () => {
await apiMethods.fetchBlockById({ blockId, studioEndpointUrl });
expect(get).toHaveBeenCalledWith(urls.block({ blockId, studioEndpointUrl }));
});
});
describe('fetchByUnitId', () => {
it('should call get with url.blockAncestor', () => {
apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
it('should call get with url.blockAncestor', async () => {
await apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
expect(get).toHaveBeenCalledWith(urls.blockAncestor({ studioEndpointUrl, blockId }), {});
});
@@ -82,7 +82,7 @@ describe('cms api', () => {
// eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow
const utils = await import('./utils');
const getSpy = jest.spyOn(utils, 'get');
apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
await apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
expect(getSpy).toHaveBeenCalledWith(urls.blockAncestor({ studioEndpointUrl, blockId }), {});
});
@@ -94,22 +94,22 @@ describe('cms api', () => {
// eslint-disable-next-line no-shadow, @typescript-eslint/no-shadow
const utils = await import('./utils');
const getSpy = jest.spyOn(utils, 'get');
apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
await apiMethods.fetchByUnitId({ blockId, studioEndpointUrl });
expect(getSpy).toHaveBeenCalledWith(urls.blockAncestor({ studioEndpointUrl, blockId }), {});
});
});
});
describe('fetchStudioView', () => {
it('should call get with url.blockStudioView', () => {
apiMethods.fetchStudioView({ blockId, studioEndpointUrl });
it('should call get with url.blockStudioView', async () => {
await apiMethods.fetchStudioView({ blockId, studioEndpointUrl });
expect(get).toHaveBeenCalledWith(urls.blockStudioView({ studioEndpointUrl, blockId }));
});
});
describe('fetchCourseImages', () => {
it('should call get with url.courseAssets', () => {
apiMethods.fetchCourseImages({
it('should call get with url.courseAssets', async () => {
await apiMethods.fetchCourseImages({
learningContextId, studioEndpointUrl, pageNumber: 0,
});
const params = {
@@ -123,8 +123,8 @@ describe('cms api', () => {
});
});
describe('fetchLibraryImages', () => {
it('should call get with urls.libraryAssets for library V2', () => {
apiMethods.fetchLibraryImages({
it('should call get with urls.libraryAssets for library V2', async () => {
await apiMethods.fetchLibraryImages({
blockId,
});
expect(get).toHaveBeenCalledWith(
@@ -134,30 +134,30 @@ describe('cms api', () => {
});
describe('fetchCourseDetails', () => {
it('should call get with url.courseDetailsUrl', () => {
apiMethods.fetchCourseDetails({ learningContextId, studioEndpointUrl });
it('should call get with url.courseDetailsUrl', async () => {
await apiMethods.fetchCourseDetails({ learningContextId, studioEndpointUrl });
expect(get).toHaveBeenCalledWith(urls.courseDetailsUrl({ studioEndpointUrl, learningContextId }));
});
});
describe('fetchVideos', () => {
it('should call get with url.courseVideos', () => {
apiMethods.fetchVideos({ learningContextId, studioEndpointUrl });
it('should call get with url.courseVideos', async () => {
await apiMethods.fetchVideos({ learningContextId, studioEndpointUrl });
expect(get).toHaveBeenCalledWith(urls.courseVideos({ studioEndpointUrl, learningContextId }));
});
});
describe('fetchAdvancedSettings', () => {
it('should call get with url.courseAdvanceSettings', () => {
apiMethods.fetchAdvancedSettings({ learningContextId, studioEndpointUrl });
it('should call get with url.courseAdvanceSettings', async () => {
await apiMethods.fetchAdvancedSettings({ learningContextId, studioEndpointUrl });
expect(get).toHaveBeenCalledWith(urls.courseAdvanceSettings({ studioEndpointUrl, learningContextId }));
});
});
describe('getHandlerUrl', () => {
it('should call get with url.handlerUrl', () => {
it('should call get with url.handlerUrl', async () => {
const handlerName = 'transcript';
apiMethods.getHandlerUrl({ studioEndpointUrl, blockId, handlerName });
await apiMethods.getHandlerUrl({ studioEndpointUrl, blockId, handlerName });
expect(get).toHaveBeenCalledWith(urls.handlerUrl({ studioEndpointUrl, blockId, handlerName }));
});
});
@@ -250,8 +250,8 @@ describe('cms api', () => {
describe('saveBlock', () => {
const content = 'Im baby palo santo ugh celiac fashion axe. La croix lo-fi venmo whatever. Beard man braid migas single-origin coffee forage ramps.';
it('should call post with urls.block and normalizeContent', () => {
apiMethods.saveBlock({
it('should call post with urls.block and normalizeContent', async () => {
await apiMethods.saveBlock({
blockId,
blockType: 'html',
content,
@@ -278,8 +278,8 @@ describe('cms api', () => {
const asset = new File([img], filename, { type: 'image/jpeg' });
const mockFormdata = new FormData();
mockFormdata.append('file', asset);
it('should call post with urls.courseAssets and imgdata', () => {
apiMethods.uploadAsset({
it('should call post with urls.courseAssets and imgdata', async () => {
await apiMethods.uploadAsset({
blockId,
learningContextId,
studioEndpointUrl,
@@ -290,10 +290,10 @@ describe('cms api', () => {
mockFormdata,
);
});
it('should call post with urls.libraryAssets and imgdata', () => {
it('should call post with urls.libraryAssets and imgdata', async () => {
learningContextId = 'lib:demo2uX';
mockFormdata.append('content', asset);
apiMethods.uploadAsset({
await apiMethods.uploadAsset({
blockId,
learningContextId,
studioEndpointUrl,
@@ -307,10 +307,10 @@ describe('cms api', () => {
});
describe('uploadVideo', () => {
it('should call post with urls.courseVideos and data', () => {
it('should call post with urls.courseVideos and data', async () => {
const data = { files: [{ file_name: 'video.mp4', content_type: 'mp4' }] };
apiMethods.uploadVideo({ data, studioEndpointUrl, learningContextId });
await apiMethods.uploadVideo({ data, studioEndpointUrl, learningContextId });
expect(urls.courseVideos).toHaveBeenCalledWith({ studioEndpointUrl, learningContextId });
expect(post).toHaveBeenCalledWith(
@@ -354,10 +354,10 @@ describe('cms api', () => {
describe('uploadThumbnail', () => {
const thumbnail = 'dAta';
const videoId = 'sOmeVIDeoiD';
it('should call post with urls.thumbnailUpload and thumbnail data', () => {
it('should call post with urls.thumbnailUpload and thumbnail data', async () => {
const mockFormdata = new FormData();
mockFormdata.append('file', thumbnail);
apiMethods.uploadThumbnail({
await apiMethods.uploadThumbnail({
studioEndpointUrl,
learningContextId,
videoId,
@@ -376,8 +376,8 @@ describe('cms api', () => {
const youTubeId = 'SOMeyoutUBeid';
describe('checkTranscriptsForImport', () => {
const getJSON = `{"locator":"${blockId}","videos":[{"mode":"youtube","video":"${youTubeId}","type":"youtube"},{"mode":"edx_video_id","type":"edx_video_id","video":"${videoId}"}]}`;
it('should call get with url.checkTranscriptsForImport', () => {
apiMethods.checkTranscriptsForImport({
it('should call get with url.checkTranscriptsForImport', async () => {
await apiMethods.checkTranscriptsForImport({
studioEndpointUrl,
blockId,
videoId,
@@ -391,8 +391,8 @@ describe('cms api', () => {
});
describe('importTranscript', () => {
const getJSON = `{"locator":"${blockId}","videos":[{"mode":"youtube","video":"${youTubeId}","type":"youtube"}]}`;
it('should call get with url.replaceTranscript', () => {
apiMethods.importTranscript({ studioEndpointUrl, blockId, youTubeId });
it('should call get with url.replaceTranscript', async () => {
await apiMethods.importTranscript({ studioEndpointUrl, blockId, youTubeId });
expect(get).toHaveBeenCalledWith(urls.replaceTranscript({
studioEndpointUrl,
parameters: encodeURIComponent(getJSON),
@@ -401,13 +401,13 @@ describe('cms api', () => {
});
describe('uploadTranscript', () => {
const transcript = new Blob(['dAta']);
it('should call post with urls.videoTranscripts and transcript data', () => {
it('should call post with urls.videoTranscripts and transcript data', async () => {
const mockFormdata = new FormData();
mockFormdata.append('file', transcript);
mockFormdata.append('edx_video_id', videoId);
mockFormdata.append('language_code', language);
mockFormdata.append('new_language_code', language);
apiMethods.uploadTranscript({
await apiMethods.uploadTranscript({
blockId,
studioEndpointUrl,
transcript,
@@ -422,14 +422,14 @@ describe('cms api', () => {
});
describe('uploadTranscriptV2', () => {
const transcript = new Blob(['dAta']);
it('should call post with urls.uploadTranscriptV2 and transcript data', () => {
it('should call post with urls.uploadTranscriptV2 and transcript data', async () => {
const mockFormdata = new FormData();
const transcriptHandlerUrl = 'handlerUrl';
mockFormdata.append('file', transcript);
mockFormdata.append('edx_video_id', videoId);
mockFormdata.append('language_code', language);
mockFormdata.append('new_language_code', language);
apiMethods.uploadTranscriptV2({
await apiMethods.uploadTranscriptV2({
handlerUrl: transcriptHandlerUrl,
transcript,
videoId,
@@ -442,9 +442,9 @@ describe('cms api', () => {
});
});
describe('transcript delete', () => {
it('should call deleteObject with urls.videoTranscripts and transcript data', () => {
it('should call deleteObject with urls.videoTranscripts and transcript data', async () => {
const mockDeleteJSON = { data: { lang: language, edx_video_id: videoId } };
apiMethods.deleteTranscript({
await apiMethods.deleteTranscript({
blockId,
studioEndpointUrl,
videoId,
@@ -455,10 +455,10 @@ describe('cms api', () => {
mockDeleteJSON,
);
});
it('should call deleteObject with urls.transcriptXblockV2 and transcript data', () => {
it('should call deleteObject with urls.transcriptXblockV2 and transcript data', async () => {
const mockDeleteJSON = { data: { lang: language, edx_video_id: videoId } };
const transcriptHandlerUrl = 'handlerUrl';
apiMethods.deleteTranscriptV2({
await apiMethods.deleteTranscriptV2({
handlerUrl: transcriptHandlerUrl,
videoId,
language,
@@ -470,9 +470,9 @@ describe('cms api', () => {
});
});
describe('transcript get', () => {
it('should call get with urls.videoTranscripts and transcript data', () => {
it('should call get with urls.videoTranscripts and transcript data', async () => {
const mockJSON = { data: { lang: language, edx_video_id: videoId } };
apiMethods.getTranscript({
await apiMethods.getTranscript({
blockId,
studioEndpointUrl,
videoId,
@@ -483,10 +483,10 @@ describe('cms api', () => {
mockJSON,
);
});
it('should call get with urls.transcriptXblockV2 and transcript data', () => {
it('should call get with urls.transcriptXblockV2 and transcript data', async () => {
const mockJSON = { data: { lang: language, edx_video_id: videoId } };
const transcriptHandlerUrl = 'handlerUrl';
apiMethods.getTranscriptV2({
await apiMethods.getTranscriptV2({
handlerUrl: transcriptHandlerUrl,
videoId,
language,
@@ -672,9 +672,9 @@ describe('cms api', () => {
});
});
describe('fetchVideoFeatures', () => {
it('should call get with url.videoFeatures', () => {
it('should call get with url.videoFeatures', async () => {
const args = { studioEndpointUrl };
apiMethods.fetchVideoFeatures({ ...args });
await apiMethods.fetchVideoFeatures({ ...args });
expect(get).toHaveBeenCalledWith(urls.videoFeatures({ ...args }));
});
});

View File

@@ -77,7 +77,7 @@ describe('<SelectableBox />', () => {
const onClickSpy = jest.fn();
render(<SelectableCheckbox onClick={onClickSpy} />);
const selectableBox = screen.getByRole('button');
await await user.click(selectableBox);
await user.click(selectableBox);
expect(onClickSpy).toHaveBeenCalledTimes(1);
});
it('renders with on key press event when onClick is passed', async () => {
@@ -86,7 +86,7 @@ describe('<SelectableBox />', () => {
render(<SelectableCheckbox onClick={onClickSpy} />);
const selectableBox = screen.getByRole('button');
selectableBox.focus();
await await user.keyboard('{enter}');
await user.keyboard('{enter}');
expect(onClickSpy).toHaveBeenCalledTimes(1);
});
it('renders with hidden input when inputHidden is passed', () => {

View File

@@ -68,7 +68,7 @@ describe('<SelectableBox.Set />', () => {
const onChangeSpy = jest.fn();
render(<SelectableCheckboxSet onChange={onChangeSpy} />);
const checkbox = screen.getByRole('button', { name: checkboxText(1) });
await await user.click(checkbox);
await user.click(checkbox);
expect(onChangeSpy).toHaveBeenCalledTimes(1);
});
it('renders with checkbox type', () => {

View File

@@ -1,8 +1,6 @@
import React from 'react';
import '@testing-library/jest-dom';
import {
act, fireEvent, render, screen,
fireEvent, render, screen,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
@@ -48,43 +46,36 @@ describe('SearchSort component', () => {
);
}
test('adds a sort option for each sortKey', async () => {
test('adds a sort option for each sortKey', () => {
const { getByRole } = getComponent();
await act(() => {
fireEvent.click(screen.getByRole('button', {
name: /By oldest/i,
}));
});
fireEvent.click(screen.getByRole('button', {
name: /By oldest/i,
}));
Object.values(sortMessages)
.forEach(({ defaultMessage }) => {
expect(getByRole('link', { name: `By ${defaultMessage}` }))
.toBeInTheDocument();
});
});
test('adds a sort option for each sortKey', async () => {
test('adds a sort option for each sortKey', () => {
const { getByRole } = getComponent();
await act(() => {
fireEvent.click(screen.getByRole('button', { name: /oldest/i }));
});
fireEvent.click(screen.getByRole('button', { name: /oldest/i }));
Object.values(sortMessages)
.forEach(({ defaultMessage }) => {
expect(getByRole('link', { name: `By ${defaultMessage}` }))
.toBeInTheDocument();
});
});
test('adds a filter option for each filter key', async () => {
test('adds a filter option for each filter key', () => {
const { getByTestId } = getComponent();
act(() => {
fireEvent.click(getByTestId('dropdown-filter'));
});
fireEvent.click(getByTestId('dropdown-filter'));
Object.keys(filterMessages)
.forEach((key) => {
expect(getByTestId(`dropdown-filter-${key}`))
.toBeInTheDocument();
});
});
test('searchbox should show clear message button when not empty', async () => {
test('searchbox should show clear message button when not empty', () => {
const { queryByRole } = getComponent({ searchString: 'some string' });
expect(queryByRole('button', { name: messages.clearSearch.defaultMessage }))
.toBeInTheDocument();

View File

@@ -280,7 +280,7 @@ describe('FilesAndUploads', () => {
it('should have disabled action buttons', async () => {
await mockStore(RequestStatus.SUCCESSFUL);
const actionsButton = await screen.getByText(messages.actionsButtonLabel.defaultMessage);
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
fireEvent.click(actionsButton);
expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled');
@@ -401,7 +401,7 @@ describe('FilesAndUploads', () => {
it('should open asset info', async () => {
await mockStore(RequestStatus.SUCCESSFUL);
const [assetMenuButton] = await screen.getAllByTestId('file-menu-dropdown-mOckID1');
const [assetMenuButton] = screen.getAllByTestId('file-menu-dropdown-mOckID1');
axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`)
.reply(201, {
@@ -465,7 +465,7 @@ describe('FilesAndUploads', () => {
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false });
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(await screen.getByText('Unlock'));
fireEvent.click(screen.getByText('Unlock'));
await executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID1',
@@ -484,7 +484,7 @@ describe('FilesAndUploads', () => {
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(201, { locked: true });
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(await screen.getByText('Lock'));
fireEvent.click(screen.getByText('Lock'));
await executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID3',
@@ -513,7 +513,7 @@ describe('FilesAndUploads', () => {
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
const confirmDelete = await screen.getByTestId('open-delete-confirmation-button');
const confirmDelete = screen.getByTestId('open-delete-confirmation-button');
fireEvent.click(confirmDelete);
await waitFor(() => {
expect(screen.getByText('Delete mOckID1')).toBeVisible();
@@ -600,7 +600,7 @@ describe('FilesAndUploads', () => {
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID3`).reply(404);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
const confirmDelete = await screen.getByTestId('open-delete-confirmation-button');
const confirmDelete = screen.getByTestId('open-delete-confirmation-button');
fireEvent.click(confirmDelete);
await waitFor(() => {
expect(screen.getByText('Delete mOckID3')).toBeVisible();

View File

@@ -546,7 +546,7 @@ describe('Videos page', () => {
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
const transcriptTab = await screen.getAllByRole('tab')[1];
const transcriptTab = screen.getAllByRole('tab')[1];
fireEvent.click(transcriptTab);
expect(screen.getByText('Transcript (1)')).toBeVisible();
@@ -721,7 +721,7 @@ describe('Videos page', () => {
expect(actionsButton).toBeVisible();
fireEvent.click(actionsButton);
const downloadButton = await screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
expect(downloadButton).not.toHaveClass('disabled');
axiosMock.onPut(`${getVideosUrl(courseId)}/download`).reply(404);

View File

@@ -196,7 +196,7 @@ describe('TranscriptTab', () => {
axiosMock.onDelete(`${getApiBaseUrl()}/transcript_delete/${courseId}/mOckID0/ar`).reply(204);
await act(async () => {
fireEvent.click(confirmButton);
executeThunk(deleteVideoTranscript({
await executeThunk(deleteVideoTranscript({
language: 'ar',
videoId: updatedProps.id,
transcripts: updatedProps.transcripts,
@@ -215,7 +215,7 @@ describe('TranscriptTab', () => {
axiosMock.onDelete(`${getApiBaseUrl()}/transcript_delete/${courseId}/mOckID0/ar`).reply(404);
await act(async () => {
fireEvent.click(confirmButton);
executeThunk(deleteVideoTranscript({
await executeThunk(deleteVideoTranscript({
language: 'ar',
videoId: updatedProps.id,
transcripts: updatedProps.transcripts,

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { AppProvider } from '@edx/frontend-platform/react';
import { fireEvent, render, act } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { initializeMockApp } from '@edx/frontend-platform';
import { IntlProvider } from '@edx/frontend-platform/i18n';
@@ -57,9 +57,7 @@ describe('<CourseUploadImage />', () => {
it('should call onChange if input value changed', async () => {
const { getByPlaceholderText } = render(<RootWrapper {...props} />);
const input = getByPlaceholderText(props.customInputPlaceholder);
await act(() => {
fireEvent.change(input, { target: { value: '/assets' } });
});
fireEvent.change(input, { target: { value: '/assets' } });
expect(onChangeMock).toHaveBeenCalledWith(
'/assets',
props.assetImageField,

View File

@@ -1,4 +1,3 @@
import React from 'react';
import {
fireEvent,
screen,
@@ -211,8 +210,8 @@ describe('<CreateOrRerunCourseForm />', () => {
fireEvent.change(numberInput, { target: { value: 'long-name-which-is-longer-than-65-characters-to-check-for-errors' } });
waitFor(() => {
expect(screen.getByText(messages.totalLengthError)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(messages.totalLengthError.defaultMessage)).toBeInTheDocument();
});
});
@@ -278,9 +277,10 @@ describe('<CreateOrRerunCourseForm />', () => {
const numberInput = await screen.findByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage);
fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } });
fireEvent.blur(numberInput);
waitFor(() => {
expect(screen.getByText(messages.noSpaceError)).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText(messages.disallowedCharsError.defaultMessage)).toBeInTheDocument();
});
});
});

View File

@@ -19,7 +19,7 @@ const renderforContainer = () => defaultRender(
close={closeMock}
category="chapter"
displayName="Introduction to Testing"
onDeleteSubmit={onUnlinkSubmitMock}
onUnlinkSubmit={onUnlinkSubmitMock}
/>
</IntlProvider>,
);
@@ -30,7 +30,7 @@ const renderforComponent = () => defaultRender(
isOpen
close={closeMock}
category="component"
onDeleteSubmit={onUnlinkSubmitMock}
onUnlinkSubmit={onUnlinkSubmitMock}
/>
</IntlProvider>,
);
@@ -65,7 +65,7 @@ describe('<UnlinkModal />', () => {
const okButton = screen.getByRole('button', { name: messages.unlinkButton.defaultMessage });
fireEvent.click(okButton);
waitFor(() => {
await waitFor(() => {
expect(onUnlinkSubmitMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -46,7 +46,7 @@ describe('<CreditSection />', () => {
const inputElement = await findByTestId('minimum-grade-credit-input');
expect(inputElement.value).toBe('10');
await fireEvent.change(inputElement, { target: { value: '.01' } });
fireEvent.change(inputElement, { target: { value: '.01' } });
await findByText('Not able to set passing grade to less than: 0.1.');
});
});

View File

@@ -208,7 +208,7 @@ describe('<AddContent />', () => {
expect(getClipboardSpy).toHaveBeenCalled(); // Hmm, this is getting called four times! Refactor to use react-query.
const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i });
userEvent.click(pasteButton);
await userEvent.click(pasteButton);
await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(pasteUrl));
});
@@ -223,7 +223,7 @@ describe('<AddContent />', () => {
render();
const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i });
userEvent.click(pasteButton);
await userEvent.click(pasteButton);
await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(pasteUrl));
expect(mockShowToast).toHaveBeenCalledWith(
@@ -250,7 +250,7 @@ describe('<AddContent />', () => {
expect(getClipboardSpy).toHaveBeenCalled(); // Hmm, this is getting called four times! Refactor to use react-query.
const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i });
userEvent.click(pasteButton);
await userEvent.click(pasteButton);
await waitFor(() => expect(axiosMock.history.post.length).toEqual(1));
expect(axiosMock.history.post[0].url).toEqual(pasteUrl);
@@ -277,7 +277,7 @@ describe('<AddContent />', () => {
expect(getClipboardSpy).toHaveBeenCalled(); // Hmm, this is getting called four times! Refactor to use react-query.
const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i });
userEvent.click(pasteButton);
await userEvent.click(pasteButton);
await waitFor(() => expect(axiosMock.history.post[0].url).toEqual(pasteUrl));
await waitFor(() => expect(axiosMock.history.patch.length).toEqual(1));
@@ -295,7 +295,7 @@ describe('<AddContent />', () => {
render();
const pasteButton = await screen.findByRole('button', { name: /paste from clipboard/i });
userEvent.click(pasteButton);
await userEvent.click(pasteButton);
await waitFor(() => {
expect(axiosMock.history.post.length).toEqual(0);
@@ -329,7 +329,7 @@ describe('<AddContent />', () => {
render();
const button = await screen.findByRole('button', { name: buttonName });
userEvent.click(button);
await userEvent.click(button);
await waitFor(() => {
expect(axiosMock.history.post.length).toEqual(1);

View File

@@ -99,13 +99,13 @@ describe('<LibraryBackupPage />', () => {
expect(exportingButton).toBeDisabled();
});
it('shows succeeded state uses ready text and triggers download', () => {
it('shows succeeded state uses ready text and triggers download', async () => {
mockStatusData = { state: 'Succeeded', url: '/fake/path.tar.gz' };
const downloadSpy = jest.spyOn(document, 'createElement');
render();
const button = screen.getByRole('button');
expect(button).toHaveTextContent(messages.downloadReadyButton.defaultMessage);
userEvent.click(button);
await userEvent.click(button);
expect(downloadSpy).toHaveBeenCalledWith('a');
downloadSpy.mockRestore();
});
@@ -118,19 +118,19 @@ describe('<LibraryBackupPage />', () => {
expect(button).toBeEnabled();
});
it('covers timeout cleanup on unmount', () => {
it('covers timeout cleanup on unmount', async () => {
mockMutate.mockImplementation((_arg: any, { onSuccess }: any) => {
onSuccess({ task_id: 'task-123' });
mockStatusData = { state: LibraryBackupStatus.Pending };
});
const { unmount } = render();
const button = screen.getByRole('button');
userEvent.click(button);
await userEvent.click(button);
unmount();
// No assertion needed, just coverage for cleanup
});
it('covers fallback download logic', () => {
it('covers fallback download logic', async () => {
mockStatusData = { state: LibraryBackupStatus.Succeeded, url: '/fake/path.tar.gz' };
// Spy on createElement to force click failure for anchor
const originalCreate = document.createElement.bind(document);
@@ -153,7 +153,7 @@ describe('<LibraryBackupPage />', () => {
window.location = { href: '' };
render();
const button = screen.getByRole('button');
userEvent.click(button);
await userEvent.click(button);
expect(window.location.href).toContain('/fake/path.tar.gz');
// restore
createSpy.mockRestore();

View File

@@ -129,7 +129,7 @@ describe('<CollectionDetails />', () => {
});
it('should render Collection stats', async () => {
mockGetBlockTypes('someBlocks');
await mockGetBlockTypes('someBlocks');
render();
expect(await screen.findByText('Description / Card Preview Text')).toBeInTheDocument();
@@ -148,7 +148,7 @@ describe('<CollectionDetails />', () => {
});
it('should render Collection stats for empty collection', async () => {
mockGetBlockTypes('noBlocks');
await mockGetBlockTypes('noBlocks');
render();
expect(await screen.findByText('Description / Card Preview Text')).toBeInTheDocument();
@@ -157,7 +157,7 @@ describe('<CollectionDetails />', () => {
});
it('should render Collection stats for big collection', async () => {
mockGetBlockTypes('moreBlocks');
await mockGetBlockTypes('moreBlocks');
render();
expect(await screen.findByText('Description / Card Preview Text')).toBeInTheDocument();

View File

@@ -409,7 +409,7 @@ describe('<LibraryCollectionPage />', () => {
await renderLibraryCollectionPage();
// Wait for the unit cards to load
waitFor(() => expect(screen.getAllByTestId('container-card-menu-toggle').length).toBeGreaterThan(0));
await waitFor(() => expect(screen.getAllByTestId('container-card-menu-toggle').length).toBeGreaterThan(0));
// open sidebar
fireEvent.click(await screen.findByText(displayName));

View File

@@ -486,7 +486,7 @@ describe('<ContainerCard />', () => {
// Click on Copy Item
const copyMenuItem = screen.getByRole('button', { name: 'Copy to clipboard' });
expect(copyMenuItem).toBeInTheDocument();
user.click(copyMenuItem);
await user.click(copyMenuItem);
await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);

View File

@@ -211,7 +211,7 @@ let mockShowToast: { (message: string, action?: ToastActionData | undefined): vo
// Click on Copy Item
const copyMenuItem = await screen.findByRole('button', { name: 'Copy to clipboard' });
expect(copyMenuItem).toBeInTheDocument();
user.click(copyMenuItem);
await user.click(copyMenuItem);
await waitFor(() => {
expect(axiosMock.history.post.length).toBe(1);

View File

@@ -323,10 +323,10 @@ describe('OpenedXConfigForm', () => {
});
test('check duplicate error is removed on deleting duplicate topic', async () => {
await await user.click(
await user.click(
await findByLabelText(duplicateTopicCard, messages.deleteAltText.defaultMessage, { selector: 'button' }),
);
await await user.click(
await user.click(
await findByRole(container, 'button', { name: messages.deleteButton.defaultMessage }),
);
await waitForElementToBeRemoved(queryByText(topicCard, messages.discussionTopicNameAlreadyExist.defaultMessage));

View File

@@ -38,7 +38,7 @@ describe('AppCard', () => {
},
});
store = await initializeStore();
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
});

View File

@@ -63,7 +63,7 @@ describe('AppList', () => {
},
});
store = await initializeStore();
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
await mockStore(piazzaApiResponse);
});
@@ -163,7 +163,7 @@ describe('AppList', () => {
},
});
store = await initializeStore();
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
await mockStore(legacyApiResponse, 'legacy');
});

View File

@@ -326,9 +326,10 @@ describe('Data layer integration tests', () => {
test('successfully saves an LTI configuration', async () => {
axiosMock.onGet(getDiscussionsProvidersUrl(courseId)).reply(200, generateProvidersApiResponse());
axiosMock.onGet(getDiscussionsSettingsUrl(courseId)).reply(200, piazzaApiResponse);
// Note: if this test is failing, it's likely because the POSTed data has changed and no longer exactly matches
// the expected data in the mock below.
axiosMock.onPost(getDiscussionsSettingsUrl(courseId), {
context_key: courseId,
enabled: true,
lti_configuration: {
lti_1p1_client_key: 'new_consumer_key',
lti_1p1_client_secret: 'new_consumer_secret',
@@ -363,7 +364,7 @@ describe('Data layer integration tests', () => {
mockedNavigator,
), store.dispatch);
waitFor(() => {
await waitFor(() => {
expect(mockedNavigator).toHaveBeenCalledWith(pagesAndResourcesPath);
expect(store.getState().discussions).toEqual(
expect.objectContaining({
@@ -388,9 +389,10 @@ describe('Data layer integration tests', () => {
test('successfully saves a Legacy configuration', async () => {
axiosMock.onGet(getDiscussionsProvidersUrl(courseId)).reply(200, generateProvidersApiResponse(false, 'legacy'));
axiosMock.onGet(getDiscussionsSettingsUrl(courseId)).reply(200, legacyApiResponse);
// Note: if this test is failing, it's likely because the POSTed data has changed and no longer exactly matches
// the expected data in the mock below.
axiosMock.onPost(getDiscussionsSettingsUrl(courseId), {
context_key: courseId,
enabled: true,
lti_configuration: {},
plugin_configuration: {
allow_anonymous: true,
@@ -458,7 +460,7 @@ describe('Data layer integration tests', () => {
pagesAndResourcesPath,
mockedNavigator,
), store.dispatch);
waitFor(() => {
await waitFor(() => {
expect(mockedNavigator).toHaveBeenCalledWith(pagesAndResourcesPath);
expect(store.getState().discussions).toEqual(
expect.objectContaining({

View File

@@ -11,7 +11,6 @@ import { getApiWaffleFlagsUrl } from '../../data/api';
import PagesAndResourcesProvider from '../PagesAndResourcesProvider';
let container;
let axiosMock;
const courseId = '123';
const mockPageConfig = [
@@ -36,12 +35,11 @@ const mockPageConfig = [
];
const renderComponent = () => {
const wrapper = render(
render(
<PagesAndResourcesProvider courseId={courseId}>
<PageGrid pages={mockPageConfig} />
</PagesAndResourcesProvider>,
);
container = wrapper.container;
};
describe('LiveSettings', () => {
@@ -60,16 +58,16 @@ describe('LiveSettings', () => {
it('should render three cards', async () => {
renderComponent();
waitFor(() => {
expect(screen.queryAllByRole(container, 'button')).toHaveLength(3);
await waitFor(() => {
expect(screen.queryAllByRole('button')).toHaveLength(3);
});
});
it('should navigate to legacyLink', async () => {
renderComponent();
const textbookPagePath = mockPageConfig[0][1];
waitFor(() => {
const textbookSettingsButton = screen.queryAllByRole(container, 'link')[1];
await waitFor(() => {
const textbookSettingsButton = screen.queryAllByRole('link')[1];
expect(textbookSettingsButton).toHaveAttribute('href', textbookPagePath);
});
});

View File

@@ -125,9 +125,7 @@ describe('<ScheduleAndDetails />', () => {
it('should display a success message when course details saves', async () => {
const { getByText } = render(<ScheduleAndDetails courseId={courseId} />);
await waitFor(() => {
executeThunk(updateCourseDetailsQuery(courseId, 'DaTa'), store.dispatch);
});
await executeThunk(updateCourseDetailsQuery(courseId, 'DaTa'), store.dispatch);
expect(getByText(messages.alertSuccess.defaultMessage)).toBeInTheDocument();
});

View File

@@ -32,7 +32,7 @@ describe('<DetailsSection />', () => {
).toBeInTheDocument();
});
it('should call onChange if dropdown value changed', () => {
it('should call onChange if dropdown value changed', async () => {
const { getByRole } = render(<RootWrapper {...props} />);
const option = getByRole('button', {
name: courseSettingsMock.languageOptions[1][1],
@@ -42,7 +42,7 @@ describe('<DetailsSection />', () => {
name: courseSettingsMock.languageOptions[0][1],
});
waitFor(() => expect(anotherOption));
await waitFor(() => expect(anotherOption));
fireEvent.click(anotherOption);
expect(onChangeMock).toHaveBeenCalledWith(
courseSettingsMock.languageOptions[0][0],

View File

@@ -1,6 +1,5 @@
import React from 'react';
import {
act, fireEvent, render, waitFor,
fireEvent, render, screen, waitFor,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
@@ -46,45 +45,56 @@ const props = {
describe('<InstructorsSection />', () => {
it('renders section successfully', () => {
const { getByText, getByRole } = render(<RootWrapper {...props} />);
expect(getByText(messages.instructorsTitle.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.instructorsDescription.defaultMessage)).toBeInTheDocument();
expect(getByRole('button', { name: messages.instructorAdd.defaultMessage })).toBeInTheDocument();
render(<RootWrapper {...props} />);
expect(screen.getByText(messages.instructorsTitle.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.instructorsDescription.defaultMessage)).toBeInTheDocument();
expect(screen.getByRole('button', { name: messages.instructorAdd.defaultMessage })).toBeInTheDocument();
});
it('should create another instructor form on click Add new instructor', () => {
const { getAllByRole, getByRole } = render(<RootWrapper {...props} />);
const addButton = getByRole('button', { name: instructorMessages.instructorDelete.defaultMessage });
act(() => {
fireEvent.click(addButton);
});
it('should create another instructor form on click Add new instructor', async () => {
const { rerender } = render(<RootWrapper {...props} />);
const addButton = screen.getByRole('button', { name: messages.instructorAdd.defaultMessage });
fireEvent.click(addButton);
waitFor(() => {
const deleteButtons = getAllByRole('button', { name: instructorMessages.instructorDelete.defaultMessage });
const newInstructors = [
props.instructors[0],
{
bio: '',
image: '',
name: '',
organization: '',
title: '',
},
];
expect(onChangeMock).toHaveBeenCalledWith({ instructors: newInstructors }, 'instructorInfo');
rerender(<RootWrapper {...props} instructors={newInstructors} />);
await waitFor(() => {
const deleteButtons = screen.getAllByRole('button', { name: instructorMessages.instructorDelete.defaultMessage });
expect(deleteButtons.length).toBe(2);
});
});
it('should delete instructor form on click Delete', () => {
const { getAllByRole, getByRole } = render(<RootWrapper {...props} />);
const deleteButton = getByRole('button', { name: instructorMessages.instructorDelete.defaultMessage });
act(() => {
fireEvent.click(deleteButton);
});
it('should delete instructor form on click Delete', async () => {
const { rerender } = render(<RootWrapper {...props} />);
const deleteButton = screen.getByRole('button', { name: instructorMessages.instructorDelete.defaultMessage });
fireEvent.click(deleteButton);
expect(onChangeMock).toHaveBeenCalledWith({ instructors: [] }, 'instructorInfo');
waitFor(() => {
const deleteButtons = getAllByRole('button', { name: instructorMessages.instructorDelete.defaultMessage });
rerender(<RootWrapper {...props} instructors={[]} />);
await waitFor(() => {
const deleteButtons = screen.queryAllByRole(
'button',
{ name: instructorMessages.instructorDelete.defaultMessage },
);
expect(deleteButtons.length).toBe(0);
});
});
it('should call onChange if input value changed', () => {
const { getByPlaceholderText } = render(<RootWrapper {...props} />);
const inputName = getByPlaceholderText(instructorMessages.instructorNameInputPlaceholder.defaultMessage);
act(() => {
fireEvent.change(inputName, { target: { value: 'abc' } });
});
render(<RootWrapper {...props} />);
const inputName = screen.getByPlaceholderText(instructorMessages.instructorNameInputPlaceholder.defaultMessage);
fireEvent.change(inputName, { target: { value: 'abc' } });
expect(onChangeMock).toHaveBeenCalledWith({
instructors: [{

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { fireEvent, render, act } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { AppProvider } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
@@ -112,18 +112,14 @@ describe('<InstructorContainer />', () => {
it('should call onChange if input value changed', async () => {
const { getByPlaceholderText } = render(<RootWrapper {...props} />);
const nameInput = getByPlaceholderText(messages.instructorNameInputPlaceholder.defaultMessage);
await act(() => {
fireEvent.change(nameInput, { target: { value: 'abc' } });
});
fireEvent.change(nameInput, { target: { value: 'abc' } });
expect(onChangeMock).toHaveBeenCalledWith('abc', props.idx, 'name');
});
it('should call onDelete if button delete clicked', async () => {
const { getByRole } = render(<RootWrapper {...props} />);
const deleteBtn = getByRole('button', { name: messages.instructorDelete.defaultMessage });
await act(() => {
fireEvent.click(deleteBtn);
});
fireEvent.click(deleteBtn);
expect(onDeleteMock).toHaveBeenCalledWith(props.idx);
});
});

View File

@@ -1,7 +1,4 @@
import React from 'react';
import {
act, fireEvent, render, waitFor,
} from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { courseDetailsMock } from '../__mocks__';
@@ -28,39 +25,44 @@ describe('<LearningOutcomesSection />', () => {
expect(getByRole('button', { name: messages.outcomesAdd.defaultMessage })).toBeInTheDocument();
});
it('should create another learning outcome form on click Add learning outcome', () => {
const { getAllByRole, getByRole } = render(<RootWrapper {...props} />);
it('should create another learning outcome form on click Add learning outcome', async () => {
const { getByRole } = render(<RootWrapper {...props} />);
const addButton = getByRole('button', { name: messages.outcomesAdd.defaultMessage });
act(() => {
fireEvent.click(addButton);
});
expect(onChangeMock).not.toHaveBeenCalled();
fireEvent.click(addButton);
expect(onChangeMock).toHaveBeenCalledWith([
props.learningInfo[0],
'', // <-- new
], 'learningInfo');
waitFor(() => {
const deleteButtons = getAllByRole('button', { name: messages.outcomesDelete.defaultMessage });
expect(deleteButtons.length).toBe(2);
});
// FIXME: the following doesn't happen, because this is a controlled component and only changes
// when the props change (in response to 'onChange'). This needs to be tested at a higher level,
// e.g. testing the whole page together, not just this component.
// await waitFor(() => {
// const deleteButtons = getAllByRole('button', { name: messages.outcomesDelete.defaultMessage });
// expect(deleteButtons.length).toBe(2);
// });
});
it('should delete learning outcome form on click Delete', () => {
const { getAllByRole, getByRole } = render(<RootWrapper {...props} />);
it('should delete learning outcome form on click Delete', async () => {
const { getByRole } = render(<RootWrapper {...props} />);
const deleteButton = getByRole('button', { name: messages.outcomesDelete.defaultMessage });
act(() => {
fireEvent.click(deleteButton);
});
fireEvent.click(deleteButton);
expect(onChangeMock).toHaveBeenCalledWith([], 'learningInfo');
waitFor(() => {
const deleteButtons = getAllByRole('button', { name: messages.outcomesDelete.defaultMessage });
expect(deleteButtons.length).toBe(0);
});
// FIXME: the following doesn't happen, because this is a controlled component and only changes
// when the props change (in response to 'onChange'). This needs to be tested at a higher level,
// e.g. testing the whole page together, not just this component.
// await waitFor(() => {
// const deleteButtons = getAllByRole('button', { name: messages.outcomesDelete.defaultMessage });
// expect(deleteButtons.length).toBe(0);
// });
});
it('should call onChange if input value changed', () => {
const { getByPlaceholderText } = render(<RootWrapper {...props} />);
const input = getByPlaceholderText(messages.outcomesInputPlaceholder.defaultMessage);
act(() => {
fireEvent.change(input, { target: { value: 'abc' } });
});
fireEvent.change(input, { target: { value: 'abc' } });
expect(onChangeMock).toHaveBeenCalledWith(['abc'], 'learningInfo');
});

View File

@@ -1,7 +1,4 @@
import React from 'react';
import {
render, fireEvent, act, waitFor,
} from '@testing-library/react';
import { render, fireEvent } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { LICENSE_COMMONS_OPTIONS } from '../constants';
@@ -44,15 +41,17 @@ describe('<LicenseCommonsOptions />', () => {
expect(checkboxList[3].checked).toBeFalsy();
});
it('should call onToggleCheckbox event onClick', () => {
it('should call onToggleCheckbox event onClick', async () => {
const { getAllByRole } = render(<RootWrapper {...props} />);
const checkboxList = getAllByRole('checkbox');
act(() => {
fireEvent.click(checkboxList[1]);
});
expect(props.onToggleCheckbox).not.toHaveBeenCalled();
fireEvent.click(checkboxList[1]);
expect(props.onToggleCheckbox).toHaveBeenCalledWith(LICENSE_COMMONS_OPTIONS.nonCommercial);
waitFor(() => {
expect(checkboxList[1].checked).toBeFalsy();
});
// Note: there is no point in asserting that the checkbox is now checked,
// because it is a controlled component that never changes unless the props change.
// This test should really be implemented in a higher level component/page.
// await waitFor(() => {
// expect(checkboxList[1].checked).toBeFalsy();
// });
});
});

View File

@@ -1,7 +1,4 @@
import React from 'react';
import {
act, fireEvent, render, waitFor,
} from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { LICENSE_TYPE } from '../constants';
@@ -36,20 +33,21 @@ describe('<LicenseSelector />', () => {
expect(anotherButton).toHaveClass('btn btn-outline-primary');
});
it('should call onChangeLicenseType if button clicked', () => {
it('should call onChangeLicenseType if button clicked', async () => {
const { getByRole } = render(<RootWrapper {...props} />);
const button = getByRole('button', { name: messages.licenseChoice2.defaultMessage });
expect(button).toHaveClass('btn btn-outline-primary');
act(() => {
fireEvent.click(button);
});
waitFor(() => {
expect(button).toHaveClass('btn btn-primary');
});
fireEvent.click(button);
expect(props.onChangeLicenseType).toHaveBeenCalledWith(
LICENSE_TYPE.creativeCommons,
'license',
);
// FIXME: the following doesn't happen, because this is a controlled component and only changes
// when the props change (in response to 'onChange'). This needs to be tested at a higher level,
// e.g. testing the whole page together, not just this component.
// await waitFor(() => {
// expect(button).toHaveClass('btn btn-primary');
// });
});
it('should show disabled buttons if license is null', () => {

View File

@@ -1,11 +1,10 @@
import React from 'react';
import {
render, fireEvent, act, waitFor,
render, screen, waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { courseDetailsMock } from '../../__mocks__';
import gradeRequirementsMessages from '../grade-requirements/messages';
import messages from './messages';
import EntranceExam from '.';
@@ -41,21 +40,21 @@ describe('<EntranceExam />', () => {
expect(checkboxList[0].checked).toBeTruthy();
});
it('should toggle grade requirements after checkbox click', () => {
const { getByText, queryAllByText, getAllByRole } = render(
<RootWrapper {...props} />,
);
const checkbox = getAllByRole('checkbox')[0];
expect(
getByText(gradeRequirementsMessages.requirementsEntranceCollapseLabel.defaultMessage),
).toBeInTheDocument();
act(() => {
fireEvent.click(checkbox);
});
waitFor(() => {
expect(
queryAllByText(gradeRequirementsMessages.requirementsEntranceCollapseLabel.defaultMessage).length,
).toBe(0);
it('should toggle grade requirements after checkbox click', async () => {
onChangeMock.mockClear();
const ui = render(<RootWrapper {...props} />);
const user = userEvent.setup();
const checkbox = screen.getByRole('checkbox', { name: 'Require students to pass an exam before beginning the course.' });
expect(checkbox).toBeChecked();
expect(screen.queryByText('Grade requirements')).toBeInTheDocument();
await user.click(checkbox);
expect(onChangeMock).toHaveBeenCalledWith('false', 'entranceExamEnabled');
ui.rerender(<RootWrapper {...props} isCheckedString="false" />);
await waitFor(() => {
expect(checkbox).not.toBeChecked();
expect(screen.queryByText('Grade requirements')).not.toBeInTheDocument();
});
});
});

View File

@@ -1,6 +1,5 @@
import React from 'react';
import {
render, fireEvent, act, waitFor,
render, fireEvent, screen, waitFor,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
@@ -34,20 +33,15 @@ describe('<GradeRequirements />', () => {
it('should call onChange on input change', () => {
const { getByDisplayValue } = render(<RootWrapper {...props} />);
const input = getByDisplayValue(props.entranceExamMinimumScorePct);
act(() => {
fireEvent.change(input, { target: { valueAsNumber: '31' } });
});
fireEvent.change(input, { target: { valueAsNumber: '31' } });
expect(props.onChange).toHaveBeenCalledWith('31', 'entranceExamMinimumScorePct');
});
it('should show feedback error', () => {
const { getByDisplayValue, getByText } = render(<RootWrapper {...props} />);
const input = getByDisplayValue(props.entranceExamMinimumScorePct);
act(() => {
fireEvent.change(input, { target: { valueAsNumber: '123' } });
});
waitFor(() => {
expect(getByText(scheduleMessage.errorMessage8.defaultMessage)).toBeInTheDocument();
it('should show feedback error', async () => {
const errorMessage = scheduleMessage.errorMessage8.defaultMessage;
render(<RootWrapper {...props} entranceExamMinimumScorePct="123" errorEffort={errorMessage} />);
await waitFor(() => {
expect(screen.getByText(errorMessage)).toBeInTheDocument();
});
});
});

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { useSelector } from 'react-redux';
import {
render, fireEvent, waitFor, act,
render, fireEvent, waitFor,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform';
@@ -83,7 +83,7 @@ describe('<CollapsibleStateWithAction />', () => {
expect(getByText(messages.deniedCollapsibleTitle.defaultMessage)).toBeInTheDocument();
});
it('renders collapsible denied state successfully opened', () => {
it('renders collapsible denied state successfully opened', async () => {
useSelector.mockReturnValue(studioHomeMock);
const initialState = { ...props, state: COURSE_CREATOR_STATES.denied };
@@ -91,11 +91,9 @@ describe('<CollapsibleStateWithAction />', () => {
const container = getByText(messages.deniedCollapsibleTitle.defaultMessage);
fireEvent.click(container);
act(async () => {
await waitFor(() => {
expect(getByText(messages.deniedCollapsibleState.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.deniedCollapsibleActionTitle.defaultMessage)).toBeInTheDocument();
});
await waitFor(() => {
expect(getByText(messages.deniedCollapsibleState.defaultMessage)).toBeInTheDocument();
expect(getByText(messages.deniedCollapsibleActionTitle.defaultMessage)).toBeInTheDocument();
});
});
});

View File

@@ -251,9 +251,9 @@ describe('<ImportTagsWizard />', () => {
} = render(<RootWrapper taxonomy={null} onClose={onClose} />);
// Check that there is no export step
expect(await queryByTestId('export-step')).not.toBeInTheDocument();
expect(queryByTestId('export-step')).not.toBeInTheDocument();
// Check that there is no back button in the upload step
expect(await queryByTestId('back-button')).not.toBeInTheDocument();
expect(queryByTestId('back-button')).not.toBeInTheDocument();
// Check that we are on the upload step
expect(getByTestId('upload-step')).toBeInTheDocument();

View File

@@ -174,11 +174,11 @@ describe('<TextbookForm />', () => {
const { findByTestId, findByRole } = renderComponent();
const button = await findByTestId('chapter-upload-button');
await await user.click(button);
await user.click(button);
const modalBackdrop = await findByTestId('modal-backdrop');
const cancelButton = await within(await findByRole('dialog')).findByText('Cancel');
await await user.click(cancelButton);
await user.click(cancelButton);
await waitFor(() => {
expect(modalBackdrop).not.toBeInTheDocument();
});