Merge pull request #426 from edx/bseverino/fix-reroute
[MST-717] Reroute user to summary panel in A/B test
This commit is contained in:
@@ -79,6 +79,10 @@ export default function IdVerificationContextProvider({ children }) {
|
||||
const [optimizelyExperimentName, setOptimizelyExperimentName] = useState('');
|
||||
const [shouldUseCamera, setShouldUseCamera] = useState(false);
|
||||
|
||||
// If the user reaches the end of the flow and goes back to retake their photos,
|
||||
// this flag ensures that they are directed straight back to the summary panel
|
||||
const [reachedSummary, setReachedSummary] = useState(false);
|
||||
|
||||
const contextValue = {
|
||||
existingIdVerification,
|
||||
facePhotoFile,
|
||||
@@ -91,12 +95,14 @@ export default function IdVerificationContextProvider({ children }) {
|
||||
profileDataManager,
|
||||
optimizelyExperimentName,
|
||||
shouldUseCamera,
|
||||
reachedSummary,
|
||||
setExistingIdVerification,
|
||||
setFacePhotoFile,
|
||||
setIdPhotoFile,
|
||||
setIdPhotoName,
|
||||
setOptimizelyExperimentName,
|
||||
setShouldUseCamera,
|
||||
setReachedSummary,
|
||||
tryGetUserMedia: async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import { getConfig, history } from '@edx/frontend-platform';
|
||||
import {
|
||||
Alert, Hyperlink, Input, Button, Spinner,
|
||||
@@ -27,11 +27,14 @@ function SummaryPanel(props) {
|
||||
idPhotoName,
|
||||
stopUserMedia,
|
||||
optimizelyExperimentName,
|
||||
setReachedSummary,
|
||||
} = useContext(IdVerificationContext);
|
||||
const nameToBeUsed = idPhotoName || nameOnAccount || '';
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submissionError, setSubmissionError] = useState(null);
|
||||
|
||||
useEffect(() => setReachedSummary(true), []);
|
||||
|
||||
function renderManagedProfileMessage() {
|
||||
if (!profileDataManager) {
|
||||
return null;
|
||||
|
||||
@@ -2,56 +2,79 @@ import { useContext } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import IdVerificationContext, { MEDIA_ACCESS } from './IdVerificationContext';
|
||||
|
||||
const SLUGS = {
|
||||
REVIEW_REQUIREMENTS: 'review-requirements',
|
||||
CHOOSE_MODE: 'choose-mode',
|
||||
REQUEST_CAMERA_ACCESS: 'request-camera-access',
|
||||
PORTRAIT_PHOTO_CONTEXT: 'portrait-photo-context',
|
||||
TAKE_PORTRAIT_PHOTO: 'take-portrait-photo',
|
||||
ID_CONTEXT: 'id-context',
|
||||
TAKE_ID_PHOTO: 'take-id-photo',
|
||||
GET_NAME_ID: 'get-name-id',
|
||||
SUMMARY: 'summary',
|
||||
SUBMITTED: 'submitted',
|
||||
};
|
||||
|
||||
const panelSteps = [
|
||||
'review-requirements',
|
||||
'choose-mode',
|
||||
'request-camera-access',
|
||||
'portrait-photo-context',
|
||||
'take-portrait-photo',
|
||||
'id-context',
|
||||
'take-id-photo',
|
||||
'get-name-id',
|
||||
'summary',
|
||||
'submitted',
|
||||
SLUGS.REVIEW_REQUIREMENTS,
|
||||
SLUGS.CHOOSE_MODE,
|
||||
SLUGS.REQUEST_CAMERA_ACCESS,
|
||||
SLUGS.PORTRAIT_PHOTO_CONTEXT,
|
||||
SLUGS.TAKE_PORTRAIT_PHOTO,
|
||||
SLUGS.ID_CONTEXT,
|
||||
SLUGS.TAKE_ID_PHOTO,
|
||||
SLUGS.GET_NAME_ID,
|
||||
SLUGS.SUMMARY,
|
||||
SLUGS.SUBMITTED,
|
||||
];
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
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, mediaAccess, optimizelyExperimentName } = useContext(IdVerificationContext);
|
||||
const {
|
||||
mediaAccess,
|
||||
optimizelyExperimentName,
|
||||
reachedSummary,
|
||||
shouldUseCamera,
|
||||
} = useContext(IdVerificationContext);
|
||||
|
||||
if (isFromSummary) {
|
||||
return 'summary';
|
||||
const canRerouteToSummary = [
|
||||
SLUGS.TAKE_PORTRAIT_PHOTO,
|
||||
SLUGS.TAKE_ID_PHOTO,
|
||||
SLUGS.GET_NAME_ID,
|
||||
];
|
||||
|
||||
if (reachedSummary && canRerouteToSummary.includes(originSlug)) {
|
||||
return SLUGS.SUMMARY;
|
||||
}
|
||||
|
||||
// the following are used as part of an A/B experiment
|
||||
if (isFromPortrait) {
|
||||
if (mediaAccess === MEDIA_ACCESS.GRANTED) {
|
||||
return 'portrait-photo-context';
|
||||
return SLUGS.PORTRAIT_PHOTO_CONTEXT;
|
||||
}
|
||||
return 'take-portrait-photo';
|
||||
return SLUGS.TAKE_PORTRAIT_PHOTO;
|
||||
}
|
||||
if (isFromId) {
|
||||
if (mediaAccess === MEDIA_ACCESS.GRANTED) {
|
||||
return 'id-context';
|
||||
return SLUGS.ID_CONTEXT;
|
||||
}
|
||||
return 'take-id-photo';
|
||||
return SLUGS.TAKE_ID_PHOTO;
|
||||
}
|
||||
if (originSlug === 'review-requirements' && !optimizelyExperimentName) {
|
||||
return 'request-camera-access';
|
||||
if (originSlug === SLUGS.REVIEW_REQUIREMENTS && !optimizelyExperimentName) {
|
||||
return SLUGS.REQUEST_CAMERA_ACCESS;
|
||||
}
|
||||
if (originSlug === 'choose-mode' && !shouldUseCamera) {
|
||||
return 'take-portrait-photo';
|
||||
if (originSlug === SLUGS.CHOOSE_MODE && !shouldUseCamera) {
|
||||
return SLUGS.TAKE_PORTRAIT_PHOTO;
|
||||
}
|
||||
if (originSlug === 'take-portrait-photo' && !shouldUseCamera) {
|
||||
return 'take-id-photo';
|
||||
if (originSlug === SLUGS.TAKE_PORTRAIT_PHOTO && !shouldUseCamera) {
|
||||
return SLUGS.TAKE_ID_PHOTO;
|
||||
}
|
||||
if (originSlug === 'request-camera-access' && mediaAccess !== MEDIA_ACCESS.GRANTED) {
|
||||
return 'take-portrait-photo';
|
||||
if (originSlug === SLUGS.REQUEST_CAMERA_ACCESS && mediaAccess !== MEDIA_ACCESS.GRANTED) {
|
||||
return SLUGS.TAKE_PORTRAIT_PHOTO;
|
||||
}
|
||||
|
||||
const nextIndex = panelSteps.indexOf(originSlug) + 1;
|
||||
@@ -63,16 +86,16 @@ export const useNextPanelSlug = (originSlug) => {
|
||||
export const useVerificationRedirectSlug = (slug) => {
|
||||
const { facePhotoFile, idPhotoFile, optimizelyExperimentName } = useContext(IdVerificationContext);
|
||||
const indexOfCurrentPanel = panelSteps.indexOf(slug);
|
||||
if (!optimizelyExperimentName && slug === 'choose-mode') {
|
||||
return 'review-requirements';
|
||||
if (!optimizelyExperimentName && slug === SLUGS.CHOOSE_MODE) {
|
||||
return SLUGS.REVIEW_REQUIREMENTS;
|
||||
}
|
||||
if (!facePhotoFile) {
|
||||
if (indexOfCurrentPanel > panelSteps.indexOf('take-portrait-photo')) {
|
||||
return 'portrait-photo-context';
|
||||
if (indexOfCurrentPanel > panelSteps.indexOf(SLUGS.TAKE_PORTRAIT_PHOTO)) {
|
||||
return SLUGS.PORTRAIT_PHOTO_CONTEXT;
|
||||
}
|
||||
} else if (!idPhotoFile) {
|
||||
if (indexOfCurrentPanel > panelSteps.indexOf('take-id-photo')) {
|
||||
return 'id-context';
|
||||
if (indexOfCurrentPanel > panelSteps.indexOf(SLUGS.TAKE_ID_PHOTO)) {
|
||||
return SLUGS.ID_CONTEXT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import {
|
||||
render, cleanup, act, screen,
|
||||
render, cleanup, act, screen, fireEvent,
|
||||
} from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
@@ -25,6 +25,7 @@ describe('ChooseModePanel', () => {
|
||||
const contextValue = {
|
||||
optimizelyExperimentName: 'test',
|
||||
shouldUseCamera: false,
|
||||
reachedSummary: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
@@ -73,6 +74,23 @@ describe('ChooseModePanel', () => {
|
||||
expect(nextButton.getAttribute('href')).toEqual('/request-camera-access');
|
||||
});
|
||||
|
||||
it('reroutes correctly if reachedSummary is true', async () => {
|
||||
contextValue.shouldUseCamera = true;
|
||||
contextValue.reachedSummary = true;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlChooseModePanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
const nextButton = await screen.findByTestId('next-button');
|
||||
fireEvent.click(nextButton);
|
||||
expect(history.location.pathname).toEqual('/request-camera-access');
|
||||
});
|
||||
|
||||
it('redirects if user is not part of experiment', async () => {
|
||||
contextValue.optimizelyExperimentName = '';
|
||||
|
||||
|
||||
@@ -26,13 +26,30 @@ describe('IdContextPanel', () => {
|
||||
const contextValue = {
|
||||
optimizelyExperimentName: '',
|
||||
facePhotoFile: 'test.jpg',
|
||||
reachedSummary: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('routes to TakeIdPhotoPanel', async () => {
|
||||
it('routes to TakeIdPhotoPanel normally', async () => {
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlIdContextPanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
const button = await screen.findByTestId('next-button');
|
||||
fireEvent.click(button);
|
||||
expect(history.location.pathname).toEqual('/take-id-photo');
|
||||
});
|
||||
|
||||
it('routes to TakeIdPhotoPanel if reachedSummary is true', async () => {
|
||||
contextValue.reachedSummary = true;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
|
||||
@@ -25,13 +25,30 @@ describe('PortraitPhotoContextPanel', () => {
|
||||
|
||||
const contextValue = {
|
||||
optimizelyExperimentName: '',
|
||||
reachedSummary: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('routes to TakePortraitPhotoPanel', async () => {
|
||||
it('routes to TakePortraitPhotoPanel normally', async () => {
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<IdVerificationContext.Provider value={contextValue}>
|
||||
<IntlPortraitPhotoContextPanel {...defaultProps} />
|
||||
</IdVerificationContext.Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
)));
|
||||
const button = await screen.findByTestId('next-button');
|
||||
fireEvent.click(button);
|
||||
expect(history.location.pathname).toEqual('/take-portrait-photo');
|
||||
});
|
||||
|
||||
it('routes to TakePortraitPhotoPanel if reachedSummary is true', async () => {
|
||||
contextValue.reachedSummary = true;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
|
||||
@@ -26,6 +26,7 @@ describe('RequestCameraAccessPanel', () => {
|
||||
};
|
||||
|
||||
const contextValue = {
|
||||
reachedSummary: false,
|
||||
tryGetUserMedia: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -241,6 +242,46 @@ describe('RequestCameraAccessPanel', () => {
|
||||
expect(history.location.pathname).toEqual('/id-context');
|
||||
});
|
||||
|
||||
it('reroutes to portrait context when reachedSummary is true', async () => {
|
||||
contextValue.mediaAccess = 'granted';
|
||||
contextValue.reachedSummary = true;
|
||||
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 to ID context when reachedSummary is true', async () => {
|
||||
contextValue.mediaAccess = 'granted';
|
||||
contextValue.reachedSummary = true;
|
||||
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');
|
||||
});
|
||||
|
||||
it('reroutes correctly to portrait context with no media access', async () => {
|
||||
contextValue.mediaAccess = 'denied';
|
||||
contextValue.optimizelyExperimentName = 'test';
|
||||
|
||||
@@ -34,6 +34,7 @@ describe('SummaryPanel', () => {
|
||||
idPhotoName: 'test name',
|
||||
optimizelyExperimentName: 'test-experiment',
|
||||
stopUserMedia: jest.fn(),
|
||||
setReachedSummary: jest.fn(),
|
||||
};
|
||||
|
||||
const getPanel = async () => {
|
||||
|
||||
@@ -27,6 +27,7 @@ describe('TakeIdPhotoPanel', () => {
|
||||
const contextValue = {
|
||||
facePhotoFile: 'test.jpg',
|
||||
idPhotoFile: null,
|
||||
reachedSummary: false,
|
||||
setIdPhotoFile: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -67,7 +68,7 @@ describe('TakeIdPhotoPanel', () => {
|
||||
|
||||
it('routes back to SummaryPanel if that was the source', async () => {
|
||||
contextValue.idPhotoFile = 'test.jpg';
|
||||
history.location.state = { fromSummary: true };
|
||||
contextValue.reachedSummary = true;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
|
||||
@@ -26,6 +26,8 @@ describe('TakePortraitPhotoPanel', () => {
|
||||
|
||||
const contextValue = {
|
||||
facePhotoFile: null,
|
||||
idPhotoFile: null,
|
||||
reachedSummary: false,
|
||||
setFacePhotoFile: jest.fn(),
|
||||
setShouldUseCamera: jest.fn(),
|
||||
};
|
||||
@@ -68,7 +70,8 @@ describe('TakePortraitPhotoPanel', () => {
|
||||
|
||||
it('routes back to SummaryPanel if that was the source', async () => {
|
||||
contextValue.facePhotoFile = 'test.jpg';
|
||||
history.location.state = { fromSummary: true };
|
||||
contextValue.idPhotoFile = 'test.jpg';
|
||||
contextValue.reachedSummary = true;
|
||||
await act(async () => render((
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
|
||||
Reference in New Issue
Block a user