Fix some test warnings (#1062)

* fix: paragon's Hyperlink no longer accepts a 'content' attribute
* test: ensure all act() calls are async
* test: Removed "async" from "describe"
Returning a Promise from "describe" is not supported.
* fix: DiscussionsSettings tests
Previous commit revealed several issues with these tests
* Don't nest userAction.click in act() -- nested act() statements have indeterminent behaviour.
* Use getBy* instead of findBy* with userAction to avoid nested act() statements
* Use fireEvent.click when the onClick handlers need to be called
* Use queryBy* instead of getBy* when using .toBeInTheDocument or waitForElementToBeRemoved
  queryBy* return null when the element is not found.
* fix: typo in data-testid
Warning: React does not recognize the `data-testId` prop on a DOM
element. If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase `data-testid` instead.
* test: Use useLocation to test route changes
---------

Co-authored-by: Yusuf Musleh <yusuf@opencraft.com>
This commit is contained in:
Jillian
2024-06-13 14:37:26 +09:30
committed by GitHub
parent e22cce9fa6
commit f20e5311a9
22 changed files with 613 additions and 817 deletions

View File

@@ -103,9 +103,7 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('mockproc');
});
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
});
fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
const zendeskTicketInput = screen.getByTestId('createZendeskTicketsNo');
expect(zendeskTicketInput.checked).toEqual(true);
});
@@ -115,9 +113,7 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('mockproc');
});
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'software_secure' } });
});
fireEvent.change(selectElement, { target: { value: 'software_secure' } });
const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes');
expect(zendeskTicketInput.checked).toEqual(true);
});
@@ -127,9 +123,7 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('mockproc');
});
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'mockproc' } });
});
fireEvent.change(selectElement, { target: { value: 'mockproc' } });
const zendeskTicketInput = screen.getByTestId('createZendeskTicketsYes');
expect(zendeskTicketInput.checked).toEqual(true);
});
@@ -176,9 +170,7 @@ describe('ProctoredExamSettings', () => {
let enabledProctoredExamCheck = screen.getAllByLabelText('Proctored exams', { exact: false })[0];
expect(enabledProctoredExamCheck.checked).toEqual(true);
await act(async () => {
fireEvent.click(enabledProctoredExamCheck, { target: { value: false } });
});
fireEvent.click(enabledProctoredExamCheck, { target: { value: false } });
enabledProctoredExamCheck = screen.getByLabelText('Proctored exams');
expect(enabledProctoredExamCheck.checked).toEqual(false);
expect(screen.queryByText('Allow opting out of proctored exams')).toBeNull();
@@ -193,9 +185,7 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('mockproc');
});
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'test_lti' } });
});
fireEvent.change(selectElement, { target: { value: 'test_lti' } });
expect(screen.queryByTestId('allowOptingOutRadio')).toBeNull();
expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull();
expect(screen.queryByTestId('createZendeskTicketsNo')).toBeNull();
@@ -237,13 +227,9 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('proctortrack');
});
const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com');
await act(async () => {
fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
});
fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
const selectButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(selectButton);
});
fireEvent.click(selectButton);
// verify alert content and focus management
const escalationEmailError = screen.getByTestId('escalationEmailError');
@@ -252,9 +238,7 @@ describe('ProctoredExamSettings', () => {
// verify alert link links to offending input
const errorLink = screen.getByTestId('escalationEmailErrorLink');
await act(async () => {
fireEvent.click(errorLink);
});
fireEvent.click(errorLink);
const escalationEmailInput = screen.getByTestId('escalationEmail');
expect(document.activeElement).toEqual(escalationEmailInput);
});
@@ -265,18 +249,12 @@ describe('ProctoredExamSettings', () => {
});
const selectElement = screen.getByDisplayValue('proctortrack');
await act(async () => {
fireEvent.change(selectElement, { target: { value: provider } });
});
fireEvent.change(selectElement, { target: { value: provider } });
const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com');
await act(async () => {
fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } });
});
const selectButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(selectButton);
});
fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } });
const proctoringForm = screen.getByTestId('proctoringForm');
fireEvent.submit(proctoringForm);
// verify alert content and focus management
const escalationEmailError = screen.getByTestId('escalationEmailError');
@@ -286,9 +264,7 @@ describe('ProctoredExamSettings', () => {
// verify alert link links to offending input
const errorLink = screen.getByTestId('escalationEmailErrorLink');
await act(async () => {
fireEvent.click(errorLink);
});
fireEvent.click(errorLink);
const escalationEmailInput = screen.getByTestId('escalationEmail');
expect(document.activeElement).toEqual(escalationEmailInput);
});
@@ -298,15 +274,11 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('proctortrack');
});
const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com');
await act(async () => {
fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } });
});
fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo.bar' } });
const enableProctoringElement = screen.getByText('Proctored exams');
await act(async () => fireEvent.click(enableProctoringElement));
fireEvent.click(enableProctoringElement);
const selectButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(selectButton);
});
fireEvent.click(selectButton);
// verify alert content and focus management
const escalationEmailError = screen.getByTestId('escalationEmailError');
@@ -320,24 +292,22 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('proctortrack');
});
const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com');
await act(async () => {
fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
});
fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
const enableProctoringElement = screen.getByText('Proctored exams');
await act(async () => fireEvent.click(enableProctoringElement));
fireEvent.click(enableProctoringElement);
const selectButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(selectButton);
});
fireEvent.click(selectButton);
// verify there is no escalation email alert, and focus has been set on save success alert
expect(screen.queryByTestId('escalationEmailError')).toBeNull();
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it(`Has no error when valid proctoring escalation email is provided with ${provider} selected`, async () => {
@@ -345,22 +315,20 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('proctortrack');
});
const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com');
await act(async () => {
fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo@bar.com' } });
});
fireEvent.change(selectEscalationEmailElement, { target: { value: 'foo@bar.com' } });
const selectButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(selectButton);
});
fireEvent.click(selectButton);
// verify there is no escalation email alert, and focus has been set on save success alert
expect(screen.queryByTestId('escalationEmailError')).toBeNull();
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it(`Escalation email field hidden when proctoring backend is not ${provider}`, async () => {
@@ -370,9 +338,7 @@ describe('ProctoredExamSettings', () => {
const proctoringBackendSelect = screen.getByDisplayValue('proctortrack');
const selectEscalationEmailElement = screen.getByTestId('escalationEmail');
expect(selectEscalationEmailElement.value).toEqual('test@example.com');
await act(async () => {
fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } });
});
fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } });
expect(screen.queryByTestId('escalationEmail')).toBeNull();
});
@@ -382,13 +348,9 @@ describe('ProctoredExamSettings', () => {
});
const proctoringBackendSelect = screen.getByDisplayValue('proctortrack');
let selectEscalationEmailElement = screen.getByTestId('escalationEmail');
await act(async () => {
fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } });
});
fireEvent.change(proctoringBackendSelect, { target: { value: 'software_secure' } });
expect(screen.queryByTestId('escalationEmail')).toBeNull();
await act(async () => {
fireEvent.change(proctoringBackendSelect, { target: { value: 'proctortrack' } });
});
fireEvent.change(proctoringBackendSelect, { target: { value: 'proctortrack' } });
expect(screen.queryByTestId('escalationEmail')).toBeDefined();
selectEscalationEmailElement = screen.getByTestId('escalationEmail');
expect(selectEscalationEmailElement.value).toEqual('test@example.com');
@@ -399,12 +361,8 @@ describe('ProctoredExamSettings', () => {
screen.getByDisplayValue('proctortrack');
});
const selectEscalationEmailElement = screen.getByDisplayValue('test@example.com');
await act(async () => {
fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
});
await act(async () => {
fireEvent.submit(selectEscalationEmailElement);
});
fireEvent.change(selectEscalationEmailElement, { target: { value: '' } });
fireEvent.submit(selectEscalationEmailElement);
// if the error appears, the form has been submitted
expect(screen.getByTestId('escalationEmailError')).toBeDefined();
});
@@ -628,9 +586,7 @@ describe('ProctoredExamSettings', () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
let submitButton = screen.getByTestId('submissionButton');
expect(screen.queryByTestId('saveInProgress')).toBeFalsy();
act(() => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
submitButton = screen.getByTestId('submissionButton');
expect(submitButton).toHaveAttribute('disabled');
@@ -640,19 +596,13 @@ describe('ProctoredExamSettings', () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
// Make a change to the provider to proctortrack and set the email
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
});
fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
const escalationEmail = screen.getByTestId('escalationEmail');
expect(escalationEmail.value).toEqual('test@example.com');
await act(async () => {
fireEvent.change(escalationEmail, { target: { value: 'proctortrack@example.com' } });
});
fireEvent.change(escalationEmail, { target: { value: 'proctortrack@example.com' } });
expect(escalationEmail.value).toEqual('proctortrack@example.com');
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1);
expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({
proctored_exam_settings: {
@@ -664,11 +614,13 @@ describe('ProctoredExamSettings', () => {
},
});
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => {
@@ -678,9 +630,7 @@ describe('ProctoredExamSettings', () => {
expect(screen.getByDisplayValue('mockproc')).toBeDefined();
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1);
expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({
proctored_exam_settings: {
@@ -691,32 +641,28 @@ describe('ProctoredExamSettings', () => {
},
});
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it('Successfully updates exam configuration and studio provider is set to "lti_external" for lti providers', async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
// Make a change to the provider to test_lti and set the email
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'test_lti' } });
});
fireEvent.change(selectElement, { target: { value: 'test_lti' } });
const escalationEmail = screen.getByTestId('escalationEmail');
expect(escalationEmail.value).toEqual('test@example.com');
await act(async () => {
fireEvent.change(escalationEmail, { target: { value: 'test_lti@example.com' } });
});
fireEvent.change(escalationEmail, { target: { value: 'test_lti@example.com' } });
expect(escalationEmail.value).toEqual('test_lti@example.com');
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
// update exam service config
expect(axiosMock.history.patch.length).toBe(1);
@@ -736,19 +682,19 @@ describe('ProctoredExamSettings', () => {
},
});
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it('Sets exam service provider to null if a non-lti provider is selected', async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
// update exam service config
expect(axiosMock.history.patch.length).toBe(1);
expect(JSON.parse(axiosMock.history.patch[0].data)).toEqual({
@@ -766,11 +712,13 @@ describe('ProctoredExamSettings', () => {
},
});
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it('Does not update exam service if lti is not enabled in studio', async () => {
@@ -790,9 +738,7 @@ describe('ProctoredExamSettings', () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
// does not update exam service config
expect(axiosMock.history.patch.length).toBe(0);
// does update studio
@@ -806,11 +752,13 @@ describe('ProctoredExamSettings', () => {
},
});
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it('Makes studio API call generated error', async () => {
@@ -820,15 +768,15 @@ describe('ProctoredExamSettings', () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1);
const errorAlert = screen.getByTestId('saveError');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveError');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it('Makes exams API call generated error', async () => {
@@ -838,15 +786,15 @@ describe('ProctoredExamSettings', () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1);
const errorAlert = screen.getByTestId('saveError');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveError');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
);
expect(document.activeElement).toEqual(errorAlert);
});
});
it('Manages focus correctly after different save statuses', async () => {
@@ -857,30 +805,30 @@ describe('ProctoredExamSettings', () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1);
const errorAlert = screen.getByTestId('saveError');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
);
expect(document.activeElement).toEqual(errorAlert);
await waitFor(() => {
const errorAlert = screen.getByTestId('saveError');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
);
expect(document.activeElement).toEqual(errorAlert);
});
// now make a call that will allow for a successful save
axiosMock.onPost(
StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
).reply(200, 'success');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(2);
const successAlert = screen.getByTestId('saveSuccess');
expect(successAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(successAlert);
await waitFor(() => {
const successAlert = screen.getByTestId('saveSuccess');
expect(successAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
expect(document.activeElement).toEqual(successAlert);
});
});
it('Include Zendesk ticket in post request if user is not an admin', async () => {
@@ -891,13 +839,9 @@ describe('ProctoredExamSettings', () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
// Make a change to the proctoring provider
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
});
fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1);
expect(JSON.parse(axiosMock.history.post[0].data)).toEqual({
proctored_exam_settings: {

View File

@@ -59,10 +59,10 @@ describe('HeaderButtons Component', () => {
expect(previewLink).toHaveAttribute('href', expect.stringContaining(certificatesDataMock.courseModes[0]));
const dropdownButton = getByRole('button', { name: certificatesDataMock.courseModes[0] });
await userEvent.click(dropdownButton);
userEvent.click(dropdownButton);
const verifiedMode = await getByRole('button', { name: certificatesDataMock.courseModes[1] });
await userEvent.click(verifiedMode);
userEvent.click(verifiedMode);
await waitFor(() => {
expect(previewLink).toHaveAttribute('href', expect.stringContaining(certificatesDataMock.courseModes[1]));
@@ -78,7 +78,7 @@ describe('HeaderButtons Component', () => {
const { getByRole, queryByRole } = renderComponent();
const activationButton = getByRole('button', { name: messages.headingActionsActivate.defaultMessage });
await userEvent.click(activationButton);
userEvent.click(activationButton);
axiosMock.onPost(
getUpdateCertificateApiUrl(courseId, certificatesDataMock.certificates[0].id),
@@ -110,7 +110,7 @@ describe('HeaderButtons Component', () => {
const { getByRole, queryByRole } = renderComponent();
const deactivateButton = getByRole('button', { name: messages.headingActionsDeactivate.defaultMessage });
await userEvent.click(deactivateButton);
userEvent.click(deactivateButton);
axiosMock.onPost(
getUpdateCertificateApiUrl(courseId, certificatesDataMock.certificates[0].id),

View File

@@ -79,10 +79,9 @@ const ChecklistItemComment = ({
<ul className="assignment-list">
{gradedAssignmentsOutsideDateRange.map(assignment => (
<li className="assignment-list-item" key={assignment.id}>
<Hyperlink
content={assignment.displayName}
destination={`${outlineUrl}#${assignment.id}`}
/>
<Hyperlink destination={`${outlineUrl}#${assignment.id}`}>
{assignment.displayName}
</Hyperlink>
</li>
))}
</ul>

View File

@@ -1,6 +1,5 @@
import {
render,
act,
fireEvent,
screen,
waitFor,
@@ -71,6 +70,15 @@ const mockStore = async (
}
renderComponent();
await executeThunk(fetchAssets(courseId), store.dispatch);
// Finish loading the expected files into the data table before returning,
// because loading new files can disrupt things like accessing file menus.
if (status === RequestStatus.SUCCESSFUL) {
const numFiles = skipNextPageFetch ? 13 : 15;
await waitFor(() => {
expect(screen.getByText(`Showing ${numFiles} of ${numFiles}`)).toBeInTheDocument();
});
}
};
const emptyMockStore = async (status) => {
@@ -126,15 +134,13 @@ describe('FilesAndUploads', () => {
it('should upload a single file', async () => {
await emptyMockStore(RequestStatus.SUCCESSFUL);
const dropzone = screen.getByTestId('files-dropzone');
await act(async () => {
axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] });
axiosMock.onPost(getAssetsUrl(courseId)).reply(204, generateNewAssetApiResponse());
Object.defineProperty(dropzone, 'files', {
value: [file],
});
fireEvent.drop(dropzone);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] });
axiosMock.onPost(getAssetsUrl(courseId)).reply(204, generateNewAssetApiResponse());
Object.defineProperty(dropzone, 'files', {
value: [file],
});
fireEvent.drop(dropzone);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
const addStatus = store.getState().assets.addingStatus;
expect(addStatus).toEqual(RequestStatus.SUCCESSFUL);
@@ -185,9 +191,7 @@ describe('FilesAndUploads', () => {
expect(screen.queryByRole('table')).toBeNull();
const listButton = screen.getByLabelText('List');
await act(async () => {
fireEvent.click(listButton);
});
fireEvent.click(listButton);
expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull();
expect(screen.getByRole('table')).toBeVisible();
@@ -200,16 +204,13 @@ describe('FilesAndUploads', () => {
await mockStore(RequestStatus.SUCCESSFUL);
axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] });
axiosMock.onPost(getAssetsUrl(courseId)).reply(200, generateNewAssetApiResponse());
let addFilesButton;
const addFilesButton = screen.getByLabelText('file-input');
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
await waitFor(() => {
addFilesButton = screen.getByLabelText('file-input');
const addStatus = store.getState().assets.addingStatus;
expect(addStatus).toEqual(RequestStatus.SUCCESSFUL);
});
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
});
const addStatus = store.getState().assets.addingStatus;
expect(addStatus).toEqual(RequestStatus.SUCCESSFUL);
});
it('should show duplicate file modal', async () => {
@@ -219,14 +220,9 @@ describe('FilesAndUploads', () => {
axiosMock.onGet(
`${getAssetsUrl(courseId)}?display_name=mOckID6&page_size=1`,
).reply(200, { assets: [{ display_name: 'mOckID6' }] });
let addFilesButton;
await waitFor(() => {
addFilesButton = screen.getByLabelText('file-input');
});
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
});
const addFilesButton = screen.getByLabelText('file-input');
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
expect(screen.getByText(filesPageMessages.overwriteConfirmMessage.defaultMessage)).toBeVisible();
});
@@ -245,26 +241,21 @@ describe('FilesAndUploads', () => {
};
axiosMock.onPost(getAssetsUrl(courseId)).reply(200, responseData);
let addFilesButton;
await waitFor(() => {
addFilesButton = screen.getByLabelText('file-input');
});
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
});
const addFilesButton = screen.getByLabelText('file-input');
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
const overwriteButton = screen.getByText(filesPageMessages.confirmOverwriteButtonLabel.defaultMessage);
await act(async () => {
fireEvent.click(overwriteButton);
});
const assetData = store.getState().models.assets.mOckID6;
const { asset: responseAssetData } = responseData;
const [defaultData] = updateFileValues([camelCaseObject(responseAssetData)]);
fireEvent.click(overwriteButton);
expect(screen.queryByText(filesPageMessages.overwriteConfirmMessage.defaultMessage)).toBeNull();
expect(assetData).toEqual(defaultData);
await waitFor(() => {
const assetData = store.getState().models.assets.mOckID6;
const { asset: responseAssetData } = responseData;
const [defaultData] = updateFileValues([camelCaseObject(responseAssetData)]);
expect(assetData).toEqual(defaultData);
});
});
it('should keep original file', async () => {
@@ -274,19 +265,12 @@ describe('FilesAndUploads', () => {
axiosMock.onGet(
`${getAssetsUrl(courseId)}?display_name=mOckID6&page_size=1`,
).reply(200, { assets: [{ display_name: 'mOckID6' }] });
let addFilesButton;
await waitFor(() => {
addFilesButton = screen.getByLabelText('file-input');
});
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
});
const addFilesButton = screen.getByLabelText('file-input');
userEvent.upload(addFilesButton, file);
await executeThunk(validateAssetFiles(courseId, [file]), store.dispatch);
const cancelButton = screen.getByText(filesPageMessages.cancelOverwriteButtonLabel.defaultMessage);
await act(async () => {
fireEvent.click(cancelButton);
});
fireEvent.click(cancelButton);
const assetData = store.getState().models.assets.mOckID6;
const defaultAssets = generateFetchAssetApiResponse().assets;
@@ -299,12 +283,9 @@ describe('FilesAndUploads', () => {
it('should have disabled action buttons', async () => {
await mockStore(RequestStatus.SUCCESSFUL);
let actionsButton;
await waitFor(() => {
actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
fireEvent.click(actionsButton);
});
const actionsButton = await screen.getByText(messages.actionsButtonLabel.defaultMessage);
fireEvent.click(actionsButton);
expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled');
expect(screen.getByText(messages.deleteTitle.defaultMessage).closest('a')).toHaveClass('disabled');
@@ -312,19 +293,14 @@ describe('FilesAndUploads', () => {
it('delete button should be enabled and delete selected file', async () => {
await mockStore(RequestStatus.SUCCESSFUL);
let selectCardButton;
await waitFor(() => {
[selectCardButton] = screen.getAllByTestId('datatable-select-column-checkbox-cell');
fireEvent.click(selectCardButton);
});
const [selectCardButton] = await screen.findAllByTestId('datatable-select-column-checkbox-cell');
fireEvent.click(selectCardButton);
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
expect(actionsButton).toBeVisible();
fireEvent.click(actionsButton);
await waitFor(() => {
fireEvent.click(actionsButton);
});
const deleteButton = screen.getByText(messages.deleteTitle.defaultMessage).closest('a');
expect(deleteButton).not.toHaveClass('disabled');
@@ -332,24 +308,21 @@ describe('FilesAndUploads', () => {
fireEvent.click(deleteButton);
expect(screen.getByText('Delete mOckID1')).toBeVisible();
await act(async () => {
userEvent.click(deleteButton);
});
fireEvent.click(deleteButton);
// Wait for the delete confirmation button to appear
const confirmDeleteButton = await screen.findByRole('button', {
name: messages.deleteFileButtonLabel.defaultMessage,
});
await act(async () => {
userEvent.click(confirmDeleteButton);
});
fireEvent.click(confirmDeleteButton);
expect(screen.queryByText('Delete mOckID1')).toBeNull();
// Check if the asset is deleted in the store and UI
const deleteStatus = store.getState().assets.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL);
await waitFor(() => {
const deleteStatus = store.getState().assets.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL);
});
expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull();
});
@@ -360,9 +333,7 @@ describe('FilesAndUploads', () => {
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
expect(actionsButton).toBeVisible();
await waitFor(() => {
fireEvent.click(actionsButton);
});
fireEvent.click(actionsButton);
const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
expect(downloadButton).not.toHaveClass('disabled');
@@ -378,9 +349,7 @@ describe('FilesAndUploads', () => {
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
expect(actionsButton).toBeVisible();
await waitFor(() => {
fireEvent.click(actionsButton);
});
fireEvent.click(actionsButton);
const mockResponseData = { ok: true, blob: () => 'Data' };
const mockFetchResponse = Promise.resolve(mockResponseData);
const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
@@ -396,19 +365,18 @@ describe('FilesAndUploads', () => {
const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
expect(sortsButton).toBeVisible();
fireEvent.click(sortsButton);
await waitFor(() => {
fireEvent.click(sortsButton);
expect(screen.getByText(messages.sortModalTitleLabel.defaultMessage)).toBeVisible();
});
const sortNameAscendingButton = screen.getByText(messages.sortByNameAscending.defaultMessage);
fireEvent.click(sortNameAscendingButton);
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
await waitFor(() => {
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
});
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
});
it('sort button should be enabled and sort files by file size', async () => {
@@ -416,30 +384,26 @@ describe('FilesAndUploads', () => {
const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
expect(sortsButton).toBeVisible();
fireEvent.click(sortsButton);
await waitFor(() => {
fireEvent.click(sortsButton);
expect(screen.getByText(messages.sortModalTitleLabel.defaultMessage)).toBeVisible();
});
const sortBySizeDescendingButton = screen.getByText(messages.sortBySizeDescending.defaultMessage);
fireEvent.click(sortBySizeDescendingButton);
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
await waitFor(() => {
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
});
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
});
});
describe('card menu actions', () => {
it('should open asset info', async () => {
await mockStore(RequestStatus.SUCCESSFUL);
let assetMenuButton;
await waitFor(() => {
[assetMenuButton] = screen.getAllByTestId('file-menu-dropdown-mOckID1');
});
const [assetMenuButton] = await screen.getAllByTestId('file-menu-dropdown-mOckID1');
axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`)
.reply(201, {
@@ -450,14 +414,15 @@ describe('FilesAndUploads', () => {
}],
},
});
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
await executeThunk(getUsagePaths({
courseId,
asset: { id: 'mOckID1', displayName: 'mOckID1' },
setSelectedRows: jest.fn(),
}), store.dispatch);
await waitFor(() => {
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
executeThunk(getUsagePaths({
courseId,
asset: { id: 'mOckID1', displayName: 'mOckID1' },
setSelectedRows: jest.fn(),
}), store.dispatch);
expect(screen.getAllByLabelText('mOckID1')[0]).toBeVisible();
});
@@ -472,23 +437,23 @@ describe('FilesAndUploads', () => {
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false });
axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`).reply(201, { usage_locations: { mOckID1: [] } });
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
await executeThunk(getUsagePaths({
courseId,
asset: { id: 'mOckID1', displayName: 'mOckID1' },
setSelectedRows: jest.fn(),
}), store.dispatch);
await waitFor(() => {
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
executeThunk(getUsagePaths({
courseId,
asset: { id: 'mOckID1', displayName: 'mOckID1' },
setSelectedRows: jest.fn(),
}), store.dispatch);
expect(screen.getAllByLabelText('mOckID1')[0]).toBeVisible();
fireEvent.click(screen.getByLabelText('Checkbox'));
executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID1',
locked: false,
}), store.dispatch);
});
fireEvent.click(screen.getByLabelText('Checkbox'));
await executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID1',
locked: false,
}), store.dispatch);
expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible();
const updateStatus = store.getState().assets.updatingStatus;
@@ -500,18 +465,18 @@ describe('FilesAndUploads', () => {
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0];
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false });
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Unlock'));
await executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID1',
locked: false,
}), store.dispatch);
await waitFor(() => {
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false });
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Unlock'));
executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID1',
locked: false,
}), store.dispatch);
const updateStatus = store.getState().assets.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
const updateStatus = store.getState().assets.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
it('should lock asset', async () => {
@@ -519,18 +484,18 @@ describe('FilesAndUploads', () => {
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(201, { locked: true });
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Lock'));
await executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID3',
locked: true,
}), store.dispatch);
await waitFor(() => {
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(201, { locked: true });
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Lock'));
executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID3',
locked: true,
}), store.dispatch);
const updateStatus = store.getState().assets.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
const updateStatus = store.getState().assets.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
it('download button should download file', async () => {
@@ -538,10 +503,8 @@ describe('FilesAndUploads', () => {
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0];
await waitFor(() => {
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Download'));
});
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Download'));
expect(saveAs).toHaveBeenCalled();
});
@@ -550,17 +513,18 @@ describe('FilesAndUploads', () => {
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID1')[0];
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
await waitFor(() => {
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID1`).reply(204);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
expect(screen.getByText('Delete mOckID1')).toBeVisible();
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
expect(screen.queryByText('Delete mOckID1')).toBeNull();
executeThunk(deleteAssetFile(courseId, 'mOckID1', 5), store.dispatch);
});
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
await waitFor(() => {
expect(screen.queryByText('Delete mOckID1')).toBeNull();
});
await executeThunk(deleteAssetFile(courseId, 'mOckID1', 5), store.dispatch);
const deleteStatus = store.getState().assets.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL);
@@ -572,9 +536,7 @@ describe('FilesAndUploads', () => {
it('404 intitial fetch should show error', async () => {
await mockStore(RequestStatus.FAILED);
const { loadingStatus } = store.getState().assets;
await waitFor(() => {
expect(screen.getByText('Error')).toBeVisible();
});
expect(screen.getByText('Error')).toBeVisible();
expect(loadingStatus).toEqual(RequestStatus.FAILED);
expect(screen.getByText('Failed to load all files.')).toBeVisible();
@@ -583,9 +545,7 @@ describe('FilesAndUploads', () => {
it('404 intitial fetch should show error', async () => {
await mockStore(RequestStatus.SUCCESSFUL, true);
const { loadingStatus } = store.getState().assets;
await waitFor(() => {
expect(screen.getByText('Error')).toBeVisible();
});
expect(screen.getByText('Error')).toBeVisible();
expect(loadingStatus).toEqual(RequestStatus.PARTIAL_FAILURE);
expect(screen.getByText('Failed to load remaining files.')).toBeVisible();
@@ -597,12 +557,11 @@ describe('FilesAndUploads', () => {
axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] });
axiosMock.onPost(getAssetsUrl(courseId)).reply(413, { error: errorMessage });
const addFilesButton = screen.getByLabelText('file-input');
await act(async () => {
userEvent.upload(addFilesButton, file);
userEvent.upload(addFilesButton, file);
await waitFor(() => {
const addStatus = store.getState().assets.addingStatus;
expect(addStatus).toEqual(RequestStatus.FAILED);
});
const addStatus = store.getState().assets.addingStatus;
expect(addStatus).toEqual(RequestStatus.FAILED);
expect(screen.getByText('Error')).toBeVisible();
});
@@ -610,10 +569,8 @@ describe('FilesAndUploads', () => {
await mockStore(RequestStatus.SUCCESSFUL);
axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(404);
const addFilesButton = screen.getByLabelText('file-input');
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(addAssetFile(courseId, file, 1), store.dispatch);
});
userEvent.upload(addFilesButton, file);
await executeThunk(addAssetFile(courseId, file, 1), store.dispatch);
const addStatus = store.getState().assets.addingStatus;
expect(addStatus).toEqual(RequestStatus.FAILED);
@@ -625,10 +582,8 @@ describe('FilesAndUploads', () => {
axiosMock.onGet(`${getAssetsUrl(courseId)}?display_name=download.png&page_size=1`).reply(200, { assets: [] });
axiosMock.onPost(getAssetsUrl(courseId)).reply(404);
const addFilesButton = screen.getByLabelText('file-input');
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(addAssetFile(courseId, file, 1), store.dispatch);
});
userEvent.upload(addFilesButton, file);
await executeThunk(addAssetFile(courseId, file, 1), store.dispatch);
const addStatus = store.getState().assets.addingStatus;
expect(addStatus).toEqual(RequestStatus.FAILED);
@@ -640,17 +595,18 @@ describe('FilesAndUploads', () => {
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID3`).reply(404);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
await waitFor(() => {
axiosMock.onDelete(`${getAssetsUrl(courseId)}mOckID3`).reply(404);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
expect(screen.getByText('Delete mOckID3')).toBeVisible();
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
expect(screen.queryByText('Delete mOckID3')).toBeNull();
executeThunk(deleteAssetFile(courseId, 'mOckID3', 5), store.dispatch);
});
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
await waitFor(() => {
expect(screen.queryByText('Delete mOckID3')).toBeNull();
});
await executeThunk(deleteAssetFile(courseId, 'mOckID3', 5), store.dispatch);
const deleteStatus = store.getState().assets.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.FAILED);
@@ -665,17 +621,17 @@ describe('FilesAndUploads', () => {
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID3/usage`).reply(404);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
await executeThunk(getUsagePaths({
courseId,
asset: { id: 'mOckID3', displayName: 'mOckID3' },
setSelectedRows: jest.fn(),
}), store.dispatch);
await waitFor(() => {
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
executeThunk(getUsagePaths({
courseId,
asset: { id: 'mOckID3', displayName: 'mOckID3' },
setSelectedRows: jest.fn(),
}), store.dispatch);
const { usageStatus } = store.getState().assets;
expect(usageStatus).toEqual(RequestStatus.FAILED);
});
const { usageStatus } = store.getState().assets;
expect(usageStatus).toEqual(RequestStatus.FAILED);
});
it('404 lock update should show error', async () => {
@@ -683,19 +639,18 @@ describe('FilesAndUploads', () => {
const assetMenuButton = screen.getAllByTestId('file-menu-dropdown-mOckID3')[0];
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(404);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Lock'));
await executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID3',
locked: true,
}), store.dispatch);
await waitFor(() => {
axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID3`).reply(404);
fireEvent.click(within(assetMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Lock'));
executeThunk(updateAssetLock({
courseId,
assetId: 'mOckID3',
locked: true,
}), store.dispatch);
const updateStatus = store.getState().assets.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.FAILED);
});
const updateStatus = store.getState().assets.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.FAILED);
expect(screen.getByText('Error')).toBeVisible();
});
@@ -707,17 +662,15 @@ describe('FilesAndUploads', () => {
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
expect(actionsButton).toBeVisible();
await waitFor(() => {
fireEvent.click(actionsButton);
});
fireEvent.click(actionsButton);
const mockResponseData = { ok: false };
const mockFetchResponse = Promise.resolve(mockResponseData);
const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
expect(downloadButton).not.toHaveClass('disabled');
global.fetch = jest.fn().mockImplementation(() => mockFetchResponse);
fireEvent.click(downloadButton);
await waitFor(() => {
fireEvent.click(downloadButton);
expect(fetch).toHaveBeenCalledTimes(2);
});

View File

@@ -104,7 +104,7 @@ GalleryCard.propTypes = {
handleOpenDeleteConfirmation: PropTypes.func.isRequired,
handleOpenFileInfo: PropTypes.func.isRequired,
thumbnailPreview: PropTypes.func.isRequired,
fileType: PropTypes.func.isRequired,
fileType: PropTypes.string.isRequired,
};
export default GalleryCard;

View File

@@ -70,6 +70,15 @@ const mockStore = async (
renderComponent();
await executeThunk(fetchVideos(courseId), store.dispatch);
// Finish loading the expected files into the data table before returning,
// because loading new files can disrupt things like accessing file menus.
if (status === RequestStatus.SUCCESSFUL) {
const numFiles = 3;
await waitFor(() => {
expect(screen.getByText(`Showing ${numFiles} of ${numFiles}`)).toBeInTheDocument();
});
}
};
const emptyMockStore = async (status) => {
@@ -127,24 +136,24 @@ describe('Videos page', () => {
it('should upload a single file', async () => {
await emptyMockStore(RequestStatus.SUCCESSFUL);
const dropzone = screen.getByTestId('files-dropzone');
await act(async () => {
const mockResponseData = { status: '200', ok: true, blob: () => 'Data' };
const mockFetchResponse = Promise.resolve(mockResponseData);
global.fetch = jest.fn().mockImplementation(() => mockFetchResponse);
const mockResponseData = { status: '200', ok: true, blob: () => 'Data' };
const mockFetchResponse = Promise.resolve(mockResponseData);
global.fetch = jest.fn().mockImplementation(() => mockFetchResponse);
axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(204, generateNewVideoApiResponse());
axiosMock.onGet(getCourseVideosApiUrl(courseId)).reply(200, generateAddVideoApiResponse());
Object.defineProperty(dropzone, 'files', {
value: [file],
});
fireEvent.drop(dropzone);
await executeThunk(addVideoFile(courseId, file, [], { current: [] }), store.dispatch);
axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(204, generateNewVideoApiResponse());
axiosMock.onGet(getCourseVideosApiUrl(courseId)).reply(200, generateAddVideoApiResponse());
Object.defineProperty(dropzone, 'files', {
value: [file],
});
fireEvent.drop(dropzone);
await executeThunk(addVideoFile(courseId, file, [], { current: [] }), store.dispatch);
await waitFor(() => {
const addStatus = store.getState().videos.addingStatus;
expect(addStatus).toEqual(RequestStatus.SUCCESSFUL);
});
const addStatus = store.getState().videos.addingStatus;
expect(addStatus).toEqual(RequestStatus.SUCCESSFUL);
expect(screen.queryByTestId('files-dropzone')).toBeNull();
expect(screen.getByTestId('files-data-table')).toBeVisible();
});
});
@@ -170,9 +179,7 @@ describe('Videos page', () => {
const transcriptSettingsButton = screen.getByText(videoMessages.transcriptSettingsButtonLabel.defaultMessage);
expect(transcriptSettingsButton).toBeVisible();
await act(async () => {
fireEvent.click(transcriptSettingsButton);
});
fireEvent.click(transcriptSettingsButton);
expect(screen.getByLabelText('close settings')).toBeVisible();
});
@@ -200,9 +207,7 @@ describe('Videos page', () => {
expect(screen.queryByRole('table')).toBeNull();
const listButton = screen.getByLabelText('List');
await act(async () => {
fireEvent.click(listButton);
});
fireEvent.click(listButton);
expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull();
expect(screen.getByRole('table')).toBeVisible();
@@ -213,10 +218,8 @@ describe('Videos page', () => {
axiosMock.onPost(`${getApiBaseUrl()}/video_images/${courseId}/mOckID1`).reply(200, { image_url: 'url' });
const addThumbnailButton = screen.getByTestId('video-thumbnail-mOckID1');
const thumbnail = new File(['test'], 'sOMEUrl.jpg', { type: 'image/jpg' });
await act(async () => {
fireEvent.click(addThumbnailButton);
await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch);
});
fireEvent.click(addThumbnailButton);
await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch);
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
@@ -247,10 +250,8 @@ describe('Videos page', () => {
const addFilesButton = screen.getAllByLabelText('file-input')[3];
const { videoIds } = store.getState().videos;
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, videoIds, { current: [] }), store.dispatch);
});
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, videoIds, { current: [] }), store.dispatch);
const addStatus = store.getState().videos.addingStatus;
expect(addStatus).toEqual(RequestStatus.SUCCESSFUL);
});
@@ -270,9 +271,7 @@ describe('Videos page', () => {
uploadSpy.mockResolvedValue(new Promise(() => {}));
const addFilesButton = screen.getAllByLabelText('file-input')[3];
act(async () => {
userEvent.upload(addFilesButton, file);
});
userEvent.upload(addFilesButton, file);
await waitFor(() => {
const addStatus = store.getState().videos.addingStatus;
expect(addStatus).toEqual(RequestStatus.IN_PROGRESS);
@@ -293,23 +292,24 @@ describe('Videos page', () => {
await mockStore(RequestStatus.SUCCESSFUL);
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
fireEvent.click(actionsButton);
await waitFor(() => {
fireEvent.click(actionsButton);
});
expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled');
expect(screen.getByText(messages.downloadTitle.defaultMessage).closest('a')).toHaveClass('disabled');
expect(screen.getByText(messages.deleteTitle.defaultMessage).closest('a')).toHaveClass('disabled');
expect(screen.getByText(messages.deleteTitle.defaultMessage).closest('a')).toHaveClass('disabled');
});
});
it('delete button should be enabled and delete selected file', async () => {
await mockStore(RequestStatus.SUCCESSFUL);
const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0];
fireEvent.click(selectCardButton);
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
await waitFor(() => {
fireEvent.click(actionsButton);
});
const [selectCardButton] = await screen.findAllByTestId('datatable-select-column-checkbox-cell');
fireEvent.click(selectCardButton);
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
expect(actionsButton).toBeVisible();
fireEvent.click(actionsButton);
const deleteButton = screen.getByText(messages.deleteTitle.defaultMessage).closest('a');
expect(deleteButton).not.toHaveClass('disabled');
@@ -317,45 +317,22 @@ describe('Videos page', () => {
fireEvent.click(deleteButton);
expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible();
await act(async () => {
userEvent.click(deleteButton);
});
fireEvent.click(deleteButton);
// Wait for the delete confirmation button to appear
const confirmDeleteButton = await screen.findByRole('button', {
name: messages.deleteFileButtonLabel.defaultMessage,
});
await act(async () => {
userEvent.click(confirmDeleteButton);
});
fireEvent.click(confirmDeleteButton);
expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull();
// Check if the video is deleted in the store and UI
const deleteStatus = store.getState().videos.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL);
expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull();
});
it('download button should be enabled and download single selected file', async () => {
await mockStore(RequestStatus.SUCCESSFUL);
const selectCardButton = screen.getAllByTestId('datatable-select-column-checkbox-cell')[0];
fireEvent.click(selectCardButton);
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
await waitFor(() => {
fireEvent.click(actionsButton);
const deleteStatus = store.getState().videos.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL);
});
const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
expect(downloadButton).not.toHaveClass('disabled');
await act(async () => {
fireEvent.click(downloadButton);
});
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull();
});
it('download button should be enabled and download multiple selected files', async () => {
@@ -365,20 +342,18 @@ describe('Videos page', () => {
fireEvent.click(selectCardButtons[1]);
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
await waitFor(() => {
fireEvent.click(actionsButton);
});
fireEvent.click(actionsButton);
axiosMock.onPut(`${getVideosUrl(courseId)}/download`).reply(200, null);
const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
expect(downloadButton).not.toHaveClass('disabled');
await act(async () => {
fireEvent.click(downloadButton);
});
fireEvent.click(downloadButton);
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
await waitFor(() => {
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
});
describe('Sort and filter button', () => {
@@ -386,9 +361,7 @@ describe('Videos page', () => {
await mockStore(RequestStatus.SUCCESSFUL);
const sortAndFilterButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
await waitFor(() => {
fireEvent.click(sortAndFilterButton);
});
fireEvent.click(sortAndFilterButton);
});
describe('sort function', () => {
@@ -396,22 +369,22 @@ describe('Videos page', () => {
const sortNameAscendingButton = screen.getByText(messages.sortByNameAscending.defaultMessage);
fireEvent.click(sortNameAscendingButton);
await waitFor(() => {
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
});
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
await waitFor(() => {
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
});
});
it('sort button should be enabled and sort files by file size', async () => {
const sortBySizeDescendingButton = screen.getByText(messages.sortBySizeDescending.defaultMessage);
fireEvent.click(sortBySizeDescendingButton);
await waitFor(() => {
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
});
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
await waitFor(() => {
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
});
});
});
@@ -425,13 +398,12 @@ describe('Videos page', () => {
fireEvent.click(notTranscribedCheckboxFilter);
fireEvent.click(transcribedCheckboxFilter);
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
await waitFor(() => {
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
const galleryCards = screen.getAllByTestId('grid-card', { exact: false });
expect(galleryCards).toHaveLength(1);
});
const galleryCards = screen.getAllByTestId('grid-card', { exact: false });
expect(galleryCards).toHaveLength(1);
});
it('should clearAll selections', async () => {
@@ -445,7 +417,7 @@ describe('Videos page', () => {
fireEvent.click(transcribedCheckboxFilter);
const clearAllButton = screen.getByText('Clear all');
await waitFor(() => fireEvent.click(clearAllButton));
fireEvent.click(clearAllButton);
expect(transcribedCheckboxFilter).toHaveProperty('checked', false);
@@ -460,11 +432,9 @@ describe('Videos page', () => {
const transcribedCheckboxFilter = screen.getByText(videoMessages.transcribedCheckboxLabel.defaultMessage);
fireEvent.click(transcribedCheckboxFilter);
await waitFor(() => {
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
});
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
const imageFilterChip = screen.getByRole('button', { name: 'Remove this filter' });
const imageFilterChip = await screen.findByRole('button', { name: 'Remove this filter' });
fireEvent.click(imageFilterChip);
expect(screen.queryByText(videoMessages.transcribedCheckboxLabel.defaultMessage)).toBeNull();
@@ -488,12 +458,12 @@ describe('Videos page', () => {
}],
});
await waitFor(() => {
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
});
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
expect(screen.getByText(messages.infoTitle.defaultMessage)).toBeVisible();
await waitFor(() => {
expect(screen.getByText(messages.infoTitle.defaultMessage)).toBeVisible();
});
const { usageStatus } = store.getState().videos;
@@ -507,13 +477,12 @@ describe('Videos page', () => {
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID1/usage`).reply(201, { usageLocations: [] });
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
await waitFor(() => {
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible();
});
expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible();
const infoTab = screen.getAllByRole('tab')[0];
expect(infoTab).toBeVisible();
@@ -525,17 +494,14 @@ describe('Videos page', () => {
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID1/usage`).reply(201, { usageLocations: [] });
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
await waitFor(() => {
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible();
});
expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible();
const transcriptTab = screen.getAllByRole('tab')[1];
await act(async () => {
fireEvent.click(transcriptTab);
});
fireEvent.click(transcriptTab);
expect(transcriptTab).toBeVisible();
expect(transcriptTab).toHaveClass('active');
@@ -546,15 +512,11 @@ describe('Videos page', () => {
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3');
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID3/usage`).reply(201, { usageLocations: [] });
await waitFor(() => {
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
});
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
const transcriptTab = screen.getAllByRole('tab')[1];
await act(async () => {
fireEvent.click(transcriptTab);
});
const transcriptTab = await screen.getAllByRole('tab')[1];
fireEvent.click(transcriptTab);
expect(screen.getByText('Transcript (1)')).toBeVisible();
});
@@ -565,14 +527,13 @@ describe('Videos page', () => {
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Download'));
await waitFor(() => {
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Download'));
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
});
it('delete button should delete file', async () => {
@@ -580,17 +541,18 @@ describe('Videos page', () => {
const fileMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(204);
fireEvent.click(within(fileMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
await waitFor(() => {
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(204);
fireEvent.click(within(fileMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible();
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull();
executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch);
});
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
await waitFor(() => {
expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull();
});
await executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch);
const deleteStatus = store.getState().videos.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.SUCCESSFUL);
@@ -614,10 +576,8 @@ describe('Videos page', () => {
axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(413, { error: errorMessage });
const addFilesButton = screen.getAllByLabelText('file-input')[3];
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch);
});
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch);
const addStatus = store.getState().videos.addingStatus;
expect(addStatus).toEqual(RequestStatus.FAILED);
@@ -629,10 +589,8 @@ describe('Videos page', () => {
axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(404);
const addFilesButton = screen.getAllByLabelText('file-input')[3];
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch);
});
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch);
const addStatus = store.getState().videos.addingStatus;
expect(addStatus).toEqual(RequestStatus.FAILED);
@@ -645,10 +603,8 @@ describe('Videos page', () => {
const addThumbnailButton = screen.getByTestId('video-thumbnail-mOckID1');
const thumbnail = new File(['test'], 'sOMEUrl.jpg', { type: 'image/jpg' });
await act(async () => {
fireEvent.click(addThumbnailButton);
await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch);
});
fireEvent.click(addThumbnailButton);
await executeThunk(addVideoThumbnail({ file: thumbnail, videoId: 'mOckID1', courseId }), store.dispatch);
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.FAILED);
@@ -664,10 +620,8 @@ describe('Videos page', () => {
axiosMock.onPost(getCourseVideosApiUrl(courseId)).reply(204, generateNewVideoApiResponse());
axiosMock.onGet(getCourseVideosApiUrl(courseId)).reply(200, generateAddVideoApiResponse());
const addFilesButton = screen.getAllByLabelText('file-input')[3];
await act(async () => {
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch);
});
userEvent.upload(addFilesButton, file);
await executeThunk(addVideoFile(courseId, file, undefined, { current: [] }), store.dispatch);
const addStatus = store.getState().videos.addingStatus;
expect(addStatus).toEqual(RequestStatus.FAILED);
@@ -679,16 +633,19 @@ describe('Videos page', () => {
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1');
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(404);
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
await waitFor(async () => {
axiosMock.onDelete(`${getCourseVideosApiUrl(courseId)}/mOckID1`).reply(404);
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByTestId('open-delete-confirmation-button'));
expect(screen.getByText('Delete mOckID1.mp4')).toBeVisible();
});
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
fireEvent.click(screen.getByText(messages.deleteFileButtonLabel.defaultMessage));
await waitFor(async () => {
expect(screen.queryByText('Delete mOckID1.mp4')).toBeNull();
});
executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch);
await executeThunk(deleteVideoFile(courseId, 'mOckID1', 5), store.dispatch);
await waitFor(() => {
const deleteStatus = store.getState().videos.deletingStatus;
expect(deleteStatus).toEqual(RequestStatus.FAILED);
@@ -705,14 +662,12 @@ describe('Videos page', () => {
const videoMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3');
axiosMock.onGet(`${getVideosUrl(courseId)}/mOckID3/usage`).reply(404);
await waitFor(() => {
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
executeThunk(getUsagePaths({
courseId,
video: { id: 'mOckID3', displayName: 'mOckID3' },
}), store.dispatch);
});
fireEvent.click(within(videoMenuButton).getByLabelText('file-menu-toggle'));
fireEvent.click(screen.getByText('Info'));
await executeThunk(getUsagePaths({
courseId,
video: { id: 'mOckID3', displayName: 'mOckID3' },
}), store.dispatch);
await waitFor(() => {
const { usageStatus } = store.getState().videos;
expect(usageStatus).toEqual(RequestStatus.FAILED);
@@ -728,18 +683,13 @@ describe('Videos page', () => {
const actionsButton = screen.getByText(messages.actionsButtonLabel.defaultMessage);
expect(actionsButton).toBeVisible();
await waitFor(() => {
fireEvent.click(actionsButton);
});
const downloadButton = screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
fireEvent.click(actionsButton);
const downloadButton = await screen.getByText(messages.downloadTitle.defaultMessage).closest('a');
expect(downloadButton).not.toHaveClass('disabled');
axiosMock.onPut(`${getVideosUrl(courseId)}/download`).reply(404);
await waitFor(() => {
fireEvent.click(downloadButton);
executeThunk(fetchVideoDownload([{ original: { displayName: 'mOckID1', id: '2', downloadLink: 'test' } }]), store.dispatch);
});
fireEvent.click(downloadButton);
await executeThunk(fetchVideoDownload([{ original: { displayName: 'mOckID1', id: '2', downloadLink: 'test' } }]), store.dispatch);
const updateStatus = store.getState().videos.updatingStatus;
expect(updateStatus).toEqual(RequestStatus.FAILED);

View File

@@ -1,6 +1,6 @@
import {
render,
act,
fireEvent,
screen,
waitFor,
within,
@@ -70,9 +70,7 @@ describe('TranscriptSettings', () => {
it('should change view to order form', async () => {
renderComponent(defaultProps);
const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage);
await act(async () => {
userEvent.click(orderButton);
});
userEvent.click(orderButton);
const selectableButtons = screen.getAllByLabelText('none radio')[0];
expect(selectableButtons).toBeVisible();
@@ -81,17 +79,14 @@ describe('TranscriptSettings', () => {
it('should return to order transcript collapsible', async () => {
renderComponent(defaultProps);
const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage);
await act(async () => {
userEvent.click(orderButton);
});
userEvent.click(orderButton);
const selectableButtons = screen.getAllByLabelText('none radio')[0];
expect(selectableButtons).toBeVisible();
const backButton = screen.getByLabelText('back button to main transcript settings view');
userEvent.click(backButton);
await waitFor(() => {
userEvent.click(backButton);
expect(screen.queryByLabelText('back button to main transcript settings view')).toBeNull();
});
});
@@ -99,13 +94,9 @@ describe('TranscriptSettings', () => {
it('discard changes should call closeTranscriptSettings', async () => {
renderComponent(defaultProps);
const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage);
await act(async () => {
userEvent.click(orderButton);
});
userEvent.click(orderButton);
const discardButton = screen.getByText(messages.discardSettingsLabel.defaultMessage);
await act(async () => {
userEvent.click(discardButton);
});
userEvent.click(discardButton);
expect(defaultProps.closeTranscriptSettings).toHaveBeenCalled();
});
@@ -145,9 +136,7 @@ describe('TranscriptSettings', () => {
it('should load page with Cielo24 selected', async () => {
const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage);
await act(async () => {
userEvent.click(orderButton);
});
userEvent.click(orderButton);
const cielo24Button = screen.getByText(messages.cieloLabel.defaultMessage);
expect(within(cielo24Button).getByLabelText('Cielo24 radio')).toHaveProperty('checked', true);
@@ -185,38 +174,32 @@ describe('TranscriptSettings', () => {
renderComponent(defaultProps);
const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage);
await act(async () => {
userEvent.click(orderButton);
});
userEvent.click(orderButton);
const noneButton = screen.getAllByLabelText('none radio')[0];
await act(async () => {
userEvent.click(noneButton);
});
userEvent.click(noneButton);
});
it('api should succeed', async () => {
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
axiosMock.onDelete(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(204);
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
});
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
});
it('should show error alert', async () => {
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
axiosMock.onDelete(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404);
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
});
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
expect(screen.getByText('Failed to update order transcripts settings.')).toBeVisible();
});
@@ -237,24 +220,18 @@ describe('TranscriptSettings', () => {
renderComponent(defaultProps);
const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage);
await act(async () => {
userEvent.click(orderButton);
});
userEvent.click(orderButton);
});
it('should ask for Cielo24 or 3Play Media credentials', async () => {
const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0];
await act(async () => {
userEvent.click(cielo24Button);
});
userEvent.click(cielo24Button);
const cieloCredentialMessage = screen.getByTestId('cieloCredentialMessage');
expect(cieloCredentialMessage).toBeVisible();
const threePlayMediaButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayMediaButton);
});
userEvent.click(threePlayMediaButton);
const threePlayMediaCredentialMessage = screen.getByTestId('threePlayMediaCredentialMessage');
expect(threePlayMediaCredentialMessage).toBeVisible();
@@ -263,9 +240,7 @@ describe('TranscriptSettings', () => {
describe('api succeeds', () => {
it('should update cielo24 credentials ', async () => {
const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0];
await act(async () => {
userEvent.click(cielo24Button);
});
userEvent.click(cielo24Button);
const firstInput = screen.getByLabelText(messages.cieloApiKeyLabel.defaultMessage);
const secondInput = screen.getByLabelText(messages.cieloUsernameLabel.defaultMessage);
@@ -279,14 +254,12 @@ describe('TranscriptSettings', () => {
});
axiosMock.onPost(`${getApiBaseUrl()}/transcript_credentials/${courseId}`).reply(200);
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
});
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
expect(screen.queryByTestId('cieloCredentialMessage')).toBeNull();
expect(screen.getByText(messages.cieloFidelityLabel.defaultMessage)).toBeVisible();
@@ -294,9 +267,7 @@ describe('TranscriptSettings', () => {
it('should update 3Play Media credentials', async () => {
const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayButton);
});
userEvent.click(threePlayButton);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const firstInput = screen.getByLabelText(messages.threePlayMediaApiKeyLabel.defaultMessage);
@@ -310,12 +281,12 @@ describe('TranscriptSettings', () => {
});
axiosMock.onPost(`${getApiBaseUrl()}/transcript_credentials/${courseId}`).reply(200);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
fireEvent.click(updateButton);
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
await waitFor(() => {
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
});
expect(screen.queryByTestId('threePlayCredentialMessage')).toBeNull();
@@ -326,9 +297,7 @@ describe('TranscriptSettings', () => {
describe('api fails', () => {
it('should show error alert on Cielo24 credentials update', async () => {
const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0];
await act(async () => {
userEvent.click(cielo24Button);
});
userEvent.click(cielo24Button);
const firstInput = screen.getByLabelText(messages.cieloApiKeyLabel.defaultMessage);
const secondInput = screen.getByLabelText(messages.cieloUsernameLabel.defaultMessage);
@@ -342,21 +311,19 @@ describe('TranscriptSettings', () => {
});
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(503);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
fireEvent.click(updateButton);
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
await waitFor(() => {
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
});
expect(screen.getByText('Failed to update Cielo24 credentials.')).toBeVisible();
});
it('should show error alert on 3PlayMedia credentials update', async () => {
const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayButton);
});
userEvent.click(threePlayButton);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const firstInput = screen.getByLabelText(messages.threePlayMediaApiKeyLabel.defaultMessage);
@@ -370,12 +337,12 @@ describe('TranscriptSettings', () => {
});
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
fireEvent.click(updateButton);
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
await waitFor(() => {
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
});
expect(screen.getByText('Failed to update 3PlayMedia credentials.')).toBeVisible();
});
@@ -408,24 +375,18 @@ describe('TranscriptSettings', () => {
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
renderComponent(defaultProps);
const orderButton = screen.getByText(messages.orderTranscriptsTitle.defaultMessage);
await act(async () => {
userEvent.click(orderButton);
});
userEvent.click(orderButton);
});
it('should not show credentials request for Cielo24 and 3Play Media', async () => {
const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0];
await act(async () => {
userEvent.click(cielo24Button);
});
userEvent.click(cielo24Button);
const cieloCredentialMessage = screen.queryByTestId('cieloCredentialMessage');
expect(cieloCredentialMessage).toBeNull();
const threePlayMediaButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayMediaButton);
});
userEvent.click(threePlayMediaButton);
const threePlayMediaCredentialMessage = screen.queryByTestId('threePlayMediaCredentialMessage');
expect(threePlayMediaCredentialMessage).toBeNull();
@@ -443,9 +404,7 @@ describe('TranscriptSettings', () => {
};
const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0];
await act(async () => {
userEvent.click(cielo24Button);
});
userEvent.click(cielo24Button);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const turnaround = screen.getByText(messages.cieloTurnaroundPlaceholder.defaultMessage);
const fidelity = screen.getByText(messages.cieloFidelityPlaceholder.defaultMessage);
@@ -469,12 +428,13 @@ describe('TranscriptSettings', () => {
expect(updateButton).not.toHaveAttribute('disabled');
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(200, apiResponse);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
fireEvent.click(updateButton);
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
await waitFor(() => {
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
});
expect(screen.getByText(messages.cieloFidelityLabel.defaultMessage)).toBeVisible();
});
@@ -488,9 +448,7 @@ describe('TranscriptSettings', () => {
global: false,
};
const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayButton);
});
userEvent.click(threePlayButton);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage);
const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage);
@@ -512,12 +470,12 @@ describe('TranscriptSettings', () => {
});
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(200, apiResponse);
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
});
});
it('should update 3Play Media preferences with spanish as source language', async () => {
@@ -529,9 +487,7 @@ describe('TranscriptSettings', () => {
global: false,
};
const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayButton);
});
userEvent.click(threePlayButton);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage);
const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage);
@@ -550,21 +506,19 @@ describe('TranscriptSettings', () => {
expect(updateButton).not.toHaveAttribute('disabled');
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(200, apiResponse);
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
expect(transcriptStatus).toEqual(RequestStatus.SUCCESSFUL);
});
});
});
describe('api fails', () => {
it('should show error alert on Cielo24 preferences update', async () => {
const cielo24Button = screen.getAllByLabelText('Cielo24 radio')[0];
await act(async () => {
userEvent.click(cielo24Button);
});
userEvent.click(cielo24Button);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const turnaround = screen.getByText(messages.cieloTurnaroundPlaceholder.defaultMessage);
const fidelity = screen.getByText(messages.cieloFidelityPlaceholder.defaultMessage);
@@ -588,21 +542,19 @@ describe('TranscriptSettings', () => {
expect(updateButton).not.toHaveAttribute('disabled');
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(503);
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
});
expect(screen.getByText('Failed to update Cielo24 transcripts settings.')).toBeVisible();
});
it('should show error alert with default message on 3PlayMedia preferences update', async () => {
const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayButton);
});
userEvent.click(threePlayButton);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage);
const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage);
@@ -621,21 +573,19 @@ describe('TranscriptSettings', () => {
expect(updateButton).not.toHaveAttribute('disabled');
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404);
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
});
expect(screen.getByText('Failed to update 3PlayMedia transcripts settings.')).toBeVisible();
});
it('should show error alert with default message on 3PlayMedia preferences update', async () => {
const threePlayButton = screen.getAllByLabelText('3PlayMedia radio')[0];
await act(async () => {
userEvent.click(threePlayButton);
});
userEvent.click(threePlayButton);
const updateButton = screen.getByText(messages.updateSettingsLabel.defaultMessage);
const turnaround = screen.getByText(messages.threePlayMediaTurnaroundPlaceholder.defaultMessage);
const source = screen.getByText(messages.threePlayMediaSourceLanguagePlaceholder.defaultMessage);
@@ -654,12 +604,12 @@ describe('TranscriptSettings', () => {
expect(updateButton).not.toHaveAttribute('disabled');
axiosMock.onPost(`${getApiBaseUrl()}/transcript_preferences/${courseId}`).reply(404, { error: 'Invalid turnaround.' });
fireEvent.click(updateButton);
await waitFor(() => {
userEvent.click(updateButton);
});
const { transcriptStatus } = store.getState().videos;
const { transcriptStatus } = store.getState().videos;
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
expect(transcriptStatus).toEqual(RequestStatus.FAILED);
});
expect(screen.getByText('Invalid turnaround.')).toBeVisible();
});

View File

@@ -1,6 +1,5 @@
import React from 'react';
import {
act,
fireEvent,
screen,
render,
@@ -129,9 +128,7 @@ describe('<CreateOrRerunCourseForm />', () => {
render(<RootWrapper {...props} />);
await mockStore();
const cancelBtn = screen.getByRole('button', { name: messages.cancelButton.defaultMessage });
await act(async () => {
fireEvent.click(cancelBtn);
});
fireEvent.click(cancelBtn);
expect(onClickCancelMock).toHaveBeenCalled();
});
@@ -147,13 +144,11 @@ describe('<CreateOrRerunCourseForm />', () => {
const runInput = screen.getByPlaceholderText(messages.courseRunPlaceholder.defaultMessage);
const createBtn = screen.getByRole('button', { name: messages.createButton.defaultMessage });
await act(async () => {
userEvent.type(displayNameInput, 'foo course name');
fireEvent.click(orgInput);
userEvent.type(numberInput, '777');
userEvent.type(runInput, '1');
userEvent.click(createBtn);
});
userEvent.type(displayNameInput, 'foo course name');
fireEvent.click(orgInput);
userEvent.type(numberInput, '777');
userEvent.type(runInput, '1');
userEvent.click(createBtn);
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url });
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
@@ -171,13 +166,11 @@ describe('<CreateOrRerunCourseForm />', () => {
const createBtn = screen.getByRole('button', { name: messages.createButton.defaultMessage });
await axiosMock.onPost(getCreateOrRerunCourseUrl()).reply(200, { url, destinationCourseKey });
await act(async () => {
userEvent.type(displayNameInput, 'foo course name');
fireEvent.click(orgInput);
userEvent.type(numberInput, '777');
userEvent.type(runInput, '1');
userEvent.click(createBtn);
});
userEvent.type(displayNameInput, 'foo course name');
fireEvent.click(orgInput);
userEvent.type(numberInput, '777');
userEvent.type(runInput, '1');
userEvent.click(createBtn);
await executeThunk(updateCreateOrRerunCourseQuery({ org: 'testX', run: 'some' }), store.dispatch);
expect(mockedUsedNavigate).toHaveBeenCalledWith(`${url}${destinationCourseKey}`);
@@ -208,12 +201,10 @@ describe('<CreateOrRerunCourseForm />', () => {
const numberInput = screen.getByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage);
const runInput = screen.getByPlaceholderText(messages.courseRunPlaceholder.defaultMessage);
await act(async () => {
fireEvent.change(displayNameInput, { target: { value: 'foo course name' } });
fireEvent.click(orgInput);
fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } });
fireEvent.change(runInput, { target: { value: 'number with invalid (=) symbol' } });
});
fireEvent.change(displayNameInput, { target: { value: 'foo course name' } });
fireEvent.click(orgInput);
fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } });
fireEvent.change(runInput, { target: { value: 'number with invalid (=) symbol' } });
waitFor(() => {
expect(createBtn).toBeDisabled();
@@ -262,9 +253,7 @@ describe('<CreateOrRerunCourseForm />', () => {
await mockStore();
const numberInput = screen.getByPlaceholderText(messages.courseNumberPlaceholder.defaultMessage);
await act(async () => {
fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } });
});
fireEvent.change(numberInput, { target: { value: 'number with invalid (+) symbol' } });
waitFor(() => {
expect(screen.getByText(messages.noSpaceError)).toBeInTheDocument();

View File

@@ -116,7 +116,7 @@ describe('<ModalDropzone />', () => {
const dropzoneInput = getByRole('presentation', { hidden: true }).firstChild;
const uploadButton = getByRole('button', { name: messages.uploadModal.defaultMessage });
await userEvent.upload(dropzoneInput, file);
userEvent.upload(dropzoneInput, file);
await waitFor(() => {
expect(uploadButton).not.toBeDisabled();

View File

@@ -5,13 +5,18 @@ import {
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider, PageWrap } from '@edx/frontend-platform/react';
import {
act, findByRole, getByRole, queryByLabelText, queryByRole, queryByTestId, queryByText, render,
screen, waitFor, waitForElementToBeRemoved,
act, findByRole, fireEvent, getByRole, queryByLabelText, queryByRole, queryByTestId, queryByText,
render, screen, waitFor, waitForElementToBeRemoved,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MockAdapter from 'axios-mock-adapter';
import React from 'react';
import { Routes, Route, MemoryRouter } from 'react-router-dom';
import {
Routes,
Route,
MemoryRouter,
useLocation,
} from 'react-router-dom';
import { fetchCourseDetail } from '../../data/thunks';
import initializeStore from '../../store';
import { executeThunk } from '../../utils';
@@ -37,6 +42,11 @@ let container;
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
ReactDOM.createPortal = jest.fn(node => node);
const LocationDisplay = () => {
const location = useLocation();
return <div data-testid="location-display">{location.pathname}</div>;
};
function renderComponent(route) {
const wrapper = render(
<AppProvider store={store} wrapWithRouter={false}>
@@ -52,6 +62,7 @@ function renderComponent(route) {
element={<PageWrap><DiscussionsSettings courseId={courseId} /></PageWrap>}
/>
</Routes>
<LocationDisplay />
</MemoryRouter>
</PagesAndResourcesProvider>
</AppProvider>,
@@ -94,7 +105,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
expect(queryByTestId(container, 'appList')).toBeInTheDocument();
expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();
@@ -104,7 +115,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
@@ -114,7 +125,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
await waitFor(() => {
userEvent.click(queryByLabelText(container, 'Select Piazza'));
@@ -134,7 +145,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
await waitFor(() => {
userEvent.click(queryByLabelText(container, 'Select edX'));
@@ -151,11 +162,11 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
await act(() => userEvent.click(queryByText(container, appMessages.backButton.defaultMessage)));
await waitFor(() => userEvent.click(queryByText(container, appMessages.backButton.defaultMessage)));
await waitFor(() => {
expect(queryByTestId(container, 'appList')).toBeInTheDocument();
@@ -167,7 +178,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
expect(queryByTestId(container, 'appList')).toBeInTheDocument();
@@ -183,22 +194,27 @@ describe('DiscussionsSettings', () => {
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
act(async () => {
userEvent.click(queryByLabelText(container, 'Select Piazza'));
userEvent.click(screen.getByLabelText('Select Piazza'));
userEvent.click(getByRole(container, 'button', { name: 'Next' }));
userEvent.click(await findByRole(container, 'button', { name: 'Save' }));
// This is an important line that ensures the Close button has been removed, which implies that
// the full screen modal has been closed following our click of Apply. Once this has happened,
// then it's safe to proceed with our expectations.
await waitFor(() => expect(screen.queryByRole(container, 'button', { name: 'Close' })).toBeNull());
await waitFor(() => expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources`));
// Have to use fireEvent.click with these Stepper buttons so that the
// onClick handler is triggered. (userEvent.click doesn't trigger onClick).
await act(async () => {
fireEvent.click(getByRole(container, 'button', { name: 'Next' }));
});
await act(async () => {
fireEvent.click(getByRole(container, 'button', { name: 'Save' }));
});
// This is an important line that ensures the Close button has been removed, which implies that
// the full screen modal has been closed following our click of Apply. Once this has happened,
// then it's safe to proceed with our expectations.
await waitFor(() => expect(screen.queryByRole(container, 'button', { name: 'Close' })).toBeNull());
// Confirm route is correct
const locationDisplay = await screen.findByTestId('location-display');
await waitFor(() => expect(locationDisplay.textContent).toEqual(`/course/${courseId}/pages-and-resources`));
});
test('requires confirmation if changing provider', async () => {
@@ -208,20 +224,18 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
act(async () => {
userEvent.click(getByRole(container, 'checkbox', { name: 'Select Discourse' }));
userEvent.click(getByRole(container, 'button', { name: 'Next' }));
userEvent.click(getByRole(container, 'checkbox', { name: 'Select Discourse' }));
userEvent.click(getByRole(container, 'button', { name: 'Next' }));
await findByRole(container, 'button', { name: 'Save' });
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'key');
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret');
userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test');
userEvent.click(getByRole(container, 'button', { name: 'Save' }));
await findByRole(container, 'button', { name: 'Save' });
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'key');
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret');
userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test');
userEvent.click(getByRole(container, 'button', { name: 'Save' }));
await waitFor(() => expect(queryByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument());
});
await waitFor(() => expect(queryByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument());
});
test('can cancel confirmation', async () => {
@@ -231,7 +245,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
const discourseBox = getByRole(container, 'checkbox', { name: 'Select Discourse' });
expect(discourseBox).not.toBeDisabled();
@@ -241,20 +255,18 @@ describe('DiscussionsSettings', () => {
await waitFor(() => expect(screen.queryByRole('status')).toBeNull());
act(async () => {
expect(await findByRole(container, 'heading', { name: 'Discourse' })).toBeInTheDocument();
expect(await findByRole(container, 'heading', { name: 'Discourse' })).toBeInTheDocument();
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'a');
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret');
userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test');
userEvent.click(getByRole(container, 'button', { name: 'Save' }));
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'a');
userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret');
userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test');
userEvent.click(getByRole(container, 'button', { name: 'Save' }));
waitFor(() => expect(getByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument());
userEvent.click(getByRole(container, 'button', { name: 'Cancel' }));
await waitFor(() => expect(getByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument());
userEvent.click(getByRole(container, 'button', { name: 'Cancel' }));
expect(queryByRole(container, 'dialog', { name: 'Confirm' })).not.toBeInTheDocument();
expect(queryByRole(container, 'dialog', { name: 'Configure discussion' }));
});
expect(queryByRole(container, 'dialog', { name: 'Confirm' })).not.toBeInTheDocument();
expect(queryByRole(container, 'dialog', { name: 'Configure discussion' }));
});
});
@@ -274,7 +286,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
const alert = queryByRole(container, 'alert');
expect(alert).toBeInTheDocument();
@@ -302,19 +314,17 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
// Apply causes an async action to take place
act(async () => {
userEvent.click(queryByText(container, appMessages.saveButton.defaultMessage));
await waitFor(() => expect(axiosMock.history.post.length).toBe(1));
userEvent.click(queryByText(container, appMessages.saveButton.defaultMessage));
await waitFor(() => expect(axiosMock.history.post.length).toBe(1));
expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
const alert = await findByRole(container, 'alert');
expect(alert).toBeInTheDocument();
expect(alert.textContent).toEqual(expect.stringContaining('We encountered a technical error when applying changes.'));
expect(alert.innerHTML).toEqual(expect.stringContaining(getConfig().SUPPORT_URL));
});
expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();
const alert = await findByRole(container, 'alert');
expect(alert).toBeInTheDocument();
expect(alert.textContent).toEqual(expect.stringContaining('We encountered a technical error when applying changes.'));
expect(alert.innerHTML).toEqual(expect.stringContaining(getConfig().SUPPORT_URL));
});
});
@@ -328,7 +338,7 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
const alert = queryByRole(container, 'alert');
expect(alert).toBeInTheDocument();
@@ -348,23 +358,23 @@ describe('DiscussionsSettings', () => {
renderComponent(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
await waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
act(async () => {
userEvent.click(getByRole(container, 'button', { name: 'Save' }));
userEvent.click(getByRole(container, 'button', { name: 'Save' }));
await waitFor(() => expect(axiosMock.history.post.length).toBe(1));
await waitFor(() => expect(axiosMock.history.post.length).toBe(1));
expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();
expect(queryByTestId(container, 'appList')).not.toBeInTheDocument();
expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();
// We don't technically leave the route in this case, though the modal is hidden.
expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`);
// Confirm route is correct
// We don't technically leave the route in this case, though the modal is hidden.
const locationDisplay = await screen.findByTestId('location-display');
await waitFor(() => expect(locationDisplay.textContent).toEqual(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`));
const alert = await findByRole(container, 'alert');
expect(alert).toBeInTheDocument();
expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.'));
});
const alert = await findByRole(container, 'alert');
expect(alert).toBeInTheDocument();
expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.'));
});
});
});
@@ -406,13 +416,13 @@ describe.each([
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
act(async () => {
userEvent.click(await screen.findByLabelText('Select Piazza'));
userEvent.click(queryByText(container, messages.nextButton.defaultMessage));
waitForElementToBeRemoved(screen.getByRole('status'));
userEvent.click(screen.getByLabelText('Select Piazza'));
userEvent.click(queryByText(container, messages.nextButton.defaultMessage));
expect(screen.queryByRole('status')).not.toBeInTheDocument();
await waitFor(() => {
if (showLTIConfig) {
expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).toBeInTheDocument();
expect(queryByTestId(container, 'ltiConfigFields')).toBeInTheDocument();
@@ -460,18 +470,16 @@ describe.each([
renderComponent(`/course/${courseId}/pages-and-resources/discussion`);
// This is an important line that ensures the spinner has been removed - and thus our main
// content has been loaded - prior to proceeding with our expectations.
waitForElementToBeRemoved(screen.getByRole('status'));
await waitForElementToBeRemoved(screen.queryByRole('status'));
act(async () => {
userEvent.click(await screen.findByLabelText('Select Piazza'));
userEvent.click(await screen.findByText(messages.nextButton.defaultMessage));
userEvent.click(screen.getByLabelText('Select Piazza'));
userEvent.click(screen.getByText(messages.nextButton.defaultMessage));
waitForElementToBeRemoved(screen.getByRole('status'));
if (enablePIISharing) {
expect(queryByTestId(container, 'piiSharingFields')).toBeInTheDocument();
} else {
expect(queryByTestId(container, 'piiSharingFields')).not.toBeInTheDocument();
}
});
expect(screen.queryByRole('status')).not.toBeInTheDocument();
if (enablePIISharing) {
expect(queryByTestId(container, 'piiSharingFields')).toBeInTheDocument();
} else {
expect(queryByTestId(container, 'piiSharingFields')).not.toBeInTheDocument();
}
});
});

View File

@@ -40,7 +40,7 @@ const DiscussionRestriction = () => {
const discussionRestrictionButtons = useMemo(() => discussionRestrictionOptions.map((restriction) => (
<Button
key={`restriction-${restriction.value}`}
data-testId={restriction.value}
data-testid={restriction.value}
variant="plain"
className={classNames('w-100 font-size-14 font-weight-500 line-height-20 py-7px border-light-400 unselected-button', {
'text-white bg-primary-500 selected-button': selectedRestrictionOption === restriction.value,

View File

@@ -50,7 +50,7 @@ const RestrictDatesInput = ({
className={fieldClasses}
onBlur={handleFocusOut}
onFocus={handleSetFocus}
data-testId={dataTestId}
data-testid={dataTestId}
/>
<FieldFeedback
feedbackCondition={inFocus}

View File

@@ -47,7 +47,7 @@ const RestrictionSchedules = () => {
}, []);
return (
<div data-testId="restriction-schedules">
<div data-testid="restriction-schedules">
<FieldArray
name="restrictedDates"
render={({ push, remove }) => (

View File

@@ -132,7 +132,7 @@ const AppList = ({ intl }) => {
labelClassName="line-height-24"
onChange={handleChange}
checked={!enabled}
data-testId="hide-discussion"
data-testid="hide-discussion"
>
{intl.formatMessage(messages.hideDiscussionTab)}
</Form.Switch>

View File

@@ -133,21 +133,22 @@ describe('<ScheduleAndDetails />', () => {
const { getAllByPlaceholderText, getByText } = render(
<RootWrapper />,
);
let inputs;
await waitFor(() => {
const inputs = getAllByPlaceholderText(DATE_FORMAT.toLocaleUpperCase());
act(() => {
fireEvent.change(inputs[0], { target: { value: '06/16/2023' } });
});
expect(
getByText(messages.alertWarning.defaultMessage),
).toBeInTheDocument();
inputs = getAllByPlaceholderText(DATE_FORMAT.toLocaleUpperCase());
});
fireEvent.change(inputs[0], { target: { value: '06/16/2023' } });
expect(
getByText(messages.alertWarning.defaultMessage),
).toBeInTheDocument();
});
it('should display a success message when course details saves', async () => {
const { getByText } = render(<RootWrapper />);
await executeThunk(updateCourseDetailsQuery(courseId, 'DaTa'), store.dispatch);
await waitFor(() => {
executeThunk(updateCourseDetailsQuery(courseId, 'DaTa'), store.dispatch);
});
expect(getByText(messages.alertSuccess.defaultMessage)).toBeInTheDocument();
});
@@ -176,7 +177,9 @@ describe('<ScheduleAndDetails />', () => {
.onPut(getCourseDetailsApiUrl(courseId))
.reply(404, 'error');
const { getByText } = render(<RootWrapper />);
await executeThunk(updateCourseDetailsQuery(courseId, 'DaTa'), store.dispatch);
await act(async () => {
await executeThunk(updateCourseDetailsQuery(courseId, 'DaTa'), store.dispatch);
});
expect(getByText(messages.alertFail.defaultMessage)).toBeInTheDocument();
});
});

View File

@@ -49,7 +49,7 @@ const RootWrapper = () => (
</AppProvider>
);
describe('<StudioHome />', async () => {
describe('<StudioHome />', () => {
describe('api fetch fails', () => {
beforeEach(async () => {
initializeMockApp({

View File

@@ -43,7 +43,7 @@ const props = {
state: COURSE_CREATOR_STATES.unrequested,
};
describe('<CollapsibleStateWithAction />', async () => {
describe('<CollapsibleStateWithAction />', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {

View File

@@ -30,7 +30,7 @@ const RootWrapper = () => (
</AppProvider>
);
describe('<OrganizationSection />', async () => {
describe('<OrganizationSection />', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {

View File

@@ -37,7 +37,7 @@ const course = {
const props = { course };
describe('<CourseItem />', async () => {
describe('<CourseItem />', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {

View File

@@ -54,7 +54,7 @@ const RootWrapper = () => (
</AppProvider>
);
describe('<TaxonomyLayout />', async () => {
describe('<TaxonomyLayout />', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {

View File

@@ -37,7 +37,7 @@ const TaxonomyCardComponent = ({ original }) => (
TaxonomyCardComponent.propTypes = TaxonomyCard.propTypes;
describe('<TaxonomyCard />', async () => {
describe('<TaxonomyCard />', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {

View File

@@ -26,7 +26,7 @@ const TaxonomyCardComponent = ({ taxonomy }) => (
TaxonomyCardComponent.propTypes = TaxonomyDetailSideCard.propTypes;
describe('<TaxonomyDetailSideCard/>', async () => {
describe('<TaxonomyDetailSideCard/>', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {