allow upload options if user is in experiment
allow upload options if user is in experiment testing removed unused import fixed unit test and corrected button text combined assignment line for values coming from context fixed error in message added testing for photo mode panel restructured added checks for portrait and ID panels removed unused import, added checks for reroutes added tests and corrected messages fix
This commit is contained in:
66
src/id-verification/CollapsibleImageHelp.jsx
Normal file
66
src/id-verification/CollapsibleImageHelp.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Collapsible } from '@edx/paragon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import IdVerificationContext, { MEDIA_ACCESS } from './IdVerificationContext';
|
||||
import messages from './IdVerification.messages';
|
||||
|
||||
function CollapsibleImageHelp(props) {
|
||||
const {
|
||||
shouldUseCamera, setShouldUseCamera, optimizelyExperimentName, mediaAccess,
|
||||
} = useContext(IdVerificationContext);
|
||||
|
||||
function handleClick() {
|
||||
setShouldUseCamera(!shouldUseCamera);
|
||||
}
|
||||
|
||||
if (optimizelyExperimentName && mediaAccess !== MEDIA_ACCESS.DENIED && mediaAccess !== MEDIA_ACCESS.UNSUPPORTED) {
|
||||
return (
|
||||
<Collapsible
|
||||
styling="card"
|
||||
title={shouldUseCamera ? props.intl.formatMessage(messages['id.verification.photo.upload.help.title']) : props.intl.formatMessage(messages['id.verification.photo.camera.help.title'])}
|
||||
className="mb-4 shadow"
|
||||
defaultOpen
|
||||
>
|
||||
<p data-testid="help-text">
|
||||
{shouldUseCamera
|
||||
? props.intl.formatMessage(messages['id.verification.photo.upload.help.text'])
|
||||
: props.intl.formatMessage(messages['id.verification.photo.camera.help.text'])}
|
||||
</p>
|
||||
{ (mediaAccess === MEDIA_ACCESS.PENDING && !shouldUseCamera)
|
||||
? (
|
||||
// if a user has not enabled camera access yet, and they are trying to switch
|
||||
// to camera mode, direct them to panel that requests camera access
|
||||
<Link
|
||||
to={{ pathname: 'request-camera-access', state: { fromPortraitCapture: props.isPortrait, fromIdCapture: !props.isPortrait } }}
|
||||
className="btn btn-primary"
|
||||
data-testid="access-link"
|
||||
>
|
||||
{props.intl.formatMessage(messages['id.verification.photo.camera.help.button'])}
|
||||
</Link>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
title={shouldUseCamera ? 'Upload Portrait Photo' : 'Take Portrait Photo'}
|
||||
data-testid="toggle-button"
|
||||
onClick={handleClick}
|
||||
style={{ marginTop: '0.5rem' }}
|
||||
>
|
||||
{shouldUseCamera ? props.intl.formatMessage(messages['id.verification.photo.upload.help.button']) : props.intl.formatMessage(messages['id.verification.photo.camera.help.button'])}
|
||||
</Button>
|
||||
)}
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
CollapsibleImageHelp.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
isPortrait: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(CollapsibleImageHelp);
|
||||
@@ -641,6 +641,56 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Return to Course',
|
||||
description: 'Return to the course which ID verification was accessed from.',
|
||||
},
|
||||
'id.verification.photo.upload.help.title': {
|
||||
id: 'id.verification.photo.upload.help.title',
|
||||
defaultMessage: 'Upload a Photo Instead',
|
||||
description: 'Title for section that allows switching to photo upload mode.',
|
||||
},
|
||||
'id.verification.photo.camera.help.title': {
|
||||
id: 'id.verification.photo.camera.help.title',
|
||||
defaultMessage: 'Use Your Camera Instead',
|
||||
description: 'Title for section that allows switching to camera mode.',
|
||||
},
|
||||
'id.verification.photo.upload.help.text': {
|
||||
id: 'id.verification.photo.upload.help.text',
|
||||
defaultMessage: 'If you are having trouble using the photo capture above, you may want to upload a photo instead. To upload a photo, click the button below.',
|
||||
description: 'Help text for switching to upload mode.',
|
||||
},
|
||||
'id.verification.photo.camera.help.text': {
|
||||
id: 'id.verification.photo.camera.help.text',
|
||||
defaultMessage: 'If you are having trouble uploading a photo above, you may want to use your camera instead. To use your camera, click the button below.',
|
||||
description: 'Help text for switching to camera mode.',
|
||||
},
|
||||
'id.verification.photo.upload.help.button': {
|
||||
id: 'id.verification.upload.help.button',
|
||||
defaultMessage: 'Switch to Upload Mode',
|
||||
description: 'Button used to switch to upload mode.',
|
||||
},
|
||||
'id.verification.photo.camera.help.button': {
|
||||
id: 'id.verification.camera.help.button',
|
||||
defaultMessage: 'Switch to Camera Mode',
|
||||
description: 'Button used to switch to camera mode.',
|
||||
},
|
||||
'id.verification.choose.mode.title': {
|
||||
id: 'id.verification.choose.mode.title',
|
||||
defaultMessage: 'Photo Requirements Options',
|
||||
description: 'Title for section that allows user to choose photo mode.',
|
||||
},
|
||||
'id.verification.choose.mode.help.text': {
|
||||
id: 'id.verification.choose.mode.hep.text',
|
||||
defaultMessage: 'To complete verification, please select one of the following options to submit photos. You will be able to switch between these options throughout the process if needed.',
|
||||
description: 'Help text for section that allows user to choose photo mode.',
|
||||
},
|
||||
'id.verification.choose.mode.radio.upload': {
|
||||
id: 'id.verification.choose.mode.radio.upload',
|
||||
defaultMessage: 'Upload photos from my device',
|
||||
description: 'Radio button to choose to upload photos.',
|
||||
},
|
||||
'id.verification.choose.mode.radio.camera': {
|
||||
id: 'id.verification.choose.mode.radio.camera',
|
||||
defaultMessage: 'Take pictures using my camera',
|
||||
description: 'Radio button to choose to use camera for photos.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -77,6 +77,7 @@ export default function IdVerificationContextProvider({ children }) {
|
||||
}, [authenticatedUser]);
|
||||
|
||||
const [optimizelyExperimentName, setOptimizelyExperimentName] = useState('');
|
||||
const [shouldUseCamera, setShouldUseCamera] = useState(false);
|
||||
|
||||
const contextValue = {
|
||||
existingIdVerification,
|
||||
@@ -89,16 +90,19 @@ export default function IdVerificationContextProvider({ children }) {
|
||||
nameOnAccount: authenticatedUser.name,
|
||||
profileDataManager,
|
||||
optimizelyExperimentName,
|
||||
shouldUseCamera,
|
||||
setExistingIdVerification,
|
||||
setFacePhotoFile,
|
||||
setIdPhotoFile,
|
||||
setIdPhotoName,
|
||||
setOptimizelyExperimentName,
|
||||
setShouldUseCamera,
|
||||
tryGetUserMedia: async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
setMediaAccess(MEDIA_ACCESS.GRANTED);
|
||||
setMediaStream(stream);
|
||||
setShouldUseCamera(true);
|
||||
// stop the stream, as we are not using it yet
|
||||
const tracks = stream.getTracks();
|
||||
tracks.forEach(track => track.stop());
|
||||
|
||||
@@ -11,6 +11,7 @@ import './getUserMediaShim';
|
||||
|
||||
import IdVerificationContextProvider from './IdVerificationContextProvider';
|
||||
import ReviewRequirementsPanel from './panels/ReviewRequirementsPanel';
|
||||
import ChooseModePanel from './panels/ChooseModePanel';
|
||||
import RequestCameraAccessPanel from './panels/RequestCameraAccessPanel';
|
||||
import PortraitPhotoContextPanel from './panels/PortraitPhotoContextPanel';
|
||||
import TakePortraitPhotoPanel from './panels/TakePortraitPhotoPanel';
|
||||
@@ -52,6 +53,7 @@ function IdVerificationPage(props) {
|
||||
<IdVerificationContextProvider>
|
||||
<Switch>
|
||||
<Route path={`${path}/review-requirements`} component={ReviewRequirementsPanel} />
|
||||
<Route path={`${path}/choose-mode`} component={ChooseModePanel} />
|
||||
<Route path={`${path}/request-camera-access`} component={RequestCameraAccessPanel} />
|
||||
<Route path={`${path}/portrait-photo-context`} component={PortraitPhotoContextPanel} />
|
||||
<Route path={`${path}/take-portrait-photo`} component={TakePortraitPhotoPanel} />
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function ImageFileUpload({ onFileChange, intl }) {
|
||||
<>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
accept=".png, .jpg, .jpeg"
|
||||
data-testid="fileUpload"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
60
src/id-verification/panels/ChooseModePanel.jsx
Normal file
60
src/id-verification/panels/ChooseModePanel.jsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Form } from '@edx/paragon';
|
||||
|
||||
import { useNextPanelSlug } from '../routing-utilities';
|
||||
import BasePanel from './BasePanel';
|
||||
import IdVerificationContext from '../IdVerificationContext';
|
||||
import messages from '../IdVerification.messages';
|
||||
|
||||
function ChooseModePanel(props) {
|
||||
const panelSlug = 'choose-mode';
|
||||
const { shouldUseCamera, setShouldUseCamera } = useContext(IdVerificationContext);
|
||||
|
||||
function onPhotoModeChange(value) {
|
||||
setShouldUseCamera(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<BasePanel
|
||||
name={panelSlug}
|
||||
title={props.intl.formatMessage(messages['id.verification.choose.mode.title'])}
|
||||
>
|
||||
<p>
|
||||
{props.intl.formatMessage(messages['id.verification.choose.mode.help.text'])}
|
||||
</p>
|
||||
<fieldset>
|
||||
<Form.Group controlId="formChoosePhotoOption" style={{ marginLeft: '1.25rem' }}>
|
||||
<Form.Check
|
||||
type="radio"
|
||||
id="useUploadMode"
|
||||
label={props.intl.formatMessage(messages['id.verification.choose.mode.radio.upload'])}
|
||||
name="photoMode"
|
||||
checked={!shouldUseCamera}
|
||||
onChange={() => onPhotoModeChange(false)}
|
||||
/>
|
||||
<Form.Check
|
||||
type="radio"
|
||||
id="useCameraMode"
|
||||
label={props.intl.formatMessage(messages['id.verification.choose.mode.radio.camera'])}
|
||||
name="photoMode"
|
||||
checked={shouldUseCamera}
|
||||
onChange={() => onPhotoModeChange(true)}
|
||||
/>
|
||||
</Form.Group>
|
||||
</fieldset>
|
||||
<div className="action-row">
|
||||
<Link to={useNextPanelSlug(panelSlug)} className="btn btn-primary" data-testid="next-button">
|
||||
{props.intl.formatMessage(messages['id.verification.next'])}
|
||||
</Link>
|
||||
</div>
|
||||
</BasePanel>
|
||||
);
|
||||
}
|
||||
|
||||
ChooseModePanel.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(ChooseModePanel);
|
||||
@@ -9,24 +9,43 @@ import IdVerificationContext from '../IdVerificationContext';
|
||||
|
||||
import messages from '../IdVerification.messages';
|
||||
import CameraHelp from '../CameraHelp';
|
||||
import ImagePreview from '../ImagePreview';
|
||||
import ImageFileUpload from '../ImageFileUpload';
|
||||
import CollapsibleImageHelp from '../CollapsibleImageHelp';
|
||||
|
||||
function TakeIdPhotoPanel(props) {
|
||||
const panelSlug = 'take-id-photo';
|
||||
const nextPanelSlug = useNextPanelSlug(panelSlug);
|
||||
const { setIdPhotoFile, idPhotoFile } = useContext(IdVerificationContext);
|
||||
const {
|
||||
setIdPhotoFile, idPhotoFile, optimizelyExperimentName, shouldUseCamera,
|
||||
} = useContext(IdVerificationContext);
|
||||
|
||||
return (
|
||||
<BasePanel
|
||||
name={panelSlug}
|
||||
title={props.intl.formatMessage(messages['id.verification.id.photo.title.camera'])}
|
||||
title={shouldUseCamera ? props.intl.formatMessage(messages['id.verification.id.photo.title.camera']) : props.intl.formatMessage(messages['id.verification.id.photo.title.upload'])}
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.camera'])}
|
||||
</p>
|
||||
<Camera onImageCapture={setIdPhotoFile} isPortrait={false} />
|
||||
{idPhotoFile && !shouldUseCamera && <ImagePreview src={idPhotoFile} alt={props.intl.formatMessage(messages['id.verification.id.photo.preview.alt'])} />}
|
||||
|
||||
{shouldUseCamera ? (
|
||||
<div>
|
||||
<p>
|
||||
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.camera'])}
|
||||
</p>
|
||||
<Camera onImageCapture={setIdPhotoFile} isPortrait={false} />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginBottom: '1.25rem' }}>
|
||||
<p data-testid="upload-text">
|
||||
{props.intl.formatMessage(messages['id.verification.id.photo.instructions.upload'])}
|
||||
</p>
|
||||
<ImageFileUpload onFileChange={setIdPhotoFile} intl={props.intl} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CameraHelp />
|
||||
{shouldUseCamera && !optimizelyExperimentName && <CameraHelp />}
|
||||
<CollapsibleImageHelp isPortrait={false} />
|
||||
<div className="action-row" style={{ visibility: idPhotoFile ? 'unset' : 'hidden' }}>
|
||||
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
|
||||
{props.intl.formatMessage(messages['id.verification.next'])}
|
||||
|
||||
@@ -11,14 +11,14 @@ import CameraHelp from '../CameraHelp';
|
||||
import IdVerificationContext from '../IdVerificationContext';
|
||||
|
||||
import messages from '../IdVerification.messages';
|
||||
import CollapsibleImageHelp from '../CollapsibleImageHelp';
|
||||
|
||||
function TakePortraitPhotoPanel(props) {
|
||||
const panelSlug = 'take-portrait-photo';
|
||||
const nextPanelSlug = useNextPanelSlug(panelSlug);
|
||||
const { setFacePhotoFile, facePhotoFile } = useContext(IdVerificationContext);
|
||||
const shouldUseCamera = true;
|
||||
// to reenable upload component:
|
||||
// const shouldUseCamera = mediaAccess === MEDIA_ACCESS.GRANTED;
|
||||
const {
|
||||
setFacePhotoFile, facePhotoFile, shouldUseCamera, optimizelyExperimentName,
|
||||
} = useContext(IdVerificationContext);
|
||||
|
||||
return (
|
||||
<BasePanel
|
||||
@@ -36,15 +36,16 @@ function TakePortraitPhotoPanel(props) {
|
||||
<Camera onImageCapture={setFacePhotoFile} isPortrait />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p>
|
||||
<div style={{ marginBottom: '1.25rem' }}>
|
||||
<p data-testid="upload-text">
|
||||
{props.intl.formatMessage(messages['id.verification.portrait.photo.instructions.upload'])}
|
||||
</p>
|
||||
<ImageFileUpload onFileChange={setFacePhotoFile} />
|
||||
<ImageFileUpload onFileChange={setFacePhotoFile} intl={props.intl} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{shouldUseCamera && <CameraHelp isPortrait />}
|
||||
{shouldUseCamera && !optimizelyExperimentName && <CameraHelp isPortrait />}
|
||||
<CollapsibleImageHelp isPortrait />
|
||||
<div className="action-row" style={{ visibility: facePhotoFile ? 'unset' : 'hidden' }}>
|
||||
<Link to={nextPanelSlug} className="btn btn-primary" data-testid="next-button">
|
||||
{props.intl.formatMessage(messages['id.verification.next'])}
|
||||
|
||||
@@ -4,6 +4,7 @@ import IdVerificationContext from './IdVerificationContext';
|
||||
|
||||
const panelSteps = [
|
||||
'review-requirements',
|
||||
'choose-mode',
|
||||
'request-camera-access',
|
||||
'portrait-photo-context',
|
||||
'take-portrait-photo',
|
||||
@@ -19,9 +20,28 @@ export const useNextPanelSlug = (originSlug) => {
|
||||
// Go back to the summary view if that's where they came from
|
||||
const location = useLocation();
|
||||
const isFromSummary = location.state && location.state.fromSummary;
|
||||
const isFromPortrait = location.state && location.state.fromPortraitCapture;
|
||||
const isFromId = location.state && location.state.fromIdCapture;
|
||||
const { shouldUseCamera, optimizelyExperimentName } = useContext(IdVerificationContext);
|
||||
|
||||
if (isFromSummary) {
|
||||
return 'summary';
|
||||
}
|
||||
if (isFromPortrait) {
|
||||
return 'portrait-photo-context';
|
||||
}
|
||||
if (isFromId) {
|
||||
return 'id-context';
|
||||
}
|
||||
if (originSlug === 'review-requirements' && !optimizelyExperimentName) {
|
||||
return 'request-camera-access';
|
||||
}
|
||||
if (originSlug === 'choose-mode' && !shouldUseCamera) {
|
||||
return 'take-portrait-photo';
|
||||
}
|
||||
if (originSlug === 'take-portrait-photo' && !shouldUseCamera) {
|
||||
return 'take-id-photo';
|
||||
}
|
||||
|
||||
const nextIndex = panelSteps.indexOf(originSlug) + 1;
|
||||
return nextIndex < panelSteps.length ? panelSteps[nextIndex] : null;
|
||||
@@ -30,9 +50,11 @@ export const useNextPanelSlug = (originSlug) => {
|
||||
// check if the user is too far into the flow and if so, return the slug of the
|
||||
// furthest panel they are allow to be.
|
||||
export const useVerificationRedirectSlug = (slug) => {
|
||||
const { facePhotoFile, idPhotoFile } = useContext(IdVerificationContext);
|
||||
const { facePhotoFile, idPhotoFile, optimizelyExperimentName } = useContext(IdVerificationContext);
|
||||
const indexOfCurrentPanel = panelSteps.indexOf(slug);
|
||||
|
||||
if (!optimizelyExperimentName && slug === 'choose-mode') {
|
||||
return 'review-requirements';
|
||||
}
|
||||
if (!facePhotoFile) {
|
||||
if (indexOfCurrentPanel > panelSteps.indexOf('take-portrait-photo')) {
|
||||
return 'portrait-photo-context';
|
||||
|
||||
153
src/id-verification/tests/CollapsibleImageHelp.test.jsx
Normal file
153
src/id-verification/tests/CollapsibleImageHelp.test.jsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import {
|
||||
render, cleanup, screen, act, fireEvent,
|
||||
} from '@testing-library/react';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import IdVerificationContext from '../IdVerificationContext';
|
||||
import CollapsibleImageHelp from '../CollapsibleImageHelp';
|
||||
|
||||
jest.mock('jslib-html5-camera-photo');
|
||||
jest.mock('@tensorflow-models/blazeface');
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
|
||||
analytics.sendTrackEvent = jest.fn();
|
||||
|
||||
window.HTMLMediaElement.prototype.play = () => {};
|
||||
|
||||
const IntlCollapsible = injectIntl(CollapsibleImageHelp);
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
describe('CollapsibleImageHelpPanel', () => {
|
||||
const defaultProps = {
|
||||
intl: {},
|
||||
isPortrait: true,
|
||||
};
|
||||
|
||||
const contextValue = {
|
||||
shouldUseCamera: true,
|
||||
setShouldUseCamera: jest.fn(),
|
||||
optimizelyExperimentName: '',
|
||||
mediaAccess: 'granted',
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('does not return if not part of experiment', async () => {
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlCollapsible {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
const titleText = screen.queryByText('Upload a Photo Instead');
|
||||
expect(titleText).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not return if media access denied or unsupported', async () => {
|
||||
let titleText = '';
|
||||
contextValue.mediaAccess = 'denied';
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlCollapsible {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
titleText = screen.queryByText('Upload a Photo Instead');
|
||||
expect(titleText).not.toBeInTheDocument();
|
||||
|
||||
contextValue.mediaAccess = 'unsupported';
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlCollapsible {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
titleText = screen.queryByText('Upload a Photo Instead');
|
||||
expect(titleText).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows the correct text if user should switch to upload', async () => {
|
||||
contextValue.optimizelyExperimentName = 'test';
|
||||
contextValue.mediaAccess = 'granted';
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlCollapsible {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
const titleText = screen.getByText('Upload a Photo Instead');
|
||||
expect(titleText).toBeInTheDocument();
|
||||
const helpText = screen.getByTestId('help-text');
|
||||
expect(helpText.textContent).toContain('If you are having trouble using the photo capture above');
|
||||
const button = screen.getByTestId('toggle-button');
|
||||
expect(button).toHaveTextContent('Switch to Upload Mode');
|
||||
});
|
||||
|
||||
it('shows the correct text if user should switch to camera', async () => {
|
||||
contextValue.optimizelyExperimentName = 'test';
|
||||
contextValue.mediaAccess = 'granted';
|
||||
contextValue.shouldUseCamera = false;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlCollapsible {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
const titleText = screen.getByText('Use Your Camera Instead');
|
||||
expect(titleText).toBeInTheDocument();
|
||||
const helpText = screen.getByTestId('help-text');
|
||||
expect(helpText.textContent).toContain('If you are having trouble uploading a photo above');
|
||||
const button = screen.getByTestId('toggle-button');
|
||||
expect(button).toHaveTextContent('Switch to Camera Mode');
|
||||
});
|
||||
|
||||
it('shows the correct text if user should switch to camera with pending media access', async () => {
|
||||
contextValue.optimizelyExperimentName = 'test';
|
||||
contextValue.mediaAccess = 'pending';
|
||||
contextValue.shouldUseCamera = false;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlCollapsible {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
const titleText = screen.getByText('Use Your Camera Instead');
|
||||
expect(titleText).toBeInTheDocument();
|
||||
const helpText = screen.getByTestId('help-text');
|
||||
expect(helpText.textContent).toContain('If you are having trouble uploading a photo above');
|
||||
const accessLink = screen.getByTestId('access-link');
|
||||
fireEvent.click(accessLink);
|
||||
expect(history.location.pathname).toEqual('/request-camera-access');
|
||||
});
|
||||
});
|
||||
93
src/id-verification/tests/panels/ChooseModePanel.test.jsx
Normal file
93
src/id-verification/tests/panels/ChooseModePanel.test.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import {
|
||||
render, cleanup, act, screen,
|
||||
} from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import IdVerificationContext from '../../IdVerificationContext';
|
||||
import ChooseModePanel from '../../panels/ChooseModePanel';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendTrackEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
const IntlChooseModePanel = injectIntl(ChooseModePanel);
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
describe('ChooseModePanel', () => {
|
||||
const defaultProps = {
|
||||
intl: {},
|
||||
};
|
||||
|
||||
const contextValue = {
|
||||
optimizelyExperimentName: 'test',
|
||||
shouldUseCamera: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('renders correctly', async () => {
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlChooseModePanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
// check that radio button for upload is selected
|
||||
const uploadRadioButton = await screen.findByLabelText('Upload photos from my device');
|
||||
expect(uploadRadioButton).toBeChecked();
|
||||
|
||||
// check that if upload is selected, next button goes to correct screen
|
||||
const nextButton = await screen.findByTestId('next-button');
|
||||
expect(nextButton.getAttribute('href')).toEqual('/take-portrait-photo');
|
||||
});
|
||||
|
||||
it('renders correctly if user wants to use camera', async () => {
|
||||
contextValue.shouldUseCamera = true;
|
||||
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlChooseModePanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
// check that radio button for camera is selected
|
||||
const cameraRadioButton = await screen.findByLabelText('Take pictures using my camera');
|
||||
expect(cameraRadioButton).toBeChecked();
|
||||
|
||||
// check that if upload is selected, next button goes to correct screen
|
||||
const nextButton = await screen.findByTestId('next-button');
|
||||
expect(nextButton.getAttribute('href')).toEqual('/request-camera-access');
|
||||
});
|
||||
|
||||
it('redirects if user is not part of experiment', async () => {
|
||||
contextValue.optimizelyExperimentName = '';
|
||||
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlChooseModePanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
// check that radio button is not in document
|
||||
const cameraRadioButton = await screen.queryByLabelText('Take pictures using my camera');
|
||||
expect(cameraRadioButton).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -181,4 +181,42 @@ describe('RequestCameraAccessPanel', () => {
|
||||
const text = await screen.findByTestId('camera-failure-instructions');
|
||||
expect(text).toHaveTextContent(/Open the Flash Player/);
|
||||
});
|
||||
|
||||
it('reroutes correctly to portrait context', async () => {
|
||||
contextValue.mediaAccess = 'granted';
|
||||
history.location.state = { fromPortraitCapture: true };
|
||||
|
||||
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlRequestCameraAccessPanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
const button = await screen.findByTestId('next-button');
|
||||
fireEvent.click(button);
|
||||
expect(history.location.pathname).toEqual('/portrait-photo-context');
|
||||
});
|
||||
|
||||
it('reroutes correctly to ID context', async () => {
|
||||
contextValue.mediaAccess = 'granted';
|
||||
history.location.state = { fromIdCapture: true };
|
||||
|
||||
Bowser.parse = jest.fn().mockReturnValue({ browser: { name: '' } });
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlRequestCameraAccessPanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
const button = await screen.findByTestId('next-button');
|
||||
fireEvent.click(button);
|
||||
expect(history.location.pathname).toEqual('/id-context');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,4 +81,26 @@ describe('TakeIdPhotoPanel', () => {
|
||||
fireEvent.click(button);
|
||||
expect(history.location.pathname).toEqual('/summary');
|
||||
});
|
||||
|
||||
it('shows correct text if user should use upload', async () => {
|
||||
contextValue.optimizelyExperimentName = 'test';
|
||||
contextValue.shouldUseCamera = false;
|
||||
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlTakeIdPhotoPanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
// check that upload title and text are correct
|
||||
const title = await screen.findByText('Upload a Photo of Your ID');
|
||||
expect(title).toBeVisible();
|
||||
|
||||
const text = await screen.findByTestId('upload-text');
|
||||
expect(text.textContent).toContain('Please upload an ID photo');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ describe('TakePortraitPhotoPanel', () => {
|
||||
const contextValue = {
|
||||
facePhotoFile: null,
|
||||
setFacePhotoFile: jest.fn(),
|
||||
setShouldUseCamera: jest.fn(),
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
@@ -49,6 +50,7 @@ describe('TakePortraitPhotoPanel', () => {
|
||||
|
||||
it('shows next button after photo is taken and routes to IdContextPanel', async () => {
|
||||
contextValue.facePhotoFile = 'test.jpg';
|
||||
contextValue.shouldUseCamera = true;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
@@ -80,4 +82,26 @@ describe('TakePortraitPhotoPanel', () => {
|
||||
fireEvent.click(button);
|
||||
expect(history.location.pathname).toEqual('/summary');
|
||||
});
|
||||
|
||||
it('shows correct text if user should use upload', async () => {
|
||||
contextValue.optimizelyExperimentName = 'test';
|
||||
contextValue.shouldUseCamera = false;
|
||||
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlTakePortraitPhotoPanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
|
||||
// check that upload title and text are correct
|
||||
const title = await screen.findByText('Upload a Photo of Yourself');
|
||||
expect(title).toBeVisible();
|
||||
|
||||
const text = await screen.findByTestId('upload-text');
|
||||
expect(text.textContent).toContain('Please upload a portrait photo');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user