Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import TipList from './tip-list';
import DocumentSideAcuantCapture from './document-side-acuant-capture';
import DocumentCaptureNotReady from './document-capture-not-ready';
import { FeatureFlagContext } from '../context';
import DocumentCaptureAbandon from './document-capture-abandon';

interface DocumentCaptureReviewIssuesProps {
isFailedDocType: boolean;
Expand Down Expand Up @@ -46,7 +47,7 @@ function DocumentCaptureReviewIssues({
hasDismissed,
}: DocumentCaptureReviewIssuesProps) {
const { t } = useI18n();
const { notReadySectionEnabled } = useContext(FeatureFlagContext);
const { notReadySectionEnabled, exitQuestionSectionEnabled } = useContext(FeatureFlagContext);
return (
<>
<PageHeading>{t('doc_auth.headings.review_issues')}</PageHeading>
Expand Down Expand Up @@ -83,6 +84,7 @@ function DocumentCaptureReviewIssues({
))}
<FormStepsButton.Submit />
{notReadySectionEnabled && <DocumentCaptureNotReady />}
{exitQuestionSectionEnabled && <DocumentCaptureAbandon />}
<Cancel />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UploadContext from '../context/upload';
import TipList from './tip-list';
import DocumentCaptureNotReady from './document-capture-not-ready';
import { FeatureFlagContext } from '../context';
import DocumentCaptureAbandon from './document-capture-abandon';

/**
* @typedef {'front'|'back'} DocumentSide
Expand Down Expand Up @@ -45,7 +46,7 @@ function DocumentsStep({
const { isMobile } = useContext(DeviceContext);
const { isLastStep } = useContext(FormStepsContext);
const { flowPath } = useContext(UploadContext);
const { notReadySectionEnabled } = useContext(FeatureFlagContext);
const { notReadySectionEnabled, exitQuestionSectionEnabled } = useContext(FeatureFlagContext);
return (
<>
{flowPath === 'hybrid' && <HybridDocCaptureWarning className="margin-bottom-4" />}
Expand Down Expand Up @@ -73,6 +74,7 @@ function DocumentsStep({
))}
{isLastStep ? <FormStepsButton.Submit /> : <FormStepsButton.Continue />}
{notReadySectionEnabled && <DocumentCaptureNotReady />}
{exitQuestionSectionEnabled && <DocumentCaptureAbandon />}
<Cancel />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ export interface FeatureFlagContextProps {
* Populated from backend configuration
*/
notReadySectionEnabled: boolean;
/**
* Specify whether to show exit optional questions on doc capture screen.
*/
exitQuestionSectionEnabled: boolean;
}

const FeatureFlagContext = createContext<FeatureFlagContextProps>({
notReadySectionEnabled: false,
exitQuestionSectionEnabled: false,
});

FeatureFlagContext.displayName = 'FeatureFlagContext';
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/packs/document-capture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface AppRootData {
idvInPersonUrl?: string;
securityAndPrivacyHowItWorksUrl: string;
uiNotReadySectionEnabled: string;
uiExitQuestionSectionEnabled: string;
}

const appRoot = document.getElementById('document-capture-form')!;
Expand Down Expand Up @@ -102,6 +103,7 @@ const {
usStatesTerritories = '',
phoneWithCamera = '',
uiNotReadySectionEnabled = '',
uiExitQuestionSectionEnabled = '',
} = appRoot.dataset as DOMStringMap & AppRootData;

let parsedUsStatesTerritories = [];
Expand Down Expand Up @@ -183,6 +185,7 @@ const App = composeComponents(
{
value: {
notReadySectionEnabled: String(uiNotReadySectionEnabled) === 'true',
exitQuestionSectionEnabled: String(uiExitQuestionSectionEnabled) === 'true',
},
},
],
Expand Down
1 change: 1 addition & 0 deletions app/views/idv/shared/_document_capture.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
us_states_territories: us_states_territories,
doc_auth_selfie_capture: IdentityConfig.store.doc_auth_selfie_capture,
ui_not_ready_section_enabled: IdentityConfig.store.doc_auth_not_ready_section_enabled,
ui_exit_question_section_enabled: IdentityConfig.store.doc_auth_exit_question_section_enabled,
} %>
<%= simple_form_for(
:doc_auth,
Expand Down
2 changes: 2 additions & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ doc_auth_s3_request_timeout: 5
doc_auth_error_dpi_threshold: 290
doc_auth_error_glare_threshold: 40
doc_auth_error_sharpness_threshold: 40
doc_auth_exit_question_section_enabled: false
doc_auth_max_attempts: 5
doc_auth_max_capture_attempts_before_native_camera: 3
doc_auth_max_submission_attempts_before_native_camera: 3
Expand Down Expand Up @@ -381,6 +382,7 @@ development:
database_worker_jobs_username: ''
database_worker_jobs_host: ''
database_worker_jobs_password: ''
doc_auth_exit_question_section_enabled: true
doc_auth_selfie_capture: '{"enabled":false}'
doc_auth_vendor: 'mock'
doc_auth_vendor_randomize: false
Expand Down
1 change: 1 addition & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def self.build_store(config_map)
config.add(:doc_auth_error_dpi_threshold, type: :integer)
config.add(:doc_auth_error_glare_threshold, type: :integer)
config.add(:doc_auth_error_sharpness_threshold, type: :integer)
config.add(:doc_auth_exit_question_section_enabled, type: :boolean)
config.add(:doc_auth_not_ready_section_enabled, type: :boolean)
config.add(:doc_auth_max_attempts, type: :integer)
config.add(:doc_auth_max_capture_attempts_before_native_camera, type: :integer)
Expand Down
18 changes: 18 additions & 0 deletions spec/features/idv/doc_auth/document_capture_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
let(:fake_analytics) { FakeAnalytics.new }
let(:sp_name) { 'Test SP' }
let(:enable_not_ready) { true }
let(:enable_exit_question) { true }
before do
allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics)
allow_any_instance_of(ServiceProviderSession).to receive(:sp_name).and_return(sp_name)
allow(IdentityConfig.store).to receive(:doc_auth_not_ready_section_enabled).
and_return(enable_not_ready)
allow(IdentityConfig.store).to receive(:doc_auth_exit_question_section_enabled).
and_return(enable_exit_question)
visit_idp_from_oidc_sp_with_ial2

sign_in_and_2fa_user(user)
Expand Down Expand Up @@ -136,6 +139,21 @@

expect(DocAuthLog.find_by(user_id: user.id).state).to be_nil
end

it 'return to sp when click on exit link', :js do
click_sp_exit_link(sp_name: sp_name)
expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
end

it 'logs event and return to sp when click on submit and exit button', :js do
click_submit_exit_button
expect(fake_analytics).to have_logged_event(
'Frontend: IdV: exit optional questions',
hash_including(:ids),
)
expect(current_url).to start_with('http://localhost:7654/auth/result?error=access_denied')
end

context 'not ready section' do
it 'renders not ready section when enabled' do
expect(page).to have_content(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { render, screen, within } from '@testing-library/react';
import DocumentCaptureReviewIssues from '@18f/identity-document-capture/components/document-capture-review-issues';
import { InPersonContext } from '@18f/identity-document-capture/context';
import { FeatureFlagContext, InPersonContext } from '@18f/identity-document-capture/context';
import { toFormEntryError } from '@18f/identity-document-capture/services/upload';
import { expect } from 'chai';
import { composeComponents } from '@18f/identity-compose-components';

describe('DocumentCaptureReviewIssues', () => {
const DEFAULT_OPTIONS = {
Expand Down Expand Up @@ -34,16 +35,34 @@ describe('DocumentCaptureReviewIssues', () => {
},
],
};
const { getByText, getByLabelText, getByRole, getAllByRole } = render(
<InPersonContext.Provider value={{ inPersonURL: '/verify/doc_capture' }}>
<DocumentCaptureReviewIssues
{...{
const App = composeComponents(
[
FeatureFlagContext.Provider,
{
value: {
exitQuestionSectionEnabled: true,
},
},
],
[
InPersonContext.Provider,
{
value: {
inPersonURL: '/verify/doc_capture',
},
},
],
[
DocumentCaptureReviewIssues,
{
...{
...DEFAULT_OPTIONS,
...props,
}}
/>
</InPersonContext.Provider>,
},
},
],
);
const { getByText, getByLabelText, getByRole, getAllByRole } = render(<App />);
const h1 = screen.getByRole('heading', { name: 'doc_auth.headings.review_issues', level: 1 });
expect(h1).to.be.ok();

Expand All @@ -68,6 +87,7 @@ describe('DocumentCaptureReviewIssues', () => {
expect(backCapture).to.be.ok();
expect(getByText('back side error')).to.be.ok();
expect(getByRole('button', { name: 'forms.buttons.submit.default' })).to.be.ok();
expect(getByRole('button', { name: 'doc_auth.exit_survey.optional.button' })).to.be.ok();
});

it('renders for a doc type failure', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
UploadContextProvider,
FailedCaptureAttemptsContextProvider,
FeatureFlagContext,
InPersonContext,
} from '@18f/identity-document-capture';
import DocumentsStep from '@18f/identity-document-capture/components/documents-step';
import { composeComponents } from '@18f/identity-compose-components';
Expand Down Expand Up @@ -86,6 +87,33 @@ describe('document-capture/components/documents-step', () => {
expect(queryByText(notExpectedText)).to.not.exist();
});

it('renders optional question part and not ready section', () => {
const App = composeComponents(
[
FeatureFlagContext.Provider,
{
value: {
notReadySectionEnabled: true,
exitQuestionSectionEnabled: true,
},
},
],
[
InPersonContext.Provider,
{
value: {
inPersonURL: '/verify/doc_capture',
},
},
],
[DocumentsStep],
);
const { getByRole, getByText } = render(<App />);
expect(getByRole('heading', { name: 'doc_auth.not_ready.header', level: 2 })).to.be.ok();
expect(getByRole('heading', { name: 'doc_auth.exit_survey.header', level: 2 })).to.be.ok();
expect(getByText('doc_auth.exit_survey.optional.button')).to.be.ok();
});

context('not ready section', () => {
it('is rendered when enabled', () => {
const App = composeComponents(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {
AnalyticsContext,
InPersonContext,
FailedCaptureAttemptsContextProvider,
FeatureFlagContext,
} from '@18f/identity-document-capture';
import { I18n } from '@18f/identity-i18n';
import { I18nContext } from '@18f/identity-react-i18n';
import ReviewIssuesStep from '@18f/identity-document-capture/components/review-issues-step';
import { toFormEntryError } from '@18f/identity-document-capture/services/upload';
import { composeComponents } from '@18f/identity-compose-components';
import { render } from '../../../support/document-capture';
import { getFixtureFile } from '../../../support/file';

Expand Down Expand Up @@ -343,6 +345,25 @@ describe('document-capture/components/review-issues-step', () => {
expect(getByLabelText('doc_auth.headings.document_capture_back')).to.be.ok();
});

it('renders optional questions', async () => {
const App = composeComponents(
[
FeatureFlagContext.Provider,
{
value: {
notReadySectionEnabled: true,
exitQuestionSectionEnabled: true,
},
},
],
[ReviewIssuesStep, DEFAULT_PROPS],
);
const { getByText, getByRole } = render(<App />);
await userEvent.click(getByRole('button', { name: 'idv.failure.button.warning' }));
expect(getByRole('heading', { name: 'doc_auth.exit_survey.header', level: 2 })).to.be.ok();
expect(getByText('doc_auth.exit_survey.optional.button')).to.be.ok();
});

context('service provider context', () => {
context('ial2', () => {
it('renders with front and back inputs', async () => {
Expand Down