improved focus management and help text (#32)
This commit is contained in:
@@ -38,6 +38,7 @@ function ExamSettings(props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const alertRef = React.createRef();
|
const alertRef = React.createRef();
|
||||||
|
const saveStatusAlertRef = React.createRef();
|
||||||
const proctoringEscalationEmailInputRef = useRef(null);
|
const proctoringEscalationEmailInputRef = useRef(null);
|
||||||
|
|
||||||
function onEnableProctoredExamsChange(event) {
|
function onEnableProctoredExamsChange(event) {
|
||||||
@@ -260,8 +261,7 @@ function ExamSettings(props) {
|
|||||||
id="authoring.examsettings.allowoptout.help"
|
id="authoring.examsettings.allowoptout.help"
|
||||||
defaultMessage={`
|
defaultMessage={`
|
||||||
If this value is "Yes", learners can choose to take proctored exams without proctoring.
|
If this value is "Yes", learners can choose to take proctored exams without proctoring.
|
||||||
If this value is "No", all learners must take the exam with proctoring.
|
If this value is "No", all learners must take the exam with proctoring.
|
||||||
This setting only applies if proctored exams are enabled for the course.
|
|
||||||
`}
|
`}
|
||||||
description="Help text for proctored exam opt out radio selection"
|
description="Help text for proctored exam opt out radio selection"
|
||||||
/>
|
/>
|
||||||
@@ -443,6 +443,8 @@ function ExamSettings(props) {
|
|||||||
variant="success"
|
variant="success"
|
||||||
dismissible
|
dismissible
|
||||||
data-test-id="saveSuccess"
|
data-test-id="saveSuccess"
|
||||||
|
tabIndex="-1"
|
||||||
|
ref={saveStatusAlertRef}
|
||||||
onClose={() => setSaveSuccess(false)}
|
onClose={() => setSaveSuccess(false)}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -463,6 +465,8 @@ function ExamSettings(props) {
|
|||||||
variant="danger"
|
variant="danger"
|
||||||
dismissible
|
dismissible
|
||||||
data-test-id="saveError"
|
data-test-id="saveError"
|
||||||
|
tabIndex="-1"
|
||||||
|
ref={saveStatusAlertRef}
|
||||||
onClose={() => setSaveError(false)}
|
onClose={() => setSaveError(false)}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -520,10 +524,13 @@ function ExamSettings(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if ((saveSuccess || saveError) && !!saveStatusAlertRef.current) {
|
||||||
|
saveStatusAlertRef.current.focus();
|
||||||
|
}
|
||||||
if (!formStatus.isValid && !!alertRef.current) {
|
if (!formStatus.isValid && !!alertRef.current) {
|
||||||
alertRef.current.focus();
|
alertRef.current.focus();
|
||||||
}
|
}
|
||||||
}, [formStatus]);
|
}, [formStatus, saveSuccess, saveError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
|
|||||||
@@ -234,9 +234,14 @@ describe('ProctoredExamSettings validation with invalid escalation email', () =>
|
|||||||
fireEvent.click(selectButton);
|
fireEvent.click(selectButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
// verify there is no alert and focus is maintained on the button
|
// verify there is no escalation email alert, and focus has been set on save success alert
|
||||||
expect(screen.queryByTestId('proctortrackEscalationEmailError')).toBeNull();
|
expect(screen.queryByTestId('proctortrackEscalationEmailError')).toBeNull();
|
||||||
expect(document.activeElement).toEqual(selectButton);
|
|
||||||
|
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 Proctortrack', async () => {
|
it('Escalation Email field hidden when proctoring backend is not Proctortrack', async () => {
|
||||||
@@ -526,6 +531,7 @@ describe('ProctoredExamSettings save settings tests', () => {
|
|||||||
expect(errorAlert.textContent).toEqual(
|
expect(errorAlert.textContent).toEqual(
|
||||||
expect.stringContaining('Proctored exam settings saved successfully.'),
|
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 () => {
|
it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => {
|
||||||
@@ -555,12 +561,12 @@ describe('ProctoredExamSettings save settings tests', () => {
|
|||||||
expect(errorAlert.textContent).toEqual(
|
expect(errorAlert.textContent).toEqual(
|
||||||
expect.stringContaining('Proctored exam settings saved successfully.'),
|
expect.stringContaining('Proctored exam settings saved successfully.'),
|
||||||
);
|
);
|
||||||
|
expect(document.activeElement).toEqual(errorAlert);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Makes API call generated error', async () => {
|
it('Makes API call generated error', async () => {
|
||||||
const mockedFunctions = mockAPI(mockGetData, false);
|
const mockedFunctions = mockAPI(mockGetData, false);
|
||||||
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
|
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
|
||||||
// Make a change to the provider to proctortrack and set the email
|
|
||||||
const submitButton = screen.getByTestId('submissionButton');
|
const submitButton = screen.getByTestId('submissionButton');
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
@@ -570,6 +576,35 @@ describe('ProctoredExamSettings save settings tests', () => {
|
|||||||
expect(errorAlert.textContent).toEqual(
|
expect(errorAlert.textContent).toEqual(
|
||||||
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
|
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 () => {
|
||||||
|
// first make a call that will cause a save error
|
||||||
|
const mockedFunctionsBadCall = mockAPI(mockGetData, false);
|
||||||
|
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)));
|
||||||
|
const submitButton = screen.getByTestId('submissionButton');
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
expect(mockedFunctionsBadCall.mockClientPost).toHaveBeenCalled();
|
||||||
|
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
|
||||||
|
const mockedFunctionsGoodCall = mockAPI(mockGetData, { data: 'success' });
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
expect(mockedFunctionsGoodCall.mockClientPost).toHaveBeenCalled();
|
||||||
|
const successAlert = screen.getByTestId('saveSuccess');
|
||||||
|
expect(successAlert.textContent).toEqual(
|
||||||
|
expect.stringContaining('Proctored exam settings saved successfully.'),
|
||||||
|
);
|
||||||
|
expect(document.activeElement).toEqual(successAlert);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user