diff --git a/src/id-verification/IdVerificationContextProvider.jsx b/src/id-verification/IdVerificationContextProvider.jsx
index 027ab5a..a07a0f8 100644
--- a/src/id-verification/IdVerificationContextProvider.jsx
+++ b/src/id-verification/IdVerificationContextProvider.jsx
@@ -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 });
diff --git a/src/id-verification/panels/SummaryPanel.jsx b/src/id-verification/panels/SummaryPanel.jsx
index a11b466..efbc45a 100644
--- a/src/id-verification/panels/SummaryPanel.jsx
+++ b/src/id-verification/panels/SummaryPanel.jsx
@@ -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;
diff --git a/src/id-verification/routing-utilities.js b/src/id-verification/routing-utilities.js
index 71d7a7c..0218a51 100644
--- a/src/id-verification/routing-utilities.js
+++ b/src/id-verification/routing-utilities.js
@@ -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;
}
}
diff --git a/src/id-verification/tests/panels/ChooseModePanel.test.jsx b/src/id-verification/tests/panels/ChooseModePanel.test.jsx
index 11bcbe4..5446fa3 100644
--- a/src/id-verification/tests/panels/ChooseModePanel.test.jsx
+++ b/src/id-verification/tests/panels/ChooseModePanel.test.jsx
@@ -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((
+
+
+
+
+
+
+
+ )));
+ 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 = '';
diff --git a/src/id-verification/tests/panels/IdContextPanel.test.jsx b/src/id-verification/tests/panels/IdContextPanel.test.jsx
index d2ba4f2..6df0ddc 100644
--- a/src/id-verification/tests/panels/IdContextPanel.test.jsx
+++ b/src/id-verification/tests/panels/IdContextPanel.test.jsx
@@ -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((
+
+
+
+
+
+
+
+ )));
+ 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((
diff --git a/src/id-verification/tests/panels/PortraitPhotoContextPanel.test.jsx b/src/id-verification/tests/panels/PortraitPhotoContextPanel.test.jsx
index 8a7c958..599430d 100644
--- a/src/id-verification/tests/panels/PortraitPhotoContextPanel.test.jsx
+++ b/src/id-verification/tests/panels/PortraitPhotoContextPanel.test.jsx
@@ -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((
+
+
+
+
+
+
+
+ )));
+ 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((
diff --git a/src/id-verification/tests/panels/RequestCameraAccessPanel.test.jsx b/src/id-verification/tests/panels/RequestCameraAccessPanel.test.jsx
index fcbcec0..f817e1e 100644
--- a/src/id-verification/tests/panels/RequestCameraAccessPanel.test.jsx
+++ b/src/id-verification/tests/panels/RequestCameraAccessPanel.test.jsx
@@ -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((
+
+
+
+
+
+
+
+ )));
+ 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((
+
+
+
+
+
+
+
+ )));
+ 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';
diff --git a/src/id-verification/tests/panels/SummaryPanel.test.jsx b/src/id-verification/tests/panels/SummaryPanel.test.jsx
index e7a9627..7cfa84b 100644
--- a/src/id-verification/tests/panels/SummaryPanel.test.jsx
+++ b/src/id-verification/tests/panels/SummaryPanel.test.jsx
@@ -34,6 +34,7 @@ describe('SummaryPanel', () => {
idPhotoName: 'test name',
optimizelyExperimentName: 'test-experiment',
stopUserMedia: jest.fn(),
+ setReachedSummary: jest.fn(),
};
const getPanel = async () => {
diff --git a/src/id-verification/tests/panels/TakeIdPhotoPanel.test.jsx b/src/id-verification/tests/panels/TakeIdPhotoPanel.test.jsx
index ccbf0db..d681425 100644
--- a/src/id-verification/tests/panels/TakeIdPhotoPanel.test.jsx
+++ b/src/id-verification/tests/panels/TakeIdPhotoPanel.test.jsx
@@ -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((
diff --git a/src/id-verification/tests/panels/TakePortraitPhotoPanel.test.jsx b/src/id-verification/tests/panels/TakePortraitPhotoPanel.test.jsx
index 49bdf82..2f877bd 100644
--- a/src/id-verification/tests/panels/TakePortraitPhotoPanel.test.jsx
+++ b/src/id-verification/tests/panels/TakePortraitPhotoPanel.test.jsx
@@ -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((