From ffa2265237d5d87ec89a60c29b161353251e37a0 Mon Sep 17 00:00:00 2001
From: Jerek Shoemaker
Date: Thu, 16 Jan 2025 13:36:44 -0600
Subject: [PATCH 09/36] [CST] Remove cstUseLighthouse5103 code (#34080)
* Removing cstUseLighthouse5103 code
* Updating tests to mock the LH 5103 endpoint instead of the EVSS one
---
.../claims-status/actions/index.js | 49 ----
.../Default5103EvidenceNotice.jsx | 23 +-
.../claims-status/containers/AskVAPage.jsx | 30 +-
.../claims-status/selectors/index.js | 29 --
.../tests/actions/index.unit.spec.js | 210 -------------
.../tests/components/AskVAPage.unit.spec.jsx | 49 +---
.../tests/components/ClaimPage.unit.spec.jsx | 4 +
.../Default5103EvidenceNotice.unit.spec.jsx | 107 +------
...laim-letters-keyboard-only.cypress.spec.js | 13 +-
.../feature-toggle-5103-update-disabled.json | 15 +
...feature-toggle-5103-update-enabled-v2.json | 12 -
.../feature-toggle-5103-update-enabled.json | 12 -
...eature-toggle-claim-detail-v2-enabled.json | 12 -
.../feature-toggle-claim-phases-enabled.json | 12 -
.../lighthouse/feature-toggle-enabled.json | 19 --
.../e2e/page-objects/TrackClaimsPageV2.js | 2 +-
.../tests/selectors/index.unit.spec.js | 276 ------------------
17 files changed, 46 insertions(+), 828 deletions(-)
create mode 100644 src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-disabled.json
delete mode 100644 src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-enabled.json
diff --git a/src/applications/claims-status/actions/index.js b/src/applications/claims-status/actions/index.js
index 884a2c4a15b0..587e33934a0a 100644
--- a/src/applications/claims-status/actions/index.js
+++ b/src/applications/claims-status/actions/index.js
@@ -225,55 +225,6 @@ export const getClaim = (id, navigate) => {
export const clearClaim = () => ({ type: CLEAR_CLAIM_DETAIL });
-export function submitRequest(id, cstClaimPhasesEnabled = false) {
- return dispatch => {
- dispatch({
- type: SUBMIT_DECISION_REQUEST,
- });
-
- if (canUseMocks()) {
- dispatch({ type: SET_DECISION_REQUESTED });
- dispatch(
- setNotification({
- title: 'Request received',
- body:
- 'Thank you. We have your claim request and will make a decision.',
- }),
- );
- return Promise.resolve();
- }
-
- return makeAuthRequest(
- `/v0/evss_claims/${id}/request_decision`,
- { method: 'POST' },
- dispatch,
- () => {
- dispatch({ type: SET_DECISION_REQUESTED });
- if (cstClaimPhasesEnabled) {
- dispatch(
- setNotification({
- title: 'We received your evidence waiver',
- body:
- 'Thank you. We’ll move your claim to the next step as soon as possible.',
- }),
- );
- } else {
- dispatch(
- setNotification({
- title: 'Request received',
- body:
- 'Thank you. We have your claim request and will make a decision.',
- }),
- );
- }
- },
- error => {
- dispatch({ type: SET_DECISION_REQUEST_ERROR, error });
- },
- );
- };
-}
-
export function submit5103(id, trackedItemId, cstClaimPhasesEnabled = false) {
return dispatch => {
dispatch({
diff --git a/src/applications/claims-status/components/claim-document-request-pages/Default5103EvidenceNotice.jsx b/src/applications/claims-status/components/claim-document-request-pages/Default5103EvidenceNotice.jsx
index 6749213f0fe6..9ca90e623dfe 100644
--- a/src/applications/claims-status/components/claim-document-request-pages/Default5103EvidenceNotice.jsx
+++ b/src/applications/claims-status/components/claim-document-request-pages/Default5103EvidenceNotice.jsx
@@ -15,12 +15,8 @@ import {
import {
// START ligthouse_migration
submit5103 as submit5103Action,
- submitRequest as submitRequestAction,
// END lighthouse_migration
} from '../../actions';
-// START lighthouse_migration
-import { cstUseLighthouse } from '../../selectors';
-// END lighthouse_migration
import { setUpPage } from '../../utils/page';
import withRouter from '../../utils/withRouter';
@@ -33,8 +29,6 @@ function Default5103EvidenceNotice({
navigate,
params,
submit5103,
- submitRequest,
- useLighthouse5103,
}) {
const [addedEvidence, setAddedEvidence] = useState(false);
const [checkboxErrorMessage, setCheckboxErrorMessage] = useState(undefined);
@@ -58,11 +52,7 @@ function Default5103EvidenceNotice({
const submit = () => {
if (addedEvidence) {
- if (useLighthouse5103) {
- submit5103(params.id, params.trackedItemId, true);
- } else {
- submitRequest(params.id, true);
- }
+ submit5103(params.id, params.trackedItemId, true);
} else {
setCheckboxErrorMessage(
`You must confirm you’re done adding evidence before submitting the evidence waiver`,
@@ -138,6 +128,7 @@ function Default5103EvidenceNotice({
review stage as quickly as possible.
+ {' '}
Note: You can add evidence to support your claim at any
time. However, if you add evidence later, your claim will move back to
this step, so we encourage you to add all your evidence now.
@@ -177,17 +168,11 @@ function mapStateToProps(state) {
decisionRequested: claimsState.claimAsk.decisionRequested,
decisionRequestError: claimsState.claimAsk.decisionRequestError,
loadingDecisionRequest: claimsState.claimAsk.loadingDecisionRequest,
- // START lighthouse_migration
- useLighthouse5103: cstUseLighthouse(state, '5103'),
- // END lighthouse_migration
};
}
const mapDispatchToProps = {
- // START lighthouse_migration
submit5103: submit5103Action,
- submitRequest: submitRequestAction,
- // END lighthouse_migration
};
export default withRouter(
@@ -204,11 +189,7 @@ Default5103EvidenceNotice.propTypes = {
loadingDecisionRequest: PropTypes.bool,
navigate: PropTypes.func,
params: PropTypes.object,
- // START lighthouse_migration
submit5103: PropTypes.func,
- submitRequest: PropTypes.func,
- useLighthouse5103: PropTypes.bool,
- // END lighthouse_migration
};
export { Default5103EvidenceNotice };
diff --git a/src/applications/claims-status/containers/AskVAPage.jsx b/src/applications/claims-status/containers/AskVAPage.jsx
index 9c08481576a0..4706761117e5 100644
--- a/src/applications/claims-status/containers/AskVAPage.jsx
+++ b/src/applications/claims-status/containers/AskVAPage.jsx
@@ -1,23 +1,18 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
+
import { VaCheckbox } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import {
- // START ligthouse_migration
- submit5103 as submit5103Action,
- submitRequest as submitRequestAction,
- // END lighthouse_migration
getClaim as getClaimAction,
+ submit5103 as submit5103Action,
} from '../actions';
-import NeedHelp from '../components/NeedHelp';
import ClaimsBreadcrumbs from '../components/ClaimsBreadcrumbs';
-// START lighthouse_migration
-import { cstUseLighthouse } from '../selectors';
-// END lighthouse_migration
+import NeedHelp from '../components/NeedHelp';
+import { setDocumentTitle } from '../utils/helpers';
import { setUpPage } from '../utils/page';
import withRouter from '../utils/withRouter';
-import { setDocumentTitle } from '../utils/helpers';
class AskVAPage extends React.Component {
constructor() {
@@ -54,11 +49,8 @@ class AskVAPage extends React.Component {
decisionRequestError,
params,
submit5103,
- submitRequest,
- useLighthouse5103,
} = this.props;
- const submitFunc = useLighthouse5103 ? submit5103 : submitRequest;
const submitDisabled =
!this.state.submittedDocs ||
loadingDecisionRequest ||
@@ -127,7 +119,7 @@ class AskVAPage extends React.Component {
submit
class="button-primary vads-u-margin-top--1"
text={buttonMsg}
- onClick={() => submitFunc(params.id)}
+ onClick={() => submit5103(params.id)}
/>
{!loadingDecisionRequest ? (
toggleValues(state).loading;
export const showClaimLettersFeature = state =>
toggleValues(state)[FEATURE_FLAG_NAMES.claimLettersAccess];
-// 'cst_use_lighthouse'
-// endpoint - one of '5103', 'index', 'show'
-export const cstUseLighthouse = (state, endpoint) => {
- const flipperOverrideMode = sessionStorage.getItem('cstFlipperOverrideMode');
- if (flipperOverrideMode) {
- switch (flipperOverrideMode) {
- case 'featureToggle':
- break;
- case 'evss':
- return false;
- case 'lighthouse':
- return true;
- default:
- break;
- }
- }
-
- // Returning true here because the feature toggle sometimes returns
- // undefined and the feature toggle should always return true anyways
- // Note: Checking for window.Cypress here because some of the Cypress
- // tests are written for EVSS and will fail if this only returns true
-
- if (endpoint === 'show' && !window.Cypress) return true;
-
- return toggleValues(state)[
- FEATURE_FLAG_NAMES[`cstUseLighthouse#${endpoint}`]
- ];
-};
-
// 'cst_include_ddl_boa_letters'
export const cstIncludeDdlBoaLetters = state =>
toggleValues(state)[FEATURE_FLAG_NAMES.cstIncludeDdlBoaLetters];
diff --git a/src/applications/claims-status/tests/actions/index.unit.spec.js b/src/applications/claims-status/tests/actions/index.unit.spec.js
index 71ba09ab9f41..0a4180ea02ff 100644
--- a/src/applications/claims-status/tests/actions/index.unit.spec.js
+++ b/src/applications/claims-status/tests/actions/index.unit.spec.js
@@ -21,7 +21,6 @@ import {
setFieldsDirty,
setLastPage,
setNotification,
- submitRequest,
updateField,
submit5103,
} from '../../actions';
@@ -445,215 +444,6 @@ describe('Actions', () => {
global.window.dataLayer = oldDataLayer;
});
});
- describe('submitRequest', () => {
- context('when cstClaimPhasesEnabled is false', () => {
- it('should submit request with canUseMocks true', done => {
- const useMocksStub = sinon.stub(constants, 'canUseMocks').returns(true);
-
- const thunk = submitRequest(5);
- const dispatch = sinon.spy();
-
- thunk(dispatch)
- .then(() => {
- expect(dispatch.firstCall.args[0].type).to.equal(
- SUBMIT_DECISION_REQUEST,
- );
- expect(dispatch.secondCall.args[0].type).to.equal(
- SET_DECISION_REQUESTED,
- );
- })
- .then(() => useMocksStub.restore())
- .then(done, done);
- });
- });
-
- context('when cstClaimPhasesEnabled is true', () => {
- it('should submit request with canUseMocks true', done => {
- const useMocksStub = sinon.stub(constants, 'canUseMocks').returns(true);
-
- const thunk = submitRequest(5, true);
- const dispatch = sinon.spy();
-
- thunk(dispatch)
- .then(() => {
- expect(dispatch.firstCall.args[0].type).to.equal(
- SUBMIT_DECISION_REQUEST,
- );
- expect(dispatch.secondCall.args[0].type).to.equal(
- SET_DECISION_REQUESTED,
- );
- })
- .then(() => useMocksStub.restore())
- .then(done, done);
- });
- });
-
- context('', () => {
- let expectedUrl;
- const server = setupServer();
-
- before(() => {
- server.listen();
- });
-
- beforeEach(() => {
- server.events.on('request:start', req => {
- expectedUrl = req.url.href;
- });
- });
-
- afterEach(() => {
- server.resetHandlers();
- expectedUrl = undefined;
- });
-
- after(() => server.close());
-
- context('when cstClaimPhasesEnabled is false', () => {
- it('should submit request', done => {
- const ID = 5;
- server.use(
- rest.post(
- `https://dev-api.va.gov/v0/evss_claims/${ID}/request_decision`,
- (req, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- // eslint-disable-next-line camelcase
- job_id: ID,
- }),
- ),
- ),
- );
-
- const thunk = submitRequest(ID);
- const dispatchSpy = sinon.spy();
- const dispatch = action => {
- dispatchSpy(action);
- if (dispatchSpy.callCount === 3) {
- expect(expectedUrl).to.contain('5/request_decision');
- expect(dispatchSpy.firstCall.args[0]).to.eql({
- type: SUBMIT_DECISION_REQUEST,
- });
- expect(dispatchSpy.secondCall.args[0]).to.eql({
- type: SET_DECISION_REQUESTED,
- });
- expect(dispatchSpy.thirdCall.args[0].type).to.eql(
- SET_NOTIFICATION,
- );
- expect(dispatchSpy.thirdCall.args[0].message.title).to.eql(
- 'Request received',
- );
- expect(dispatchSpy.thirdCall.args[0].message.body).to.eql(
- 'Thank you. We have your claim request and will make a decision.',
- );
- done();
- }
- };
-
- thunk(dispatch);
- });
- it('should fail on error', done => {
- const ID = 5;
- server.use(
- rest.post(
- `https://dev-api.va.gov/v0/evss_claims/${ID}/request_decision`,
- (req, res, ctx) =>
- res(ctx.status(400), ctx.json({ status: 400 })),
- ),
- );
- const thunk = submitRequest(ID);
- const dispatchSpy = sinon.spy();
- const dispatch = action => {
- dispatchSpy(action);
- if (dispatchSpy.callCount === 2) {
- expect(dispatchSpy.firstCall.args[0]).to.eql({
- type: SUBMIT_DECISION_REQUEST,
- });
- expect(dispatchSpy.secondCall.args[0].type).to.eql(
- SET_DECISION_REQUEST_ERROR,
- );
- done();
- }
- };
-
- thunk(dispatch);
- });
- });
-
- context('when cstClaimPhasesEnabled is true', () => {
- it('should submit request', done => {
- const ID = 5;
- server.use(
- rest.post(
- `https://dev-api.va.gov/v0/evss_claims/${ID}/request_decision`,
- (req, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- // eslint-disable-next-line camelcase
- job_id: ID,
- }),
- ),
- ),
- );
-
- const thunk = submitRequest(ID, true);
- const dispatchSpy = sinon.spy();
- const dispatch = action => {
- dispatchSpy(action);
- if (dispatchSpy.callCount === 3) {
- expect(expectedUrl).to.contain('5/request_decision');
- expect(dispatchSpy.firstCall.args[0]).to.eql({
- type: SUBMIT_DECISION_REQUEST,
- });
- expect(dispatchSpy.secondCall.args[0]).to.eql({
- type: SET_DECISION_REQUESTED,
- });
- expect(dispatchSpy.thirdCall.args[0].type).to.eql(
- SET_NOTIFICATION,
- );
- expect(dispatchSpy.thirdCall.args[0].message.title).to.eql(
- 'We received your evidence waiver',
- );
- expect(dispatchSpy.thirdCall.args[0].message.body).to.eql(
- 'Thank you. We’ll move your claim to the next step as soon as possible.',
- );
- done();
- }
- };
-
- thunk(dispatch);
- });
- it('should fail on error', done => {
- const ID = 5;
- server.use(
- rest.post(
- `https://dev-api.va.gov/v0/evss_claims/${ID}/request_decision`,
- (req, res, ctx) =>
- res(ctx.status(400), ctx.json({ status: 400 })),
- ),
- );
- const thunk = submitRequest(ID);
- const dispatchSpy = sinon.spy();
- const dispatch = action => {
- dispatchSpy(action);
- if (dispatchSpy.callCount === 2) {
- expect(dispatchSpy.firstCall.args[0]).to.eql({
- type: SUBMIT_DECISION_REQUEST,
- });
- expect(dispatchSpy.secondCall.args[0].type).to.eql(
- SET_DECISION_REQUEST_ERROR,
- );
- done();
- }
- };
-
- thunk(dispatch);
- });
- });
- });
- });
describe('getStemClaims', () => {
const server = setupServer();
diff --git a/src/applications/claims-status/tests/components/AskVAPage.unit.spec.jsx b/src/applications/claims-status/tests/components/AskVAPage.unit.spec.jsx
index 4cc453f822e6..4e355e9a321b 100644
--- a/src/applications/claims-status/tests/components/AskVAPage.unit.spec.jsx
+++ b/src/applications/claims-status/tests/components/AskVAPage.unit.spec.jsx
@@ -37,13 +37,11 @@ describe('', () => {
it('should render enabled submit button when va-checkbox checked', () => {
const router = getRouter();
- const submitRequest = sinon.spy();
const { container, rerender } = renderWithRouter(
,
);
@@ -60,7 +58,6 @@ describe('', () => {
,
);
@@ -73,14 +70,12 @@ describe('', () => {
const router = {
push: sinon.spy(),
};
- const submitRequest = sinon.spy();
const { container } = renderWithRouter(
,
);
@@ -93,15 +88,10 @@ describe('', () => {
it('should update claims and redirect after success', () => {
const navigate = sinon.spy();
- const submitRequest = sinon.spy();
const getClaim = sinon.spy();
const tree = SkinDeep.shallowRender(
- ,
+ ,
);
tree.getMountedInstance().UNSAFE_componentWillReceiveProps({
decisionRequested: true,
@@ -111,8 +101,7 @@ describe('', () => {
expect(navigate.calledWith('../status')).to.be.true;
});
- // START lighthouse_migration
- context('cst_use_lighthouse_5103 feature toggle', () => {
+ context('5103 Submission', () => {
const params = { id: 1 };
const props = {
@@ -121,38 +110,8 @@ describe('', () => {
router: getRouter(),
};
- it('calls submitRequest when disabled', () => {
- props.submitRequest = sinon.spy();
- props.submit5103 = sinon.spy();
- props.useLighthouse5103 = false;
-
- const { container, rerender } = renderWithRouter(
-
-
- ,
- );
- // Check the checkbox
- $('va-checkbox', container).__events.vaChange({
- detail: { checked: true },
- });
-
- rerenderWithRouter(
- rerender,
-
-
- ,
- );
- // Click submit button
- fireEvent.click($('.button-primary', container));
-
- expect(props.submitRequest.called).to.be.true;
- expect(props.submit5103.called).to.be.false;
- });
-
- it('calls submit5103 when enabled', () => {
- props.submitRequest = sinon.spy();
+ it('calls submit5103 ', () => {
props.submit5103 = sinon.spy();
- props.useLighthouse5103 = true;
const { container, rerender } = renderWithRouter(
@@ -173,9 +132,7 @@ describe('', () => {
// Click submit button
fireEvent.click($('.button-primary', container));
- expect(props.submitRequest.called).to.be.false;
expect(props.submit5103.called).to.be.true;
});
});
- // END lighthouse_migration
});
diff --git a/src/applications/claims-status/tests/components/ClaimPage.unit.spec.jsx b/src/applications/claims-status/tests/components/ClaimPage.unit.spec.jsx
index beec70264e4b..596d1862648b 100644
--- a/src/applications/claims-status/tests/components/ClaimPage.unit.spec.jsx
+++ b/src/applications/claims-status/tests/components/ClaimPage.unit.spec.jsx
@@ -8,6 +8,7 @@ import { renderWithRouter } from '../utils';
const params = { id: 1 };
const props = {
+ clearClaim: () => {},
params,
};
@@ -24,13 +25,16 @@ describe('', () => {
expect(props.getClaim.called).to.be.true;
});
+
it('calls clearClaim when it unmounts', () => {
props.clearClaim = sinon.spy();
+
const { unmount } = renderWithRouter(
,
);
+
unmount();
expect(props.clearClaim.called).to.be.true;
});
diff --git a/src/applications/claims-status/tests/components/claim-document-request-pages/Default5103EvidenceNotice.unit.spec.jsx b/src/applications/claims-status/tests/components/claim-document-request-pages/Default5103EvidenceNotice.unit.spec.jsx
index 9eafc5808a16..0fa68daab417 100644
--- a/src/applications/claims-status/tests/components/claim-document-request-pages/Default5103EvidenceNotice.unit.spec.jsx
+++ b/src/applications/claims-status/tests/components/claim-document-request-pages/Default5103EvidenceNotice.unit.spec.jsx
@@ -80,6 +80,7 @@ describe('', () => {
);
expect($('#default-5103-notice-page', container)).to.not.exist;
});
+
it('link has the correct href to upload additional evidence', () => {
const { getByText } = renderWithRouter(
', () => {
);
});
- context('when useLighthouse5103 false', () => {
- const props = {
- item: automated5103,
- params: { id: claimId },
- useLighthouse5103: false,
- };
-
- context('when checkbox is checked and submit button clicked', () => {
- it('should submitRequest notice and redirect to files tab', () => {
- const submitRequest = sinon.spy();
- const submit5103 = sinon.spy();
- const navigate = sinon.spy();
-
- const { container, rerender } = renderWithRouter(
- ,
- );
-
- expect($('#default-5103-notice-page', container)).to.exist;
- expect($('va-checkbox', container)).to.exist;
- expect($('va-button', container)).to.exist;
-
- // Check the checkbox
- $('va-checkbox', container).__events.vaChange({
- detail: { checked: true },
- });
-
- rerenderWithRouter(
- rerender,
- ,
- );
-
- // Click submit button
- fireEvent.click($('#submit', container));
-
- expect(submitRequest.called).to.be.true;
- expect(submit5103.called).to.be.false;
- expect(navigate.calledWith('../files')).to.be.true;
- });
- });
- context('when checkbox is not checked and submit button clicked', () => {
- it('should not submit 5103 notice and error message displayed', () => {
- const submitRequest = sinon.spy();
- const submit5103 = sinon.spy();
- const navigate = sinon.spy();
-
- const { container } = renderWithRouter(
- ,
- );
- expect($('#default-5103-notice-page', container)).to.exist;
- expect($('va-checkbox', container)).to.exist;
- expect($('va-button', container)).to.exist;
- expect($('va-checkbox', container).getAttribute('error')).to.be.null;
-
- // Click submit button
- fireEvent.click($('#submit', container));
-
- expect($('va-checkbox', container).getAttribute('checked')).to.equal(
- 'false',
- );
- expect($('va-checkbox', container).getAttribute('required')).to.equal(
- 'true',
- );
- expect(submitRequest.called).to.be.false;
- expect(submit5103.called).to.be.false;
- expect(navigate.calledWith('../files')).to.be.false;
- expect($('va-checkbox', container).getAttribute('error')).to.equal(
- 'You must confirm you’re done adding evidence before submitting the evidence waiver',
- );
- });
- });
- });
-
- context('when useLighthouse5103 true', () => {
+ context('submit5103', () => {
const props = {
item: automated5103,
params: { id: claimId },
@@ -194,15 +104,13 @@ describe('', () => {
context('when checkbox is checked and submit button clicked', () => {
it('should submit5103 notice and redirect to files tab', () => {
- const submitRequest = sinon.spy();
- const submit5103 = sinon.spy();
const navigate = sinon.spy();
+ const submit5103 = sinon.spy();
const { container, rerender } = renderWithRouter(
', () => {
', () => {
// Click submit button
fireEvent.click($('#submit', container));
- expect(submitRequest.called).to.be.false;
expect(submit5103.called).to.be.true;
expect(navigate.calledWith('../files')).to.be.true;
});
});
+
context('when checkbox is not checked and submit button clicked', () => {
it('should not submit 5103 notice and error message displayed', () => {
- const submitRequest = sinon.spy();
- const submit5103 = sinon.spy();
const navigate = sinon.spy();
+ const submit5103 = sinon.spy();
const { container } = renderWithRouter(
,
);
+
expect($('#default-5103-notice-page', container)).to.exist;
expect($('va-checkbox', container)).to.exist;
expect($('va-button', container)).to.exist;
@@ -262,7 +168,6 @@ describe('', () => {
expect($('va-checkbox', container).getAttribute('checked')).to.equal(
'false',
);
- expect(submitRequest.called).to.be.false;
expect(submit5103.called).to.be.false;
expect(navigate.calledWith('../files')).to.be.false;
expect($('va-checkbox', container).getAttribute('error')).to.equal(
diff --git a/src/applications/claims-status/tests/e2e/08.claim-letters-keyboard-only.cypress.spec.js b/src/applications/claims-status/tests/e2e/08.claim-letters-keyboard-only.cypress.spec.js
index da5bca2cb30b..10e6d06fd94d 100644
--- a/src/applications/claims-status/tests/e2e/08.claim-letters-keyboard-only.cypress.spec.js
+++ b/src/applications/claims-status/tests/e2e/08.claim-letters-keyboard-only.cypress.spec.js
@@ -1,4 +1,3 @@
-import featureToggleEnabled from './fixtures/mocks/claim-letters/feature-toggle-enabled.json';
import claimLetters from './fixtures/mocks/claim-letters/list.json';
describe('Claim Letters Page', () => {
@@ -6,9 +5,15 @@ describe('Claim Letters Page', () => {
cy.intercept('GET', '/v0/claim_letters', claimLetters.data).as(
'claimLetters',
);
- cy.intercept('GET', '/v0/feature_toggles?*', featureToggleEnabled).as(
- 'featureToggleEnabled',
- );
+
+ cy.intercept('GET', '/v0/feature_toggles*', {
+ data: {
+ features: [
+ { name: 'cst_include_ddl_boa_letters', value: true },
+ { name: 'claim_letters_access', value: true },
+ ],
+ },
+ });
cy.login();
});
diff --git a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-disabled.json b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-disabled.json
new file mode 100644
index 000000000000..3637dcd01648
--- /dev/null
+++ b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-disabled.json
@@ -0,0 +1,15 @@
+{
+ "data": {
+ "type": "feature_toggles",
+ "features": [
+ {
+ "name": "cst_claim_phases",
+ "value": true
+ },
+ {
+ "name": "cst_5103_update_enabled",
+ "value": false
+ }
+ ]
+ }
+}
diff --git a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled-v2.json b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled-v2.json
index 2f7f386eb1b2..46e07b419285 100644
--- a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled-v2.json
+++ b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled-v2.json
@@ -2,18 +2,6 @@
"data": {
"type": "feature_toggles",
"features": [
- {
- "name": "cst_use_lighthouse_5103",
- "value": false
- },
- {
- "name": "cst_use_lighthouse_index",
- "value": true
- },
- {
- "name": "cst_use_lighthouse_show",
- "value": true
- },
{
"name": "cst_claim_phases",
"value": true
diff --git a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled.json b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled.json
index 2f7f386eb1b2..46e07b419285 100644
--- a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled.json
+++ b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-5103-update-enabled.json
@@ -2,18 +2,6 @@
"data": {
"type": "feature_toggles",
"features": [
- {
- "name": "cst_use_lighthouse_5103",
- "value": false
- },
- {
- "name": "cst_use_lighthouse_index",
- "value": true
- },
- {
- "name": "cst_use_lighthouse_show",
- "value": true
- },
{
"name": "cst_claim_phases",
"value": true
diff --git a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-detail-v2-enabled.json b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-detail-v2-enabled.json
index 35c474035f0c..11a3a0ef5ab9 100644
--- a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-detail-v2-enabled.json
+++ b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-detail-v2-enabled.json
@@ -2,18 +2,6 @@
"data": {
"type": "feature_toggles",
"features": [
- {
- "name": "cst_use_lighthouse_5103",
- "value": false
- },
- {
- "name": "cst_use_lighthouse_index",
- "value": true
- },
- {
- "name": "cst_use_lighthouse_show",
- "value": true
- },
{
"name": "cst_use_claim_details_v2",
"value": true
diff --git a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-phases-enabled.json b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-phases-enabled.json
index ee2d10685ff2..85b6e0fca419 100644
--- a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-phases-enabled.json
+++ b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-claim-phases-enabled.json
@@ -2,18 +2,6 @@
"data": {
"type": "feature_toggles",
"features": [
- {
- "name": "cst_use_lighthouse_5103",
- "value": false
- },
- {
- "name": "cst_use_lighthouse_index",
- "value": true
- },
- {
- "name": "cst_use_lighthouse_show",
- "value": true
- },
{
"name": "cst_use_claim_details_v2",
"value": true
diff --git a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-enabled.json b/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-enabled.json
deleted file mode 100644
index 5e1858427082..000000000000
--- a/src/applications/claims-status/tests/e2e/fixtures/mocks/lighthouse/feature-toggle-enabled.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "data": {
- "type": "feature_toggles",
- "features": [
- {
- "name": "cst_use_lighthouse_5103",
- "value": true
- },
- {
- "name": "cst_use_lighthouse_index",
- "value": true
- },
- {
- "name": "cst_use_lighthouse_show",
- "value": true
- }
- ]
- }
-}
diff --git a/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js b/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js
index 5e85eafa7277..96d14c3f9c51 100644
--- a/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js
+++ b/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js
@@ -19,7 +19,7 @@ class TrackClaimsPageV2 {
cst5103UpdateEnabledV2 = false,
) {
if (submitForm) {
- cy.intercept('POST', `/v0/evss_claims/189685/request_decision`, {
+ cy.intercept('POST', `/v0/benefits_claims/189685/submit5103`, {
body: {},
}).as('askVA');
}
diff --git a/src/applications/claims-status/tests/selectors/index.unit.spec.js b/src/applications/claims-status/tests/selectors/index.unit.spec.js
index 6276ee452d7e..cc7c3a20193d 100644
--- a/src/applications/claims-status/tests/selectors/index.unit.spec.js
+++ b/src/applications/claims-status/tests/selectors/index.unit.spec.js
@@ -1,6 +1,5 @@
import { expect } from 'chai';
-import FEATURE_FLAG_NAMES from '@department-of-veterans-affairs/platform-utilities/featureFlagNames';
import backendServices from '@department-of-veterans-affairs/platform-user/profile/backendServices';
import * as selectors from '../../selectors';
@@ -46,281 +45,6 @@ describe('selectors', () => {
});
});
- describe('cstUseLighthouse', () => {
- context('when endpoint is show', () => {
- const endpoint = 'show';
- const cstUseLighthouseShow =
- FEATURE_FLAG_NAMES[`cstUseLighthouse#${endpoint}`];
- context('when featureToggles are true', () => {
- const state = {
- featureToggles: {
- [cstUseLighthouseShow]: true,
- // eslint-disable-next-line camelcase
- cst_use_lighthouse_5103: true,
- },
- };
- context('when cstFlipperOverrideMode is set to featureToggle', () => {
- it('should return true when window.cypress false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
- window.Cypress = false;
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
-
- it('should return true when window.cypress true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
- window.Cypress = true;
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is set to evss', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'evss');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to lighthouse', () => {
- it('should return true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'lighthouse');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is null', () => {
- it('should return true when window.cypress false', () => {
- // sessionStorage.setItem('cstFlipperOverrideMode', '');
- window.Cypress = false;
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
-
- it('should return true when window.cypress true', () => {
- // sessionStorage.setItem('cstFlipperOverrideMode', '');
- window.Cypress = true;
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
- });
-
- context('when featureToggles are false', () => {
- const state = {
- featureToggles: {
- [cstUseLighthouseShow]: false,
- // eslint-disable-next-line camelcase
- cst_use_lighthouse_5103: false,
- },
- };
- context('when cstFlipperOverrideMode is set to featureToggle', () => {
- it('should return true when window.cypress false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
- window.Cypress = false;
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
-
- it('should return true when window.cypress true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
- window.Cypress = true;
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to evss', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'evss');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to lighthouse', () => {
- it('should return true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'lighthouse');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is null', () => {
- it('should return true when window.cypress false', () => {
- // sessionStorage.setItem('cstFlipperOverrideMode', '');
- window.Cypress = false;
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
-
- it('should return true when window.cypress true', () => {
- // sessionStorage.setItem('cstFlipperOverrideMode', '');
- window.Cypress = true;
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
- });
- });
-
- context('when endpoint is index', () => {
- const endpoint = 'index';
- const cstUseLighthouseShow =
- FEATURE_FLAG_NAMES[`cstUseLighthouse#${endpoint}`];
- context('when featureToggles are true', () => {
- const state = {
- featureToggles: {
- [cstUseLighthouseShow]: true,
- // eslint-disable-next-line camelcase
- cst_use_lighthouse_5103: true,
- },
- };
- context('when cstFlipperOverrideMode is set to featureToggle', () => {
- it('should return true ', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is set to evss', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'evss');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to lighthouse', () => {
- it('should return true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'lighthouse');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is null', () => {
- it('should return true', () => {
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
- });
-
- context('when featureToggles are false', () => {
- const state = {
- featureToggles: {
- [cstUseLighthouseShow]: false,
- // eslint-disable-next-line camelcase
- cst_use_lighthouse_5103: false,
- },
- };
- context('when cstFlipperOverrideMode is set to featureToggle', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to evss', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'evss');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to lighthouse', () => {
- it('should return true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'lighthouse');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is null', () => {
- it('should return false', () => {
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
- });
- });
-
- context('when endpoint is 5103', () => {
- const endpoint = '5103';
- const cstUseLighthouseShow =
- FEATURE_FLAG_NAMES[`cstUseLighthouse#${endpoint}`];
- context('when featureToggles are true', () => {
- const state = {
- featureToggles: {
- [cstUseLighthouseShow]: true,
- // eslint-disable-next-line camelcase
- cst_use_lighthouse_5103: true,
- },
- };
- context('when cstFlipperOverrideMode is set to featureToggle', () => {
- it('should return true ', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is set to evss', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'evss');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to lighthouse', () => {
- it('should return true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'lighthouse');
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is null', () => {
- it('should return true', () => {
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
- });
-
- context('when featureToggles are false', () => {
- const state = {
- featureToggles: {
- [cstUseLighthouseShow]: false,
- // eslint-disable-next-line camelcase
- cst_use_lighthouse_5103: false,
- },
- };
- context('when cstFlipperOverrideMode is set to featureToggle', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'featureToggle');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to evss', () => {
- it('should return false', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'evss');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
-
- context('when cstFlipperOverrideMode is set to lighthouse', () => {
- it('should return true', () => {
- sessionStorage.setItem('cstFlipperOverrideMode', 'lighthouse');
-
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.true;
- });
- });
-
- context('when cstFlipperOverrideMode is null', () => {
- it('should return false', () => {
- expect(selectors.cstUseLighthouse(state, endpoint)).to.be.false;
- });
- });
- });
- });
- });
-
describe('cstIncludeDdlBoaLetters', () => {
const state = {
featureToggles: {
From dc613b26cfe9b1f4aafad079350cffee9a0b47ee Mon Sep 17 00:00:00 2001
From: Kyle Henson <145150351+khenson-oddball@users.noreply.github.com>
Date: Thu, 16 Jan 2025 12:58:55 -0700
Subject: [PATCH 10/36] Add veteran status confirmation integration (#34067)
---
.../ProofOfVeteranStatus.jsx | 320 +++++++++++++-----
.../ProofOfVeteranStatusNew.jsx | 280 +++++++++++----
.../ProofOfVeteranStatusNew.unit.spec.jsx | 129 ++++++-
.../vet-verification-status/index.js | 29 ++
.../personalization/profile/mocks/server.js | 6 +
.../MilitaryInformation.unit.spec.jsx | 23 +-
.../PeriodOfServiceTypeText.unit.spec.jsx | 12 +
.../proof-of-veteran-status.cypress.spec.js | 6 +
8 files changed, 660 insertions(+), 145 deletions(-)
create mode 100644 src/applications/personalization/profile/mocks/endpoints/vet-verification-status/index.js
diff --git a/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatus.jsx b/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatus.jsx
index e5424c1625e0..0fe58bade12b 100644
--- a/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatus.jsx
+++ b/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatus.jsx
@@ -6,6 +6,8 @@ import { generatePdf } from '~/platform/pdf';
import { focusElement } from '~/platform/utilities/ui';
import { captureError } from '~/platform/user/profile/vap-svc/util/analytics';
import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
+import { useFeatureToggle } from '~/platform/utilities/feature-toggles';
+import { apiRequest } from '~/platform/utilities/api';
import { formatFullName } from '../../../common/helpers';
import { getServiceBranchDisplayName } from '../../helpers';
@@ -23,6 +25,8 @@ const ProofOfVeteranStatus = ({
mockUserAgent,
}) => {
const [errors, setErrors] = useState([]);
+ const [data, setData] = useState(null);
+ const [shouldFocusError, setShouldFocusError] = useState(false);
const { first, middle, last, suffix } = userFullName;
const userAgent =
@@ -69,13 +73,45 @@ const ProofOfVeteranStatus = ({
},
};
+ const { TOGGLE_NAMES, useToggleValue } = useFeatureToggle();
+ const useLighthouseApi = useToggleValue(
+ TOGGLE_NAMES.veteranStatusCardUseLighthouseFrontend,
+ );
+
+ useEffect(() => {
+ let isMounted = true;
+
+ const fetchVerificationStatus = async () => {
+ try {
+ const path = '/profile/vet_verification_status';
+ const response = await apiRequest(path);
+ if (isMounted) {
+ setData(response.data);
+ }
+ } catch (error) {
+ if (isMounted) {
+ setErrors([
+ "We're sorry. There's a problem with our system. We can't show your Veteran status card right now. Try again later.",
+ ]);
+ captureError(error, { eventName: 'vet-status-fetch-verification' });
+ }
+ }
+ };
+ fetchVerificationStatus();
+
+ return () => {
+ isMounted = false;
+ };
+ }, []);
+
useEffect(
() => {
- if (errors?.length > 0) {
+ if (shouldFocusError && errors?.length > 0) {
focusElement('.vet-status-pdf-download-error');
+ setShouldFocusError(false);
}
},
- [errors],
+ [shouldFocusError, errors],
);
const createPdf = async () => {
@@ -93,6 +129,7 @@ const ProofOfVeteranStatus = ({
"We're sorry. Something went wrong on our end. Please try to download your Veteran status card later.",
]);
captureError(error, { eventName: 'vet-status-pdf-download' });
+ setShouldFocusError(true);
}
};
@@ -123,93 +160,216 @@ const ProofOfVeteranStatus = ({
);
});
+ const contactInfoElements = data?.attributes?.message?.map(item => {
+ const contactNumber = `${CONTACTS.DS_LOGON.slice(
+ 0,
+ 3,
+ )}-${CONTACTS.DS_LOGON.slice(3, 6)}-${CONTACTS.DS_LOGON.slice(6)}`;
+ const startIndex = item.indexOf(contactNumber);
+
+ if (startIndex === -1) {
+ return item;
+ }
+
+ const before = item.slice(0, startIndex);
+ const telephone = item.slice(
+ startIndex,
+ startIndex + contactNumber.length + 11,
+ );
+ const after = item.slice(startIndex + telephone.length);
+
+ return (
+ <>
+ {before}
+ (
+ ){after}
+ >
+ );
+ });
+
return (
<>
-
-
- Proof of Veteran status
-
-
- You can use your Veteran status card to get discounts offered to
- Veterans at many restaurants, hotels, stores, and other businesses.
-
-
- Note:
- This card doesn’t entitle you to any VA benefits.
-
-
- {vetStatusEligibility.confirmed ? (
- <>
-
-
-
+ {useLighthouseApi ? (
+
+
+ Proof of Veteran status
+
+
+ You can use your Veteran status card to get discounts offered to
+ Veterans at many restaurants, hotels, stores, and other businesses.
+
+
+ Note:
+ This card doesn’t entitle you to any VA benefits.
+
- {errors?.length > 0 ? (
-
-
- {errors[0]}
-
+ {data?.attributes?.veteranStatus === 'confirmed' ? (
+ <>
+
+
- ) : null}
-
-
-
-
-
+
+ {errors?.length > 0 ? (
+
+
+ {errors[0]}
+
+
+ ) : null}
+
+
+
+
+
+
-
-
-
- You can use our mobile app to get proof of Veteran status.
- To get started, download the{' '}
- VA: Health and Benefits mobile app.
- >
- }
- />
-
- >
- ) : null}
-
- {!vetStatusEligibility.confirmed &&
- vetStatusEligibility.message.length > 0 ? (
- <>
-
-
- {componentizedMessage.map((message, i) => {
- if (i === 0) {
- return (
-
- {message}
-
- );
+
+
+ You can use our mobile app to get proof of Veteran status.
+ To get started, download the{' '}
+ VA: Health and Benefits mobile app.
+ >
}
- return {message}
;
- })}
+ />
+
+ >
+ ) : null}
+
+ {data?.attributes?.veteranStatus !== 'confirmed' &&
+ data?.attributes?.message.length > 0 ? (
+ <>
+
+
+ {contactInfoElements.map((message, i) => {
+ if (i === 0) {
+ return (
+
+ {message}
+
+ );
+ }
+ return {message}
;
+ })}
+
+
+ >
+ ) : null}
+
+ {errors?.length > 0 ? (
+
+
+ {errors[0]}
- >
- ) : null}
-
+ ) : null}
+
+ ) : (
+
+
+ Proof of Veteran status
+
+
+ You can use your Veteran status card to get discounts offered to
+ Veterans at many restaurants, hotels, stores, and other businesses.
+
+
+ Note:
+ This card doesn’t entitle you to any VA benefits.
+
+
+ {vetStatusEligibility.confirmed ? (
+ <>
+
+
+
+
+ {errors?.length > 0 ? (
+
+
+ {errors[0]}
+
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+ You can use our mobile app to get proof of Veteran status.
+ To get started, download the{' '}
+ VA: Health and Benefits mobile app.
+ >
+ }
+ />
+
+ >
+ ) : null}
+
+ {!vetStatusEligibility.confirmed &&
+ vetStatusEligibility.message.length > 0 ? (
+ <>
+
+
+ {componentizedMessage.map((message, i) => {
+ if (i === 0) {
+ return (
+
+ {message}
+
+ );
+ }
+ return {message}
;
+ })}
+
+
+ >
+ ) : null}
+
+ )}
>
);
};
diff --git a/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatusNew.jsx b/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatusNew.jsx
index 81df2cad97f7..d2533e9c4ba9 100644
--- a/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatusNew.jsx
+++ b/src/applications/personalization/profile/components/proof-of-veteran-status/ProofOfVeteranStatusNew.jsx
@@ -6,6 +6,8 @@ import { generatePdf } from '~/platform/pdf';
import { focusElement } from '~/platform/utilities/ui';
import { captureError } from '~/platform/user/profile/vap-svc/util/analytics';
import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
+import { useFeatureToggle } from '~/platform/utilities/feature-toggles';
+import { apiRequest } from '~/platform/utilities/api';
import { formatFullName } from '../../../common/helpers';
import { getServiceBranchDisplayName } from '../../helpers';
import ProofOfVeteranStatusCard from './ProofOfVeteranStatusCard/ProofOfVeteranStatusCard';
@@ -24,6 +26,8 @@ const ProofOfVeteranStatusNew = ({
mockUserAgent,
}) => {
const [errors, setErrors] = useState([]);
+ const [data, setData] = useState(null);
+ const [shouldFocusError, setShouldFocusError] = useState(false);
const { first, middle, last, suffix } = userFullName;
const userAgent =
@@ -59,6 +63,8 @@ const ProofOfVeteranStatusNew = ({
serviceHistory.length && formattedFullName
);
+ const hasConfirmationData = !!(data && data.attributes);
+
const pdfData = {
title: `Veteran status card for ${formattedFullName}`,
details: {
@@ -86,13 +92,45 @@ const ProofOfVeteranStatusNew = ({
},
};
+ const { TOGGLE_NAMES, useToggleValue } = useFeatureToggle();
+ const useLighthouseApi = useToggleValue(
+ TOGGLE_NAMES.veteranStatusCardUseLighthouseFrontend,
+ );
+
+ useEffect(() => {
+ let isMounted = true;
+
+ const fetchVerificationStatus = async () => {
+ try {
+ const path = '/profile/vet_verification_status';
+ const response = await apiRequest(path);
+ if (isMounted) {
+ setData(response.data);
+ }
+ } catch (error) {
+ if (isMounted) {
+ setErrors([
+ "We're sorry. There's a problem with our system. We can't show your Veteran status card right now. Try again later.",
+ ]);
+ captureError(error, { eventName: 'vet-status-fetch-verification' });
+ }
+ }
+ };
+ fetchVerificationStatus();
+
+ return () => {
+ isMounted = false;
+ };
+ }, []);
+
useEffect(
() => {
- if (errors?.length > 0) {
+ if (shouldFocusError && errors?.length > 0) {
focusElement('.vet-status-pdf-download-error');
+ setShouldFocusError(false);
}
},
- [errors],
+ [shouldFocusError, errors],
);
const createPdf = async () => {
@@ -140,6 +178,33 @@ const ProofOfVeteranStatusNew = ({
);
});
+ const contactInfoElements = data?.attributes?.message?.map(item => {
+ const contactNumber = `${CONTACTS.DS_LOGON.slice(
+ 0,
+ 3,
+ )}-${CONTACTS.DS_LOGON.slice(3, 6)}-${CONTACTS.DS_LOGON.slice(6)}`;
+ const startIndex = item.indexOf(contactNumber);
+
+ if (startIndex === -1) {
+ return item;
+ }
+
+ const before = item.slice(0, startIndex);
+ const telephone = item.slice(
+ startIndex,
+ startIndex + contactNumber.length + 11,
+ );
+ const after = item.slice(startIndex + telephone.length);
+
+ return (
+ <>
+ {before}
+
(
+
){after}
+ >
+ );
+ });
+
return (
<>
@@ -152,74 +217,165 @@ const ProofOfVeteranStatusNew = ({
{userHasRequiredCardData ? (
<>
- {vetStatusEligibility.confirmed ? (
+ {!useLighthouseApi ? (
<>
- {errors?.length > 0 ? (
-
-
- {errors[0]}
-
-
+ {vetStatusEligibility.confirmed ? (
+ <>
+ {errors?.length > 0 ? (
+
+
+ {errors[0]}
+
+
+ ) : null}
+
+
+
+
+
+
+ You can use our mobile app to get proof of Veteran
+ status. To get started, download the{' '}
+ VA: Health and Benefits mobile
+ app.
+ >
+ }
+ />
+
+ >
+ ) : null}
+ {!vetStatusEligibility.confirmed &&
+ vetStatusEligibility.message.length > 0 ? (
+ <>
+
+
+ {componentizedMessage.map((message, i) => {
+ if (i === 0) {
+ return (
+
+ {message}
+
+ );
+ }
+ return {message}
;
+ })}
+
+
+ >
) : null}
-
-
-
-
-
-
- You can use our mobile app to get proof of Veteran
- status. To get started, download the{' '}
- VA: Health and Benefits mobile app.
- >
- }
- />
-
>
) : null}
- {!vetStatusEligibility.confirmed &&
- vetStatusEligibility.message.length > 0 ? (
+ {useLighthouseApi && hasConfirmationData ? (
<>
-
-
- {componentizedMessage.map((message, i) => {
- if (i === 0) {
- return (
-
- {message}
-
- );
- }
- return {message}
;
- })}
-
-
+ {data?.attributes?.veteranStatus === 'confirmed' ? (
+ <>
+ {errors?.length > 0 ? (
+
+
+ {errors[0]}
+
+
+ ) : null}
+
+
+
+
+
+
+ You can use our mobile app to get proof of Veteran
+ status. To get started, download the{' '}
+ VA: Health and Benefits mobile
+ app.
+ >
+ }
+ />
+
+ >
+ ) : null}
+
+ {data?.attributes?.veteranStatus !== 'confirmed' &&
+ data?.attributes?.message.length > 0 ? (
+ <>
+
+
+ {contactInfoElements.map((message, i) => {
+ if (i === 0) {
+ return (
+
+ {message}
+
+ );
+ }
+ return {message}
;
+ })}
+
+
+ >
+ ) : null}
>
) : null}
+
+ {useLighthouseApi && !hasConfirmationData ? (
+
+
+ We’re sorry. There’s a problem with our system. We can’t show
+ your Veteran status card right now. Try again later.
+
+
+ ) : null}
>
) : (
{
});
});
+ describe('should fetch verification status on render', () => {
+ let apiRequestStub;
+ const initialState = createBasicInitialState(
+ [eligibleServiceHistoryItem],
+ confirmedEligibility,
+ true,
+ );
+
+ beforeEach(() => {
+ apiRequestStub = sinon.stub(api, 'apiRequest');
+ });
+
+ afterEach(() => {
+ apiRequestStub.restore();
+ });
+
+ it('displays the card successfully', async () => {
+ const mockData = {
+ data: {
+ id: '',
+ type: 'veteran_status_confirmations',
+ attributes: { veteranStatus: 'confirmed' },
+ },
+ };
+
+ apiRequestStub.resolves(mockData);
+
+ const view = renderWithProfileReducers( , {
+ initialState,
+ });
+
+ sinon.assert.calledWith(
+ apiRequestStub,
+ '/profile/vet_verification_status',
+ );
+ await waitFor(() => {
+ expect(
+ view.queryByText(
+ /Get proof of Veteran status on your mobile device/i,
+ ),
+ ).to.exist;
+ expect(
+ view.queryByText(
+ /We’re sorry. There’s a problem with your discharge status records. We can’t provide a Veteran status card for you right now./,
+ ),
+ ).to.not.exist;
+ });
+ });
+
+ it('displays the returned not confirmed message', async () => {
+ const mockData = {
+ data: {
+ id: '',
+ type: 'veteran_status_confirmations',
+ attributes: {
+ veteranStatus: 'not confirmed',
+ notConfirmedReason: 'PERSON_NOT_FOUND',
+ message: problematicEligibility.message,
+ },
+ },
+ };
+
+ apiRequestStub.resolves(mockData);
+ const view = renderWithProfileReducers( , {
+ initialState,
+ });
+
+ await waitFor(() => {
+ expect(
+ view.queryByText(
+ /Get proof of Veteran Status on your mobile device/i,
+ ),
+ ).to.not.exist;
+ expect(
+ view.queryByText(
+ /We’re sorry. There’s a problem with your discharge status records. We can’t provide a Veteran status card for you right now./,
+ ),
+ ).to.exist;
+ });
+ });
+
+ it('handles empty API response', async () => {
+ const mockData = {
+ data: {},
+ };
+ apiRequestStub.resolves(mockData);
+ const view = renderWithProfileReducers( , {
+ initialState,
+ });
+
+ await waitFor(() => {
+ expect(
+ view.queryByText(
+ 'We’re sorry. There’s a problem with our system. We can’t show your Veteran status card right now. Try again later.',
+ ),
+ ).to.exist;
+ });
+ });
+
+ it('handles API error', async () => {
+ apiRequestStub.rejects(new Error('API Error'));
+ const view = renderWithProfileReducers( , {
+ initialState,
+ });
+
+ await waitFor(() => {
+ expect(
+ view.getByText(
+ 'We’re sorry. There’s a problem with our system. We can’t show your Veteran status card right now. Try again later.',
+ ),
+ ).to.exist;
+ });
+ });
+ });
+
describe('when eligible', () => {
const initialState = createBasicInitialState(
[
diff --git a/src/applications/personalization/profile/mocks/endpoints/vet-verification-status/index.js b/src/applications/personalization/profile/mocks/endpoints/vet-verification-status/index.js
new file mode 100644
index 000000000000..ba62fadf4807
--- /dev/null
+++ b/src/applications/personalization/profile/mocks/endpoints/vet-verification-status/index.js
@@ -0,0 +1,29 @@
+const confirmed = {
+ data: {
+ id: '',
+ type: 'veteran_status_confirmations',
+ attributes: {
+ veteranStatus: 'confirmed',
+ },
+ },
+};
+
+const notConfirmed = {
+ data: {
+ id: null,
+ type: 'veteran_status_confirmations',
+ attributes: {
+ veteranStatus: 'not confirmed',
+ notConfirmedReason: 'PERSON_NOT_FOUND',
+ message: [
+ 'We’re sorry. There’s a problem with your discharge status records. We can’t provide a Veteran status card for you right now.',
+ 'To fix the problem with your records, call the Defense Manpower Data Center at 800-538-9552 (TTY: 711). They’re open Monday through Friday, 8:00 a.m. to 8:00 p.m. ET.',
+ ],
+ },
+ },
+};
+
+module.exports = {
+ confirmed,
+ notConfirmed,
+};
diff --git a/src/applications/personalization/profile/mocks/server.js b/src/applications/personalization/profile/mocks/server.js
index 9177f471ca1d..fcb081e41bc3 100644
--- a/src/applications/personalization/profile/mocks/server.js
+++ b/src/applications/personalization/profile/mocks/server.js
@@ -22,6 +22,7 @@ const mockDisabilityCompensations = require('./endpoints/disability-compensation
const directDeposits = require('./endpoints/direct-deposits');
const bankAccounts = require('./endpoints/bank-accounts');
const serviceHistory = require('./endpoints/service-history');
+const vetVerificationStatus = require('./endpoints/vet-verification-status');
const fullName = require('./endpoints/full-name');
const {
baseUserTransitionAvailabilities,
@@ -107,6 +108,7 @@ const responses = {
profileShowPrivacyPolicy: true,
veteranOnboardingContactInfoFlow: true,
veteranStatusCardUseLighthouse: true,
+ veteranStatusCardUseLighthouseFrontend: true,
}),
),
secondsOfDelay,
@@ -236,6 +238,10 @@ const responses = {
// .status(200)
// .json(serviceHistory.generateServiceHistoryError('403'));
},
+ 'GET /v0/profile/vet_verification_status': (_req, res) => {
+ return res.status(200).json(vetVerificationStatus.confirmed);
+ // return res.status(200).json(vetVerificationStatus.notConfirmed);
+ },
'GET /v0/disability_compensation_form/rating_info': (_req, res) => {
// return res.status(200).json(ratingInfo.success.serviceConnected0);
return res.status(200).json(ratingInfo.success.serviceConnected40);
diff --git a/src/applications/personalization/profile/tests/components/military-information/MilitaryInformation.unit.spec.jsx b/src/applications/personalization/profile/tests/components/military-information/MilitaryInformation.unit.spec.jsx
index 3ac7fdcd0b6d..fcf3bb8ebf91 100644
--- a/src/applications/personalization/profile/tests/components/military-information/MilitaryInformation.unit.spec.jsx
+++ b/src/applications/personalization/profile/tests/components/military-information/MilitaryInformation.unit.spec.jsx
@@ -1,8 +1,8 @@
import React from 'react';
import { expect } from 'chai';
-
+import * as api from '~/platform/utilities/api';
+import sinon from 'sinon';
import { renderWithProfileReducers } from '../../unit-test-helpers';
-
import MilitaryInformation from '../../../components/military-information/MilitaryInformation';
function createBasicInitialState(toggles = {}) {
@@ -61,6 +61,16 @@ function createBasicInitialState(toggles = {}) {
describe('MilitaryInformation', () => {
let initialState;
let view;
+ let apiRequestStub;
+
+ beforeEach(() => {
+ apiRequestStub = sinon.stub(api, 'apiRequest');
+ });
+
+ afterEach(() => {
+ apiRequestStub.restore();
+ });
+
describe('when military history exists', () => {
it('should render data for each entry of military history', () => {
initialState = createBasicInitialState();
@@ -91,6 +101,15 @@ describe('MilitaryInformation', () => {
initialState = createBasicInitialState();
initialState.vaProfile.militaryInformation.serviceHistory.serviceHistory[0].branchOfService = null;
initialState.vaProfile.militaryInformation.serviceHistory.serviceHistory[1].branchOfService = undefined;
+ const mockData = {
+ data: {
+ id: '',
+ type: 'veteran_status_confirmations',
+ attributes: { veteranStatus: 'confirmed' },
+ },
+ };
+
+ apiRequestStub.resolves(mockData);
view = renderWithProfileReducers( , {
initialState,
});
diff --git a/src/applications/personalization/profile/tests/components/military-information/PeriodOfServiceTypeText.unit.spec.jsx b/src/applications/personalization/profile/tests/components/military-information/PeriodOfServiceTypeText.unit.spec.jsx
index 2cd5b272046a..3b6a2c289116 100644
--- a/src/applications/personalization/profile/tests/components/military-information/PeriodOfServiceTypeText.unit.spec.jsx
+++ b/src/applications/personalization/profile/tests/components/military-information/PeriodOfServiceTypeText.unit.spec.jsx
@@ -1,5 +1,7 @@
import React from 'react';
import { expect } from 'chai';
+import * as api from '~/platform/utilities/api';
+import sinon from 'sinon';
import { renderWithProfileReducers } from '../../unit-test-helpers';
import MilitaryInformation from '../../../components/military-information/MilitaryInformation';
@@ -58,6 +60,16 @@ function createBasicInitialState(toggles = {}) {
}
describe('MilitaryInformation - Period of Service Type Text', () => {
+ let apiRequestStub;
+
+ beforeEach(() => {
+ apiRequestStub = sinon.stub(api, 'apiRequest');
+ });
+
+ afterEach(() => {
+ apiRequestStub.restore();
+ });
+
describe('when military history exists', () => {
it('should render periodOfServiceTypeText when present and when periodOfServiceTypeCode is A or V', () => {
const initialState = createBasicInitialState();
diff --git a/src/applications/personalization/profile/tests/e2e/proof-of-veteran-status/proof-of-veteran-status.cypress.spec.js b/src/applications/personalization/profile/tests/e2e/proof-of-veteran-status/proof-of-veteran-status.cypress.spec.js
index 5f007816d5c0..08cb01bf1704 100644
--- a/src/applications/personalization/profile/tests/e2e/proof-of-veteran-status/proof-of-veteran-status.cypress.spec.js
+++ b/src/applications/personalization/profile/tests/e2e/proof-of-veteran-status/proof-of-veteran-status.cypress.spec.js
@@ -8,6 +8,10 @@ import {
dishonorableDischarge,
unknownDischarge,
} from '../../../mocks/endpoints/service-history';
+import {
+ confirmed,
+ notConfirmed,
+} from '../../../mocks/endpoints/vet-verification-status';
import MilitaryInformation from '../military-information/MilitaryInformation';
describe('Proof of Veteran status', () => {
@@ -16,6 +20,7 @@ describe('Proof of Veteran status', () => {
cy.intercept('GET', '/v0/user', loa3User72);
cy.intercept('GET', '/v0/profile/full_name', fullName.success);
cy.intercept('GET', '/v0/profile/service_history', airForce);
+ cy.intercept('GET', '/v0/profile/vet_verification_status', confirmed);
});
it('Should display the Proof of Veteran Status component', () => {
@@ -31,6 +36,7 @@ const login = ({ dischargeCode }) => {
cy.intercept('GET', '/v0/user', loa3User72);
cy.intercept('GET', '/v0/profile/full_name', fullName.success);
cy.intercept('GET', '/v0/profile/service_history', dischargeCode);
+ cy.intercept('GET', '/v0/profile/vet_verification_status', notConfirmed);
};
describe('Veteran is not eligible', () => {
From cc6bacea9bdee619c8347cb858d02b0c01ee4e8e Mon Sep 17 00:00:00 2001
From: Ian Magenta <59981318+ianmagenta@users.noreply.github.com>
Date: Thu, 16 Jan 2025 13:50:07 -0800
Subject: [PATCH 11/36] [VI-1007] Added Missing buttonContent Assignment
(#34134)
* added missing buttonContent assignment
* fixed unit test ci
---------
Co-authored-by: Caitlin <78328496+CaitHawk@users.noreply.github.com>
---
.github/workflows/continuous-integration.yml | 2 +-
src/applications/verify/components/UnifiedVerify.jsx | 10 ++++++----
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 88c5d6b6196c..e27444d75b09 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -266,7 +266,7 @@ jobs:
name: Unit Tests
needs: [fetch-allow-lists, tests-prep]
timeout-minutes: 30
- runs-on: ubuntu-16-cores-latest
+ runs-on: ubuntu-16-cores-22.04
outputs:
app_folders: ${{ steps.get-changed-apps.outputs.folders }}
changed-files: ${{ steps.get-changed-apps.outputs.changed_files }}
diff --git a/src/applications/verify/components/UnifiedVerify.jsx b/src/applications/verify/components/UnifiedVerify.jsx
index 19c590dff3e4..fbdf69f82322 100644
--- a/src/applications/verify/components/UnifiedVerify.jsx
+++ b/src/applications/verify/components/UnifiedVerify.jsx
@@ -19,10 +19,12 @@ const Verify = () => {
let buttonContent;
if (isAuthenticated) {
- <>
-
-
- >;
+ buttonContent = (
+ <>
+
+
+ >
+ );
} else if (isAuthenticatedOAuth) {
// Use the loginServiceName to determine which button to show
if (loginServiceName === 'idme') {
From f59cfff1d74395ad9da8cd1186e9f0f356dfb996 Mon Sep 17 00:00:00 2001
From: Jami Gibbs
Date: Thu, 16 Jan 2025 16:01:59 -0600
Subject: [PATCH 12/36] pin the 16 core runner to ubuntu 22.04 (#34139)
From f5b716e4f64e9f5e05009314409941d124d895f3 Mon Sep 17 00:00:00 2001
From: Taras Kurilo
Date: Thu, 16 Jan 2025 17:29:14 -0500
Subject: [PATCH 13/36] EDM-451 456 496 SOB Updates (#34133)
* edm-451 456 496 sob updates
* edm-456 update text
---
.../components/UserInfoSection.jsx | 6 ++++-
.../containers/StatusPage.jsx | 26 ++++++++++++++-----
.../sass/post-911-gib-status.scss | 16 +++++++-----
.../components/UserInfoSection.unit.spec.jsx | 25 ++++++++++++++++++
4 files changed, 58 insertions(+), 15 deletions(-)
diff --git a/src/applications/post-911-gib-status/components/UserInfoSection.jsx b/src/applications/post-911-gib-status/components/UserInfoSection.jsx
index e30b9e3507d1..21e4d247f3c7 100644
--- a/src/applications/post-911-gib-status/components/UserInfoSection.jsx
+++ b/src/applications/post-911-gib-status/components/UserInfoSection.jsx
@@ -101,7 +101,11 @@ function UserInfoSection({ enrollmentData = {}, showCurrentAsOfAlert }) {
You can print your statement and use it as a replacement for a
Certificate of Eligibility (COE) to show that you qualify for
- benefits.
+ benefits. This statement only includes entitlement earned through
+ your own military service. If you recently transferred entitlement,
+ it may not be reflected here.
+
+ The Supreme Court’s Rudisill decision may increase your months of
+ entitlement if you have two or more qualifying periods of active
+ duty.
+
+
);
printButton = (
-
+
How can I see my Post-9/11 GI Bill benefit payments?
-
+
-
- Call us at . We're here
- Monday through Friday, 8:00 a.m to 9:00 p.m ET. If you have
- hearing loss, call .
+
+ Call 888-GI-BILL-1 (
+ ). We're here from Monday through Friday, 8:00 a.m to 7:00 p.m
+ ET. If you have hearing loss, call{' '}
+ .
diff --git a/src/applications/post-911-gib-status/sass/post-911-gib-status.scss b/src/applications/post-911-gib-status/sass/post-911-gib-status.scss
index 21206b6383ce..f28651b3da1a 100644
--- a/src/applications/post-911-gib-status/sass/post-911-gib-status.scss
+++ b/src/applications/post-911-gib-status/sass/post-911-gib-status.scss
@@ -5,8 +5,8 @@
margin-bottom: 5em;
h3 {
- margin: 1em 0 .5em 0;
- padding: 0 0 .25em 0;
+ margin-bottom: 8.5px;
+ padding: 0;
}
hr {
@@ -16,18 +16,20 @@
margin-top: 1em;
margin-bottom: 1em;
}
+ h2 {
+ margin-top: 0;
+ }
.section {
margin-bottom: 2em;
}
.section-line {
- margin-bottom: .25em;
+ margin-bottom: 0.25em;
+ }
+ #benefit-level {
+ margin-top: 2em;
}
-
- /*.usa-alert {
- margin: 1em 0 1em 0;
- }*/
.not-qualified h5 {
margin-top: 1em;
diff --git a/src/applications/post-911-gib-status/tests/components/UserInfoSection.unit.spec.jsx b/src/applications/post-911-gib-status/tests/components/UserInfoSection.unit.spec.jsx
index 6f111e5aa446..fb00ccb41e83 100644
--- a/src/applications/post-911-gib-status/tests/components/UserInfoSection.unit.spec.jsx
+++ b/src/applications/post-911-gib-status/tests/components/UserInfoSection.unit.spec.jsx
@@ -136,4 +136,29 @@ describe('
', () => {
expect(benefitEndDate.text()).to.contain('Since you’re on active duty');
});
});
+ describe('date of birth InfoPair', () => {
+ it('should display the formatted date of birth if present', () => {
+ const tree = SkinDeep.shallowRender( );
+ const dobInfoPair = tree
+ .everySubTree('InfoPair')
+ .find(pair => pair.props.label === 'Date of birth');
+ expect(dobInfoPair).to.exist;
+ expect(dobInfoPair.props.value).to.equal('November 12, 1995');
+ });
+
+ it('should display "Unavailable" if dateOfBirth is missing', () => {
+ // Create a copy of props but remove dateOfBirth
+ const noDobProps = _.merge({}, props, {
+ enrollmentData: {
+ dateOfBirth: null,
+ },
+ });
+ const tree = SkinDeep.shallowRender( );
+ const dobInfoPair = tree
+ .everySubTree('InfoPair')
+ .find(pair => pair.props.label === 'Date of birth');
+ expect(dobInfoPair).to.exist;
+ expect(dobInfoPair.props.value).to.equal('Unavailable');
+ });
+ });
});
From 32fcd0b48636fb4ba300f01282e84450ed7442d3 Mon Sep 17 00:00:00 2001
From: Jami Gibbs
Date: Thu, 16 Jan 2025 16:54:58 -0600
Subject: [PATCH 14/36] Disable no import linting error (#34143)
* disable no import linting error
* disable esline error
---
.../vaos/referral-appointments/ChooseDateAndTime.jsx | 2 ++
.../vaos/referral-appointments/components/RequestsList.jsx | 1 +
2 files changed, 3 insertions(+)
diff --git a/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx b/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx
index 24b172a4758b..10f49b1b9bff 100644
--- a/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx
+++ b/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx
@@ -3,8 +3,10 @@ import PropTypes from 'prop-types';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { useLocation } from 'react-router-dom';
import ReferralLayout from './components/ReferralLayout';
+// eslint-disable-next-line import/no-restricted-paths
import { getUpcomingAppointmentListInfo } from '../appointment-list/redux/selectors';
import { setFormCurrentPage, fetchProviderDetails } from './redux/actions';
+// eslint-disable-next-line import/no-restricted-paths
import { fetchFutureAppointments } from '../appointment-list/redux/actions';
import { getProviderInfo } from './redux/selectors';
import { FETCH_STATUS } from '../utils/constants';
diff --git a/src/applications/vaos/referral-appointments/components/RequestsList.jsx b/src/applications/vaos/referral-appointments/components/RequestsList.jsx
index d0dac5e7c89a..a88b485a1b71 100644
--- a/src/applications/vaos/referral-appointments/components/RequestsList.jsx
+++ b/src/applications/vaos/referral-appointments/components/RequestsList.jsx
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import RequestAppointmentLayout from '../../components/RequestAppointmentLayout';
import { APPOINTMENT_STATUS } from '../../utils/constants';
import InfoAlert from '../../components/InfoAlert';
+// eslint-disable-next-line import/no-restricted-paths
import ScheduleAppointmentLink from '../../appointment-list/components/ScheduleAppointmentLink';
const RequestList = ({ appointments, requestsError }) => {
From e00c7c5765c50003d5a70f5c189bbd01da57a26a Mon Sep 17 00:00:00 2001
From: Simi Adebowale <47654119+simiadebowale@users.noreply.github.com>
Date: Thu, 16 Jan 2025 18:04:33 -0600
Subject: [PATCH 15/36] Add required indicator to DateTimeSelectPage heading
and corresponding test (#34132)
---
.../components/DateTimeSelectPage/index.jsx | 7 ++++-
.../DateTimeSelectPage/index.unit.spec.js | 27 +++++++++++++++++++
2 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/src/applications/vaos/new-appointment/components/DateTimeSelectPage/index.jsx b/src/applications/vaos/new-appointment/components/DateTimeSelectPage/index.jsx
index 41a46b1243a4..57d8c9334324 100644
--- a/src/applications/vaos/new-appointment/components/DateTimeSelectPage/index.jsx
+++ b/src/applications/vaos/new-appointment/components/DateTimeSelectPage/index.jsx
@@ -178,7 +178,12 @@ export default function DateTimeSelectPage() {
return (
-
{pageTitle}
+
+ {pageTitle}
+
+ (*Required)
+
+
{!loadingSlots && (
{
});
expect(screen.history.push.called).to.be.false;
});
+ it('should show required text next to page heading', async () => {
+ const preferredDate = moment();
+ const slot308Date = moment().add(6, 'days');
+
+ setDateTimeSelectMockFetches({
+ typeOfCareId: 'outpatientMentalHealth',
+ slotDatesByClinicId: {
+ '308': [slot308Date],
+ },
+ });
+
+ const store = createTestStore(initialState);
+
+ await setTypeOfCare(store, /mental health/i);
+ await setVAFacility(store, '983', 'outpatientMentalHealth');
+ await setClinic(store, '983_308');
+ await setPreferredDate(store, preferredDate);
+
+ const screen = renderWithStoreAndRouter(
+ ,
+ {
+ store,
+ },
+ );
+
+ expect(await screen.findByText(/Required/i)).to.exist;
+ });
});
From 2d7bdb8b5fa312fbf0a584d18dbf1a2c7480620e Mon Sep 17 00:00:00 2001
From: Colin <143013011+cosu419@users.noreply.github.com>
Date: Fri, 17 Jan 2025 06:15:13 -0800
Subject: [PATCH 16/36] Appoint a rep - submission method (#34050)
* Added feature toggle for appoint v2 features
* Moving page depends logic into schema files to clean up form config
* Made use of feature toggle for v2 features
* Adjusted feature toggle + env checks. Added constant for local testing without vets-api
* Created rep submission method screen
* Updated routing for to include submission method
* Added to submissionmethod page conditional
* Added unit tests for submission method
* Added return statement
* Disabled feature toggle for e2e tests
* comment
* Corrected error state copy
* Added unit test for error state
* File + component naming
* Updated pageDepends conditional
* Make use of new pagedepends in representativeSubmissionMethod
---
.../ContactAccreditedRepresentative.jsx | 9 +
.../RepresentativeSubmissionMethod.jsx | 95 +++++++++++
.../representative-appoint/config/form.js | 32 ++--
.../constants/enableV2FeaturesLocally.js | 3 +
.../representative-appoint/containers/App.jsx | 26 ++-
.../hooks/useV2FeatureVisibility.js | 31 ++++
.../representative-appoint/pages/index.js | 2 +
.../representativeSubmissionMethod.js | 43 +++++
.../selectAccreditedOrganization.js | 20 ++-
.../tests/e2e/navigation/2122.cypress.spec.js | 4 +
.../e2e/navigation/2122a.cypress.spec.js | 4 +
...presentativeSubmissionMethod.unit.spec.jsx | 159 ++++++++++++++++++
.../feature-toggles/featureFlagNames.json | 1 +
13 files changed, 396 insertions(+), 33 deletions(-)
create mode 100644 src/applications/representative-appoint/components/RepresentativeSubmissionMethod.jsx
create mode 100644 src/applications/representative-appoint/constants/enableV2FeaturesLocally.js
create mode 100644 src/applications/representative-appoint/hooks/useV2FeatureVisibility.js
create mode 100644 src/applications/representative-appoint/pages/representative/representativeSubmissionMethod.js
create mode 100644 src/applications/representative-appoint/tests/pages/representative/representativeSubmissionMethod.unit.spec.jsx
diff --git a/src/applications/representative-appoint/components/ContactAccreditedRepresentative.jsx b/src/applications/representative-appoint/components/ContactAccreditedRepresentative.jsx
index cf3d6507433e..4602b79a158f 100644
--- a/src/applications/representative-appoint/components/ContactAccreditedRepresentative.jsx
+++ b/src/applications/representative-appoint/components/ContactAccreditedRepresentative.jsx
@@ -4,10 +4,12 @@ import FormNavButtons from 'platform/forms-system/src/js/components/FormNavButto
import PropTypes from 'prop-types';
import { useReviewPage } from '../hooks/useReviewPage';
import { getEntityAddressAsObject } from '../utilities/helpers';
+import useV2FeatureToggle from '../hooks/useV2FeatureVisibility';
import AddressEmailPhone from './AddressEmailPhone';
const ContactAccreditedRepresentative = props => {
+ const v2IsEnabled = useV2FeatureToggle();
const { formData, goBack, goForward, goToPath } = props;
const rep = props?.formData?.['view:selectedRepresentative'];
const repAttributes = rep?.attributes;
@@ -26,6 +28,9 @@ const ContactAccreditedRepresentative = props => {
) &&
representative.attributes?.accreditedOrganizations?.data?.length > 1;
+ // will need to update this when we can determine submission methods
+ const submissionMethodRequired = orgSelectionRequired && v2IsEnabled;
+
const handleGoBack = () => {
if (isReviewPage) {
goToPath('/representative-select?review=true');
@@ -36,6 +41,10 @@ const ContactAccreditedRepresentative = props => {
const handleGoForward = () => {
if (isReviewPage) {
+ if (submissionMethodRequired) {
+ goToPath('/representative-submission-method?review=true');
+ return;
+ }
if (orgSelectionRequired) {
goToPath('/representative-organization?review=true');
} else {
diff --git a/src/applications/representative-appoint/components/RepresentativeSubmissionMethod.jsx b/src/applications/representative-appoint/components/RepresentativeSubmissionMethod.jsx
new file mode 100644
index 000000000000..8fd82cb1e45b
--- /dev/null
+++ b/src/applications/representative-appoint/components/RepresentativeSubmissionMethod.jsx
@@ -0,0 +1,95 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { VaRadio } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import FormNavButtons from 'platform/forms-system/src/js/components/FormNavButtons';
+import { scrollToFirstError } from 'platform/utilities/ui';
+import { useReviewPage } from '../hooks/useReviewPage';
+
+const RepresentativeSubmissionMethod = props => {
+ const { formData, setFormData, goBack, goForward, goToPath } = props;
+ const [error, setError] = useState(null);
+
+ const isReviewPage = useReviewPage();
+
+ const handleGoBack = () => {
+ if (isReviewPage) {
+ goToPath('representative-contact?review=true');
+ } else {
+ goBack(formData);
+ }
+ };
+ const handleGoForward = () => {
+ if (!formData?.representativeSubmissionMethod) {
+ setError('Choose how to submit your request by selecting an option');
+ scrollToFirstError({ focusOnAlertRole: true });
+ } else if (isReviewPage) {
+ goToPath('/representative-organization?review=true');
+ } else {
+ goForward(formData);
+ }
+ };
+
+ const handleRadioSelect = e => {
+ setError(null);
+ setFormData({
+ ...formData,
+ representativeSubmissionMethod: e.detail.value,
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+};
+
+RepresentativeSubmissionMethod.propTypes = {
+ formData: PropTypes.object,
+ goBack: PropTypes.func,
+ goForward: PropTypes.func,
+ goToPath: PropTypes.func,
+ setFormData: PropTypes.func,
+};
+
+function mapStateToProps(state) {
+ return {
+ formData: state.form.data,
+ };
+}
+
+export { RepresentativeSubmissionMethod }; // Named export for testing
+
+export default connect(
+ mapStateToProps,
+ null,
+)(RepresentativeSubmissionMethod);
diff --git a/src/applications/representative-appoint/config/form.js b/src/applications/representative-appoint/config/form.js
index 0add5f0207a0..d74bb684f16a 100644
--- a/src/applications/representative-appoint/config/form.js
+++ b/src/applications/representative-appoint/config/form.js
@@ -1,7 +1,6 @@
import commonDefinitions from 'vets-json-schema/dist/definitions.json';
-// import environment from '@department-of-veterans-affairs/platform-utilities/environment';
-// import profileContactInfo from 'platform/forms-system/src/js/definitions/profileContactInfo';
import FormFooter from 'platform/forms/components/FormFooter';
+
import GetFormHelp from '../components/GetFormHelp';
import configService from '../utilities/configService';
import manifest from '../manifest.json';
@@ -33,6 +32,7 @@ import {
replaceAccreditedRepresentative,
selectedAccreditedOrganizationId,
contactAccreditedRepresentative,
+ representativeSubmissionMethod,
} from '../pages';
// import initialData from '../tests/fixtures/data/test-data.json';
@@ -41,6 +41,7 @@ import SelectAccreditedRepresentative from '../components/SelectAccreditedRepres
import SelectedAccreditedRepresentativeReview from '../components/SelectAccreditedRepresentativeReview';
import ContactAccreditedRepresentative from '../components/ContactAccreditedRepresentative';
import SelectOrganization from '../components/SelectOrganization';
+import RepresentativeSubmissionMethod from '../components/RepresentativeSubmissionMethod';
import SubmissionError from '../components/SubmissionError';
@@ -136,29 +137,26 @@ const formConfig = {
uiSchema: contactAccreditedRepresentative.uiSchema,
schema: contactAccreditedRepresentative.schema,
},
+ RepresentativeSubmissionMethod: {
+ title: 'Representative Submission Method',
+ path: 'representative-submission-method',
+ CustomPage: RepresentativeSubmissionMethod,
+ depends: formData =>
+ representativeSubmissionMethod.pageDepends(formData),
+ uiSchema: representativeSubmissionMethod.uiSchema,
+ schema: representativeSubmissionMethod.schema,
+ },
selectAccreditedOrganization: {
path: 'representative-organization',
title: 'Organization Select',
hideOnReview: true,
CustomPage: SelectOrganization,
depends: formData =>
- !!formData['view:selectedRepresentative'] &&
- ['representative', 'veteran_service_officer'].includes(
- formData['view:selectedRepresentative'].attributes
- ?.individualType,
- ) &&
- formData['view:selectedRepresentative'].attributes
- ?.accreditedOrganizations?.data?.length > 1,
+ selectedAccreditedOrganizationId.pageDepends(formData),
uiSchema: selectedAccreditedOrganizationId.uiSchema,
- schema: {
- type: 'object',
- properties: {
- selectedAccreditedOrganizationId: {
- type: 'string',
- },
- },
- },
+ schema: selectedAccreditedOrganizationId.schema,
},
+
replaceAccreditedRepresentative: {
title: 'Representative Replace',
path: 'representative-replace',
diff --git a/src/applications/representative-appoint/constants/enableV2FeaturesLocally.js b/src/applications/representative-appoint/constants/enableV2FeaturesLocally.js
new file mode 100644
index 000000000000..c5c1800af150
--- /dev/null
+++ b/src/applications/representative-appoint/constants/enableV2FeaturesLocally.js
@@ -0,0 +1,3 @@
+// toggle this eg when testing PRs locally
+
+export const enableV2FeaturesForLocalTesting = true;
diff --git a/src/applications/representative-appoint/containers/App.jsx b/src/applications/representative-appoint/containers/App.jsx
index 0b397ca4d7f4..4d3cd330117d 100644
--- a/src/applications/representative-appoint/containers/App.jsx
+++ b/src/applications/representative-appoint/containers/App.jsx
@@ -15,6 +15,8 @@ import formConfig from '../config/form';
import configService from '../utilities/configService';
import { getFormSubtitle } from '../utilities/helpers';
+import useV2FeatureToggle from '../hooks/useV2FeatureVisibility';
+
function App({ loggedIn, location, children, formData, setFormData }) {
const subTitle = getFormSubtitle(formData);
@@ -25,6 +27,7 @@ function App({ loggedIn, location, children, formData, setFormData }) {
} = useFeatureToggle();
const appIsEnabled = useToggleValue(appToggleKey);
+ const v2FeatureToggle = useV2FeatureToggle();
const isProduction = window.Cypress || environment.isProduction();
const isAppToggleLoading = useToggleLoadingValue(appToggleKey);
@@ -40,6 +43,7 @@ function App({ loggedIn, location, children, formData, setFormData }) {
[pathname],
);
+ // dynamically updates the form subtitle to 21-22 or 21-22A
useEffect(
() => {
configService.setFormConfig({ subTitle });
@@ -50,26 +54,18 @@ function App({ loggedIn, location, children, formData, setFormData }) {
useEffect(
() => {
- const defaultViewFields = {
+ const updatedFormData = {
+ ...formData,
+ v2IsEnabled: v2FeatureToggle,
'view:isLoggedIn': loggedIn,
+ 'view:representativeQueryInput': '',
+ 'view:representativeSearchResults': [],
};
- setFormData({
- ...formData,
- ...defaultViewFields,
- });
+ setFormData(updatedFormData);
},
- [loggedIn],
+ [v2FeatureToggle, loggedIn],
);
- // resetting user query between sessions
- useEffect(() => {
- setFormData({
- ...formData,
- 'view:representativeQueryInput': '',
- 'view:representativeSearchResults': [],
- });
- }, []);
-
if (isAppToggleLoading) {
return (
diff --git a/src/applications/representative-appoint/hooks/useV2FeatureVisibility.js b/src/applications/representative-appoint/hooks/useV2FeatureVisibility.js
new file mode 100644
index 000000000000..e58ffe290db5
--- /dev/null
+++ b/src/applications/representative-appoint/hooks/useV2FeatureVisibility.js
@@ -0,0 +1,31 @@
+import { useFeatureToggle } from '~/platform/utilities/feature-toggles';
+import environment from 'platform/utilities/environment';
+import { enableV2FeaturesForLocalTesting } from '../constants/enableV2FeaturesLocally';
+
+const useV2FeatureToggle = () => {
+ const {
+ TOGGLE_NAMES: { appointARepresentativeEnableV2Features: appToggleKey },
+ useToggleLoadingValue,
+ useToggleValue,
+ } = useFeatureToggle();
+
+ const appointV2FeaturesEnabled = useToggleValue(appToggleKey);
+ const toggleIsLoading = useToggleLoadingValue(appToggleKey);
+
+ if (toggleIsLoading) {
+ return false;
+ }
+
+ // can remove this after verifying the toggle in staging
+ if (environment.isProduction() || window.Cypress) {
+ return false;
+ }
+
+ if (environment.isLocalhost()) {
+ return enableV2FeaturesForLocalTesting;
+ }
+
+ return appointV2FeaturesEnabled;
+};
+
+export default useV2FeatureToggle;
diff --git a/src/applications/representative-appoint/pages/index.js b/src/applications/representative-appoint/pages/index.js
index ae5338060abf..dcbc97651470 100644
--- a/src/applications/representative-appoint/pages/index.js
+++ b/src/applications/representative-appoint/pages/index.js
@@ -20,6 +20,7 @@ import * as selectAccreditedRepresentative from './representative/selectAccredit
import * as replaceAccreditedRepresentative from './representative/replaceAccreditedRepresentative';
import * as selectedAccreditedOrganizationId from './representative/selectAccreditedOrganization';
import * as contactAccreditedRepresentative from './representative/contactAccreditedRepresentative';
+import * as representativeSubmissionMethod from './representative/representativeSubmissionMethod';
export {
authorizeMedical,
@@ -44,4 +45,5 @@ export {
replaceAccreditedRepresentative,
selectedAccreditedOrganizationId,
contactAccreditedRepresentative,
+ representativeSubmissionMethod,
};
diff --git a/src/applications/representative-appoint/pages/representative/representativeSubmissionMethod.js b/src/applications/representative-appoint/pages/representative/representativeSubmissionMethod.js
new file mode 100644
index 000000000000..7c4b6ac43c69
--- /dev/null
+++ b/src/applications/representative-appoint/pages/representative/representativeSubmissionMethod.js
@@ -0,0 +1,43 @@
+import RepresentativeSubmissionMethod from '../../components/RepresentativeSubmissionMethod';
+
+export const uiSchema = {
+ representativeSubmissionMethod: {
+ 'ui:title': "Select how you'd like to submit your request",
+ 'ui:widget': RepresentativeSubmissionMethod,
+ 'ui:options': {
+ hideLabelText: true,
+ hideOnReview: true,
+ },
+ 'ui:required': () => true,
+ },
+};
+
+export const schema = {
+ type: 'object',
+ properties: {
+ representativeSubmissionMethod: {
+ type: 'string',
+ },
+ },
+};
+
+export const pageDepends = formData => {
+ const { v2IsEnabled } = formData;
+ // temporarily hardcoding these values
+ const repHasMultipleOrganizations =
+ !!formData['view:selectedRepresentative'] &&
+ ['representative', 'veteran_service_officer'].includes(
+ formData['view:selectedRepresentative'].attributes?.individualType,
+ ) &&
+ formData['view:selectedRepresentative'].attributes?.accreditedOrganizations
+ ?.data?.length > 1;
+ const userCanSubmitDigitally = true;
+ const representativeAcceptsDigitalSubmission = true;
+
+ return (
+ v2IsEnabled &&
+ repHasMultipleOrganizations &&
+ userCanSubmitDigitally &&
+ representativeAcceptsDigitalSubmission
+ );
+};
diff --git a/src/applications/representative-appoint/pages/representative/selectAccreditedOrganization.js b/src/applications/representative-appoint/pages/representative/selectAccreditedOrganization.js
index 21ddac36f596..47ef06e7bd84 100644
--- a/src/applications/representative-appoint/pages/representative/selectAccreditedOrganization.js
+++ b/src/applications/representative-appoint/pages/representative/selectAccreditedOrganization.js
@@ -12,4 +12,22 @@ export const uiSchema = {
},
};
-export const schema = {};
+export const schema = {
+ type: 'object',
+ properties: {
+ selectedAccreditedOrganizationId: {
+ type: 'string',
+ },
+ },
+};
+
+export const pageDepends = formData => {
+ return (
+ !!formData['view:selectedRepresentative'] &&
+ ['representative', 'veteran_service_officer'].includes(
+ formData['view:selectedRepresentative'].attributes?.individualType,
+ ) &&
+ formData['view:selectedRepresentative'].attributes?.accreditedOrganizations
+ ?.data?.length > 1
+ );
+};
diff --git a/src/applications/representative-appoint/tests/e2e/navigation/2122.cypress.spec.js b/src/applications/representative-appoint/tests/e2e/navigation/2122.cypress.spec.js
index 420d24a7644a..4312fd9fe670 100644
--- a/src/applications/representative-appoint/tests/e2e/navigation/2122.cypress.spec.js
+++ b/src/applications/representative-appoint/tests/e2e/navigation/2122.cypress.spec.js
@@ -17,6 +17,10 @@ describe('Authenticated', () => {
data: {
features: [
{ name: 'appoint_a_representative_enable_frontend', value: true },
+ {
+ name: 'appoint_a_representative_enable_v2_features',
+ value: false,
+ },
],
},
});
diff --git a/src/applications/representative-appoint/tests/e2e/navigation/2122a.cypress.spec.js b/src/applications/representative-appoint/tests/e2e/navigation/2122a.cypress.spec.js
index 1c6df30fbb0c..c1ec9e80484d 100644
--- a/src/applications/representative-appoint/tests/e2e/navigation/2122a.cypress.spec.js
+++ b/src/applications/representative-appoint/tests/e2e/navigation/2122a.cypress.spec.js
@@ -18,6 +18,10 @@ describe('Unauthenticated', () => {
data: {
features: [
{ name: 'appoint_a_representative_enable_frontend', value: true },
+ {
+ name: 'appoint_a_representative_enable_v2_features',
+ value: false,
+ },
],
},
});
diff --git a/src/applications/representative-appoint/tests/pages/representative/representativeSubmissionMethod.unit.spec.jsx b/src/applications/representative-appoint/tests/pages/representative/representativeSubmissionMethod.unit.spec.jsx
new file mode 100644
index 000000000000..8ec8f1784f96
--- /dev/null
+++ b/src/applications/representative-appoint/tests/pages/representative/representativeSubmissionMethod.unit.spec.jsx
@@ -0,0 +1,159 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import { expect } from 'chai';
+import { render, fireEvent, waitFor } from '@testing-library/react';
+import sinon from 'sinon';
+import { RepresentativeSubmissionMethod } from '../../../components/RepresentativeSubmissionMethod';
+import * as reviewPageHook from '../../../hooks/useReviewPage';
+
+describe('
', () => {
+ const getProps = () => {
+ return {
+ props: {
+ formData: {},
+ goBack: sinon.spy(),
+ goForward: sinon.spy(),
+ goToPath: sinon.spy(),
+ },
+ mockStore: {
+ getState: () => ({}),
+ subscribe: () => {},
+ },
+ };
+ };
+
+ const renderContainer = (props, mockStore) => {
+ return render(
+
+
+ ,
+ );
+ };
+
+ it('should render component', () => {
+ const { props, mockStore } = getProps();
+
+ const { container } = render(
+
+
+ ,
+ );
+ expect(container).to.exist;
+ });
+
+ it('displays an error', async () => {
+ const { props, mockStore } = getProps();
+
+ const { container } = renderContainer(props, mockStore);
+
+ const radioSelector = container.querySelector('va-radio');
+
+ const continueButton = container.querySelector('.usa-button-primary');
+
+ fireEvent.click(continueButton);
+
+ await waitFor(() => {
+ expect(radioSelector).to.have.attr(
+ 'error',
+ 'Choose how to submit your request by selecting an option',
+ );
+ });
+ });
+
+ context('non-review mode', () => {
+ it('should call goBack with formData when handleGoBack is triggered and isReviewPage is false', () => {
+ const { props, mockStore } = getProps();
+
+ const useReviewPageStub = sinon
+ .stub(reviewPageHook, 'call')
+ .returns(false);
+
+ const { getByText } = renderContainer(props, mockStore);
+
+ fireEvent.click(getByText('Back'));
+
+ expect(props.goBack.calledOnce).to.be.true;
+ expect(props.goBack.calledWith(props.formData)).to.be.true;
+
+ useReviewPageStub.restore();
+ });
+
+ it('should call goForward with formData when handleGoForward is triggered and isReviewPage is false', () => {
+ const { props, mockStore } = getProps();
+
+ const useReviewPageStub = sinon
+ .stub(reviewPageHook, 'useReviewPage')
+ .returns(false);
+
+ props.formData.representativeSubmissionMethod = 'mail';
+
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ fireEvent.click(getByText('Continue'));
+
+ expect(props.goForward.calledOnce).to.be.true;
+ expect(props.goForward.calledWith(props.formData)).to.be.true;
+ expect(props.goToPath.called).to.be.false;
+
+ useReviewPageStub.restore();
+ });
+ });
+
+ context('review mode', () => {
+ beforeEach(function() {
+ Object.defineProperty(window, 'location', {
+ value: { search: '?review=true' },
+ writable: true,
+ });
+ });
+
+ it('should call goToPath with the correct path when handleGoBack is triggered and isReviewPage is true', () => {
+ const useReviewPageStub = sinon
+ .stub(reviewPageHook, 'useReviewPage')
+ .returns(true);
+
+ const { props, mockStore } = getProps();
+
+ const { getByText } = renderContainer(props, mockStore);
+
+ fireEvent.click(getByText('Back'));
+
+ expect(props.goBack.called).to.be.false;
+ expect(props.goToPath.calledOnce).to.be.true;
+ expect(props.goToPath.calledWith('representative-contact?review=true')).to
+ .be.true;
+
+ useReviewPageStub.restore();
+ });
+
+ it('should call goToPath with /representative-organization?review=true when handleGoForward is triggered, isReviewPage is true, and isReplacingRep is true', () => {
+ const { props, mockStore } = getProps();
+
+ const useReviewPageStub = sinon
+ .stub(reviewPageHook, 'useReviewPage')
+ .returns(true);
+
+ props.formData.representativeSubmissionMethod = 'mail';
+
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ fireEvent.click(getByText('Continue'));
+
+ expect(props.goForward.called).to.be.false;
+ expect(props.goToPath.calledOnce).to.be.true;
+ expect(
+ props.goToPath.calledWith('/representative-organization?review=true'),
+ ).to.be.true;
+
+ useReviewPageStub.restore();
+ });
+ });
+});
diff --git a/src/platform/utilities/feature-toggles/featureFlagNames.json b/src/platform/utilities/feature-toggles/featureFlagNames.json
index fc321f66f4e1..327eeed7d3fa 100644
--- a/src/platform/utilities/feature-toggles/featureFlagNames.json
+++ b/src/platform/utilities/feature-toggles/featureFlagNames.json
@@ -5,6 +5,7 @@
"aedpPrefill": "aedp_prefill",
"allClaimsAddDisabilitiesEnhancement": "all_claims_add_disabilities_enhancement",
"appointARepresentativeEnableFrontend": "appoint_a_representative_enable_frontend",
+ "appointARepresentativeEnableV2Features": "appoint_a_representative_enable_v2_features",
"askVaDashboardFeature": "ask_va_dashboard_feature",
"askVaFormFeature": "ask_va_form_feature",
"askVaIntroductionPageFeature": "ask_va_introduction_page_feature",
From d3abcb7b709ec1f21e1266558697627e93b0f13d Mon Sep 17 00:00:00 2001
From: Brandon Cooper
Date: Fri, 17 Jan 2025 09:16:07 -0500
Subject: [PATCH 17/36] fix typo (#34140)
---
.../caregivers/components/FormFields/FacilitySearch.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/applications/caregivers/components/FormFields/FacilitySearch.jsx b/src/applications/caregivers/components/FormFields/FacilitySearch.jsx
index 025a203c168a..d29e1483ec55 100644
--- a/src/applications/caregivers/components/FormFields/FacilitySearch.jsx
+++ b/src/applications/caregivers/components/FormFields/FacilitySearch.jsx
@@ -301,7 +301,7 @@ const FacilitySearch = props => {
You’ll need to find and select the VA medical center or clinic where
- the Veteran receives or plans to recieve care.
+ the Veteran receives or plans to receive care.
The VA medical center or clinic may be in a different city, state, or
From 96272b8316e06e31ac056e3ea44f8f11f30ad9c8 Mon Sep 17 00:00:00 2001
From: Peri-Ann McLaren <141954992+pmclaren19@users.noreply.github.com>
Date: Fri, 17 Jan 2025 07:36:14 -0700
Subject: [PATCH 18/36] remove request by from breadcrumbs, tab title and
document request page header (#34138)
---
.../claim-document-request-pages/DefaultPage.jsx | 2 +-
.../components/DocumentRequestPage.unit.spec.jsx | 6 +++---
.../DefaultPage.unit.spec.jsx | 15 +++++++++------
.../tests/e2e/page-objects/TrackClaimsPage.js | 2 +-
.../tests/e2e/page-objects/TrackClaimsPageV2.js | 2 +-
.../tests/utils/helpers.unit.spec.js | 4 ++--
src/applications/claims-status/utils/helpers.js | 3 ++-
7 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/src/applications/claims-status/components/claim-document-request-pages/DefaultPage.jsx b/src/applications/claims-status/components/claim-document-request-pages/DefaultPage.jsx
index da9f6e9ae1a4..e22cc22c2632 100644
--- a/src/applications/claims-status/components/claim-document-request-pages/DefaultPage.jsx
+++ b/src/applications/claims-status/components/claim-document-request-pages/DefaultPage.jsx
@@ -21,7 +21,7 @@ export default function DefaultPage({
}) {
return (
-
Request for {item.displayName}
+
{item.displayName}
{item.status === 'NEEDED_FROM_YOU' ? (
) : null}
diff --git a/src/applications/claims-status/tests/components/DocumentRequestPage.unit.spec.jsx b/src/applications/claims-status/tests/components/DocumentRequestPage.unit.spec.jsx
index a777678891e4..0b8a3734d2a0 100644
--- a/src/applications/claims-status/tests/components/DocumentRequestPage.unit.spec.jsx
+++ b/src/applications/claims-status/tests/components/DocumentRequestPage.unit.spec.jsx
@@ -125,10 +125,10 @@ describe('
', () => {
`../document-request/${trackedItem.id}`,
);
expect(breadcrumbs.breadcrumbList[3].label).to.equal(
- `Request for ${trackedItem.displayName}`,
+ trackedItem.displayName,
);
expect(document.title).to.equal(
- `Request for ${trackedItem.displayName} | Veterans Affairs`,
+ `${trackedItem.displayName} | Veterans Affairs`,
);
});
});
@@ -465,7 +465,7 @@ describe('', () => {
,
);
- expect(document.title).to.equal('Request for Testing | Veterans Affairs');
+ expect(document.title).to.equal('Testing | Veterans Affairs');
expect(resetUploads.called).to.be.true;
});
diff --git a/src/applications/claims-status/tests/components/claim-document-request-pages/DefaultPage.unit.spec.jsx b/src/applications/claims-status/tests/components/claim-document-request-pages/DefaultPage.unit.spec.jsx
index bc17cec4197b..ae721b033752 100644
--- a/src/applications/claims-status/tests/components/claim-document-request-pages/DefaultPage.unit.spec.jsx
+++ b/src/applications/claims-status/tests/components/claim-document-request-pages/DefaultPage.unit.spec.jsx
@@ -1,6 +1,5 @@
import React from 'react';
import { expect } from 'chai';
-import moment from 'moment-timezone';
import { $ } from '@department-of-veterans-affairs/platform-forms-system/ui';
@@ -41,6 +40,12 @@ describe('', () => {
documents: '[]',
date: '2024-03-07',
};
+ const today = new Date();
+ const past = new Date(item.suspenseDate);
+ const monthsDue =
+ today.getMonth() -
+ past.getMonth() +
+ 12 * (today.getFullYear() - past.getFullYear());
const { getByText, container } = renderWithRouter(
,
);
@@ -49,12 +54,10 @@ describe('', () => {
expect($('.due-date-header', container)).to.exist;
const formattedClaimDate = formatDate(item.suspenseDate);
getByText(
- `Needed from you by ${formattedClaimDate} - Due ${moment(
- item.suspenseDate,
- ).fromNow()}`,
+ `Needed from you by ${formattedClaimDate} - Due ${monthsDue} months ago`,
);
expect($('.optional-upload', container)).to.not.exist;
- getByText('Request for Submit buddy statement(s)');
+ getByText('Submit buddy statement(s)');
getByText(scrubDescription(item.description));
expect($('va-additional-info', container)).to.exist;
expect($('va-file-input', container)).to.exist;
@@ -85,7 +88,7 @@ describe('', () => {
getByText(
'- We’ve asked others to send this to us, but you may upload it if you have it.',
);
- getByText('Request for Submit buddy statement(s)');
+ getByText('Submit buddy statement(s)');
getByText(scrubDescription(item.description));
expect($('va-additional-info', container)).to.exist;
expect($('va-file-input', container)).to.exist;
diff --git a/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPage.js b/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPage.js
index 42c2f0cf3d85..229e4b5e6438 100644
--- a/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPage.js
+++ b/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPage.js
@@ -336,7 +336,7 @@ class TrackClaimsPage {
} else {
cy.get('.usa-breadcrumb__list > li:nth-child(4) a').should(
'contain',
- 'Request for Submit Buddy Statement(s)',
+ 'Submit Buddy Statement(s)',
);
}
}
diff --git a/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js b/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js
index 96d14c3f9c51..307789b936fd 100644
--- a/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js
+++ b/src/applications/claims-status/tests/e2e/page-objects/TrackClaimsPageV2.js
@@ -660,7 +660,7 @@ class TrackClaimsPageV2 {
} else {
cy.get('.usa-breadcrumb__list > li:nth-child(4) a').should(
'contain',
- 'Request for Submit Buddy Statement(s)',
+ 'Submit Buddy Statement(s)',
);
}
}
diff --git a/src/applications/claims-status/tests/utils/helpers.unit.spec.js b/src/applications/claims-status/tests/utils/helpers.unit.spec.js
index d12c3c3b7588..07ec9782084b 100644
--- a/src/applications/claims-status/tests/utils/helpers.unit.spec.js
+++ b/src/applications/claims-status/tests/utils/helpers.unit.spec.js
@@ -1196,11 +1196,11 @@ describe('Disability benefits helpers: ', () => {
'Review evidence list (5103 notice)',
);
});
- it('should display Request for Submit buddy statement(s)', () => {
+ it('should display Submit buddy statement(s)', () => {
const displayName = 'Submit buddy statement(s)';
const documentRequestPageTitle = setDocumentRequestPageTitle(displayName);
- expect(documentRequestPageTitle).to.equal(`Request for ${displayName}`);
+ expect(documentRequestPageTitle).to.equal(displayName);
});
});
diff --git a/src/applications/claims-status/utils/helpers.js b/src/applications/claims-status/utils/helpers.js
index 9ed672c0eaf2..b9138949573e 100644
--- a/src/applications/claims-status/utils/helpers.js
+++ b/src/applications/claims-status/utils/helpers.js
@@ -1190,10 +1190,11 @@ export const generateClaimTitle = (claim, placement, tab) => {
};
// Use this function to set the Document Request Page Title, Page Tab and Page Breadcrumb Title
+// It is also used to set the Document Request Page breadcrumb text
export function setDocumentRequestPageTitle(displayName) {
return isAutomated5103Notice(displayName)
? 'Review evidence list (5103 notice)'
- : `Request for ${displayName}`;
+ : displayName;
}
// Used to set page title for the CST Tabs
From fff065779f9296088747f20637c3de89024949a2 Mon Sep 17 00:00:00 2001
From: Kevin Suarez
Date: Fri, 17 Jan 2025 09:56:23 -0500
Subject: [PATCH 19/36] 100539 Only hit AC-API if user is verified (#34082)
---
.../mhv-landing-page/containers/LandingPageContainer.jsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/applications/mhv-landing-page/containers/LandingPageContainer.jsx b/src/applications/mhv-landing-page/containers/LandingPageContainer.jsx
index b20605be4f21..c38f6212eb96 100644
--- a/src/applications/mhv-landing-page/containers/LandingPageContainer.jsx
+++ b/src/applications/mhv-landing-page/containers/LandingPageContainer.jsx
@@ -12,6 +12,7 @@ import {
} from '../utilities/data';
import {
isAuthenticatedWithSSOe,
+ isLOA3,
isVAPatient,
selectProfile,
signInServiceEnabled,
@@ -32,6 +33,7 @@ const LandingPageContainer = () => {
const profile = useSelector(selectProfile);
const ssoe = useSelector(isAuthenticatedWithSSOe);
const useSiS = useSelector(signInServiceEnabled);
+ const userVerified = useSelector(isLOA3);
const registered = useSelector(isVAPatient);
const unreadMessageAriaLabel = resolveUnreadMessageAriaLabel(
unreadMessageCount,
@@ -77,7 +79,7 @@ const LandingPageContainer = () => {
useEffect(
() => {
- if (!profile.loading) {
+ if (!profile.loading && userVerified) {
if (userHasMhvAccount) {
dispatch({
type: fetchAccountStatusSuccess,
@@ -95,7 +97,7 @@ const LandingPageContainer = () => {
}
}
},
- [userHasMhvAccount, profile.loading, dispatch],
+ [userHasMhvAccount, profile.loading, userVerified, dispatch],
);
if (loading)
From 6af03351eafe53b2b382a973c9d2bd9c83a912e4 Mon Sep 17 00:00:00 2001
From: Robert Hasselle <123402053+rhasselle-oddball@users.noreply.github.com>
Date: Fri, 17 Jan 2025 10:21:59 -0600
Subject: [PATCH 20/36] 0966 new confirmation page followups (#34101)
* 0966 followup changes for confirmation page
---
.../21-0966/containers/ConfirmationPage.jsx | 239 +++++++++++++++---
.../containers/ConfirmationPage.unit.spec.jsx | 63 ++++-
2 files changed, 257 insertions(+), 45 deletions(-)
diff --git a/src/applications/simple-forms/21-0966/containers/ConfirmationPage.jsx b/src/applications/simple-forms/21-0966/containers/ConfirmationPage.jsx
index 1889373e7dba..a9594862dc86 100644
--- a/src/applications/simple-forms/21-0966/containers/ConfirmationPage.jsx
+++ b/src/applications/simple-forms/21-0966/containers/ConfirmationPage.jsx
@@ -28,6 +28,9 @@ import {
} from '../definitions/constants';
import { useNewConfirmationPage } from '../config/features';
+const { COMPENSATION, PENSION } = veteranBenefits;
+const { SURVIVOR } = survivingDependentBenefits;
+
const NextSteps = ({ formData }) => (
What are my next steps?
@@ -37,13 +40,15 @@ const NextSteps = ({ formData }) => (
{confirmationPageNextStepsParagraph(formData)}
>
) : (
-
- You should complete and file your claims as soon as possible. If you
- complete and file your claim before the intent to file expires and we
- approve your claim, you may be able to get retroactive payments.
- Retroactive payments are payments for the time between when we processed
- your intent to file and when we approved your claim.
-
+ <>
+
You should complete and file your claims as soon as possible.
+
+ If you complete and file your claim before the intent to file expires
+ and we approve your claim, you may be able to get retroactive
+ payments. Retroactive payments are payments for the time between when
+ we processed your intent to file and when we approved your claim.
+
+ >
)}
);
@@ -51,7 +56,7 @@ const NextSteps = ({ formData }) => (
const ActionLinksToCompleteClaims = ({ formData }) => (
<>
{(hasActiveCompensationITF({ formData }) ||
- formData.benefitSelection[veteranBenefits.COMPENSATION]) && (
+ formData.benefitSelection[COMPENSATION]) && (
(
)}
{(hasActivePensionITF({ formData }) ||
- formData.benefitSelection[veteranBenefits.PENSION]) && (
+ formData.benefitSelection[PENSION]) && (
(
/>
)}
- {formData.benefitSelection[survivingDependentBenefits.SURVIVOR] && (
+ {formData.benefitSelection[SURVIVOR] && (
(
>
);
+const CompensationAction = () => (
+
+
+
+);
+
+const PensionAction = () => (
+
+
+
+);
+
+const SurvivorAction = () => (
+
+
+
+);
+
+const PensionExistingAction = ({ formData }) => (
+
+
+ Our records show that you already have an intent to file for pension
+ claims. Your intent to file for pension claims expires on{' '}
+ {format(
+ new Date(formData['view:activePensionITF'].expirationDate),
+ 'MMMM d, yyyy',
+ )}
+ .
+
+
+
+);
+
+const CompensationExistingAction = ({ formData }) => (
+
+
+ Our records show that you already have an intent to file for disability
+ compensation. Your intent to file for disability compensation expires on{' '}
+ {format(
+ new Date(formData['view:activeCompensationITF'].expirationDate),
+ 'MMMM d, yyyy',
+ )}
+ .
+
+
+
+);
+
+const newActionMap = {
+ [COMPENSATION]: CompensationAction,
+ [PENSION]: PensionAction,
+ [SURVIVOR]: SurvivorAction,
+};
+
+const existingActionMap = {
+ [COMPENSATION]: CompensationExistingAction,
+ [PENSION]: PensionExistingAction,
+};
+
+/**
+ * Returns a data only object of string constants like this:
+ * {
+ * actionsNew: ["compensation", "pension"],
+ * actionsExisting: ["compensation", "pension"]
+ * }
+ *
+ * This makes it easier to test the logic in isolation without rendering
+ */
+export const getNextStepsActionsPlaceholders = formData => {
+ const actionsExisting = [];
+ const actionsNew = [];
+
+ const isCompensationApplicable =
+ hasActiveCompensationITF({ formData }) ||
+ formData.benefitSelection?.[COMPENSATION];
+
+ const isPensionApplicable =
+ hasActivePensionITF({ formData }) || formData.benefitSelection?.[PENSION];
+
+ const isSurvivorApplicable = formData.benefitSelection?.[SURVIVOR];
+ const hasCompensationExisting = hasActiveCompensationITF({ formData });
+ const hasPensionExisting = hasActivePensionITF({ formData });
+
+ if (hasCompensationExisting) {
+ actionsExisting.push(COMPENSATION);
+ } else if (isCompensationApplicable) {
+ actionsNew.push(COMPENSATION);
+ }
+
+ if (hasPensionExisting) {
+ actionsExisting.push(PENSION);
+ } else if (isPensionApplicable) {
+ actionsNew.push(PENSION);
+ }
+
+ if (isSurvivorApplicable) {
+ actionsNew.push(SURVIVOR);
+ }
+
+ return { actionsNew, actionsExisting };
+};
+
+const NextStepsV2Actions = ({ formData }) => {
+ let { actionsNew, actionsExisting } = getNextStepsActionsPlaceholders(
+ formData,
+ );
+
+ // convert arrays of strings to arrays of JSX
+ // e.g. ["compensation", "pension"] => [ , ]
+ actionsNew = actionsNew.map(action => {
+ const Component = newActionMap[action];
+ return ;
+ });
+ actionsExisting = actionsExisting.map(action => {
+ const Component = existingActionMap[action];
+ return ;
+ });
+
+ return (
+
+ {actionsNew}
+ {actionsExisting}
+
+ );
+};
+
+const NextStepsV2 = ({ formData }) => {
+ return (
+
+
What are my next steps?
+ {confirmationPageNextStepsParagraph(formData) ? (
+ <>
+
You should complete and file your claims as soon as possible.
+
{confirmationPageNextStepsParagraph(formData)}
+ >
+ ) : (
+ <>
+
You should complete and file your claims as soon as possible.
+
+ If you complete and file your claim before the intent to file
+ expires and we approve your claim, you may be able to get
+ retroactive payments. Retroactive payments are payments for the time
+ between when we processed your intent to file and when we approved
+ your claim.
+
+ >
+ )}
+
+
+ );
+};
+
const AlreadySubmittedHeader = ({ formData }) => (
<>
{!confirmationPageFormBypassed(formData) && (
<>
{hasActiveCompensationITF({ formData }) &&
- formData.benefitSelection[veteranBenefits.PENSION] && (
+ formData.benefitSelection[PENSION] && (
You’ve already submitted an intent to file for disability
@@ -108,7 +277,7 @@ const AlreadySubmittedHeader = ({ formData }) => (
)}
{hasActivePensionITF({ formData }) &&
- formData.benefitSelection[veteranBenefits.COMPENSATION] && (
+ formData.benefitSelection[COMPENSATION] && (
You’ve already submitted an intent to file for pension claims
@@ -142,14 +311,12 @@ export const ConfirmationPage = props => {
const currentFormData = {
...data,
benefitSelection: {
- [veteranBenefits.COMPENSATION]:
- data.benefitSelection?.[veteranBenefits.COMPENSATION] ||
+ [COMPENSATION]:
+ data.benefitSelection?.[COMPENSATION] ||
data.benefitSelectionCompensation,
- [veteranBenefits.PENSION]:
- data.benefitSelection?.[veteranBenefits.PENSION] ||
- data.benefitSelectionPension,
- [survivingDependentBenefits.SURVIVOR]:
- data.benefitSelection?.[survivingDependentBenefits.SURVIVOR],
+ [PENSION]:
+ data.benefitSelection?.[PENSION] || data.benefitSelectionPension,
+ [SURVIVOR]: data.benefitSelection?.[SURVIVOR],
},
};
@@ -183,6 +350,7 @@ export const ConfirmationPage = props => {
expirationDate: submissionResponse?.expirationDate,
confirmationNumber,
})}
+ actions={<>>}
/>
{!confirmationPageFormBypassed(formData) && (
<>
@@ -191,34 +359,23 @@ export const ConfirmationPage = props => {
/>
-
After we review your form, we’ll confirm next steps. Then
you’ll have 1 year to file your claim.
- ) : (
- <>
-
- After we process your request, we’ll confirm next steps.
- If we don’t already have a form showing that you’re
- authorized as a signer, we’ll contact the Veteran or
- family member who intends to file the claim.
-
-
- If we need information after reviewing your form, we’ll
- contact you
-
- >
- )
- }
- />
+ }
+ item1Actions={<>>}
+ />
+ )}
>
)}
-
-
-
+
diff --git a/src/applications/simple-forms/21-0966/tests/containers/ConfirmationPage.unit.spec.jsx b/src/applications/simple-forms/21-0966/tests/containers/ConfirmationPage.unit.spec.jsx
index 6e4e7d8efd4d..551045cd7731 100644
--- a/src/applications/simple-forms/21-0966/tests/containers/ConfirmationPage.unit.spec.jsx
+++ b/src/applications/simple-forms/21-0966/tests/containers/ConfirmationPage.unit.spec.jsx
@@ -7,7 +7,9 @@ import thunk from 'redux-thunk';
import { expect } from 'chai';
import formConfig from '../../config/form';
import * as features from '../../config/features';
-import ConfirmationPage from '../../containers/ConfirmationPage';
+import ConfirmationPage, {
+ getNextStepsActionsPlaceholders,
+} from '../../containers/ConfirmationPage';
const veteranData = {
benefitSelection: {
@@ -167,7 +169,6 @@ describe('Confirmation page V2', () => {
getByText(/Jack/);
getByText(/Your form submission was successful on/);
getByText(/You have until/);
- getByText(/After we process your request/);
expect(
container.querySelector(
'va-link-action[text="Complete your pension claim"]',
@@ -219,7 +220,6 @@ describe('Confirmation page V2', () => {
getByText(/Alternate/);
getByText(/Your form submission was successful on/);
getByText(/You have until/);
- getByText(/After we process your request/);
expect(
container.querySelector(
'va-link-action[text="Complete your pension for survivors claim"]',
@@ -261,7 +261,6 @@ describe('Confirmation page V2', () => {
getByText(/Jack/);
getByText(/Your form submission was successful on/);
getByText(/You have until/);
- getByText(/After we process your request/);
expect(
container.querySelector(
'va-link-action[text="Complete your pension claim"]',
@@ -294,6 +293,60 @@ describe('Confirmation page V2', () => {
),
).to.exist;
});
+
+ it('should return correct getNextStepsActionsPlaceholders for a veteran with new benefits', () => {
+ const formData = {
+ benefitSelection: {
+ compensation: true,
+ pension: true,
+ },
+ };
+ const placeholders = getNextStepsActionsPlaceholders(formData);
+ expect(placeholders.actionsNew).to.deep.equal(['compensation', 'pension']);
+ expect(placeholders.actionsExisting).to.deep.equal([]);
+ });
+
+ it('should return correct getNextStepsActionsPlaceholders for a veteran with mixed benefits', () => {
+ const formData = {
+ benefitSelection: {
+ pension: true,
+ },
+ 'view:activeCompensationITF': responseExisting.compensationIntent,
+ };
+ const placeholders = getNextStepsActionsPlaceholders(formData);
+ expect(placeholders.actionsNew).to.deep.equal(['pension']);
+ expect(placeholders.actionsExisting).to.deep.equal(['compensation']);
+ });
+
+ it('should return correct getNextStepsActionsPlaceholders for a veteran with existing benefits', () => {
+ const formData = {
+ benefitSelection: {},
+ 'view:activePensionITF': responseExisting.pensionIntent,
+ 'view:activeCompensationITF': responseExisting.compensationIntent,
+ };
+ const placeholders = getNextStepsActionsPlaceholders(formData);
+ expect(placeholders.actionsNew).to.deep.equal([]);
+ expect(placeholders.actionsExisting).to.deep.equal([
+ 'compensation',
+ 'pension',
+ ]);
+ });
+
+ it('should return correct getNextStepsActionsPlaceholders for a survivor', () => {
+ const formData = {
+ benefitSelection: {
+ survivor: true,
+ },
+ 'view:activePensionITF': responseExisting.pensionIntent,
+ 'view:activeCompensationITF': responseExisting.compensationIntent,
+ };
+ const placeholders = getNextStepsActionsPlaceholders(formData);
+ expect(placeholders.actionsNew).to.deep.equal(['survivor']);
+ expect(placeholders.actionsExisting).to.deep.equal([
+ 'compensation',
+ 'pension',
+ ]);
+ });
});
describe('Confirmation page V1', () => {
@@ -379,4 +432,6 @@ describe('Confirmation page V1', () => {
'va-link-action[text="Complete your pension for survivors claim"]',
);
});
+
+ // confirmation v1 tests end
});
From 1913280804d9d45609d3036551006a4680d51acb Mon Sep 17 00:00:00 2001
From: jvcAdHoc <144135615+jvcAdHoc@users.noreply.github.com>
Date: Fri, 17 Jan 2025 11:42:39 -0500
Subject: [PATCH 21/36] [99786] Appoint a Rep add submission types to search
results (#34105)
* add submission types to search results
* fix spec nesting
---
.../components/SearchResult.jsx | 16 +-
.../SelectAccreditedRepresentative.jsx | 1 +
.../config/prefillTransformer.js | 4 +
.../components/SearchResult.unit.spec.jsx | 145 ++++++++++++++++++
.../config/prefillTransformer.unit.spec.js | 36 +++++
.../getFormNumberFromEntity.unit.spec.jsx | 38 +++++
.../utilities/helpers.js | 8 +
7 files changed, 247 insertions(+), 1 deletion(-)
create mode 100644 src/applications/representative-appoint/tests/utilities/getFormNumberFromEntity.unit.spec.jsx
diff --git a/src/applications/representative-appoint/components/SearchResult.jsx b/src/applications/representative-appoint/components/SearchResult.jsx
index cd154a3b3e25..b1376b4bff99 100644
--- a/src/applications/representative-appoint/components/SearchResult.jsx
+++ b/src/applications/representative-appoint/components/SearchResult.jsx
@@ -4,12 +4,15 @@ import { connect } from 'react-redux';
import { setData } from '~/platform/forms-system/src/js/actions';
import { VaButton } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { parsePhoneNumber } from '../utilities/parsePhoneNumber';
+import { getFormNumberFromEntity } from '../utilities/helpers';
+import useV2FeatureToggle from '../hooks/useV2FeatureVisibility';
const SearchResult = ({
representative,
query,
handleSelectRepresentative,
loadingPOA,
+ userIsDigitalSubmitEligible,
}) => {
const { id } = representative.data;
const {
@@ -26,6 +29,9 @@ const SearchResult = ({
accreditedOrganizations,
} = representative.data.attributes;
+ const formNumber = getFormNumberFromEntity(representative.data);
+ const v2IsEnabled = useV2FeatureToggle();
+
const representativeName = name || fullName;
const { contact, extension } = parsePhoneNumber(phone);
@@ -43,6 +49,13 @@ const SearchResult = ({
(stateCode ? ` ${stateCode}` : '') +
(zipCode ? ` ${zipCode}` : '');
+ const submissionTypeContent = v2IsEnabled &&
+ userIsDigitalSubmitEligible && (
+
+ Accepts VA Form {formNumber} online, by mail, and in person
+
+ );
+
return (
@@ -82,7 +95,7 @@ const SearchResult = ({
)}
-
+ {submissionTypeContent}
{addressExists && (
@@ -181,6 +194,7 @@ SearchResult.propTypes = {
router: PropTypes.object,
routes: PropTypes.array,
stateCode: PropTypes.string,
+ userIsDigitalSubmitEligible: PropTypes.bool,
zipCode: PropTypes.string,
};
diff --git a/src/applications/representative-appoint/components/SelectAccreditedRepresentative.jsx b/src/applications/representative-appoint/components/SelectAccreditedRepresentative.jsx
index 2be2f28e02fb..22227efaded2 100644
--- a/src/applications/representative-appoint/components/SelectAccreditedRepresentative.jsx
+++ b/src/applications/representative-appoint/components/SelectAccreditedRepresentative.jsx
@@ -204,6 +204,7 @@ const SelectAccreditedRepresentative = props => {
currentSelectedRep={currentSelectedRep.current}
goToPath={goToPath}
handleSelectRepresentative={handleSelectRepresentative}
+ userIsDigitalSubmitEligible={formData?.userIsDigitalSubmitEligible}
/>
))}
diff --git a/src/applications/representative-appoint/config/prefillTransformer.js b/src/applications/representative-appoint/config/prefillTransformer.js
index 671d21c25482..42cd9065d3ef 100644
--- a/src/applications/representative-appoint/config/prefillTransformer.js
+++ b/src/applications/representative-appoint/config/prefillTransformer.js
@@ -70,5 +70,9 @@ export default function prefillTransformer(formData) {
newFormData['Branch of Service'] = undefined;
}
+ newFormData.userIsDigitalSubmitEligible =
+ formData?.identityValidation?.hasIcn &&
+ formData?.identityValidation?.hasParticipantId;
+
return newFormData;
}
diff --git a/src/applications/representative-appoint/tests/components/SearchResult.unit.spec.jsx b/src/applications/representative-appoint/tests/components/SearchResult.unit.spec.jsx
index 7b947bca0dd1..11a73ca494d5 100644
--- a/src/applications/representative-appoint/tests/components/SearchResult.unit.spec.jsx
+++ b/src/applications/representative-appoint/tests/components/SearchResult.unit.spec.jsx
@@ -1,7 +1,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import { expect } from 'chai';
+import sinon from 'sinon';
import { SearchResult } from '../../components/SearchResult';
+import * as useV2FeatureToggle from '../../hooks/useV2FeatureVisibility';
describe('SearchResult Component', () => {
it('evaluates addressExists correctly', () => {
@@ -16,6 +18,10 @@ describe('SearchResult Component', () => {
},
};
+ const useV2FeatureVisibilityStub = sinon
+ .stub(useV2FeatureToggle, 'default')
+ .returns(false);
+
const { container } = render(
{
const addressAnchor = container.querySelector('.address-anchor');
expect(addressAnchor).to.exist;
expect(addressAnchor.textContent).to.contain('123 Main St');
+
+ useV2FeatureVisibilityStub.restore();
});
it('evaluates addressExists correctly when only city, stateCode, and zipCode exist', () => {
@@ -41,6 +49,10 @@ describe('SearchResult Component', () => {
},
};
+ const useV2FeatureVisibilityStub = sinon
+ .stub(useV2FeatureToggle, 'default')
+ .returns(false);
+
const { container } = render(
{
const addressAnchor = container.querySelector('.address-anchor');
expect(addressAnchor).to.exist;
expect(addressAnchor.textContent).to.contain('Anytown, CT');
+
+ useV2FeatureVisibilityStub.restore();
});
it('includes the representative name in the select button text', () => {
@@ -69,6 +83,10 @@ describe('SearchResult Component', () => {
},
};
+ const useV2FeatureVisibilityStub = sinon
+ .stub(useV2FeatureToggle, 'default')
+ .returns(false);
+
const { container } = render(
{
);
expect(selectButton).to.exist;
expect(selectButton.getAttribute('text')).to.contain('Robert Smith');
+
+ useV2FeatureVisibilityStub.restore();
+ });
+
+ context('when v2 is enabled', () => {
+ context('when the user is userIsDigitalSubmitEligible', () => {
+ it('displays submission methods', () => {
+ const representative = {
+ data: {
+ id: 1,
+ type: 'individual',
+ attributes: {
+ addressLine1: '123 Main St',
+ city: '',
+ stateCode: '',
+ zipCode: '',
+ fullName: 'Robert Smith',
+ individualType: 'representative',
+ },
+ },
+ };
+
+ const useV2FeatureVisibilityStub = sinon
+ .stub(useV2FeatureToggle, 'default')
+ .returns(true);
+
+ const { container } = render(
+ {}}
+ loadingPOA={false}
+ userIsDigitalSubmitEligible
+ />,
+ );
+
+ const submissionMethods = container.querySelector(
+ '[data-testid="submission-methods"]',
+ );
+
+ expect(submissionMethods).to.exist;
+
+ useV2FeatureVisibilityStub.restore();
+ });
+ });
+
+ context('when the user is not userIsDigitalSubmitEligible', () => {
+ it('does not display submission methods', () => {
+ const representative = {
+ data: {
+ id: 1,
+ type: 'individual',
+ attributes: {
+ addressLine1: '123 Main St',
+ city: '',
+ stateCode: '',
+ zipCode: '',
+ fullName: 'Robert Smith',
+ individualType: 'representative',
+ },
+ },
+ };
+
+ const useV2FeatureVisibilityStub = sinon
+ .stub(useV2FeatureToggle, 'default')
+ .returns(true);
+
+ const { container } = render(
+ {}}
+ loadingPOA={false}
+ userIsDigitalSubmitEligible={false}
+ />,
+ );
+
+ const submissionMethods = container.querySelector(
+ '[data-testid="submission-methods"]',
+ );
+
+ expect(submissionMethods).not.to.exist;
+
+ useV2FeatureVisibilityStub.restore();
+ });
+ });
+ });
+
+ context('when v2 is not enabled', () => {
+ it('does not display submission methods', () => {
+ const representative = {
+ data: {
+ id: 1,
+ type: 'individual',
+ attributes: {
+ addressLine1: '123 Main St',
+ city: '',
+ stateCode: '',
+ zipCode: '',
+ fullName: 'Robert Smith',
+ individualType: 'representative',
+ },
+ },
+ };
+
+ const useV2FeatureVisibilityStub = sinon
+ .stub(useV2FeatureToggle, 'default')
+ .returns(false);
+
+ const { container } = render(
+ {}}
+ loadingPOA={false}
+ userIsDigitalSubmitEligible
+ />,
+ );
+
+ const submissionMethods = container.querySelector(
+ '[data-testid="submission-methods"]',
+ );
+
+ expect(submissionMethods).not.to.exist;
+
+ useV2FeatureVisibilityStub.restore();
+ });
});
});
diff --git a/src/applications/representative-appoint/tests/config/prefillTransformer.unit.spec.js b/src/applications/representative-appoint/tests/config/prefillTransformer.unit.spec.js
index 7c5fec8c6afd..cf6e6938568f 100644
--- a/src/applications/representative-appoint/tests/config/prefillTransformer.unit.spec.js
+++ b/src/applications/representative-appoint/tests/config/prefillTransformer.unit.spec.js
@@ -120,4 +120,40 @@ describe('prefillTransformer', () => {
expect(result.veteranSocialSecurityNumber).to.be.undefined;
});
});
+
+ context('when the user does not have an ICN', () => {
+ it('sets userIsDigitalSubmitEligible to false', () => {
+ const data = {
+ ...prefill,
+ identityValidation: { hasIcn: false, hasParticipantId: true },
+ };
+
+ const result = prefillTransformer(data);
+
+ expect(result.userIsDigitalSubmitEligible).to.be.false;
+ });
+ });
+
+ context('when the user does not have a participant id', () => {
+ it('sets userIsDigitalSubmitEligible to false', () => {
+ const data = {
+ ...prefill,
+ identityValidation: { hasIcn: true, hasParticipantId: false },
+ };
+
+ const result = prefillTransformer(data);
+
+ expect(result.userIsDigitalSubmitEligible).to.be.false;
+ });
+ });
+
+ context('when the user has an ICN and a participant id', () => {
+ it('sets userIsDigitalSubmitEligible to true', () => {
+ const data = { ...prefill };
+
+ const result = prefillTransformer(data);
+
+ expect(result.userIsDigitalSubmitEligible).to.be.true;
+ });
+ });
});
diff --git a/src/applications/representative-appoint/tests/utilities/getFormNumberFromEntity.unit.spec.jsx b/src/applications/representative-appoint/tests/utilities/getFormNumberFromEntity.unit.spec.jsx
new file mode 100644
index 000000000000..3def6d21c030
--- /dev/null
+++ b/src/applications/representative-appoint/tests/utilities/getFormNumberFromEntity.unit.spec.jsx
@@ -0,0 +1,38 @@
+import { expect } from 'chai';
+
+import { getFormNumberFromEntity } from '../../utilities/helpers';
+
+describe('getFormNumberFromEntity', () => {
+ it('should return "21-22" when entity type is organization', () => {
+ const mockFormData = { type: 'organization' };
+ const result = getFormNumberFromEntity(mockFormData);
+ expect(result).to.equal('21-22');
+ });
+
+ it('should return "21-22a" when individual type is attorney', () => {
+ const mockFormData = {
+ type: 'individual',
+ attributes: { individualType: 'attorney' },
+ };
+ const result = getFormNumberFromEntity(mockFormData);
+ expect(result).to.equal('21-22a');
+ });
+
+ it('should return "21-22a" when individual type is claimsAgent', () => {
+ const mockFormData = {
+ type: 'individual',
+ attributes: { individualType: 'claimsAgent' },
+ };
+ const result = getFormNumberFromEntity(mockFormData);
+ expect(result).to.equal('21-22a');
+ });
+
+ it('should return "21-22" when individual type is representative', () => {
+ const mockFormData = {
+ type: 'individual',
+ attributes: { individualType: 'representative' },
+ };
+ const result = getFormNumberFromEntity(mockFormData);
+ expect(result).to.equal('21-22');
+ });
+});
diff --git a/src/applications/representative-appoint/utilities/helpers.js b/src/applications/representative-appoint/utilities/helpers.js
index 49e210e37346..a777f757f624 100644
--- a/src/applications/representative-appoint/utilities/helpers.js
+++ b/src/applications/representative-appoint/utilities/helpers.js
@@ -115,6 +115,14 @@ export const getRepType = entity => {
return 'VSO Representative';
};
+export const getFormNumberFromEntity = entity => {
+ const repType = getRepType(entity);
+
+ return ['Organization', 'VSO Representative'].includes(repType)
+ ? '21-22'
+ : '21-22a';
+};
+
export const getFormNumber = formData => {
const entity = formData['view:selectedRepresentative'];
const entityType = entity?.type;
From 0d621157a643a4d7760be1e65ecf085bc40d79dc Mon Sep 17 00:00:00 2001
From: Hemesh Patel <49699643+hemeshvpatel@users.noreply.github.com>
Date: Fri, 17 Jan 2025 10:52:59 -0600
Subject: [PATCH 22/36] fix flow back issue, remove unused code (#34131)
---
.../categoryAndTopic/selectCategory.js | 18 --------
.../categoryAndTopic/selectSubtopic.js | 18 --------
.../chapters/categoryAndTopic/selectTopic.js | 18 --------
.../yourQuestion/whoIsYourQuestionAbout.js | 22 ----------
src/applications/ask-va/config/form.js | 36 ++++++++++-----
.../config/schema-helpers/formFlowHelper.js | 1 -
.../whoIsYourQuestionAbout.unit.spec.js | 44 -------------------
7 files changed, 24 insertions(+), 133 deletions(-)
delete mode 100644 src/applications/ask-va/config/chapters/categoryAndTopic/selectCategory.js
delete mode 100644 src/applications/ask-va/config/chapters/categoryAndTopic/selectSubtopic.js
delete mode 100644 src/applications/ask-va/config/chapters/categoryAndTopic/selectTopic.js
delete mode 100644 src/applications/ask-va/config/chapters/yourQuestion/whoIsYourQuestionAbout.js
delete mode 100644 src/applications/ask-va/tests/config/chapters/yourQuestion/whoIsYourQuestionAbout.unit.spec.js
diff --git a/src/applications/ask-va/config/chapters/categoryAndTopic/selectCategory.js b/src/applications/ask-va/config/chapters/categoryAndTopic/selectCategory.js
deleted file mode 100644
index 1c90ad77c548..000000000000
--- a/src/applications/ask-va/config/chapters/categoryAndTopic/selectCategory.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { CHAPTER_1 } from '../../../constants';
-
-const selectCategoryPage = {
- uiSchema: {
- selectCategory: { 'ui:title': CHAPTER_1.PAGE_1.QUESTION_1 },
- },
- schema: {
- type: 'object',
- required: ['selectCategory'],
- properties: {
- selectCategory: {
- type: 'string',
- },
- },
- },
-};
-
-export default selectCategoryPage;
diff --git a/src/applications/ask-va/config/chapters/categoryAndTopic/selectSubtopic.js b/src/applications/ask-va/config/chapters/categoryAndTopic/selectSubtopic.js
deleted file mode 100644
index a0b9e8581f91..000000000000
--- a/src/applications/ask-va/config/chapters/categoryAndTopic/selectSubtopic.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { CHAPTER_1 } from '../../../constants';
-
-const selectSubtopicPage = {
- uiSchema: {
- selectTopic: { 'ui:title': CHAPTER_1.PAGE_3.QUESTION_1 },
- },
- schema: {
- type: 'object',
- required: ['selectSubtopic'],
- properties: {
- selectSubtopic: {
- type: 'string',
- },
- },
- },
-};
-
-export default selectSubtopicPage;
diff --git a/src/applications/ask-va/config/chapters/categoryAndTopic/selectTopic.js b/src/applications/ask-va/config/chapters/categoryAndTopic/selectTopic.js
deleted file mode 100644
index 2a3c5942452e..000000000000
--- a/src/applications/ask-va/config/chapters/categoryAndTopic/selectTopic.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { CHAPTER_1 } from '../../../constants';
-
-const selectTopicPage = {
- uiSchema: {
- selectTopic: { 'ui:title': CHAPTER_1.PAGE_2.QUESTION_1 },
- },
- schema: {
- type: 'object',
- required: ['selectTopic'],
- properties: {
- selectTopic: {
- type: 'string',
- },
- },
- },
-};
-
-export default selectTopicPage;
diff --git a/src/applications/ask-va/config/chapters/yourQuestion/whoIsYourQuestionAbout.js b/src/applications/ask-va/config/chapters/yourQuestion/whoIsYourQuestionAbout.js
deleted file mode 100644
index 3e7386348c39..000000000000
--- a/src/applications/ask-va/config/chapters/yourQuestion/whoIsYourQuestionAbout.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import PageFieldSummary from '../../../components/PageFieldSummary';
-import { CHAPTER_2 } from '../../../constants';
-
-const whoIsYourQuestionAboutPage = {
- uiSchema: {
- 'ui:objectViewField': PageFieldSummary,
- whoIsYourQuestionAbout: {
- 'ui:title': CHAPTER_2.PAGE_1.TITLE,
- },
- },
- schema: {
- type: 'object',
- required: ['whoIsYourQuestionAbout'],
- properties: {
- whoIsYourQuestionAbout: {
- type: 'string',
- },
- },
- },
-};
-
-export default whoIsYourQuestionAboutPage;
diff --git a/src/applications/ask-va/config/form.js b/src/applications/ask-va/config/form.js
index a65a9fb83a7f..d66abc76240a 100644
--- a/src/applications/ask-va/config/form.js
+++ b/src/applications/ask-va/config/form.js
@@ -11,12 +11,8 @@ import ConfirmationPage from '../containers/ConfirmationPage';
import IntroductionPage from '../containers/IntroductionPage';
// Category and Topic pages
-import selectCategoryPage from './chapters/categoryAndTopic/selectCategory';
-import selectSubtopicPage from './chapters/categoryAndTopic/selectSubtopic';
-import selectTopicPage from './chapters/categoryAndTopic/selectTopic';
// Your Question
-import whoIsYourQuestionAboutPage from './chapters/yourQuestion/whoIsYourQuestionAbout';
import yourQuestionPage from './chapters/yourQuestion/yourQuestion';
// Your Personal Information - Authenticated
@@ -118,28 +114,40 @@ const formConfig = {
title: CHAPTER_1.PAGE_1.TITLE,
CustomPage: CategorySelectPage,
CustomPageReview: CustomPageReviewField,
- uiSchema: selectCategoryPage.uiSchema,
- schema: selectCategoryPage.schema,
editModeOnReviewPage: false,
+ schema: {
+ // This does still need to be here or it'll throw an error
+ type: 'object',
+ properties: {}, // The properties can be empty
+ },
+ uiSchema: {},
},
selectTopic: {
path: CHAPTER_1.PAGE_2.PATH,
title: CHAPTER_1.PAGE_2.TITLE,
CustomPage: TopicSelectPage,
CustomPageReview: CustomPageReviewField,
- uiSchema: selectTopicPage.uiSchema,
- schema: selectTopicPage.schema,
editModeOnReviewPage: false,
+ schema: {
+ // This does still need to be here or it'll throw an error
+ type: 'object',
+ properties: {}, // The properties can be empty
+ },
+ uiSchema: {},
},
selectSubtopic: {
path: CHAPTER_1.PAGE_3.PATH,
title: CHAPTER_1.PAGE_3.TITLE,
CustomPage: SubTopicSelectPage,
CustomPageReview: CustomPageReviewField,
- uiSchema: selectSubtopicPage.uiSchema,
- schema: selectSubtopicPage.schema,
depends: form => requiredForSubtopicPage.includes(form.selectTopic),
editModeOnReviewPage: false,
+ schema: {
+ // This does still need to be here or it'll throw an error
+ type: 'object',
+ properties: {}, // The properties can be empty
+ },
+ uiSchema: {},
},
},
},
@@ -153,11 +161,15 @@ const formConfig = {
title: CHAPTER_2.PAGE_1.TITLE,
CustomPage: WhoIsYourQuestionAboutCustomPage,
CustomPageReview: CustomPageReviewField,
- uiSchema: whoIsYourQuestionAboutPage.uiSchema,
- schema: whoIsYourQuestionAboutPage.schema,
depends: formData => {
return whoIsYourQuestionAboutCondition(formData);
},
+ schema: {
+ // This does still need to be here or it'll throw an error
+ type: 'object',
+ properties: {}, // The properties can be empty
+ },
+ uiSchema: {},
},
relationshipToVeteran: {
editModeOnReviewPage: false,
diff --git a/src/applications/ask-va/config/schema-helpers/formFlowHelper.js b/src/applications/ask-va/config/schema-helpers/formFlowHelper.js
index 0fa03979a42b..6d5be28cf4fa 100644
--- a/src/applications/ask-va/config/schema-helpers/formFlowHelper.js
+++ b/src/applications/ask-va/config/schema-helpers/formFlowHelper.js
@@ -488,7 +488,6 @@ export const aboutSomeoneElseRelationshipVeteranPages = flowPages(
const aboutSomeoneElseRelationshipFamilyMember = [
'isQuestionAboutVeteranOrSomeoneElse',
- 'aboutTheVeteran', // Needed for list, should not render
];
export const aboutSomeoneElseRelationshipFamilyMemberPages = flowPages(
ch3Pages,
diff --git a/src/applications/ask-va/tests/config/chapters/yourQuestion/whoIsYourQuestionAbout.unit.spec.js b/src/applications/ask-va/tests/config/chapters/yourQuestion/whoIsYourQuestionAbout.unit.spec.js
deleted file mode 100644
index 3ed0d6418999..000000000000
--- a/src/applications/ask-va/tests/config/chapters/yourQuestion/whoIsYourQuestionAbout.unit.spec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { $$ } from '@department-of-veterans-affairs/platform-forms-system/ui';
-import { DefinitionTester } from '@department-of-veterans-affairs/platform-testing/schemaform-utils';
-import { render } from '@testing-library/react';
-import { expect } from 'chai';
-import React from 'react';
-import { Provider } from 'react-redux';
-
-import formConfig from '../../../../config/form';
-import { getData } from '../../../fixtures/data/mock-form-data';
-
-const {
- schema,
- uiSchema,
-} = formConfig.chapters.yourQuestionPart1.pages.whoIsYourQuestionAbout;
-
-describe('whoIsYourQuestionAboutPage', () => {
- it('should render', () => {
- const { container, getByLabelText } = render(
-
-
- ,
- ,
- );
-
- const radioLabels = $$('.form-radio-buttons > label', container);
- const radioLabelList = [
- 'Myself',
- 'Someone else',
- "It's a general question",
- ];
-
- expect(getByLabelText(/Who is your question about/)).to.exist;
-
- radioLabels.forEach(
- radio => expect(radioLabelList.includes(radio.textContent)).to.be.true,
- );
- });
-});
From c0e9012a734e52b3cd314fada0f4b5d864f32ec4 Mon Sep 17 00:00:00 2001
From: Nicholas Hibbits <172406954+nicholashibbits@users.noreply.github.com>
Date: Fri, 17 Jan 2025 09:23:25 -0800
Subject: [PATCH 23/36] Edm 493 521 finalize lacs UI (#34130)
* update capitalizeFirstLetter function, update word casing where needed
* change UI for result details based on lac type, fix element spacing
* refactor capitalizeFirstLetter function
* scroll to top of each lac page when mounted
* Fix table width issue and add pagination
* make FAQs single select, change React bindings to web-components
* change react bindings to web components
* focus correctly on faqs | cleanup markup styles
---------
Co-authored-by: Nick Hibbits <172406954+nicholashibbits1@users.noreply.github.com>
---
.../LicenseCertificationAdminInfo.jsx | 52 ++++++++------
.../LicenseCertificationTestInfo.jsx | 61 +++++++++++++++-
.../LicenseCertificationSearchPage.jsx | 71 +++++++++++--------
.../LicenseCertificationSearchResult.jsx | 19 +++--
.../LicenseCertificationSearchResults.jsx | 31 ++++----
src/applications/gi/sass/gi.scss | 2 +
src/applications/gi/utils/helpers.js | 24 +++++--
7 files changed, 177 insertions(+), 83 deletions(-)
diff --git a/src/applications/gi/components/LicenseCertificationAdminInfo.jsx b/src/applications/gi/components/LicenseCertificationAdminInfo.jsx
index 5f3bfe86708f..f849cc0df481 100644
--- a/src/applications/gi/components/LicenseCertificationAdminInfo.jsx
+++ b/src/applications/gi/components/LicenseCertificationAdminInfo.jsx
@@ -1,39 +1,51 @@
-import {
- VaIcon,
- VaLink,
-} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import React from 'react';
+import { capitalizeFirstLetter } from '../utils/helpers';
-function LicenseCertificationAdminInfo({ institution }) {
+function LicenseCertificationAdminInfo({ institution, type }) {
const { name, mailingAddress } = institution;
+
return (
-
Admin Info
+
+ Admin info
+
-
-
{name}
+
{capitalizeFirstLetter(name)}
-
The following is the headquarters address.
+ {type === 'Certification' ? (
+
+ Certification tests are available to be taken nationally, search for a
+ testing site near you.
+
+ ) : (
+ <>
+
The following is the headquarters address.
-
- {mailingAddress.address1}
-
- {mailingAddress.city}, {mailingAddress.state} {mailingAddress.zip}
-
-
+
+ {capitalizeFirstLetter(mailingAddress.address1)}
+
+ {capitalizeFirstLetter(mailingAddress.city)}, {mailingAddress.state}{' '}
+ {mailingAddress.zip}
+
+
+ >
+ )}
Print and fill out form Request for Reimbursement of Licensing or
Certification Test Fees. Send the completed application to the Regional
Processing Office for your region listed in the form.
-
+
+
+
);
}
diff --git a/src/applications/gi/components/LicenseCertificationTestInfo.jsx b/src/applications/gi/components/LicenseCertificationTestInfo.jsx
index ac57b73ecc75..8b5940652472 100644
--- a/src/applications/gi/components/LicenseCertificationTestInfo.jsx
+++ b/src/applications/gi/components/LicenseCertificationTestInfo.jsx
@@ -1,17 +1,64 @@
-import React from 'react';
+import React, { useLayoutEffect, useState } from 'react';
+import { VaPagination } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { formatCurrency } from '../utils/helpers';
function LcTestInfo({ tests }) {
+ const [currentPage, setCurrentPage] = useState(1);
+
+ useLayoutEffect(
+ // eslint-disable-next-line consistent-return
+ () => {
+ const observer = new MutationObserver(() => {
+ const vaTableInner = document.querySelector(
+ '.table-wrapper va-table-inner',
+ );
+ if (vaTableInner?.shadowRoot) {
+ const { shadowRoot } = vaTableInner;
+ const usaTable = shadowRoot.querySelector('.usa-table');
+ if (usaTable) {
+ usaTable.style.width = '100%';
+ }
+ }
+ });
+ const vaTable = document.querySelector('.table-wrapper va-table');
+ if (vaTable) {
+ observer.observe(vaTable, {
+ attributes: true,
+ childList: true,
+ subtree: true,
+ });
+ }
+ return () => observer.disconnect();
+ },
+ [tests],
+ );
+
+ const itemsPerPage = 10;
+
+ const totalPages = Math.ceil(tests.length / itemsPerPage);
+ const currentResults =
+ tests.length > itemsPerPage
+ ? tests.slice(
+ (currentPage - 1) * itemsPerPage,
+ currentPage * itemsPerPage,
+ )
+ : tests;
+
+ const handlePageChange = page => {
+ setCurrentPage(page);
+ };
+
return (
<>
+ Test info
Test Name
Fees
- {tests &&
- tests.map((test, index) => {
+ {currentResults &&
+ currentResults.map((test, index) => {
return (
{test.name}
@@ -20,6 +67,14 @@ function LcTestInfo({ tests }) {
);
})}
+ {tests.length > itemsPerPage && (
+ handlePageChange(e.detail.page)}
+ page={currentPage}
+ pages={totalPages}
+ maxPageListLength={itemsPerPage}
+ />
+ )}
>
);
diff --git a/src/applications/gi/containers/LicenseCertificationSearchPage.jsx b/src/applications/gi/containers/LicenseCertificationSearchPage.jsx
index d02ff59eebab..3d375f46da47 100644
--- a/src/applications/gi/containers/LicenseCertificationSearchPage.jsx
+++ b/src/applications/gi/containers/LicenseCertificationSearchPage.jsx
@@ -2,13 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import {
- VaAccordion,
- VaAccordionItem,
- VaLink,
- VaLoadingIndicator,
- VaModal,
-} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import { VaModal } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import LicenseCertificationSearchForm from '../components/LicenseCertificationSearchForm';
import { handleLcResultsSearch, updateQueryParam } from '../utils/helpers';
import { fetchLicenseCertificationResults } from '../actions';
@@ -39,7 +33,7 @@ const faqs = [
'How do I get reimbursed for the licenses, certifications, and prep courses?',
answer: (
<>
-
@@ -52,7 +46,7 @@ const faqs = [
-
@@ -94,12 +88,9 @@ function LicenseCertificationSearchPage({
message: '',
});
- const handleUpdateQueryParam = () => updateQueryParam(history, location);
-
- const handleReset = callback => {
- history.replace('/lc-search');
- callback?.();
- };
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
useEffect(
() => {
@@ -110,6 +101,27 @@ function LicenseCertificationSearchPage({
[hasFetchedOnce, dispatchFetchLicenseCertificationResults],
);
+ const handleFaqClick = index => {
+ const element = document.getElementById(`faq-${index}`);
+ element?.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ });
+ };
+
+ const handleKeyDown = (event, index) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ handleFaqClick(index);
+ }
+ };
+
+ const handleUpdateQueryParam = () => updateQueryParam(history, location);
+
+ const handleReset = callback => {
+ history.replace('/lc-search');
+ callback?.();
+ };
+
const handleShowModal = (changedField, message, callback) => {
return setModal({
visible: true,
@@ -136,12 +148,7 @@ function LicenseCertificationSearchPage({
return (
- {fetchingLc && (
-
- )}
+ {fetchingLc && }
{!fetchingLc &&
hasFetchedOnce &&
lcResults.length !== 0 && (
@@ -175,16 +182,24 @@ function LicenseCertificationSearchPage({
/>
-
FAQs
-
+
+ FAQs
+
+
{faqs.map((faq, index) => {
return (
-
+ handleFaqClick(index)}
+ onKeyDown={e => handleKeyDown(e, index)}
+ >
{faq.answer}
-
+
);
})}
-
+
{
modal.callback();
@@ -203,7 +217,6 @@ function LicenseCertificationSearchPage({
primaryButtonText="Continue to change"
onSecondaryButtonClick={toggleModal}
secondaryButtonText="Go Back"
- // status={status}
visible={modal.visible}
>
{modal.message}
@@ -219,14 +232,12 @@ LicenseCertificationSearchPage.propTypes = {
fetchingLc: PropTypes.bool.isRequired,
hasFetchedOnce: PropTypes.bool.isRequired,
lcResults: PropTypes.array,
- // error: Proptypes // verify error Proptypes
};
const mapStateToProps = state => ({
lcResults: state.licenseCertificationSearch.lcResults,
fetchingLc: state.licenseCertificationSearch.fetchingLc,
hasFetchedOnce: state.licenseCertificationSearch.hasFetchedOnce,
- // error: // create error state in redux store
});
const mapDispatchToProps = {
diff --git a/src/applications/gi/containers/LicenseCertificationSearchResult.jsx b/src/applications/gi/containers/LicenseCertificationSearchResult.jsx
index 8ba551bdbace..7a7e144f5120 100644
--- a/src/applications/gi/containers/LicenseCertificationSearchResult.jsx
+++ b/src/applications/gi/containers/LicenseCertificationSearchResult.jsx
@@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { useParams } from 'react-router-dom';
-import { VaLoadingIndicator } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { fetchLcResult } from '../actions';
import LicenseCertificationAdminInfo from '../components/LicenseCertificationAdminInfo';
import LicenseCertificationTestInfo from '../components/LicenseCertificationTestInfo';
@@ -16,6 +15,8 @@ function LicenseCertificationSearchResult({
const { id } = useParams();
useEffect(() => {
+ window.scrollTo(0, 0);
+
if (!hasFetchedResult) {
dispatchFetchLcResult(id);
}
@@ -25,22 +26,20 @@ function LicenseCertificationSearchResult({
return (
- {fetchingLcResult && (
-
- )}
+ {fetchingLcResult &&
}
{!fetchingLcResult &&
- institution &&
- tests && ( // better check for empty resultInfo
+ institution &&
+ tests && (
{lacNm}
{eduLacTypeNm}
-
+
diff --git a/src/applications/gi/containers/LicenseCertificationSearchResults.jsx b/src/applications/gi/containers/LicenseCertificationSearchResults.jsx
index c6be2e45a5b9..65f9c9adcd1c 100644
--- a/src/applications/gi/containers/LicenseCertificationSearchResults.jsx
+++ b/src/applications/gi/containers/LicenseCertificationSearchResults.jsx
@@ -2,13 +2,7 @@ import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ADDRESS_DATA from 'platform/forms/address/data';
-import {
- VaCard,
- VaLink,
- VaLinkAction,
- VaLoadingIndicator,
- VaPagination,
-} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import { VaPagination } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { useHistory, useLocation } from 'react-router-dom';
import { fetchLicenseCertificationResults } from '../actions';
import {
@@ -62,8 +56,13 @@ function LicenseCertificationSearchResults({
[lcResults],
);
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
+
const handlePageChange = page => {
setCurrentPage(page);
+ window.scroll({ top: 0, bottom: 0, behavior: 'smooth' }); // troubleshoot scrollTo functions in platform to align with standards
};
const handleRouteChange = id => event => {
@@ -83,7 +82,7 @@ function LicenseCertificationSearchResults({
return (
{fetchingLc && (
-
@@ -97,7 +96,7 @@ function LicenseCertificationSearchResults({
Search Results
-
+
Showing{' '}
@@ -108,9 +107,9 @@ function LicenseCertificationSearchResults({
itemsPerPage,
)} of ${filteredResults.length} results for:`}
-
- License/Certification Name: {' '}
+ License/Certification name: {' '}
{`"${nameParam}"`}
@@ -140,7 +139,7 @@ function LicenseCertificationSearchResults({
{currentResults.map((result, index) => {
return (
-
+
{result.lacNm}
{result.eduLacTypeNm}
@@ -150,7 +149,7 @@ function LicenseCertificationSearchResults({
{ADDRESS_DATA.states[result.state]}
)}
-
-
+
);
})}
@@ -170,7 +169,7 @@ function LicenseCertificationSearchResults({
)}
- {filteredResults.length > 0 && (
+ {filteredResults.length > itemsPerPage && (
{
};
export function capitalizeFirstLetter(string) {
- if (string) {
- return string.charAt(0).toUpperCase() + string.slice(1);
- }
+ if (!string) return null;
+
+ const exceptions = ['NW', 'SW', 'NE', 'SE', 'of', 'and'];
+
+ return string
+ .split(' ')
+ .map(word => {
+ if (exceptions.includes(word)) {
+ return word;
+ }
- return null;
+ if (word === 'OF') {
+ return 'of';
+ }
+ if (word === 'AND') {
+ return 'and';
+ }
+
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
+ })
+ .join(' ');
}
export const mappedStates = Object.entries(ADDRESS_DATA.states).map(state => {
From 4f280529e4021fa0c3ea04f40f57cc6f2bbf8aa1 Mon Sep 17 00:00:00 2001
From: Taras Kurilo
Date: Fri, 17 Jan 2025 12:47:25 -0500
Subject: [PATCH 24/36] add feature flag for programs route (#34137)
---
src/applications/gi/routes.jsx | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/applications/gi/routes.jsx b/src/applications/gi/routes.jsx
index a53d7ba850ec..8827a194b90e 100644
--- a/src/applications/gi/routes.jsx
+++ b/src/applications/gi/routes.jsx
@@ -21,6 +21,9 @@ const BuildRoutes = () => {
const lcToggleValue = useToggleValue(
TOGGLE_NAMES.giComparisonToolLceToggleFlag,
);
+ const toggleGiProgramsFlag = useToggleValue(
+ TOGGLE_NAMES.giComparisonToolProgramsToggleFlag,
+ );
return (
<>
@@ -31,10 +34,12 @@ const BuildRoutes = () => {
from="/profile/:facilityCode"
to="/institution/:facilityCode"
/>
- }
- />
+ {toggleGiProgramsFlag && (
+ }
+ />
+ )}
}
From a5d10517651a4313484a1d1c5d2807ac250812b3 Mon Sep 17 00:00:00 2001
From: Oren Mittman
Date: Fri, 17 Jan 2025 12:50:16 -0500
Subject: [PATCH 25/36] 4: [ART] Tweak var names in return URL fn (#34036)
---
.../utilities/constants.js | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/applications/accredited-representative-portal/utilities/constants.js b/src/applications/accredited-representative-portal/utilities/constants.js
index 9ea188b06b1b..b9da0acc651d 100644
--- a/src/applications/accredited-representative-portal/utilities/constants.js
+++ b/src/applications/accredited-representative-portal/utilities/constants.js
@@ -13,7 +13,7 @@ import {
const USIP_PATH = '/sign-in';
const USIP_BASE_URL = environment.BASE_URL;
-const baseReturnUrl =
+const BASE_RETURN_URL =
externalApplicationsConfig[USIP_APPLICATIONS.ARP].externalRedirectUrl;
export const getSignInUrl = ({ returnUrl } = {}) => {
@@ -22,8 +22,12 @@ export const getSignInUrl = ({ returnUrl } = {}) => {
url.searchParams.set(USIP_QUERY_PARAMS.OAuth, true);
if (returnUrl) {
- const returnUrlPrefix = new URL(returnUrl).href.replace(baseReturnUrl, '');
- url.searchParams.set(USIP_QUERY_PARAMS.to, returnUrlPrefix);
+ const returnUrlSuffix = new URL(returnUrl).href.replace(
+ BASE_RETURN_URL,
+ '',
+ );
+
+ url.searchParams.set(USIP_QUERY_PARAMS.to, returnUrlSuffix);
}
return url;
From b4e43a96fd260f3c54f58dc059b5a5d1e7fd8881 Mon Sep 17 00:00:00 2001
From: Jennifer Quispe
Date: Fri, 17 Jan 2025 13:32:21 -0500
Subject: [PATCH 26/36] 4: Art/98327/poa-request-details (#34031)
* 98327 - details cherry pick
* 98327 - request details
* 98327 - pr feedback
* 98327 - update mock json
* 98327 - removing tests
---
.../components/POARequestCard.jsx | 83 ++-
.../containers/POARequestDetailsPage.jsx | 483 ++++++++++------
.../containers/POARequestSearchPage.jsx | 7 +-
.../sass/POARequestCard.scss | 2 +-
.../sass/POARequestDetails.scss | 71 ++-
.../components/POARequestCard.unit.spec.jsx | 16 -
.../utilities/mockApi.js | 26 +-
.../utilities/mocks/poaRequests.json | 540 ++++++++++++------
.../utilities/poaRequests.js | 40 ++
9 files changed, 815 insertions(+), 453 deletions(-)
delete mode 100644 src/applications/accredited-representative-portal/tests/unit/components/POARequestCard.unit.spec.jsx
create mode 100644 src/applications/accredited-representative-portal/utilities/poaRequests.js
diff --git a/src/applications/accredited-representative-portal/components/POARequestCard.jsx b/src/applications/accredited-representative-portal/components/POARequestCard.jsx
index 50c2fd7c2cf3..65b7fe19f2bc 100644
--- a/src/applications/accredited-representative-portal/components/POARequestCard.jsx
+++ b/src/applications/accredited-representative-portal/components/POARequestCard.jsx
@@ -1,28 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
-import { differenceInDays } from 'date-fns';
-
import {
- formatDateParsedZoneLong,
- timeFromNow,
-} from 'platform/utilities/date/index';
-
-const expiresSoon = expDate => {
- const EXPIRES_SOON_THRESHOLD_DURATION = 7 * 24 * 60 * 60 * 1000;
- const now = new Date();
- const expiresAt = new Date(expDate);
- const daysLeft = timeFromNow(expiresAt, now);
- if (
- differenceInDays(expiresAt, now) > 0 &&
- differenceInDays(expiresAt, now) < EXPIRES_SOON_THRESHOLD_DURATION
- ) {
- return `(in ${daysLeft})`;
- }
- return null;
-};
+ expiresSoon,
+ formatStatus,
+ resolutionDate,
+} from '../utilities/poaRequests';
const POARequestCard = ({ poaRequest, id }) => {
+ const lastName = poaRequest?.power_of_attorney_form?.claimant?.name?.last;
+ const firstName = poaRequest?.power_of_attorney_form?.claimant?.name?.first;
+ const city = poaRequest?.power_of_attorney_form?.claimant?.address.city;
+ const state =
+ poaRequest?.power_of_attorney_form?.claimant?.address.state_code;
+ const zipCode =
+ poaRequest?.power_of_attorney_form?.claimant?.address.zip_code;
+ const poaStatus =
+ poaRequest.resolution?.decision_type || poaRequest.resolution?.type;
return (
@@ -30,62 +24,59 @@ const POARequestCard = ({ poaRequest, id }) => {
data-testid={`poa-request-card-${id}-status`}
className="usa-label poa-request__card-field poa-request__card-field--status"
>
- {poaRequest.status}
+ {formatStatus(poaStatus)}
-
+
View details for
- {`${poaRequest.claimant.lastName}, ${
- poaRequest.claimant.firstName
- }`}
+ {`${lastName}, ${firstName}`}
-
- {poaRequest.claimantAddress.city}
-
+ {city}
{', '}
-
- {poaRequest.claimantAddress.state}
-
+ {state}
{', '}
-
- {poaRequest.claimantAddress.zip}
-
+ {zipCode}
- {poaRequest.status === 'Declined' && (
+ {poaStatus === 'declination' && (
<>
POA request declined on:
-
- {formatDateParsedZoneLong(poaRequest.acceptedOrDeclinedAt)}
-
+ {resolutionDate(poaRequest.resolution?.created_at, id)}
>
)}
- {poaRequest.status === 'Accepted' && (
+ {poaStatus === 'acceptance' && (
<>
POA request accepted on:
-
- {formatDateParsedZoneLong(poaRequest.acceptedOrDeclinedAt)}
+ {resolutionDate(poaRequest.resolution?.created_at, id)}
+ >
+ )}
+
+ {poaStatus === 'expiration' && (
+ <>
+
+ POA request expired on:
+ {resolutionDate(poaRequest.resolution?.created_at, id)}
>
)}
- {poaRequest.status === 'Pending' && (
+ {!poaRequest.resolution && (
<>
- {expiresSoon(poaRequest.expiresAt) && (
+ {expiresSoon(poaRequest.expires_at) && (
{
POA request expires on:
-
- {formatDateParsedZoneLong(poaRequest.expiresAt)}
-
+ {resolutionDate(poaRequest.expires_at, id)}
- {expiresSoon(poaRequest.expiresAt)}
+ {expiresSoon(poaRequest.expires_at)}
>
)}
@@ -113,6 +102,8 @@ const POARequestCard = ({ poaRequest, id }) => {
POARequestCard.propTypes = {
cssClass: PropTypes.string,
+ id: PropTypes.string,
+ poaRequest: PropTypes.object,
};
export default POARequestCard;
diff --git a/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx b/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
index 7d4f3b42392b..8303c3439437 100644
--- a/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
+++ b/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
@@ -1,11 +1,14 @@
import React, { useState } from 'react';
-import { Link, useLoaderData, Form, redirect } from 'react-router-dom';
-import { formatDateShort } from 'platform/utilities/date/index';
+import { useLoaderData, Form, redirect } from 'react-router-dom';
import {
VaRadio,
VaRadioOption,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
-
+import {
+ expiresSoon,
+ formatStatus,
+ resolutionDate,
+} from '../utilities/poaRequests';
import api from '../utilities/api';
const DECISION_TYPES = {
@@ -48,24 +51,54 @@ const DECISION_OPTIONS = {
...DECLINATION_OPTIONS,
};
-const checkAuthorizations = (
- isTreatmentDisclosureAuthorized,
- isAddressChangingAuthorized,
- status,
-) => {
- const authorizations = [];
- if (isTreatmentDisclosureAuthorized === status) {
- authorizations.push('Health');
- }
+const Authorized = () => {
+ return (
+
+
+ Authorized
+
+ );
+};
+
+const NoAccess = () => {
+ return (
+
+
+ No Access
+
+ );
+};
- if (isAddressChangingAuthorized === status) {
- authorizations.push('Address');
+const AccessToSome = () => {
+ return (
+
+
+ Access to some
+
+ );
+};
+const checkAuthorizations = x => {
+ if (x) {
+ return ;
}
- return authorizations.length > 0 ? authorizations.join(', ') : 'None';
+ return ;
+};
+const checkLimitations = (limitations, limit) => {
+ const checkLimitation = limitations.includes(limit);
+ return checkAuthorizations(checkLimitation);
};
const POARequestDetailsPage = () => {
- const poaRequest = useLoaderData().attributes;
+ const poaRequest = useLoaderData();
const [error, setError] = useState(false);
const handleChange = e => {
e.preventDefault();
@@ -76,194 +109,278 @@ const POARequestDetailsPage = () => {
setError(true);
}
};
- return (
-
-
- POA request:
- {poaRequest?.claimant?.firstName} {poaRequest?.claimant?.lastName}
-
-
- Veteran information
-
+ const relationship = poaRequest?.power_of_attorney_form.claimant.relationship;
+ const city = poaRequest?.power_of_attorney_form.claimant.address.city;
+ const state = poaRequest?.power_of_attorney_form.claimant.address.state_code;
+ const zipCode = poaRequest?.power_of_attorney_form.claimant.address.zip_code;
+ const phone = poaRequest?.power_of_attorney_form.claimant.phone;
+ const email = poaRequest.power_of_attorney_form.claimant.emaill;
+ const claimantFirstName =
+ poaRequest?.power_of_attorney_form.claimant.name.first;
+ const claimantLastName =
+ poaRequest?.power_of_attorney_form.claimant.name.last;
+ const recordDisclosureLimitations =
+ poaRequest.power_of_attorney_form.authorizations
+ .record_disclosure_limitations;
+ return (
+
+ POA request
+
+ {claimantLastName}, {claimantFirstName}
+
+ {formatStatus(poaStatus)}
+
+
- POA request information
-
- POA submission date
-
- {poaRequest?.submittedAt &&
- formatDateShort(poaRequest?.submittedAt)}
-
-
- {poaRequest?.status === 'Declined' && (
-
- )}
- {poaRequest?.status === 'Accepted' && (
-
- )}
- POA status
-
- {poaRequest?.status}
-
-
- Represented through
-
- Disabled American Veterans
-
-
-
-
- Claimant information
-
-
- Claimant name
-
- {poaRequest?.claimant.lastName}, {poaRequest?.claimant.firstName}
+ Requesting representation through
-
-
- Relationship to Veteran
- {poaRequest?.claimant.relationshipToVeteran}
+ {poaRequest?.power_of_attorney_holder?.name}
- Address
-
- {poaRequest?.claimantAddress.city},{' '}
- {poaRequest?.claimantAddress.state}
-
- {poaRequest?.claimantAddress.zip}
-
-
-
-
- Email
- email
+ {poaRequest?.created_at && (
+ <>
+ Request submitted on
+ {resolutionDate(poaRequest?.created_at, poaStatus.id)}
+ >
+ )}
-
- Phone
- 1231231234
+ {poaStatus === 'declination' && (
+ <>
+
+ POA request declined on
+
+ {resolutionDate(poaRequest.resolution?.created_at, poaStatus.id)}
+ >
+ )}
+ {poaStatus === 'acceptance' && (
+ <>
+
+ {' '}
+ POA request accepted on
+
+ {resolutionDate(poaRequest.resolution?.created_at, poaStatus.id)}
+ >
+ )}
+ {poaStatus === 'expiration' && (
+ <>
+
+ {' '}
+ POA request expired on
+
+ {resolutionDate(poaRequest.resolution?.created_at, poaStatus.id)}
+ >
+ )}
+ {poaStatus === 'Pending' && (
+ <>
+
+ {expiresSoon(poaRequest.expires_at) && (
+
+ )}
+ POA request expires on
+
+ {resolutionDate(poaRequest?.expires_at, poaStatus.id)}
+ >
+ )}
- Limitations of consent
-
-
-
-
- Approves
-
- {checkAuthorizations(
- poaRequest?.isTreatmentDisclosureAuthorized,
- poaRequest?.isAddressChangingAuthorized,
- true,
- )}
-
-
-
-
- Declines
-
+
- {checkAuthorizations(
- poaRequest?.isTreatmentDisclosureAuthorized,
- poaRequest?.isAddressChangingAuthorized,
- false,
+
+
Claimant information
+
+
+ Relationship to veteran
+ {relationship}
+
+
+ Address
+
+ {city}, {state}, {zipCode}
+
+
+
+ Phone
+ {phone}
+
+
+ Email
+ {email}
+
+ {relationship === 'Self' && (
+ <>
+
+ Social security number
+ {poaRequest?.power_of_attorney_form?.claimant?.ssn}
+
+
+ VA file number
+
+ {poaRequest?.power_of_attorney_form?.claimant?.va_file_number}
+
+
+ >
)}
-
-
- Back to power of attorney list
+
-
+ {poaStatus === 'Pending' && (
+
+ )}
+
);
};
diff --git a/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx b/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
index fb1c67653a7e..83e73e6fd6ff 100644
--- a/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
+++ b/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
@@ -29,8 +29,8 @@ const SearchResults = ({ poaRequests }) => {
className="poa-request__list"
sort-column={1}
>
- {poaRequests.map(({ id, attributes: poaRequest }) => {
- return ;
+ {poaRequests.map((request, index) => {
+ return ;
})}
);
@@ -78,7 +78,6 @@ const DigitalSubmissionAlert = () => (
const POARequestSearchPage = () => {
const poaRequests = useLoaderData();
const searchStatus = useSearchParams()[0].get('status');
-
return (
<>
Power of attorney requests
@@ -90,7 +89,7 @@ const POARequestSearchPage = () => {
tabStatus={STATUSES.PENDING}
searchStatus={searchStatus}
>
- Pending requests
+ Pending
{
- it('renders a card', () => {
- const { attributes: poaRequest, id } = mockPOARequestsResponse[0];
- const component = ;
-
- const { getByTestId } = renderTestComponent(component);
-
- expect(getByTestId('poa-request-card-12345-status')).to.exist;
- });
-});
diff --git a/src/applications/accredited-representative-portal/utilities/mockApi.js b/src/applications/accredited-representative-portal/utilities/mockApi.js
index 8a7c50a9d814..afbe9475bc16 100644
--- a/src/applications/accredited-representative-portal/utilities/mockApi.js
+++ b/src/applications/accredited-representative-portal/utilities/mockApi.js
@@ -11,24 +11,22 @@ const apiFetch = data => {
const mockApi = {
getPOARequests({ status }) {
- const filteredPoaRequests = poaRequests.filter(
- ({ attributes: poaRequest }) => {
- switch (status) {
- case 'completed':
- return ['Declined', 'Accepted'].includes(poaRequest.status);
- case 'pending':
- return poaRequest.status === 'Pending';
- default:
- throw new Error(`Unexpected status: ${status}`);
- }
- },
- );
+ const filteredPoaRequests = poaRequests.filter(poaRequest => {
+ switch (status) {
+ case 'completed':
+ return poaRequest.resolution !== null;
+ case 'pending':
+ return poaRequest.resolution === null;
+ default:
+ throw new Error(`Unexpected status: ${status}`);
+ }
+ });
return apiFetch(filteredPoaRequests);
},
getPOARequest(id) {
- const poaRequest = poaRequests.find(r => r.id === +id);
+ const poaRequest = poaRequests.find(r => r.id === id);
return apiFetch(poaRequest);
},
@@ -37,7 +35,7 @@ const mockApi = {
},
createPOARequestDecision(id, { type }) {
- const poaRequest = poaRequests.find(r => r.id === +id).attributes;
+ const poaRequest = poaRequests.find(r => r.id === id);
switch (type) {
case 'acceptance':
diff --git a/src/applications/accredited-representative-portal/utilities/mocks/poaRequests.json b/src/applications/accredited-representative-portal/utilities/mocks/poaRequests.json
index 024901d91180..a0bb43ab622e 100644
--- a/src/applications/accredited-representative-portal/utilities/mocks/poaRequests.json
+++ b/src/applications/accredited-representative-portal/utilities/mocks/poaRequests.json
@@ -1,219 +1,385 @@
[
{
- "id": 12345,
- "type": "powerOfAttorneyRequest",
- "attributes": {
- "status": "Declined",
- "declinedReason": "Refused to disclose health information",
- "powerOfAttorneyCode": "012",
- "submittedAt": "2024-04-10T04:51:12Z",
- "expiresAt": "2024-11-31T15:20:00Z",
- "acceptedOrDeclinedAt": "2024-04-10T04:51:12Z",
- "isAddressChangingAuthorized": true,
- "isTreatmentDisclosureAuthorized": false,
- "representative": {
- "firstName": "John",
- "lastName": "Smith",
- "email": "john.smith@vsorg.org"
+ "claimant_id": "7de8ce1e-af1c-4c5d-8caf-4002593dac69",
+ "created_at": "2024-11-27T17:51:27Z",
+ "expires_at": null,
+ "power_of_attorney_form": {
+ "authorizations": {
+ "record_disclosure": false,
+ "record_disclosure_limitations": [
+ "DRUG_ABUSE",
+ "HIV",
+ "SICKLE_CELL"
+ ],
+ "address_change": true
},
"claimant": {
- "firstName": "Morgan",
- "lastName": "Fox",
- "participantId": 23456,
- "relationshipToVeteran": "Child"
- },
- "claimantAddress": {
- "city": "Baltimore",
- "state": "MD",
- "zip": "21218",
- "country": "US",
- "militaryPostOffice": null,
- "militaryPostalCode": null
+ "name": {
+ "first": "Racquel",
+ "middle": null,
+ "last": "Cornell"
+ },
+ "address": {
+ "address_line1": "7805 Wintheiser Flats",
+ "address_line2": null,
+ "city": "Port Idella",
+ "state_code": "IA",
+ "country": "US",
+ "zip_code": "21317",
+ "zip_code_suffix": null
+ },
+ "ssn": "808470130",
+ "va_file_number": "225762772",
+ "date_of_birth": "1982-03-29",
+ "service_number": "888606132",
+ "service_branch": "ARMY",
+ "phone": "(973) 688-6630",
+ "email": "rochel@deckow-bernhard.test"
}
- }
+ },
+ "resolution": {
+ "created_at": "2024-11-28T17:51:27Z",
+ "type": "expiration",
+ "id": "e35c2787-cd86-4b69-a714-707a424d70bb"
+ },
+ "power_of_attorney_holder": {
+ "type": "veteran_service_organization",
+ "name": "Trustworthy Organization",
+ "id": "23563963-e31c-4797-a259-e26b727072e1"
+ },
+ "accredited_individual": {
+ "full_name": "Bob Representative",
+ "id": "d9d7e941-7156-4eca-827c-bcabe731d7ea"
+ },
+ "id": "345dd27a-db3f-4e8f-8588-e0d589026e44"
},
{
- "id": 67890,
- "type": "powerOfAttorneyRequest",
- "attributes": {
- "status": "Pending",
- "powerOfAttorneyCode": "034",
- "submittedAt": "2024-03-29T15:20:00Z",
- "expiresAt": "2024-12-07T12:15:47Z",
- "acceptedOrDeclinedAt": null,
- "isAddressChangingAuthorized": false,
- "isTreatmentDisclosureAuthorized": true,
- "representative": {
- "firstName": "Michael",
- "lastName": "Johnson",
- "email": "michael.johnson@vsorg.org"
+ "claimant_id": "7de8ce1e-af1c-4c5d-8caf-4002593dac69",
+ "created_at": "2024-12-07T17:51:27Z",
+ "expires_at": null,
+ "power_of_attorney_form": {
+ "authorizations": {
+ "record_disclosure": false,
+ "record_disclosure_limitations": [
+ "DRUG_ABUSE",
+ "HIV",
+ "SICKLE_CELL"
+ ],
+ "address_change": true
},
"claimant": {
- "firstName": "Emily",
- "lastName": "Wells",
- "participantId": 56789,
- "relationshipToVeteran": "Parent"
- },
- "claimantAddress": {
- "city": "New York",
- "state": "NY",
- "zip": "10001",
- "country": "US",
- "militaryPostOffice": null,
- "militaryPostalCode": null
+ "name": {
+ "first": "Racquel",
+ "middle": null,
+ "last": "Cornell"
+ },
+ "address": {
+ "address_line1": "7805 Wintheiser Flats",
+ "address_line2": null,
+ "city": "Port Idella",
+ "state_code": "IA",
+ "country": "US",
+ "zip_code": "21317",
+ "zip_code_suffix": null
+ },
+ "ssn": "808470130",
+ "va_file_number": "225762772",
+ "date_of_birth": "1982-03-29",
+ "service_number": "888606132",
+ "service_branch": "ARMY",
+ "phone": "(973) 688-6630",
+ "email": "rochel@deckow-bernhard.test"
}
- }
+ },
+ "resolution": {
+ "created_at": "2024-12-08T17:51:27Z",
+ "type": "decision",
+ "decision_type": "declination",
+ "reason": "Didn't authorize treatment record disclosure",
+ "creator_id": "37f4491b-5cf2-42b1-93c6-bc329c935c51",
+ "id": "1d86c16e-19be-4344-b4f6-06c22d9496cb"
+ },
+ "power_of_attorney_holder": {
+ "type": "veteran_service_organization",
+ "name": "Good Representatives R Us",
+ "id": "41d36596-67d1-4b23-ab58-927447e88d07"
+ },
+ "accredited_individual": {
+ "full_name": "Bob Representative",
+ "id": "d9d7e941-7156-4eca-827c-bcabe731d7ea"
+ },
+ "id": "0b255a94-b9e0-45c8-b313-3c1179178b4f"
},
{
- "id": 54321,
- "type": "powerOfAttorneyRequest",
- "attributes": {
- "status": "Accepted",
- "powerOfAttorneyCode": "056",
- "submittedAt": "2024-04-20T09:22:33Z",
- "expiresAt": "2024-12-31T15:20:00Z",
- "acceptedOrDeclinedAt": "2024-10-31T15:20:00Z",
- "isAddressChangingAuthorized": true,
- "isTreatmentDisclosureAuthorized": false,
- "veteran": {
- "firstName": "Bob",
- "middleName": "G",
- "lastName": "Norris",
- "participantId": 800067890
- },
- "representative": {
- "firstName": "Christopher",
- "lastName": "Lee",
- "email": "christopher.lee@vsorg.org"
+ "claimant_id": "7de8ce1e-af1c-4c5d-8caf-4002593dac69",
+ "created_at": "2024-12-17T17:51:27Z",
+ "expires_at": null,
+ "power_of_attorney_form": {
+ "authorizations": {
+ "record_disclosure": false,
+ "record_disclosure_limitations": [
+ "DRUG_ABUSE",
+ "HIV",
+ "SICKLE_CELL"
+ ],
+ "address_change": true
},
"claimant": {
- "firstName": "Gamma",
- "lastName": "Theta",
- "participantId": 34567,
- "relationshipToVeteran": "Child"
- },
- "claimantAddress": {
- "city": "San Francisco",
- "state": "CA",
- "zip": "94102",
- "country": "US",
- "militaryPostOffice": null,
- "militaryPostalCode": null
+ "name": {
+ "first": "Racquel",
+ "middle": null,
+ "last": "Cornell"
+ },
+ "address": {
+ "address_line1": "7805 Wintheiser Flats",
+ "address_line2": null,
+ "city": "Port Idella",
+ "state_code": "IA",
+ "country": "US",
+ "zip_code": "21317",
+ "zip_code_suffix": null
+ },
+ "ssn": "808470130",
+ "va_file_number": "225762772",
+ "date_of_birth": "1982-03-29",
+ "service_number": "888606132",
+ "service_branch": "ARMY",
+ "phone": "(973) 688-6630",
+ "email": "rochel@deckow-bernhard.test"
}
- }
+ },
+ "resolution": {
+ "created_at": "2024-12-18T17:51:27Z",
+ "type": "decision",
+ "decision_type": "acceptance",
+ "creator_id": "e6438b18-28ab-43a7-854f-4c27c7daa23c",
+ "id": "7f33a272-afc8-47cc-bbaa-cf5d6771f8a1"
+ },
+ "power_of_attorney_holder": {
+ "type": "veteran_service_organization",
+ "name": "Trustworthy Organization",
+ "id": "23563963-e31c-4797-a259-e26b727072e1"
+ },
+ "accredited_individual": {
+ "full_name": "Robert Lowe",
+ "id": "3f47a6c7-ce14-4ab5-9567-6e3803336fe1"
+ },
+ "id": "521ea854-2dee-4b52-b681-401c6c8542f5"
},
{
- "id": 24680,
- "type": "powerOfAttorneyRequest",
- "attributes": {
- "status": "Accepted",
- "powerOfAttorneyCode": "077",
- "submittedAt": "2024-02-14T10:30:00Z",
- "expiresAt": "2024-11-31T15:20:00Z",
- "acceptedOrDeclinedAt": "2024-02-15T14:45:22Z",
- "isAddressChangingAuthorized": false,
- "isTreatmentDisclosureAuthorized": false,
- "veteran": {
- "firstName": "Larry",
- "middleName": "David",
- "lastName": "Allen",
- "participantId": 900077600
- },
- "representative": {
- "firstName": "Alexander",
- "lastName": "Williams",
- "email": "alexander.williams@vsorg.org"
+ "claimant_id": "3e6f543c-1d24-456e-be14-49e0a0d6eeb1",
+ "created_at": "2024-11-27T23:51:27Z",
+ "expires_at": null,
+ "power_of_attorney_form": {
+ "authorizations": {
+ "record_disclosure": true,
+ "record_disclosure_limitations": [
+ "ALCOHOLISM",
+ "DRUG_ABUSE",
+ "SICKLE_CELL"
+ ],
+ "address_change": true
},
"claimant": {
- "firstName": "Jacob",
- "lastName": "Parker",
- "participantId": 45678,
- "relationshipToVeteran": "Brother"
- },
- "claimantAddress": {
- "city": "Chicago",
- "state": "IL",
- "zip": "60607",
- "country": "US",
- "militaryPostOffice": null,
- "militaryPostalCode": null
+ "name": {
+ "first": "Morgan",
+ "middle": null,
+ "last": "Tiffany"
+ },
+ "address": {
+ "address_line1": "3186 Yundt Estate",
+ "address_line2": null,
+ "city": "Erneststad",
+ "state_code": "NY",
+ "country": "US",
+ "zip_code": "10739-6818",
+ "zip_code_suffix": null
+ },
+ "ssn": "135456143",
+ "va_file_number": "193891177",
+ "date_of_birth": "1976-10-16",
+ "service_number": "333731267",
+ "service_branch": "USPHS",
+ "phone": "670-453-4163",
+ "email": "clarence.vonrueden@adams.test"
}
- }
+ },
+ "resolution": {
+ "created_at": "2024-11-28T23:51:27Z",
+ "type": "expiration",
+ "id": "043a6d05-549e-4a13-bb30-8cdda34b6f40"
+ },
+ "power_of_attorney_holder": {
+ "type": "veteran_service_organization",
+ "name": "We Help Vets",
+ "id": "83b7de4d-a2cd-4b15-9e32-af48804c3e31"
+ },
+ "accredited_individual": {
+ "full_name": "Robert Lowe",
+ "id": "3f47a6c7-ce14-4ab5-9567-6e3803336fe1"
+ },
+ "id": "6cb8c41a-7ede-4124-91e3-aeb31932207a"
},
{
- "id": 13579,
- "type": "powerOfAttorneyRequest",
- "attributes": {
- "status": "Pending",
- "powerOfAttorneyCode": "089",
- "submittedAt": "2024-05-01T12:15:47Z",
- "expiresAt": "2024-05-01T12:15:47Z",
- "acceptedOrDeclinedAt": null,
- "isAddressChangingAuthorized": true,
- "isTreatmentDisclosureAuthorized": true,
- "veteran": {
- "firstName": "Suzanne",
- "middleName": "Jane",
- "lastName": "Baker",
- "participantId": 101112130
- },
- "representative": {
- "firstName": "Benjamin",
- "lastName": "Davis",
- "email": "benjamin.davis@vsorg.org"
+ "claimant_id": "3e6f543c-1d24-456e-be14-49e0a0d6eeb1",
+ "created_at": "2024-12-07T23:51:27Z",
+ "expires_at": null,
+ "power_of_attorney_form": {
+ "authorizations": {
+ "record_disclosure": true,
+ "record_disclosure_limitations": [
+ "ALCOHOLISM",
+ "DRUG_ABUSE",
+ "SICKLE_CELL"
+ ],
+ "address_change": true
},
"claimant": {
- "firstName": "Samantha",
- "lastName": "Jones",
- "participantId": 98765,
- "relationshipToVeteran": "Sister"
- },
- "claimantAddress": {
- "city": "Miami",
- "state": "FL",
- "zip": "33101",
- "country": "US",
- "militaryPostOffice": null,
- "militaryPostalCode": null
+ "name": {
+ "first": "Morgan",
+ "middle": null,
+ "last": "Tiffany"
+ },
+ "address": {
+ "address_line1": "3186 Yundt Estate",
+ "address_line2": null,
+ "city": "Erneststad",
+ "state_code": "NY",
+ "country": "US",
+ "zip_code": "10739-6818",
+ "zip_code_suffix": null
+ },
+ "ssn": "135456143",
+ "va_file_number": "193891177",
+ "date_of_birth": "1976-10-16",
+ "service_number": "333731267",
+ "service_branch": "USPHS",
+ "phone": "670-453-4163",
+ "email": "clarence.vonrueden@adams.test"
}
- }
+ },
+ "resolution": {
+ "created_at": "2024-12-08T23:51:27Z",
+ "type": "decision",
+ "decision_type": "acceptance",
+ "creator_id": "2b4a721f-0882-433f-bb51-b0f30270555e",
+ "id": "73fdf18e-6fac-44a3-9b69-0e7c3149e8d1"
+ },
+ "power_of_attorney_holder": {
+ "type": "veteran_service_organization",
+ "name": "The Swift Reps",
+ "id": "31f61e0f-8993-4c57-86f2-7f22e9ca9e68"
+ },
+ "accredited_individual": {
+ "full_name": "Robert Lowe",
+ "id": "3f47a6c7-ce14-4ab5-9567-6e3803336fe1"
+ },
+ "id": "53a1c8f1-0ac2-442b-b6df-768a5324fd78"
},
{
- "id": 97531,
- "type": "powerOfAttorneyRequest",
- "attributes": {
- "status": "Pending",
- "powerOfAttorneyCode": "102",
- "submittedAt": "2024-03-20T08:00:00Z",
- "expiresAt": "2024-05-01T12:15:47Z",
- "acceptedOrDeclinedAt": null,
- "isAddressChangingAuthorized": false,
- "isTreatmentDisclosureAuthorized": false,
- "veteran": {
- "firstName": "Michael",
- "middleName": "Karl",
- "lastName": "Sage",
- "participantId": 111213140
- },
- "representative": {
- "firstName": "Nicholas",
- "lastName": "Brown",
- "email": "nicholas.brown@vsorg.org"
+ "claimant_id": "3e6f543c-1d24-456e-be14-49e0a0d6eeb1",
+ "created_at": "2024-12-17T23:51:27Z",
+ "expires_at": null,
+ "power_of_attorney_form": {
+ "authorizations": {
+ "record_disclosure": true,
+ "record_disclosure_limitations": [
+ "ALCOHOLISM",
+ "DRUG_ABUSE",
+ "SICKLE_CELL"
+ ],
+ "address_change": true
},
"claimant": {
- "firstName": "Lucas",
- "lastName": "Brown",
- "participantId": 65432,
- "relationshipToVeteran": "Child"
+ "name": {
+ "first": "Morgan",
+ "middle": null,
+ "last": "Tiffany"
+ },
+ "address": {
+ "address_line1": "3186 Yundt Estate",
+ "address_line2": null,
+ "city": "Erneststad",
+ "state_code": "NY",
+ "country": "US",
+ "zip_code": "10739-6818",
+ "zip_code_suffix": null
+ },
+ "ssn": "135456143",
+ "va_file_number": "193891177",
+ "date_of_birth": "1976-10-16",
+ "service_number": "333731267",
+ "service_branch": "USPHS",
+ "phone": "670-453-4163",
+ "email": "clarence.vonrueden@adams.test"
+ }
+ },
+ "resolution": {
+ "created_at": "2024-12-18T23:51:27Z",
+ "type": "decision",
+ "decision_type": "declination",
+ "reason": "Didn't authorize treatment record disclosure",
+ "creator_id": "f31cb32a-23bf-48ca-995a-db20e6bdf951",
+ "id": "3580e76b-33b5-408e-b925-b6c308fa0003"
+ },
+ "power_of_attorney_holder": {
+ "type": "veteran_service_organization",
+ "name": "Good Representatives R Us",
+ "id": "41d36596-67d1-4b23-ab58-927447e88d07"
+ },
+ "accredited_individual": {
+ "full_name": "Suzie Lowe",
+ "id": "90e83d10-8ce1-482b-8f06-45bea0cc7f3c"
+ },
+ "id": "0657aca0-a8d2-4d09-bf60-114fe0fe468d"
+ },
+ {
+ "claimant_id": "4cdad2aa-87b9-4879-96fe-3effd6a13d83",
+ "created_at": "2024-12-15T05:51:27Z",
+ "expires_at": "2024-12-22T05:51:27Z",
+ "power_of_attorney_form": {
+ "authorizations": {
+ "record_disclosure": true,
+ "record_disclosure_limitations": [
+ "ALCOHOLISM",
+ "HIV"
+ ],
+ "address_change": false
},
- "claimantAddress": {
- "city": "Los Angeles",
- "state": "CA",
- "zip": "90001",
- "country": "US",
- "militaryPostOffice": null,
- "militaryPostalCode": null
+ "claimant": {
+ "name": {
+ "first": "Kelly",
+ "middle": null,
+ "last": "Miguel"
+ },
+ "address": {
+ "address_line1": "5266 Minh Coves",
+ "address_line2": null,
+ "city": "Port America",
+ "state_code": "NC",
+ "country": "US",
+ "zip_code": "63751",
+ "zip_code_suffix": null
+ },
+ "ssn": "254457366",
+ "va_file_number": "639106878",
+ "date_of_birth": "1944-02-11",
+ "service_number": "342545363",
+ "service_branch": "ARMY",
+ "phone": "(802) 806-8772",
+ "email": "darryl.hayes@morissette-schaden.example"
}
- }
+ },
+ "resolution": null,
+ "power_of_attorney_holder": {
+ "type": "veteran_service_organization",
+ "name": "The Swift Reps",
+ "id": "31f61e0f-8993-4c57-86f2-7f22e9ca9e68"
+ },
+ "accredited_individual": {
+ "full_name": "Erica Representative",
+ "id": "c50410bb-7341-4d95-9a2f-aed22178ff08"
+ },
+ "id": "beb394d6-006a-4f2a-918c-6d35355f3d41"
}
-]
+ ]
\ No newline at end of file
diff --git a/src/applications/accredited-representative-portal/utilities/poaRequests.js b/src/applications/accredited-representative-portal/utilities/poaRequests.js
new file mode 100644
index 000000000000..4070086da7d9
--- /dev/null
+++ b/src/applications/accredited-representative-portal/utilities/poaRequests.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import { differenceInDays } from 'date-fns';
+import {
+ formatDateParsedZoneLong,
+ timeFromNow,
+} from 'platform/utilities/date/index';
+
+export const expiresSoon = expDate => {
+ const EXPIRES_SOON_THRESHOLD_DURATION = 7;
+ const now = new Date();
+ const expiresAt = new Date(expDate);
+ const daysLeft = timeFromNow(expiresAt, now);
+ if (
+ differenceInDays(expiresAt, now) > 0 &&
+ differenceInDays(expiresAt, now) < EXPIRES_SOON_THRESHOLD_DURATION
+ ) {
+ return `(in ${daysLeft})`;
+ }
+ return null;
+};
+
+export const formatStatus = x => {
+ if (x === 'declination') {
+ return 'Declined';
+ }
+ if (x === 'acceptance') {
+ return 'Accepted';
+ }
+ if (x === 'expiration') {
+ return 'Expired';
+ }
+ return 'Pending';
+};
+export const resolutionDate = (date, requestId) => {
+ return (
+
+ {formatDateParsedZoneLong(date)}
+
+ );
+};
From f3bc3a90ab3f354b9c07125160f9ffcaebf70ee9 Mon Sep 17 00:00:00 2001
From: Liz Wendling <170662489+GovCIOLiz@users.noreply.github.com>
Date: Fri, 17 Jan 2025 10:42:08 -0800
Subject: [PATCH 27/36] GI CT Search by program (#34150)
* Wip
* Program search required fields
* Search by program components
* Testing
* Testing
* Testing, require distance field
* Testing and focusLocationInput on location modal close
* Distance dropdown validation message, testing
* Toggle
---
src/applications/gi/actions/index.js | 26 ++--
.../gi/tests/actions/index.unit.spec.jsx | 1 +
.../updated-gi/components/SearchByProgram.jsx | 75 -----------
.../school-and-employers/UseMyLocation.jsx | 26 ++++
.../UseMyLocationModal.jsx | 30 +++++
.../containers/SchoolsAndEmployers.jsx | 40 +++---
.../SearchByName.jsx | 0
.../updated-gi/containers/SearchByProgram.jsx | 125 ++++++++++++++++++
.../components/AboutThisTool.unit.spec.jsx | 11 ++
.../components/UseMyLocation.unit.spec.jsx | 27 ++++
.../UseMyLocationModal.unit.spec.jsx | 64 +++++++++
.../SchoolsAndEmployers.unit.spec.jsx | 10 +-
.../containers/SearchByProgram.unit.spec.jsx | 105 +++++++++++++++
.../e2e/updated_gi_homepage.cypress.spec.js | 6 +
14 files changed, 444 insertions(+), 102 deletions(-)
delete mode 100644 src/applications/gi/updated-gi/components/SearchByProgram.jsx
create mode 100644 src/applications/gi/updated-gi/components/school-and-employers/UseMyLocation.jsx
create mode 100644 src/applications/gi/updated-gi/components/school-and-employers/UseMyLocationModal.jsx
rename src/applications/gi/updated-gi/{components => containers}/SearchByName.jsx (100%)
create mode 100644 src/applications/gi/updated-gi/containers/SearchByProgram.jsx
create mode 100644 src/applications/gi/updated-gi/tests/components/AboutThisTool.unit.spec.jsx
create mode 100644 src/applications/gi/updated-gi/tests/components/UseMyLocation.unit.spec.jsx
create mode 100644 src/applications/gi/updated-gi/tests/components/UseMyLocationModal.unit.spec.jsx
create mode 100644 src/applications/gi/updated-gi/tests/containers/SearchByProgram.unit.spec.jsx
diff --git a/src/applications/gi/actions/index.js b/src/applications/gi/actions/index.js
index f7e46dec4a8e..d6ec51cdd4fa 100644
--- a/src/applications/gi/actions/index.js
+++ b/src/applications/gi/actions/index.js
@@ -507,15 +507,23 @@ export function fetchSearchByLocationCoords(
distance,
filters,
version,
+ description,
) {
const [longitude, latitude] = coordinates;
-
- const params = {
- latitude,
- longitude,
- distance,
- ...rubyifyKeys(buildSearchFilters(filters)),
- };
+ // If description - search by program, else search by location w/ filters
+ const params = description
+ ? {
+ latitude,
+ longitude,
+ distance,
+ description,
+ }
+ : {
+ latitude,
+ longitude,
+ distance,
+ ...rubyifyKeys(filters && buildSearchFilters(filters)),
+ };
if (version) {
params.version = version;
}
@@ -523,7 +531,7 @@ export function fetchSearchByLocationCoords(
return dispatch => {
dispatch({
type: SEARCH_STARTED,
- payload: { location, latitude, longitude, distance },
+ payload: { location, latitude, longitude, distance, description },
});
return fetch(url, api.settings)
@@ -563,6 +571,7 @@ export function fetchSearchByLocationResults(
distance,
filters,
version,
+ description,
) {
// Prevent empty search request to Mapbox, which would result in error, and
// clear results list to respond with message of no facilities found.
@@ -593,6 +602,7 @@ export function fetchSearchByLocationResults(
distance,
filters,
version,
+ description,
),
);
})
diff --git a/src/applications/gi/tests/actions/index.unit.spec.jsx b/src/applications/gi/tests/actions/index.unit.spec.jsx
index bf0fdca7c268..e9dc1355b2ab 100644
--- a/src/applications/gi/tests/actions/index.unit.spec.jsx
+++ b/src/applications/gi/tests/actions/index.unit.spec.jsx
@@ -549,6 +549,7 @@ describe('actionCreators', () => {
10,
{ filter1: 'value1', excludedSchoolTypes: 'value2' },
'version',
+ null,
)(mockDispatch)
.then(() => {
expect(
diff --git a/src/applications/gi/updated-gi/components/SearchByProgram.jsx b/src/applications/gi/updated-gi/components/SearchByProgram.jsx
deleted file mode 100644
index 138497071fd8..000000000000
--- a/src/applications/gi/updated-gi/components/SearchByProgram.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { useState } from 'react';
-import {
- VaButton,
- VaSelect,
- VaTextInput,
-} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
-
-const SearchByProgram = () => {
- const distanceDropdownOptions = [
- { value: '5', label: 'within 5 miles' },
- { value: '15', label: 'within 15 miles' },
- { value: '25', label: 'within 25 miles' },
- { value: '50', label: 'within 50 miles' },
- { value: '75', label: 'within 75 miles' },
- ];
- const [distance, setDistance] = useState('25');
-
- const useLocation = e => {
- e.preventDefault();
- };
-
- const onSelectChange = e => {
- setDistance(e.target.value);
- };
-
- const search = () => {};
-
- return (
-
-
-
-
- {/* eslint-disable-next-line @department-of-veterans-affairs/prefer-button-component */}
-
-
- Use my location
-
-
- {distanceDropdownOptions.map(option => (
-
- {option.label}
-
- ))}
-
-
-
-
- );
-};
-
-export default SearchByProgram;
diff --git a/src/applications/gi/updated-gi/components/school-and-employers/UseMyLocation.jsx b/src/applications/gi/updated-gi/components/school-and-employers/UseMyLocation.jsx
new file mode 100644
index 000000000000..07972e0a52ac
--- /dev/null
+++ b/src/applications/gi/updated-gi/components/school-and-employers/UseMyLocation.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+export const UseMyLocation = ({ geolocationInProgress, handleLocateUser }) => {
+ return (
+ <>
+ {geolocationInProgress ? (
+
+
+ Finding your location...
+
+ ) : (
+ <>
+ {/* eslint-disable-next-line @department-of-veterans-affairs/prefer-button-component */}
+
+
+ Use my location
+
+ >
+ )}
+ >
+ );
+};
diff --git a/src/applications/gi/updated-gi/components/school-and-employers/UseMyLocationModal.jsx b/src/applications/gi/updated-gi/components/school-and-employers/UseMyLocationModal.jsx
new file mode 100644
index 000000000000..0489ce2c6d11
--- /dev/null
+++ b/src/applications/gi/updated-gi/components/school-and-employers/UseMyLocationModal.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { useDispatch } from 'react-redux';
+import { VaModal } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import { clearGeocodeError } from '../../../actions';
+
+export const UseMyLocationModal = ({ geocodeError, focusLocationInput }) => {
+ const dispatch = useDispatch();
+
+ return (
+ {
+ focusLocationInput();
+ dispatch(clearGeocodeError());
+ }}
+ status="warning"
+ visible={geocodeError > 0}
+ >
+
+ {geocodeError === 1
+ ? 'Please enable location sharing in your browser to use this feature.'
+ : 'Sorry, something went wrong when trying to find your location. Please make sure location sharing is enabled and try again.'}
+
+
+ );
+};
diff --git a/src/applications/gi/updated-gi/containers/SchoolsAndEmployers.jsx b/src/applications/gi/updated-gi/containers/SchoolsAndEmployers.jsx
index 707e8e10dcb4..621b18cc58d1 100644
--- a/src/applications/gi/updated-gi/containers/SchoolsAndEmployers.jsx
+++ b/src/applications/gi/updated-gi/containers/SchoolsAndEmployers.jsx
@@ -1,14 +1,14 @@
import React, { useState } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
-import SearchByName from '../components/SearchByName';
-import SearchByProgram from '../components/SearchByProgram';
+import SearchByName from './SearchByName';
+import SearchByProgram from './SearchByProgram';
const SchoolAndEmployers = () => {
const [currentTab, setCurrentTab] = useState(0);
const tabPanelClassList =
'vads-u-border-bottom--1px vads-u-border-left--1px vads-u-border-right--1px vads-u-border-color--primary medium-screen:vads-u-padding--4 mobile:vads-u-padding--2';
const baseTabClassList =
- 'vads-l-col vads-u-display--flex vads-u-justify-content--center vads-u-align-items--center vads-u-margin-bottom--0 vads-u-text-align--center vads-u-border-top--5px vads-u-border-left--1px vads-u-border-right--1px';
+ 'vads-u-font-family--serif vads-u-font-size--h3 vads-l-col vads-u-display--flex vads-u-justify-content--center vads-u-align-items--center vads-u-margin-bottom--0 vads-u-text-align--center vads-u-border-top--5px vads-u-border-left--1px vads-u-border-right--1px';
const inactiveTabClassList = `${baseTabClassList} vads-u-background-color--base-lightest vads-u-border-color--base-lightest vads-u-border-bottom--1px`;
const activeTabClassList = `${baseTabClassList} vads-u-border-color--primary`;
const inactiveTabText = 'vads-u-color--gray-dark vads-u-margin--0';
@@ -27,30 +27,36 @@ const SchoolAndEmployers = () => {
style={{ listStyle: 'none', cursor: 'pointer' }}
>
-
- Search by name
-
+
+
+ Search by name
+
+
-
- Search by program
-
+
+
+ Search by program
+
+
diff --git a/src/applications/gi/updated-gi/components/SearchByName.jsx b/src/applications/gi/updated-gi/containers/SearchByName.jsx
similarity index 100%
rename from src/applications/gi/updated-gi/components/SearchByName.jsx
rename to src/applications/gi/updated-gi/containers/SearchByName.jsx
diff --git a/src/applications/gi/updated-gi/containers/SearchByProgram.jsx b/src/applications/gi/updated-gi/containers/SearchByProgram.jsx
new file mode 100644
index 000000000000..2145f36e798f
--- /dev/null
+++ b/src/applications/gi/updated-gi/containers/SearchByProgram.jsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import recordEvent from 'platform/monitoring/record-event';
+import {
+ VaButton,
+ VaSelect,
+ VaTextInput,
+} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import { geolocateUser, fetchSearchByLocationResults } from '../../actions';
+import { UseMyLocation } from '../components/school-and-employers/UseMyLocation';
+import { UseMyLocationModal } from '../components/school-and-employers/UseMyLocationModal';
+
+const SearchByProgram = () => {
+ const locationRef = useRef(null);
+ const distanceDropdownOptions = [
+ { value: '5', label: 'within 5 miles' },
+ { value: '15', label: 'within 15 miles' },
+ { value: '25', label: 'within 25 miles' },
+ { value: '50', label: 'within 50 miles' },
+ { value: '75', label: 'within 75 miles' },
+ ];
+ const dispatch = useDispatch();
+ const search = useSelector(state => state.search);
+ const [distance, setDistance] = useState(search.query.distance);
+ const [location, setLocation] = useState(search.query.location);
+ const [programName, setProgramName] = useState(null);
+ const [searchDirty, setSearchDirty] = useState(false);
+
+ const focusLocationInput = () => {
+ locationRef?.current?.shadowRoot?.querySelector('input').focus();
+ };
+
+ const handleLocateUser = e => {
+ e.preventDefault();
+ recordEvent({
+ event: 'map-use-my-location',
+ });
+ dispatch(geolocateUser());
+ };
+
+ const handleSearch = () => {
+ if (!searchDirty) {
+ setSearchDirty(true);
+ return;
+ }
+ const description = programName;
+ dispatch(
+ fetchSearchByLocationResults(location, distance, null, null, description),
+ );
+ // Show program results...
+ };
+
+ useEffect(
+ () => {
+ const { searchString } = search.query.streetAddress;
+ if (searchString) {
+ setLocation(searchString);
+ focusLocationInput();
+ }
+ },
+ [search],
+ );
+
+ return (
+
+
+
setProgramName(e.target.value)}
+ />
+ setLocation(e.target.value)}
+ />
+
+
+ setDistance(e.target.value)}
+ value={distance}
+ required
+ error={searchDirty && !distance ? 'Please select a distance' : null}
+ >
+ {distanceDropdownOptions.map(option => (
+
+ {option.label}
+
+ ))}
+
+
+
+
+ );
+};
+
+export default SearchByProgram;
diff --git a/src/applications/gi/updated-gi/tests/components/AboutThisTool.unit.spec.jsx b/src/applications/gi/updated-gi/tests/components/AboutThisTool.unit.spec.jsx
new file mode 100644
index 000000000000..1da76eda73e4
--- /dev/null
+++ b/src/applications/gi/updated-gi/tests/components/AboutThisTool.unit.spec.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import { expect } from 'chai';
+import { render } from '@testing-library/react';
+import { AboutThisTool } from '../../components/AboutThisTool';
+
+describe('About this tool', () => {
+ it('Should render two links', () => {
+ const { container } = render( );
+ expect(container.querySelectorAll('a').length).to.equal(2);
+ });
+});
diff --git a/src/applications/gi/updated-gi/tests/components/UseMyLocation.unit.spec.jsx b/src/applications/gi/updated-gi/tests/components/UseMyLocation.unit.spec.jsx
new file mode 100644
index 000000000000..680decb3f4fd
--- /dev/null
+++ b/src/applications/gi/updated-gi/tests/components/UseMyLocation.unit.spec.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import { expect } from 'chai';
+import { render } from '@testing-library/react';
+import createCommonStore from '@department-of-veterans-affairs/platform-startup/store';
+import { UseMyLocation } from '../../components/school-and-employers/UseMyLocation';
+
+const defaultStore = createCommonStore();
+
+describe('Use my location', () => {
+ it('Should show finding your location when in progress', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+ expect(getByText('Finding your location...')).to.exist;
+ });
+ it('Should show finding your location when in progress', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+ expect(getByText('Use my location')).to.exist;
+ });
+});
diff --git a/src/applications/gi/updated-gi/tests/components/UseMyLocationModal.unit.spec.jsx b/src/applications/gi/updated-gi/tests/components/UseMyLocationModal.unit.spec.jsx
new file mode 100644
index 000000000000..1f12a5157111
--- /dev/null
+++ b/src/applications/gi/updated-gi/tests/components/UseMyLocationModal.unit.spec.jsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+import { expect } from 'chai';
+import { render, waitFor } from '@testing-library/react';
+import { $ } from 'platform/forms-system/src/js/utilities/ui';
+import configureStore from 'redux-mock-store';
+import createCommonStore from '@department-of-veterans-affairs/platform-startup/store';
+import SearchByProgram from '../../containers/SearchByProgram';
+import { UseMyLocationModal } from '../../components/school-and-employers/UseMyLocationModal';
+
+const defaultStore = createCommonStore();
+
+describe('Use my location modal', () => {
+ it('Renders enable location modal', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+ expect(
+ getByText(
+ 'Please enable location sharing in your browser to use this feature.',
+ ),
+ ).to.exist;
+ });
+ it('Renders couldnt locate modal', () => {
+ const { getByText } = render(
+
+
+ ,
+ );
+ expect(
+ getByText(
+ 'Sorry, something went wrong when trying to find your location. Please make sure location sharing is enabled and try again.',
+ ),
+ ).to.exist;
+ });
+ it('Should close', async () => {
+ const middlewares = [thunk];
+ const mockStore = configureStore(middlewares);
+ const store = mockStore({
+ search: {
+ query: {
+ distance: '',
+ location: '',
+ streetAddress: { searchString: '' },
+ },
+ },
+ });
+ const { container } = render(
+
+
+
+
+ ,
+ );
+ const event = new CustomEvent('closeEvent');
+ await $('va-modal', container).__events.closeEvent(event);
+ waitFor(() => {
+ expect($('va-modal[visible="false"]', container)).to.exist;
+ });
+ });
+});
diff --git a/src/applications/gi/updated-gi/tests/containers/SchoolsAndEmployers.unit.spec.jsx b/src/applications/gi/updated-gi/tests/containers/SchoolsAndEmployers.unit.spec.jsx
index a53bbe59d7db..e34f39f02321 100644
--- a/src/applications/gi/updated-gi/tests/containers/SchoolsAndEmployers.unit.spec.jsx
+++ b/src/applications/gi/updated-gi/tests/containers/SchoolsAndEmployers.unit.spec.jsx
@@ -5,7 +5,13 @@ import SchoolsAndEmployers from '../../containers/SchoolsAndEmployers';
describe('Schools and employers', () => {
it('Renders without crashing', () => {
- const { container } = render( );
- expect(container).to.exist;
+ const { getByText } = render( );
+ expect(getByText('Schools and employers')).to.exist;
+ });
+
+ it('Renders with Search by name as default tab', () => {
+ const { getByRole } = render( );
+ const nameTab = getByRole('tab', { name: 'Search by name' });
+ expect(nameTab.getAttribute('aria-selected')).to.equal('true');
});
});
diff --git a/src/applications/gi/updated-gi/tests/containers/SearchByProgram.unit.spec.jsx b/src/applications/gi/updated-gi/tests/containers/SearchByProgram.unit.spec.jsx
new file mode 100644
index 000000000000..34d2f32dedb8
--- /dev/null
+++ b/src/applications/gi/updated-gi/tests/containers/SearchByProgram.unit.spec.jsx
@@ -0,0 +1,105 @@
+import React from 'react';
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+import { expect } from 'chai';
+import { render } from '@testing-library/react';
+import configureStore from 'redux-mock-store';
+import { $$ } from 'platform/forms-system/src/js/utilities/ui';
+import userEvent from '@testing-library/user-event';
+import SearchByProgram from '../../containers/SearchByProgram';
+
+const middlewares = [thunk];
+const mockStore = configureStore(middlewares);
+
+describe('Search by program', () => {
+ let store;
+
+ it('Renders inputs and search button', () => {
+ store = mockStore({
+ search: {
+ query: {
+ distance: '25',
+ location: '',
+ streetAddress: { searchString: '' },
+ },
+ },
+ });
+ const { container } = render(
+
+
+ ,
+ );
+ expect($$('va-text-input', container).length).to.equal(2);
+ expect($$('va-select', container).length).to.equal(1);
+ expect($$('va-button', container).length).to.equal(1);
+ });
+
+ it('Shows required input errors', () => {
+ store = mockStore({
+ search: {
+ query: {
+ distance: '',
+ location: '',
+ streetAddress: { searchString: '' },
+ },
+ },
+ });
+ const { container } = render(
+
+
+ ,
+ );
+ userEvent.click(container.getElementsByTagName('va-button')[0]);
+ expect(container.querySelectorAll('va-text-input[error]').length).to.equal(
+ 2,
+ );
+ expect(container.querySelectorAll('va-select[error]').length).to.equal(1);
+ });
+
+ it('Shows failed attempt to locate user - user not sharing location', () => {
+ store = mockStore({
+ search: {
+ query: {
+ distance: '25',
+ location: '',
+ streetAddress: { searchString: '' },
+ },
+ },
+ });
+ delete global.navigator.geolocation;
+ const { getByText } = render(
+
+
+ ,
+ );
+ userEvent.click(getByText('Use my location'));
+ expect(
+ getByText(
+ 'Sorry, something went wrong when trying to find your location. Please make sure location sharing is enabled and try again.',
+ ),
+ ).to.exist;
+ });
+
+ it('Fills user location if location found', () => {
+ store = mockStore({
+ search: {
+ query: {
+ distance: '25',
+ location: '',
+ streetAddress: {
+ searchString: '1313 Disneyland Dr Anaheim, CA 92802',
+ },
+ },
+ },
+ });
+ const { container } = render(
+
+
+ ,
+ );
+ userEvent.click(container.getElementsByTagName('va-button')[0]);
+ expect(container.querySelectorAll('va-text-input[error]').length).to.equal(
+ 1,
+ );
+ });
+});
diff --git a/src/applications/gi/updated-gi/tests/e2e/updated_gi_homepage.cypress.spec.js b/src/applications/gi/updated-gi/tests/e2e/updated_gi_homepage.cypress.spec.js
index 25390569cdf9..411a658e31ab 100644
--- a/src/applications/gi/updated-gi/tests/e2e/updated_gi_homepage.cypress.spec.js
+++ b/src/applications/gi/updated-gi/tests/e2e/updated_gi_homepage.cypress.spec.js
@@ -20,4 +20,10 @@ describe('go bill CT new homepage', () => {
'Discover how your GI Bill benefits can support your education.',
);
});
+ it('should direct to the schools and employers search tabs', () => {
+ cy.injectAxeThenAxeCheck();
+ cy.contains('.comparison-tool-link', 'Schools and employers').click();
+ cy.url().should('contain', '/schools-and-employers');
+ cy.injectAxeThenAxeCheck();
+ });
});
From c5a33b2ee12864a6d1296faa2e5efae1009652ef Mon Sep 17 00:00:00 2001
From: Vitalii Menshutin
<133991282+vmenshutin-bylight@users.noreply.github.com>
Date: Fri, 17 Jan 2025 14:16:14 -0500
Subject: [PATCH 28/36] MHV-66032: Print or Download dropdown working
incorrectly - fixed (#34151)
---
.../components/shared/PrintDownload.jsx | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/applications/mhv-medications/components/shared/PrintDownload.jsx b/src/applications/mhv-medications/components/shared/PrintDownload.jsx
index 31c8d34af27c..9f5e2a01a9d7 100644
--- a/src/applications/mhv-medications/components/shared/PrintDownload.jsx
+++ b/src/applications/mhv-medications/components/shared/PrintDownload.jsx
@@ -62,16 +62,16 @@ const PrintDownload = props => {
document.addEventListener('mousedown', closeMenu);
const handleUserKeyPress = e => {
- const NUM_OF_DROPDOWN_OPTIONS = 4;
+ const NUM_OF_DROPDOWN_OPTIONS = list ? 4 : 3;
if (printIndex > 0 && e.keyCode === 38) {
// If user pressed up arrow
e.preventDefault();
- document.getElementById(`printButton-${printIndex - 2}`).focus();
+ document.getElementById(`printButton-${printIndex - 1}`)?.focus();
setPrintIndex(printIndex - 1);
- } else if (printIndex < NUM_OF_DROPDOWN_OPTIONS && e.keyCode === 40) {
+ } else if (printIndex < NUM_OF_DROPDOWN_OPTIONS - 1 && e.keyCode === 40) {
// If user pressed down arrow
e.preventDefault();
- document.getElementById(`printButton-${printIndex}`).focus();
+ document.getElementById(`printButton-${printIndex + 1}`)?.focus();
setPrintIndex(printIndex + 1);
} else if (e.keyCode === 27) {
// If user pressed escape
@@ -80,7 +80,7 @@ const PrintDownload = props => {
};
const handleFocus = () => {
// Reset printIndex to 0 every time the element receives focus
- setPrintIndex(0);
+ setPrintIndex(-1);
};
return (
@@ -135,7 +135,6 @@ const PrintDownload = props => {
role="none"
onKeyDown={handleUserKeyPress}
ref={containerEl}
- onFocus={handleFocus}
>
{
data-testid="print-records-button"
aria-expanded={menuOpen}
ref={toggleButton}
+ onFocus={handleFocus}
>
Print or download
{
list ? pageType.LIST : pageType.DETAILS
}`}
className="vads-u-padding-x--2 print-download-btn-min-height"
- id="printButton-2"
+ id={`printButton-${list ? '2' : '1'}`}
type="button"
data-testid="download-pdf-button"
onClick={() => handleDownload(DOWNLOAD_FORMAT.PDF)}
@@ -212,7 +212,7 @@ const PrintDownload = props => {
list ? pageType.LIST : pageType.DETAILS
}`}
className="vads-u-padding-x--2 print-download-btn-min-height"
- id="printButton-3"
+ id={`printButton-${list ? '3' : '2'}`}
data-testid="download-txt-button"
onClick={() => handleDownload(DOWNLOAD_FORMAT.TXT)}
>
From 1f7f47f03828d8939eb36a645ad7696ccf9f88c6 Mon Sep 17 00:00:00 2001
From: Mike Moyer <87040148+mmoyer-va@users.noreply.github.com>
Date: Fri, 17 Jan 2025 14:28:32 -0500
Subject: [PATCH 29/36] MHV-66506 Used a better CVIX example for unit test
(#34152)
---
.../tests/util/radiologyUtil.unit.spec.js | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/src/applications/mhv-medical-records/tests/util/radiologyUtil.unit.spec.js b/src/applications/mhv-medical-records/tests/util/radiologyUtil.unit.spec.js
index 11bcca45e1f0..d38b96a02010 100644
--- a/src/applications/mhv-medical-records/tests/util/radiologyUtil.unit.spec.js
+++ b/src/applications/mhv-medical-records/tests/util/radiologyUtil.unit.spec.js
@@ -11,15 +11,17 @@ import {
describe('parseRadiologyReport', () => {
it('parses a radiology report', () => {
const report =
- "1^KNEE 4 OR MORE VIEWS (LEFT)^PATIENTLAST,JOHN\r\nGroup ID# 2487450\r\n_______________________________________________________________________________\r\nPATIENTLAST,JOHN 521-45-2884 DOB-DEC 25, 1934 M \r\nExm Date: APR 04, 2024@17:03\r\nReq Phys: PHYSLASTNAME,BOB Pat Loc: NHM/CARDIOLOGY/CASEY (Req'g Lo\r\n Img Loc: WOR/X-RAY\r\n Service: Unknown\r\n\r\n WORCESTER CBOC\r\n WORCESTER, MA 01605\r\n \r\n \r\n\r\n(Case 93 CALLED F) KNEE 4 OR MORE VIEWS (LEFT) (RAD Detailed) CPT:73564\r\n Reason for Study: Test data number2 Todd\r\n\r\n Clinical History:\r\n\r\n Report Status: Verified Date Reported: APR 05, 2024\r\n Date Verified: APR 05, 2024\r\n Verifier E-Sig:\r\n\r\n Report:\r\n For providers and interpreters, identification of normal bony\r\n anatomical landmarks is important. On the AP view the adductor\r\n tubercle, the site of the attachment of the adductor magnus\r\n tendon, can be seen as a bony protrusion just above the medi al\r\n border of the medial femoral condyle and a groove in the lateral\r\n profile of the lateral femoral condyle is formed by the popliteus\r\n sulcus [4]. \r\n\r\n Impression:\r\n 1. Degenerative arthritis of left knee which has shown\r\n progression since 1980. 2. Advanced degenerative changes of\r\n right knee with evidence of previous patellectomy. Not much\r\n change is seen since the last exam of 6-21-89. \r\n\r\n Primary Diagnostic Code: MINOR ABNORMALITY\r\n\r\nPrimary Interpreting Staff:\r\n JANE J LASTNAME, RADIOLOGIST\r\n Verified by transcriptionist for JANE J LASTNAME\r\n/DP\r\n\r\n\r\n** END REPORT Nov 06, 2024 11:04:23 am **";
+ "1^CT THORAX W/CONT^PATIENTLAST,JOHN\r\nGroup ID# 2487448\r\n_______________________________________________________________________________\r\nPATIENTLAST,JOHN 521-45-2884 DOB-DEC 25, 1934 M \r\nExm Date: APR 04, 2024@16:54\r\nReq Phys: PHYSLAST,MARK Pat Loc: NHM/CARDIOLOGY/CASEY (Req'g Lo\r\n Img Loc: NHM/CT\r\n Service: Unknown\r\n\r\n IPO TEST 2\r\n , \r\n \r\n \r\n\r\n(Case 92 CALLED F) CT THORAX W/CONT (CT Detailed) CPT:71260\r\n Reason for Study: Test data Todd\r\n\r\n Clinical History:\r\n\r\n Additional Clinical History:\r\n Test data for Todd CT 16 bit \r\n\r\n Report Status: Verified Date Reported: APR 05, 2024\r\n Date Verified: APR 05, 2024\r\n Verifier E-Sig:\r\n\r\n Report:\r\n CT SCAN OF THE ABDOMEN: Bolus injection of 150 cc of non-ionic\r\n contrast material was administered during the scan. \r\n \r\n The liver, spleen, pancreas and kidneys appear normal. Both the\r\n colon and small bowel appear normal throughout the abdominal\r\n cavity. There is no evidence of adenopathy. \r\n \r\n The lung bases appear clear. \r\n\r\n Impression:\r\n IMPRESSION: Normal CT scan of the abdomen. \r\n\r\n Primary Diagnostic Code: NORMAL\r\n\r\nPrimary Interpreting Staff:\r\n JANE J INTERPRETER, RADIOLOGIST\r\n Verified by transcriptionist for JANE J INTERPRETER\r\n/DP\r\n\r\n\r\n** END REPORT Nov 06, 2024 11:04:23 am **";
const parsedReport = parseRadiologyReport(report);
- expect(parsedReport['Exm Date']).to.eq('APR 04, 2024@17:03');
- expect(parsedReport['Req Phys']).to.eq('PHYSLASTNAME,BOB');
- expect(parsedReport['Reason for Study']).to.eq('Test data number2 Todd');
+ expect(parsedReport['Exm Date']).to.eq('APR 04, 2024@16:54');
+ expect(parsedReport['Req Phys']).to.eq('PHYSLAST,MARK');
+ expect(parsedReport['Reason for Study']).to.eq('Test data Todd');
expect(parsedReport['Report Status']).to.eq('Verified');
- expect(parsedReport['Clinical History']).to.eq('');
- expect(parsedReport.Report.length).to.eq(391);
- expect(parsedReport.Impression.length).to.eq(266);
+ expect(parsedReport['Clinical History']).to.eq(
+ 'Additional Clinical History:\nTest data for Todd CT 16 bit',
+ );
+ expect(parsedReport.Report.length).to.eq(315);
+ expect(parsedReport.Impression.length).to.eq(75);
});
});
From 2f83338f2fab38db0f8be5ac550a58fbe3f5bb5b Mon Sep 17 00:00:00 2001
From: Jennie McGibney <54327023+jenniemc@users.noreply.github.com>
Date: Fri, 17 Jan 2025 11:30:11 -0800
Subject: [PATCH 30/36] Fix flaky unit test (#34145)
* Enable test
* fix flaky test
* remove comment
---
.../components/ContactInfoPage.unit.spec.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js b/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js
index ebd725a35a8f..fd9fce9fee5c 100644
--- a/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js
+++ b/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js
@@ -11,8 +11,7 @@ import {
import { FACILITY_TYPES, FLOW_TYPES } from '../../utils/constants';
describe('VAOS Page: ContactInfoPage', () => {
- // Flaky test: https://github.com/department-of-veterans-affairs/va.gov-team/issues/82968
- it.skip('should accept email, phone, and preferred time and continue', async () => {
+ it('should accept email, phone, and preferred time and continue', async () => {
const store = createTestStore({
user: {
profile: {
@@ -53,10 +52,11 @@ describe('VAOS Page: ContactInfoPage', () => {
/You can update your contact information for most of your benefits and services in your VA.gov profile./,
),
).to.be.ok;
- const button = await screen.findByText(/^Continue/);
+ userEvent.click(screen.getByText(/^Continue/));
+ await waitFor(() => {
+ expect(screen.history.push.called).to.be.true;
+ });
- userEvent.click(button);
- expect(screen.history.push.called).to.be.true;
expect(window.dataLayer).to.deep.include({
event: 'vaos-contact-info-email-not-populated',
});
From c4db56d12a5ba79aef77370e4743c71c2152c2fc Mon Sep 17 00:00:00 2001
From: Rob Garrison <136959+Mottie@users.noreply.github.com>
Date: Fri, 17 Jan 2025 13:33:40 -0600
Subject: [PATCH 31/36] HLR & NOD | Clean up text around link (#34124)
* Remove period after link
* Remove content before link
---
src/applications/appeals/10182/content/issueSummary.js | 2 --
src/applications/appeals/996/content/issueSummary.jsx | 1 -
src/applications/appeals/testing/hlr/content/issueSummary.jsx | 2 --
3 files changed, 5 deletions(-)
diff --git a/src/applications/appeals/10182/content/issueSummary.js b/src/applications/appeals/10182/content/issueSummary.js
index f14ee40cb6be..fcf7b1c6fec7 100644
--- a/src/applications/appeals/10182/content/issueSummary.js
+++ b/src/applications/appeals/10182/content/issueSummary.js
@@ -17,7 +17,6 @@ export const SummaryTitle = ({ formData }) => {
{ShowIssuesList({ issues })}
- If an issue is missing,{' '}
{
>
Go back to add more issues
- .
>
);
diff --git a/src/applications/appeals/996/content/issueSummary.jsx b/src/applications/appeals/996/content/issueSummary.jsx
index 6c5a5f83592b..af64b00ef57e 100644
--- a/src/applications/appeals/996/content/issueSummary.jsx
+++ b/src/applications/appeals/996/content/issueSummary.jsx
@@ -25,7 +25,6 @@ export const SummaryTitle = ({ formData }) => {
>
Go back to add more issues
- .
>
);
diff --git a/src/applications/appeals/testing/hlr/content/issueSummary.jsx b/src/applications/appeals/testing/hlr/content/issueSummary.jsx
index 437180065e69..4732f04c0b54 100644
--- a/src/applications/appeals/testing/hlr/content/issueSummary.jsx
+++ b/src/applications/appeals/testing/hlr/content/issueSummary.jsx
@@ -17,7 +17,6 @@ export const SummaryTitle = ({ formData }) => {
{ShowIssuesList({ issues })}
- If an issue is missing, please{' '}
{
>
Go back to add more issues
- .
>
);
From fa7a5350f1870606ad94e908f2d77e8c5e4c1772 Mon Sep 17 00:00:00 2001
From: Oren Mittman
Date: Fri, 17 Jan 2025 15:38:48 -0500
Subject: [PATCH 32/36] 4: [ART] Tweak routes helper fn (#34040)
---
.../accredited-representative-portal/routes.jsx | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/applications/accredited-representative-portal/routes.jsx b/src/applications/accredited-representative-portal/routes.jsx
index 40776d591497..add21a91ee08 100644
--- a/src/applications/accredited-representative-portal/routes.jsx
+++ b/src/applications/accredited-representative-portal/routes.jsx
@@ -11,12 +11,9 @@ import ErrorBoundary from './components/ErrorBoundary';
import { userPromise } from './utilities/auth';
import { getSignInUrl } from './utilities/constants';
-const transformRoutes = (transform, route) => {
- transform(route);
-
- const children = route.children || [];
- children.forEach(childRoute => transformRoutes(transform, childRoute));
-
+const forEachRoute = (callbackFn, route) => {
+ callbackFn(route);
+ route.children?.forEach(childRoute => forEachRoute(callbackFn, childRoute));
return route;
};
@@ -70,7 +67,7 @@ const routes = [
index: true,
element: ,
},
- transformRoutes(addSignInRedirection, {
+ forEachRoute(addSignInRedirection, {
element: ,
children: [
{
From ec68b7b965befd93942e3b55a2715923263433f8 Mon Sep 17 00:00:00 2001
From: Oren Mittman
Date: Fri, 17 Jan 2025 15:39:15 -0500
Subject: [PATCH 33/36] 4: Art/create decision action.tweak (#34041)
---
.../containers/POARequestDetailsPage.jsx | 6 ++++--
.../accredited-representative-portal/utilities/api.js | 5 ++---
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx b/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
index 8303c3439437..23a989bbf55b 100644
--- a/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
+++ b/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
@@ -391,11 +391,13 @@ POARequestDetailsPage.loader = ({ params, request }) => {
});
};
-POARequestDetailsPage.createDecisionAction = async ({ request, params }) => {
+POARequestDetailsPage.createDecisionAction = async ({ params, request }) => {
const key = (await request.formData()).get('decision');
const decision = DECISION_OPTIONS[key];
- await api.createPOARequestDecision(params.id, decision);
+ await api.createPOARequestDecision(params.id, decision, {
+ signal: request.signal,
+ });
return redirect('..');
};
diff --git a/src/applications/accredited-representative-portal/utilities/api.js b/src/applications/accredited-representative-portal/utilities/api.js
index d3eb475bf8c8..287922b984b6 100644
--- a/src/applications/accredited-representative-portal/utilities/api.js
+++ b/src/applications/accredited-representative-portal/utilities/api.js
@@ -4,9 +4,8 @@ const API_VERSION = 'accredited_representative_portal/v0';
/**
* This abstraction was introduced to let us pass fetch options to API methods
- * so that we could forward an abort signal from route loaders. This
- * abstraction only accomodates inner functions that don't have default
- * parameters.
+ * so that we could forward an abort signal from route loaders. This abstraction
+ * only accomodates inner functions that don't have default parameters.
*
* Not every API method needs to be defined using this abstraction. Furthermore,
* it is okay to refactor this abstraction, or even just unwind it altogether,
From 8a2bd0bcf5189f33889929542b0f19f0721c62e3 Mon Sep 17 00:00:00 2001
From: Oren Mittman
Date: Fri, 17 Jan 2025 15:39:41 -0500
Subject: [PATCH 34/36] 4: [ART] poa request search params constant (#34042)
---
.../containers/POARequestSearchPage.jsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx b/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
index 83e73e6fd6ff..32d28677bb03 100644
--- a/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
+++ b/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
@@ -9,6 +9,10 @@ import {
import api from '../utilities/api';
import POARequestCard from '../components/POARequestCard';
+const SEARCH_PARAMS = {
+ STATUS: 'status',
+};
+
const STATUSES = {
PENDING: 'pending',
COMPLETED: 'completed',
@@ -130,10 +134,10 @@ const POARequestSearchPage = () => {
POARequestSearchPage.loader = ({ request }) => {
const { searchParams } = new URL(request.url);
- const status = searchParams.get('status');
+ const status = searchParams.get(SEARCH_PARAMS.STATUS);
if (!Object.values(STATUSES).includes(status)) {
- searchParams.set('status', STATUSES.PENDING);
+ searchParams.set(SEARCH_PARAMS.STATUS, STATUSES.PENDING);
throw redirect(`?${searchParams}`);
}
From cf1bba70e3b71b277f534c7f6ac569b0ca5ef159 Mon Sep 17 00:00:00 2001
From: Sean Midgley <57480791+Midge-dev@users.noreply.github.com>
Date: Fri, 17 Jan 2025 13:00:04 -0800
Subject: [PATCH 35/36] Dependents | 81671: QA fixes (1st round) (#34064)
* issue #8
* Remove deceased location view
* everything else
* tests round 1
* tests round 2
* tests round 3
* tests final
* clean up
---
.../currentMarriageInformationPartThree.js | 17 ++-
.../spouseMarriageHistoryArrayPages.js | 84 +++++++----
.../veteranMarriageHistoryArrayPages.js | 84 +++++++----
.../chapters/report-add-child/placeOfBirth.js | 23 +--
.../deceasedDependentArrayPages.js | 66 ++++++---
.../formerSpouseInformationPartTwo.js | 19 ++-
src/applications/686c-674/config/helpers.js | 12 ++
.../currentMarriageInformation.unit.spec.jsx | 6 +-
...ouseMarriageHistoryArrayPages.unit.spec.js | 131 ++++++++++++++++-
...eranMarriageHistoryArrayPages.unit.spec.js | 133 +++++++++++++++++-
.../deceasedDependentsArray.unit.spec.js | 71 +++++-----
.../e2e/686C-674-ancilliary.cypress.spec.js | 14 +-
.../tests/e2e/686C-674.cypress.spec.js | 14 +-
.../e2e/fixtures/spouse-child-all-fields.json | 5 +-
.../e2e/fixtures/spouse-report-divorce.json | 8 +-
15 files changed, 527 insertions(+), 160 deletions(-)
diff --git a/src/applications/686c-674/config/chapters/report-add-a-spouse/current-marriage-information/currentMarriageInformationPartThree.js b/src/applications/686c-674/config/chapters/report-add-a-spouse/current-marriage-information/currentMarriageInformationPartThree.js
index 65df2095f9cd..fbc6891bda04 100644
--- a/src/applications/686c-674/config/chapters/report-add-a-spouse/current-marriage-information/currentMarriageInformationPartThree.js
+++ b/src/applications/686c-674/config/chapters/report-add-a-spouse/current-marriage-information/currentMarriageInformationPartThree.js
@@ -28,17 +28,28 @@ export const uiSchema = {
},
'ui:webComponentField': VaTextInputField,
},
-
state: {
'ui:title': 'State',
'ui:webComponentField': VaSelectField,
+ 'ui:errorMessages': {
+ required: 'Select a state',
+ },
'ui:required': formData =>
!formData?.currentMarriageInformation?.outsideUsa,
+ 'ui:options': {
+ hideIf: formData => formData?.currentMarriageInformation?.outsideUsa,
+ },
+ },
+ country: {
+ 'ui:title': 'Country',
+ 'ui:webComponentField': VaSelectField,
'ui:errorMessages': {
- required: 'Select a state',
+ required: 'Select a country',
},
+ 'ui:required': formData =>
+ formData?.currentMarriageInformation?.outsideUsa,
'ui:options': {
- hideIf: form => form?.currentMarriageInformation?.outsideUsa,
+ hideIf: formData => !formData?.currentMarriageInformation?.outsideUsa,
},
},
},
diff --git a/src/applications/686c-674/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.js b/src/applications/686c-674/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.js
index fe9e4d4e0308..4e567b06c95a 100644
--- a/src/applications/686c-674/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.js
+++ b/src/applications/686c-674/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.js
@@ -47,8 +47,12 @@ export const spouseMarriageHistoryOptions = {
!item?.endLocation?.location?.city ||
(item?.startLocation?.outsideUsa === false &&
!item?.startLocation?.location?.state) ||
+ (item?.startLocation?.outsideUsa === true &&
+ !item?.startLocation?.location?.country) ||
(item?.endLocation?.outsideUsa === false &&
- !item?.endLocation?.location?.state),
+ !item?.endLocation?.location?.state) ||
+ (item?.endLocation?.outsideUsa === true &&
+ !item?.endLocation?.location?.country),
maxItems: 20,
text: {
summaryTitle: 'Review your spouse’s former marriages',
@@ -195,9 +199,7 @@ export const formerMarriageEndDatePage = {
export const formerMarriageStartLocationPage = {
uiSchema: {
- ...arrayBuilderItemSubsequentPageTitleUI(() => {
- return 'Spouse’s former marriage';
- }),
+ ...arrayBuilderItemSubsequentPageTitleUI(() => 'Spouse’s former marriage'),
startLocation: {
'ui:title': 'Where did they get married?',
'ui:options': {
@@ -220,24 +222,35 @@ export const formerMarriageStartLocationPage = {
state: {
'ui:title': 'State',
'ui:webComponentField': VaSelectField,
- 'ui:required': (formData, index) => {
- // See above comment
- const isEditMode = formData?.startLocation?.outsideUsa;
- const isAddMode =
- formData?.spouseMarriageHistory?.[index]?.startLocation
- ?.outsideUsa;
-
- return !isAddMode && !isEditMode;
+ 'ui:errorMessages': {
+ required: 'Select a state',
},
+ 'ui:required': (formData, index) =>
+ !(
+ formData?.spouseMarriageHistory?.[index]?.startLocation
+ ?.outsideUsa || formData?.startLocation?.outsideUsa
+ ),
'ui:options': {
hideIf: (formData, index) =>
- // See above comment
- formData?.startLocation?.outsideUsa ||
formData?.spouseMarriageHistory?.[index]?.startLocation
- ?.outsideUsa,
+ ?.outsideUsa || formData?.startLocation?.outsideUsa,
},
+ },
+ country: {
+ 'ui:title': 'Country',
+ 'ui:webComponentField': VaSelectField,
'ui:errorMessages': {
- required: 'Select a state',
+ required: 'Select a country',
+ },
+ 'ui:required': (formData, index) =>
+ formData?.spouseMarriageHistory?.[index]?.startLocation
+ ?.outsideUsa || formData?.startLocation?.outsideUsa,
+ 'ui:options': {
+ hideIf: (formData, index) =>
+ !(
+ formData?.spouseMarriageHistory?.[index]?.startLocation
+ ?.outsideUsa || formData?.startLocation?.outsideUsa
+ ),
},
},
},
@@ -253,9 +266,7 @@ export const formerMarriageStartLocationPage = {
export const formerMarriageEndLocationPage = {
uiSchema: {
- ...arrayBuilderItemSubsequentPageTitleUI(() => {
- return 'Spouse’s former marriage';
- }),
+ ...arrayBuilderItemSubsequentPageTitleUI(() => 'Spouse’s former marriage'),
endLocation: {
'ui:title': 'Where did the marriage end?',
'ui:options': {
@@ -281,22 +292,35 @@ export const formerMarriageEndLocationPage = {
state: {
'ui:title': 'State',
'ui:webComponentField': VaSelectField,
- 'ui:required': (formData, index) => {
- // See above comment
- const isEditMode = formData?.endLocation?.outsideUsa;
- const isAddMode =
- formData?.spouseMarriageHistory?.[index]?.endLocation?.outsideUsa;
-
- return !isAddMode && !isEditMode;
+ 'ui:errorMessages': {
+ required: 'Select a state',
},
+ 'ui:required': (formData, index) =>
+ !(
+ formData?.spouseMarriageHistory?.[index]?.endLocation
+ ?.outsideUsa || formData?.endLocation?.outsideUsa
+ ),
'ui:options': {
hideIf: (formData, index) =>
- // See above comment
- formData?.endLocation?.outsideUsa ||
- formData?.spouseMarriageHistory?.[index]?.endLocation?.outsideUsa,
+ formData?.spouseMarriageHistory?.[index]?.endLocation
+ ?.outsideUsa || formData?.endLocation?.outsideUsa,
},
+ },
+ country: {
+ 'ui:title': 'Country',
+ 'ui:webComponentField': VaSelectField,
'ui:errorMessages': {
- required: 'Select a state',
+ required: 'Select a country',
+ },
+ 'ui:required': (formData, index) =>
+ formData?.spouseMarriageHistory?.[index]?.endLocation?.outsideUsa ||
+ formData?.endLocation?.outsideUsa,
+ 'ui:options': {
+ hideIf: (formData, index) =>
+ !(
+ formData?.spouseMarriageHistory?.[index]?.endLocation
+ ?.outsideUsa || formData?.endLocation?.outsideUsa
+ ),
},
},
},
diff --git a/src/applications/686c-674/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.js b/src/applications/686c-674/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.js
index b13a06bb9a34..19c33526a414 100644
--- a/src/applications/686c-674/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.js
+++ b/src/applications/686c-674/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.js
@@ -47,8 +47,12 @@ export const veteranMarriageHistoryOptions = {
!item?.endLocation?.location?.city ||
(item?.startLocation?.outsideUsa === false &&
!item?.startLocation?.location?.state) ||
+ (item?.startLocation?.outsideUsa === true &&
+ !item?.startLocation?.location?.country) ||
(item?.endLocation?.outsideUsa === false &&
- !item?.endLocation?.location?.state),
+ !item?.endLocation?.location?.state) ||
+ (item?.endLocation?.outsideUsa === true &&
+ !item?.endLocation?.location?.country),
maxItems: 20,
text: {
getItemName: item =>
@@ -194,9 +198,7 @@ export const vetFormerMarriageEndDatePage = {
export const vetFormerMarriageStartLocationPage = {
uiSchema: {
- ...arrayBuilderItemSubsequentPageTitleUI(() => {
- return 'Your former marriage';
- }),
+ ...arrayBuilderItemSubsequentPageTitleUI(() => 'Your former marriage'),
startLocation: {
'ui:title': 'Where did they get married?',
'ui:options': {
@@ -219,24 +221,35 @@ export const vetFormerMarriageStartLocationPage = {
state: {
'ui:title': 'State',
'ui:webComponentField': VaSelectField,
- 'ui:required': (formData, index) => {
- // See above comment
- const isEditMode = formData?.startLocation?.outsideUsa;
- const isAddMode =
- formData?.veteranMarriageHistory?.[index]?.startLocation
- ?.outsideUsa;
-
- return !isAddMode && !isEditMode;
+ 'ui:errorMessages': {
+ required: 'Select a state',
},
+ 'ui:required': (formData, index) =>
+ !(
+ formData?.veteranMarriageHistory?.[index]?.startLocation
+ ?.outsideUsa || formData?.startLocation?.outsideUsa
+ ),
'ui:options': {
hideIf: (formData, index) =>
- // See above comment
- formData?.startLocation?.outsideUsa ||
formData?.veteranMarriageHistory?.[index]?.startLocation
- ?.outsideUsa,
+ ?.outsideUsa || formData?.startLocation?.outsideUsa,
},
+ },
+ country: {
+ 'ui:title': 'Country',
+ 'ui:webComponentField': VaSelectField,
'ui:errorMessages': {
- required: 'Select a state',
+ required: 'Select a country',
+ },
+ 'ui:required': (formData, index) =>
+ formData?.veteranMarriageHistory?.[index]?.startLocation
+ ?.outsideUsa || formData?.startLocation?.outsideUsa,
+ 'ui:options': {
+ hideIf: (formData, index) =>
+ !(
+ formData?.veteranMarriageHistory?.[index]?.startLocation
+ ?.outsideUsa || formData?.startLocation?.outsideUsa
+ ),
},
},
},
@@ -252,9 +265,7 @@ export const vetFormerMarriageStartLocationPage = {
export const vetFormerMarriageEndLocationPage = {
uiSchema: {
- ...arrayBuilderItemSubsequentPageTitleUI(() => {
- return 'Your former marriage';
- }),
+ ...arrayBuilderItemSubsequentPageTitleUI(() => 'Your former marriage'),
endLocation: {
'ui:title': 'Where did the marriage end?',
'ui:options': {
@@ -280,24 +291,35 @@ export const vetFormerMarriageEndLocationPage = {
state: {
'ui:title': 'State',
'ui:webComponentField': VaSelectField,
- 'ui:required': (formData, index) => {
- // See above comment
- const isEditMode = formData?.endLocation?.outsideUsa;
- const isAddMode =
- formData?.veteranMarriageHistory?.[index]?.endLocation
- ?.outsideUsa;
-
- return !isAddMode && !isEditMode;
+ 'ui:errorMessages': {
+ required: 'Select a state',
},
+ 'ui:required': (formData, index) =>
+ !(
+ formData?.veteranMarriageHistory?.[index]?.endLocation
+ ?.outsideUsa || formData?.endLocation?.outsideUsa
+ ),
'ui:options': {
hideIf: (formData, index) =>
- // See above comment
- formData?.endLocation?.outsideUsa ||
formData?.veteranMarriageHistory?.[index]?.endLocation
- ?.outsideUsa,
+ ?.outsideUsa || formData?.endLocation?.outsideUsa,
},
+ },
+ country: {
+ 'ui:title': 'Country',
+ 'ui:webComponentField': VaSelectField,
'ui:errorMessages': {
- required: 'Select a state',
+ required: 'Select a country',
+ },
+ 'ui:required': (formData, index) =>
+ formData?.veteranMarriageHistory?.[index]?.endLocation
+ ?.outsideUsa || formData?.endLocation?.outsideUsa,
+ 'ui:options': {
+ hideIf: (formData, index) =>
+ !(
+ formData?.veteranMarriageHistory?.[index]?.endLocation
+ ?.outsideUsa || formData?.endLocation?.outsideUsa
+ ),
},
},
},
diff --git a/src/applications/686c-674/config/chapters/report-add-child/placeOfBirth.js b/src/applications/686c-674/config/chapters/report-add-child/placeOfBirth.js
index 0242dd5bd46c..6a8912d5e449 100644
--- a/src/applications/686c-674/config/chapters/report-add-child/placeOfBirth.js
+++ b/src/applications/686c-674/config/chapters/report-add-child/placeOfBirth.js
@@ -23,27 +23,32 @@ export const placeOfBirth = {
state: {
'ui:title': 'State',
'ui:webComponentField': VaSelectField,
- 'ui:required': formData => {
- return formData?.birthLocation?.outsideUsa;
- },
'ui:errorMessages': {
required: 'Select a state',
},
'ui:options': {
- hideIf: formData => {
- return formData?.birthLocation?.outsideUsa;
- },
+ hideIf: formData => formData?.birthLocation?.outsideUsa,
+ },
+ 'ui:required': formData => !formData?.birthLocation?.outsideUsa,
+ },
+ country: {
+ 'ui:title': 'Country',
+ 'ui:webComponentField': VaSelectField,
+ 'ui:errorMessages': {
+ required: 'Select a country',
+ },
+ 'ui:options': {
+ hideIf: formData => !formData?.birthLocation?.outsideUsa,
},
+ 'ui:required': formData => formData?.birthLocation?.outsideUsa,
},
postalCode: {
'ui:title': 'Postal Code',
'ui:webComponentField': VaTextInputField,
- 'ui:required': formData => {
- return !formData?.birthLocation?.outsideUsa;
- },
'ui:errorMessages': {
required: 'Enter a postal code',
},
+ 'ui:required': formData => !formData?.birthLocation?.outsideUsa,
},
},
},
diff --git a/src/applications/686c-674/config/chapters/report-dependent-death/deceasedDependentArrayPages.js b/src/applications/686c-674/config/chapters/report-dependent-death/deceasedDependentArrayPages.js
index 6acf21b27254..9f459c88e350 100644
--- a/src/applications/686c-674/config/chapters/report-dependent-death/deceasedDependentArrayPages.js
+++ b/src/applications/686c-674/config/chapters/report-dependent-death/deceasedDependentArrayPages.js
@@ -1,4 +1,3 @@
-import { format, parseISO } from 'date-fns';
import { capitalize } from 'lodash';
import {
titleUI,
@@ -45,22 +44,30 @@ export const deceasedDependentOptions = {
!item?.dependentDeathLocation?.location?.city ||
!item?.dependentDeathDate ||
(item?.dependentDeathLocation?.outsideUsa === false &&
- !item?.dependentDeathLocation?.location?.state),
+ !item?.dependentDeathLocation?.location?.state) ||
+ (item?.dependentDeathLocation?.outsideUsa === true &&
+ !item?.dependentDeathLocation?.location?.country),
maxItems: 20,
text: {
- getItemName: item =>
- `${capitalize(item.fullName?.first) || ''} ${capitalize(
- item.fullName?.last,
- ) || ''}`,
- cardDescription: item => {
- const birthDate = item?.birthDate
- ? format(parseISO(item.birthDate), 'MM/dd/yyyy')
- : 'Unknown';
- const dependentDeathDate = item?.dependentDeathDate
- ? format(parseISO(item.dependentDeathDate), 'MM/dd/yyyy')
- : 'Unknown';
+ getItemName: item => {
+ const dependentType = item?.dependentType;
+
+ if (!dependentType) {
+ return 'Unknown';
+ }
+
+ const label = relationshipLabels[dependentType];
+
+ if (label) {
+ return label;
+ }
- return `${birthDate} - ${dependentDeathDate}`;
+ return 'Unknown'; // Default if `dependentType` is null for some reason
+ },
+ cardDescription: item => {
+ const firstName = capitalize(item?.fullName?.first || '');
+ const lastName = capitalize(item?.fullName?.last || '');
+ return `${firstName} ${lastName}`.trim();
},
summaryTitle: 'Review your dependents who have died',
},
@@ -233,17 +240,32 @@ export const deceasedDependentLocationOfDeathPage = {
'ui:errorMessages': {
required: 'Enter a state',
},
- 'ui:required': (formData, index) => {
- const isEditMode = formData?.dependentDeathLocation?.outsideUsa;
- const isAddMode =
- formData?.deaths?.[index]?.dependentDeathLocation?.outsideUsa;
-
- return !isAddMode && !isEditMode;
+ 'ui:required': (formData, index) =>
+ !(
+ formData?.deaths?.[index]?.dependentDeathLocation?.outsideUsa ||
+ formData?.dependentDeathLocation?.outsideUsa
+ ),
+ 'ui:options': {
+ hideIf: (formData, index) =>
+ formData?.deaths?.[index]?.dependentDeathLocation?.outsideUsa ||
+ formData?.dependentDeathLocation?.outsideUsa,
+ },
+ },
+ country: {
+ 'ui:title': 'Select a country',
+ 'ui:webComponentField': VaSelectField,
+ 'ui:errorMessages': {
+ required: 'Select a country',
},
+ 'ui:required': (formData, index) =>
+ formData?.deaths?.[index]?.dependentDeathLocation?.outsideUsa ||
+ formData?.dependentDeathLocation?.outsideUsa,
'ui:options': {
hideIf: (formData, index) =>
- formData?.dependentDeathLocation?.outsideUsa ||
- formData?.deaths?.[index]?.dependentDeathLocation?.outsideUsa,
+ !(
+ formData?.deaths?.[index]?.dependentDeathLocation?.outsideUsa ||
+ formData?.dependentDeathLocation?.outsideUsa
+ ),
},
},
},
diff --git a/src/applications/686c-674/config/chapters/report-divorce/former-spouse-information/formerSpouseInformationPartTwo.js b/src/applications/686c-674/config/chapters/report-divorce/former-spouse-information/formerSpouseInformationPartTwo.js
index c34d2bbc2c9a..f0347f478185 100644
--- a/src/applications/686c-674/config/chapters/report-divorce/former-spouse-information/formerSpouseInformationPartTwo.js
+++ b/src/applications/686c-674/config/chapters/report-divorce/former-spouse-information/formerSpouseInformationPartTwo.js
@@ -45,23 +45,36 @@ export const uiSchema = {
'ui:required': () => true,
'ui:autocomplete': 'address-level2',
'ui:errorMessages': {
- required: 'Enter the city where you were married',
+ required: 'Enter the city where this occurred',
},
'ui:webComponentField': VaTextInputField,
},
state: {
'ui:title': 'State',
'ui:webComponentField': VaSelectField,
- 'ui:required': formData =>
- !formData?.reportDivorce?.divorceLocation?.outsideUsa,
'ui:errorMessages': {
required: 'Select a state',
},
+ 'ui:required': formData =>
+ !formData?.reportDivorce?.divorceLocation?.outsideUsa,
'ui:options': {
hideIf: formData =>
formData?.reportDivorce?.divorceLocation?.outsideUsa,
},
},
+ country: {
+ 'ui:title': 'Country',
+ 'ui:webComponentField': VaSelectField,
+ 'ui:errorMessages': {
+ required: 'Select a country',
+ },
+ 'ui:required': formData =>
+ formData?.reportDivorce?.divorceLocation?.outsideUsa,
+ 'ui:options': {
+ hideIf: formData =>
+ !formData?.reportDivorce?.divorceLocation?.outsideUsa,
+ },
+ },
},
},
reasonMarriageEnded: radioUI({
diff --git a/src/applications/686c-674/config/helpers.js b/src/applications/686c-674/config/helpers.js
index a40d6cb055fb..de8d76951df7 100644
--- a/src/applications/686c-674/config/helpers.js
+++ b/src/applications/686c-674/config/helpers.js
@@ -206,6 +206,8 @@ const filteredStates = constants.states.USA.filter(
const STATE_VALUES = filteredStates.map(state => state.value);
const STATE_NAMES = filteredStates.map(state => state.label);
+const COUNTRY_VALUES = constants.countries.map(country => country.value);
+const COUNTRY_NAMES = constants.countries.map(country => country.label);
export const customLocationSchema = {
type: 'object',
@@ -224,6 +226,11 @@ export const customLocationSchema = {
enum: STATE_VALUES,
enumNames: STATE_NAMES,
},
+ country: {
+ type: 'string',
+ enum: COUNTRY_VALUES,
+ enumNames: COUNTRY_NAMES,
+ },
},
},
},
@@ -243,6 +250,11 @@ export const customLocationSchemaStatePostal = {
enum: STATE_VALUES,
enumNames: STATE_NAMES,
},
+ country: {
+ type: 'string',
+ enum: COUNTRY_VALUES,
+ enumNames: COUNTRY_NAMES,
+ },
postalCode: {
type: 'string',
},
diff --git a/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/currentMarriageInformation.unit.spec.jsx b/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/currentMarriageInformation.unit.spec.jsx
index dca1fe7f3903..09918672ddb5 100644
--- a/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/currentMarriageInformation.unit.spec.jsx
+++ b/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/currentMarriageInformation.unit.spec.jsx
@@ -124,7 +124,7 @@ describe('686 current marriage information: Marriage start location', () => {
expect($$('option', container).length).to.equal(59);
});
- it('should render w/o select field if Outside US is checked', () => {
+ it('should render country select field if Outside US is checked', () => {
const { container } = render(
{
expect($$('va-checkbox', container).length).to.equal(1);
expect($$('va-text-input', container).length).to.equal(1);
- expect($$('va-select', container).length).to.equal(0);
- expect($$('option', container).length).to.equal(0);
+ expect($$('va-select', container).length).to.equal(1);
+ expect($$('option', container).length).to.equal(228);
});
});
diff --git a/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.unit.spec.js b/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.unit.spec.js
index 06d3466e43e9..9df40b717478 100644
--- a/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.unit.spec.js
+++ b/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/spouseMarriageHistoryArrayPages.unit.spec.js
@@ -82,7 +82,7 @@ describe('spouseMarriageHistoryOptions', () => {
.be.true;
});
- it('should return false if all required fields are present', () => {
+ it('should return false if all required fields are present (inside USA, with state)', () => {
const completeItem = {
fullName: { first: 'John', last: 'Doe' },
startDate: '1991-02-19',
@@ -101,17 +101,138 @@ describe('spouseMarriageHistoryOptions', () => {
.false;
});
- it('should handle locations outside the USA where state is not required', () => {
- const itemOutsideUsa = {
+ it('should return false if all required fields are present (outside USA, with country)', () => {
+ const completeItemOutsideUsa = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ spouseMarriageHistoryOptions.isItemIncomplete(completeItemOutsideUsa),
+ ).to.be.false;
+ });
+
+ it('should return true if startLocation city is missing', () => {
+ const itemMissingStartLocationCity = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: { location: {}, outsideUsa: true },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ spouseMarriageHistoryOptions.isItemIncomplete(
+ itemMissingStartLocationCity,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if startLocation state is missing (inside USA)', () => {
+ const itemMissingStartLocationState = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: { location: { city: 'Some City' }, outsideUsa: false },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ spouseMarriageHistoryOptions.isItemIncomplete(
+ itemMissingStartLocationState,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if startLocation country is missing (outside USA)', () => {
+ const itemMissingStartLocationCountry = {
fullName: { first: 'Jane', last: 'Smith' },
startDate: '1995-06-15',
endDate: '2005-06-15',
reasonMarriageEnded: 'Annulment',
startLocation: { location: { city: 'Paris' }, outsideUsa: true },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ spouseMarriageHistoryOptions.isItemIncomplete(
+ itemMissingStartLocationCountry,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if endLocation city is missing', () => {
+ const itemMissingEndLocationCity = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
+ endLocation: { location: {}, outsideUsa: true },
+ };
+ expect(
+ spouseMarriageHistoryOptions.isItemIncomplete(
+ itemMissingEndLocationCity,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if endLocation state is missing (inside USA)', () => {
+ const itemMissingEndLocationState = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
+ endLocation: { location: { city: 'Berlin' }, outsideUsa: false },
+ };
+ expect(
+ spouseMarriageHistoryOptions.isItemIncomplete(
+ itemMissingEndLocationState,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if endLocation country is missing (outside USA)', () => {
+ const itemMissingEndLocationCountry = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
endLocation: { location: { city: 'Berlin' }, outsideUsa: true },
};
- expect(spouseMarriageHistoryOptions.isItemIncomplete(itemOutsideUsa)).to
- .be.false;
+ expect(
+ spouseMarriageHistoryOptions.isItemIncomplete(
+ itemMissingEndLocationCountry,
+ ),
+ ).to.be.true;
});
});
diff --git a/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.unit.spec.js b/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.unit.spec.js
index 08cc1e68e5e5..2f683dc950b2 100644
--- a/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.unit.spec.js
+++ b/src/applications/686c-674/tests/config/chapters/report-add-a-spouse/veteranMarriageHistoryArrayPages.unit.spec.js
@@ -71,7 +71,7 @@ describe('veteranMarriageHistoryOptions', () => {
describe('isItemIncomplete', () => {
it('should return true if any required fields are missing', () => {
const incompleteItem = {
- fullName: { first: 'John' },
+ fullName: { first: 'John' }, // Missing last name
startDate: '1991-02-19',
endDate: '2000-02-19',
reasonMarriageEnded: 'Divorce',
@@ -82,7 +82,7 @@ describe('veteranMarriageHistoryOptions', () => {
.be.true;
});
- it('should return false if all required fields are present', () => {
+ it('should return false if all required fields are present (inside USA, with state)', () => {
const completeItem = {
fullName: { first: 'John', last: 'Doe' },
startDate: '1991-02-19',
@@ -101,17 +101,138 @@ describe('veteranMarriageHistoryOptions', () => {
.false;
});
- it('should handle locations outside the USA where state is not required', () => {
- const itemOutsideUsa = {
+ it('should return false if all required fields are present (outside USA, with country)', () => {
+ const completeItemOutsideUsa = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ veteranMarriageHistoryOptions.isItemIncomplete(completeItemOutsideUsa),
+ ).to.be.false;
+ });
+
+ it('should return true if startLocation city is missing', () => {
+ const itemMissingStartLocationCity = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: { location: {}, outsideUsa: true },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ veteranMarriageHistoryOptions.isItemIncomplete(
+ itemMissingStartLocationCity,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if startLocation state is missing (inside USA)', () => {
+ const itemMissingStartLocationState = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: { location: { city: 'Some City' }, outsideUsa: false },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ veteranMarriageHistoryOptions.isItemIncomplete(
+ itemMissingStartLocationState,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if startLocation country is missing (outside USA)', () => {
+ const itemMissingStartLocationCountry = {
fullName: { first: 'Jane', last: 'Smith' },
startDate: '1995-06-15',
endDate: '2005-06-15',
reasonMarriageEnded: 'Annulment',
startLocation: { location: { city: 'Paris' }, outsideUsa: true },
+ endLocation: {
+ location: { city: 'Berlin', country: 'Germany' },
+ outsideUsa: true,
+ },
+ };
+ expect(
+ veteranMarriageHistoryOptions.isItemIncomplete(
+ itemMissingStartLocationCountry,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if endLocation city is missing', () => {
+ const itemMissingEndLocationCity = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
+ endLocation: { location: {}, outsideUsa: true },
+ };
+ expect(
+ veteranMarriageHistoryOptions.isItemIncomplete(
+ itemMissingEndLocationCity,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if endLocation state is missing (inside USA)', () => {
+ const itemMissingEndLocationState = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
+ endLocation: { location: { city: 'Berlin' }, outsideUsa: false },
+ };
+ expect(
+ veteranMarriageHistoryOptions.isItemIncomplete(
+ itemMissingEndLocationState,
+ ),
+ ).to.be.true;
+ });
+
+ it('should return true if endLocation country is missing (outside USA)', () => {
+ const itemMissingEndLocationCountry = {
+ fullName: { first: 'Jane', last: 'Smith' },
+ startDate: '1995-06-15',
+ endDate: '2005-06-15',
+ reasonMarriageEnded: 'Annulment',
+ startLocation: {
+ location: { city: 'Paris', country: 'France' },
+ outsideUsa: true,
+ },
endLocation: { location: { city: 'Berlin' }, outsideUsa: true },
};
- expect(veteranMarriageHistoryOptions.isItemIncomplete(itemOutsideUsa)).to
- .be.false;
+ expect(
+ veteranMarriageHistoryOptions.isItemIncomplete(
+ itemMissingEndLocationCountry,
+ ),
+ ).to.be.true;
});
});
diff --git a/src/applications/686c-674/tests/config/chapters/report-dependent-death/deceasedDependentsArray.unit.spec.js b/src/applications/686c-674/tests/config/chapters/report-dependent-death/deceasedDependentsArray.unit.spec.js
index 21c43605d161..b9e683be711d 100644
--- a/src/applications/686c-674/tests/config/chapters/report-dependent-death/deceasedDependentsArray.unit.spec.js
+++ b/src/applications/686c-674/tests/config/chapters/report-dependent-death/deceasedDependentsArray.unit.spec.js
@@ -7,6 +7,7 @@ import { DefinitionTester } from 'platform/testing/unit/schemaform-utils';
import { $$ } from 'platform/forms-system/src/js/utilities/ui';
import { deceasedDependentOptions } from '../../../../config/chapters/report-dependent-death/deceasedDependentArrayPages';
import formConfig from '../../../../config/form';
+import { relationshipLabels } from '../../../../config/chapters/report-dependent-death/helpers';
const defaultStore = createCommonStore();
@@ -30,6 +31,7 @@ describe('deceasedDependentOptions', () => {
dependentType: 'spouse',
dependentDeathLocation: {
location: { city: 'Some City' },
+ outsideUsa: false,
},
dependentDeathDate: '1991-01-19',
};
@@ -44,7 +46,8 @@ describe('deceasedDependentOptions', () => {
birthDate: '1991-02-19',
dependentType: 'spouse',
dependentDeathLocation: {
- location: { city: 'Some City' },
+ location: { city: 'Some City', state: 'Some State' },
+ outsideUsa: false,
},
dependentDeathDate: '1991-01-19',
};
@@ -52,7 +55,7 @@ describe('deceasedDependentOptions', () => {
.false;
});
- it('should return true if state is missing and outsideUsa is false', () => {
+ it('should return true if "outsideUsa" is true but "country" is missing', () => {
const incompleteItem = {
fullName: { first: 'John', last: 'Doe' },
ssn: '333445555',
@@ -60,7 +63,7 @@ describe('deceasedDependentOptions', () => {
dependentType: 'spouse',
dependentDeathLocation: {
location: { city: 'Some City' },
- outsideUsa: false,
+ outsideUsa: true,
},
dependentDeathDate: '1991-01-19',
};
@@ -68,73 +71,71 @@ describe('deceasedDependentOptions', () => {
.true;
});
- it('should return false if state is missing but outsideUsa is true', () => {
- const completeItemWithoutState = {
+ it('should return false if "outsideUsa" is true and "country" is present', () => {
+ const completeItem = {
fullName: { first: 'John', last: 'Doe' },
ssn: '333445555',
birthDate: '1991-02-19',
dependentType: 'spouse',
dependentDeathLocation: {
- location: { city: 'Some City' },
+ location: { city: 'Some City', country: 'Some Country' },
outsideUsa: true,
},
dependentDeathDate: '1991-01-19',
};
- expect(
- deceasedDependentOptions.isItemIncomplete(completeItemWithoutState),
- ).to.be.false;
+ expect(deceasedDependentOptions.isItemIncomplete(completeItem)).to.be
+ .false;
});
});
describe('text.getItemName', () => {
- it('should return the full name of the item', () => {
- const item = {
- fullName: { first: 'John', last: 'Doe' },
- };
+ it('should return the relationship label if dependentType is valid', () => {
+ const item = { dependentType: 'spouse' };
expect(deceasedDependentOptions.text.getItemName(item)).to.equal(
- 'John Doe',
+ relationshipLabels.spouse,
);
});
- it('should return an empty string if first or last name is missing', () => {
- const incompleteItem = { fullName: { first: 'John' } };
- expect(
- deceasedDependentOptions.text.getItemName(incompleteItem),
- ).to.equal('John ');
+ it('should return "Unknown" if dependentType is missing', () => {
+ const item = {};
+ expect(deceasedDependentOptions.text.getItemName(item)).to.equal(
+ 'Unknown',
+ );
+ });
- const missingBoth = { fullName: {} };
- expect(deceasedDependentOptions.text.getItemName(missingBoth)).to.equal(
- ' ',
+ it('should return "Unknown" if dependentType is invalid', () => {
+ const item = { dependentType: 'invalidType' };
+ expect(deceasedDependentOptions.text.getItemName(item)).to.equal(
+ 'Unknown',
);
});
});
describe('text.cardDescription', () => {
- it('should return formatted birth and death dates', () => {
+ it('should return a formatted full name with capitalized first and last names', () => {
const item = {
- birthDate: '1991-02-19',
- dependentDeathDate: '1991-01-19',
+ fullName: { first: 'john', last: 'doe' },
};
expect(deceasedDependentOptions.text.cardDescription(item)).to.equal(
- '02/19/1991 - 01/19/1991',
+ 'John Doe',
);
});
- it('should return "Unknown" if birth or death date is missing', () => {
- const missingBirthDate = { dependentDeathDate: '1991-01-19' };
+ it('should handle missing first or last name gracefully', () => {
+ const missingLastName = { fullName: { first: 'John' } };
expect(
- deceasedDependentOptions.text.cardDescription(missingBirthDate),
- ).to.equal('Unknown - 01/19/1991');
+ deceasedDependentOptions.text.cardDescription(missingLastName),
+ ).to.equal('John');
- const missingDeathDate = { birthDate: '1991-02-19' };
+ const missingFirstName = { fullName: { last: 'Doe' } };
expect(
- deceasedDependentOptions.text.cardDescription(missingDeathDate),
- ).to.equal('02/19/1991 - Unknown');
+ deceasedDependentOptions.text.cardDescription(missingFirstName),
+ ).to.equal('Doe');
- const missingBoth = {};
+ const missingBoth = { fullName: {} };
expect(
deceasedDependentOptions.text.cardDescription(missingBoth),
- ).to.equal('Unknown - Unknown');
+ ).to.equal('');
});
});
});
diff --git a/src/applications/686c-674/tests/e2e/686C-674-ancilliary.cypress.spec.js b/src/applications/686c-674/tests/e2e/686C-674-ancilliary.cypress.spec.js
index 02141289ba79..764f11218d77 100644
--- a/src/applications/686c-674/tests/e2e/686C-674-ancilliary.cypress.spec.js
+++ b/src/applications/686c-674/tests/e2e/686C-674-ancilliary.cypress.spec.js
@@ -73,12 +73,18 @@ const testConfig = createTestConfig(
});
},
- 'current-marriage-information': ({ afterHook }) => {
+ 'current-marriage-information/location-of-marriage': ({ afterHook }) => {
afterHook(() => {
cy.fillPage();
- cy.get('#root_currentMarriageInformation_location_country').select(
- 'Argentina',
- );
+ cy.get(
+ 'select#options[name="root_currentMarriageInformation_location_country"]',
+ { timeout: 1000 },
+ )
+ .should('be.visible')
+ .should('not.be.disabled');
+ cy.get(
+ 'select#options[name="root_currentMarriageInformation_location_country"]',
+ ).select('AUS');
cy.get('.usa-button-primary').click();
});
},
diff --git a/src/applications/686c-674/tests/e2e/686C-674.cypress.spec.js b/src/applications/686c-674/tests/e2e/686C-674.cypress.spec.js
index c574cd2b6f36..32ece60a6c10 100644
--- a/src/applications/686c-674/tests/e2e/686C-674.cypress.spec.js
+++ b/src/applications/686c-674/tests/e2e/686C-674.cypress.spec.js
@@ -88,12 +88,18 @@ const testConfig = createTestConfig(
});
},
- 'current-marriage-information': ({ afterHook }) => {
+ 'current-marriage-information/location-of-marriage': ({ afterHook }) => {
afterHook(() => {
cy.fillPage();
- cy.get('#root_currentMarriageInformation_location_country').select(
- 'Argentina',
- );
+ cy.get(
+ 'select#options[name="root_currentMarriageInformation_location_country"]',
+ { timeout: 1000 },
+ )
+ .should('be.visible')
+ .should('not.be.disabled');
+ cy.get(
+ 'select#options[name="root_currentMarriageInformation_location_country"]',
+ ).select('AUS');
cy.get('.usa-button-primary').click();
});
},
diff --git a/src/applications/686c-674/tests/e2e/fixtures/spouse-child-all-fields.json b/src/applications/686c-674/tests/e2e/fixtures/spouse-child-all-fields.json
index 05e4b56a4563..1be60a499fa0 100644
--- a/src/applications/686c-674/tests/e2e/fixtures/spouse-child-all-fields.json
+++ b/src/applications/686c-674/tests/e2e/fixtures/spouse-child-all-fields.json
@@ -78,7 +78,8 @@
"startLocation": {
"outsideUsa": true,
"location": {
- "city": "Foreign city"
+ "city": "Foreign city",
+ "country": "AUS"
}
},
"endLocation": {
@@ -111,7 +112,7 @@
"outsideUsa": true,
"location": {
"city": "Fakesville",
- "state": "CA"
+ "country": "AUS"
},
"date": "2009-04-01",
"type": "CIVIL",
diff --git a/src/applications/686c-674/tests/e2e/fixtures/spouse-report-divorce.json b/src/applications/686c-674/tests/e2e/fixtures/spouse-report-divorce.json
index 75cc3b3a08fd..6fd761b9e50a 100644
--- a/src/applications/686c-674/tests/e2e/fixtures/spouse-report-divorce.json
+++ b/src/applications/686c-674/tests/e2e/fixtures/spouse-report-divorce.json
@@ -11,7 +11,8 @@
"startLocation": {
"outsideUsa": true,
"location": {
- "city": "Foreign city"
+ "city": "Foreign city",
+ "country": "AUS"
}
},
"endLocation": {
@@ -38,7 +39,8 @@
"startLocation": {
"outsideUsa": true,
"location": {
- "city": "Foreign city"
+ "city": "Fakesville",
+ "country": "AUS"
}
},
"endLocation": {
@@ -71,7 +73,7 @@
"outsideUsa": true,
"location": {
"city": "Fakesville",
- "state": "CA"
+ "country": "AUS"
},
"date": "2009-04-01",
"type": "CIVIL",
From 1cbc54194dabdb627f9392ec3cdeb6d1e200e53e Mon Sep 17 00:00:00 2001
From: Chris Kim <42885441+chriskim2311@users.noreply.github.com>
Date: Fri, 17 Jan 2025 13:16:37 -0800
Subject: [PATCH 36/36] VACMS-20089 DUW Discharge Year Input Field (#34087)
---
.../components/questions/DischargeYear.jsx | 26 +--
.../components/questions/shared/Dropdown.jsx | 2 +-
.../questions/shared/RadioGroup.jsx | 2 +-
.../components/questions/shared/YearInput.jsx | 208 ++++++++++++++++++
.../constants/display-conditions.js | 5 +-
.../discharge-wizard/constants/index.js | 2 +-
.../discharge-wizard/helpers/index.jsx | 5 +-
...ischarge_form_update_DD215.cypress.spec.js | 6 +-
.../discharge_unjust_flow.cypress.spec.js | 18 +-
.../cypress/flows/mainflow.cypress.spec.js | 19 +-
.../sexual_orientation_flow.cypress.spec.js | 24 +-
.../flows/transgender_flow.cypress.spec.js | 12 +-
.../discharge-wizard/tests/cypress/helpers.js | 11 +
.../review/review_edit_flows.cypress.spec.js | 54 +----
.../tests/helpers/index.unit.spec.js | 6 +-
.../tests/utilities/shared.unit.spec.js | 2 +-
.../discharge-wizard/utilities/shared.js | 4 +
17 files changed, 256 insertions(+), 150 deletions(-)
create mode 100644 src/applications/discharge-wizard/components/questions/shared/YearInput.jsx
diff --git a/src/applications/discharge-wizard/components/questions/DischargeYear.jsx b/src/applications/discharge-wizard/components/questions/DischargeYear.jsx
index ba1de8bdcee2..9598870e28a3 100644
--- a/src/applications/discharge-wizard/components/questions/DischargeYear.jsx
+++ b/src/applications/discharge-wizard/components/questions/DischargeYear.jsx
@@ -1,7 +1,6 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { range } from 'lodash';
import {
QUESTION_MAP,
@@ -11,7 +10,7 @@ import { updateDischargeYear } from '../../actions';
import { pageSetup } from '../../utilities/page-setup';
import { ROUTES } from '../../constants';
-import Dropdown from './shared/Dropdown';
+import YearInput from './shared/YearInput';
const DischargeYear = ({
formResponses,
@@ -40,29 +39,9 @@ const DischargeYear = ({
);
const dischargeYear = formResponses[shortName];
- const currentYear = new Date().getFullYear();
- const yearOptions = range(currentYear - 1991).map(i => {
- const year = currentYear - i;
- return (
-
- {year.toString()}
-
- );
- });
- const before1992Key = yearOptions.length + 1;
-
- yearOptions.push(
-
- Before 1992
- ,
- );
return (
-
);
diff --git a/src/applications/discharge-wizard/components/questions/shared/Dropdown.jsx b/src/applications/discharge-wizard/components/questions/shared/Dropdown.jsx
index 627f153ad1bc..6ea6d1f3708d 100644
--- a/src/applications/discharge-wizard/components/questions/shared/Dropdown.jsx
+++ b/src/applications/discharge-wizard/components/questions/shared/Dropdown.jsx
@@ -167,10 +167,10 @@ const Dropdown = ({
};
Dropdown.propTypes = {
+ H1: PropTypes.string.isRequired,
editMode: PropTypes.bool.isRequired,
formError: PropTypes.bool.isRequired,
formResponses: PropTypes.object.isRequired,
- H1: PropTypes.string.isRequired,
options: PropTypes.array.isRequired,
questionFlowChanged: PropTypes.bool.isRequired,
questionSelectedToEdit: PropTypes.string.isRequired,
diff --git a/src/applications/discharge-wizard/components/questions/shared/RadioGroup.jsx b/src/applications/discharge-wizard/components/questions/shared/RadioGroup.jsx
index 827d5d02c932..9b0258f632c2 100644
--- a/src/applications/discharge-wizard/components/questions/shared/RadioGroup.jsx
+++ b/src/applications/discharge-wizard/components/questions/shared/RadioGroup.jsx
@@ -180,10 +180,10 @@ const RadioGroup = ({
};
RadioGroup.propTypes = {
+ H1: PropTypes.string.isRequired,
editMode: PropTypes.bool.isRequired,
formError: PropTypes.bool.isRequired,
formResponses: PropTypes.object.isRequired,
- H1: PropTypes.string.isRequired,
questionFlowChanged: PropTypes.bool.isRequired,
questionSelectedToEdit: PropTypes.string.isRequired,
responses: PropTypes.arrayOf(PropTypes.string).isRequired,
diff --git a/src/applications/discharge-wizard/components/questions/shared/YearInput.jsx b/src/applications/discharge-wizard/components/questions/shared/YearInput.jsx
new file mode 100644
index 000000000000..3c30452781d8
--- /dev/null
+++ b/src/applications/discharge-wizard/components/questions/shared/YearInput.jsx
@@ -0,0 +1,208 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import {
+ VaButtonPair,
+ VaTextInput,
+} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+
+import { waitForRenderThenFocus } from '@department-of-veterans-affairs/platform-utilities/ui';
+import { forkableQuestions } from '../../../constants';
+import {
+ navigateBackward,
+ navigateForward,
+} from '../../../utilities/page-navigation';
+import { cleanUpAnswers } from '../../../utilities/answer-cleanup';
+import {
+ updateEditMode,
+ updateFormStore,
+ updateQuestionFlowChanged,
+ updateRouteMap,
+ updateAnswerChanged,
+} from '../../../actions';
+import {
+ determineErrorMessage,
+ determineLabel,
+ isValidYear,
+} from '../../../utilities/shared';
+import { applyErrorFocus } from '../../../utilities/page-setup';
+
+const YearInput = ({
+ shortName,
+ router,
+ formResponses,
+ formValue,
+ formError,
+ H1,
+ valueSetter,
+ setFormError,
+ testId,
+ updateCleanedFormStore,
+ editMode,
+ toggleEditMode,
+ toggleQuestionsFlowChanged,
+ toggleAnswerChanged,
+ setRouteMap,
+ routeMap,
+ questionFlowChanged,
+ questionSelectedToEdit,
+}) => {
+ const [valueHasChanged, setValueHasChanged] = useState(false);
+ const isForkableQuestion = forkableQuestions.includes(shortName);
+
+ useEffect(() => {
+ waitForRenderThenFocus('h1');
+ }, []);
+
+ const onValueChange = value => {
+ valueSetter(value);
+
+ if (formValue) {
+ setValueHasChanged(true);
+ }
+
+ if (value) {
+ setFormError(false);
+ }
+ };
+
+ const onContinueClick = () => {
+ if (!formValue || !isValidYear(formValue)) {
+ setFormError(true);
+ applyErrorFocus('duw-input');
+ } else {
+ if (valueHasChanged) {
+ // Remove answers from the Redux store if the display path ahead will change.
+ cleanUpAnswers(formResponses, updateCleanedFormStore, shortName);
+
+ if (editMode) {
+ toggleAnswerChanged(true);
+ if (forkableQuestions.includes(shortName)) {
+ toggleQuestionsFlowChanged(true);
+ }
+ }
+ }
+
+ toggleEditMode(false);
+ setFormError(false);
+ navigateForward(
+ shortName,
+ formResponses,
+ router,
+ editMode,
+ setRouteMap,
+ routeMap,
+ questionFlowChanged,
+ valueHasChanged,
+ questionSelectedToEdit,
+ );
+ }
+ };
+
+ const onBackClick = () => {
+ if (valueHasChanged) {
+ // Remove answers from the Redux store if the display path ahead will change.
+ cleanUpAnswers(formResponses, updateCleanedFormStore, shortName);
+
+ if (editMode) {
+ toggleAnswerChanged(true);
+ if (forkableQuestions.includes(shortName)) {
+ toggleQuestionsFlowChanged(true);
+ }
+ }
+ }
+
+ toggleEditMode(false);
+ navigateBackward(
+ router,
+ setRouteMap,
+ routeMap,
+ shortName,
+ editMode,
+ isForkableQuestion,
+ valueHasChanged,
+ );
+ };
+
+ return (
+ <>
+ {H1}
+ onValueChange(e.target.value)}
+ label={determineLabel(shortName)}
+ error={formError ? determineErrorMessage(shortName) : null}
+ data-testid={testId}
+ id="duw-input"
+ required
+ show-input-error
+ />
+ {editMode && (
+
+
+ If you change your answer to this question, you may be asked for
+ more information to ensure that we provide you with the best
+ results.
+
+
+ )}
+
+ >
+ );
+};
+
+YearInput.propTypes = {
+ H1: PropTypes.string.isRequired,
+ editMode: PropTypes.bool.isRequired,
+ formError: PropTypes.bool.isRequired,
+ formResponses: PropTypes.object.isRequired,
+ questionFlowChanged: PropTypes.bool.isRequired,
+ questionSelectedToEdit: PropTypes.string.isRequired,
+ routeMap: PropTypes.array.isRequired,
+ router: PropTypes.object.isRequired,
+ setFormError: PropTypes.func.isRequired,
+ setRouteMap: PropTypes.func.isRequired,
+ shortName: PropTypes.string.isRequired,
+ testId: PropTypes.string.isRequired,
+ toggleAnswerChanged: PropTypes.func.isRequired,
+ toggleEditMode: PropTypes.func.isRequired,
+ toggleQuestionsFlowChanged: PropTypes.func.isRequired,
+ updateCleanedFormStore: PropTypes.func.isRequired,
+ valueSetter: PropTypes.func.isRequired,
+ formValue: PropTypes.string,
+};
+
+const mapDispatchToProps = {
+ updateCleanedFormStore: updateFormStore,
+ toggleEditMode: updateEditMode,
+ toggleQuestionsFlowChanged: updateQuestionFlowChanged,
+ toggleAnswerChanged: updateAnswerChanged,
+ setRouteMap: updateRouteMap,
+};
+
+const mapStateToProps = state => ({
+ editMode: state?.dischargeUpgradeWizard?.duwForm?.editMode,
+ routeMap: state?.dischargeUpgradeWizard?.duwForm?.routeMap,
+ questionFlowChanged:
+ state?.dischargeUpgradeWizard?.duwForm?.questionFlowChanged,
+ questionSelectedToEdit:
+ state?.dischargeUpgradeWizard?.duwForm?.questionSelectedToEdit,
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(YearInput);
diff --git a/src/applications/discharge-wizard/constants/display-conditions.js b/src/applications/discharge-wizard/constants/display-conditions.js
index 7b9f969c8b6b..f24f70e7c51b 100644
--- a/src/applications/discharge-wizard/constants/display-conditions.js
+++ b/src/applications/discharge-wizard/constants/display-conditions.js
@@ -4,14 +4,11 @@ import { RESPONSES } from './question-data-map';
const get15YearsPast = () => `${new Date().getFullYear() - 15}`;
const currentYear = new Date().getFullYear();
-const yearResponses = range(currentYear - 1991).map(i => {
+const yearResponses = range(currentYear - 1900).map(i => {
const year = currentYear - i;
return year.toString();
});
-// This accounts for the before 1992 answer for the Discharge Year question.
-yearResponses.push('Before 1992');
-
const validYearsForNonOldDischarge = yearResponses.filter(year => {
return currentYear - year < 15;
});
diff --git a/src/applications/discharge-wizard/constants/index.js b/src/applications/discharge-wizard/constants/index.js
index c451f765fd1d..8660fb54c441 100644
--- a/src/applications/discharge-wizard/constants/index.js
+++ b/src/applications/discharge-wizard/constants/index.js
@@ -116,7 +116,7 @@ export const forkableQuestions = [
export const errorTextMap = Object.freeze({
SERVICE_BRANCH: 'Select a branch.',
- DISCHARGE_YEAR: 'Select a year.',
+ DISCHARGE_YEAR: 'Enter a valid 4 digit year.',
DISCHARGE_MONTH: 'Select a month.',
REASON: 'Select a reason.',
});
diff --git a/src/applications/discharge-wizard/helpers/index.jsx b/src/applications/discharge-wizard/helpers/index.jsx
index adfe3cd08836..395a80e1e777 100644
--- a/src/applications/discharge-wizard/helpers/index.jsx
+++ b/src/applications/discharge-wizard/helpers/index.jsx
@@ -22,10 +22,7 @@ export const answerReviewLabel = (key, formValues) => {
case SHORT_NAME_MAP.SERVICE_BRANCH:
return `I served in the ${formValues[key]}.`;
case SHORT_NAME_MAP.DISCHARGE_YEAR:
- if (
- answer === 'Before 1992' &&
- !formValues[SHORT_NAME_MAP.DISCHARGE_MONTH]
- ) {
+ if (answer < 1992 && !formValues[SHORT_NAME_MAP.DISCHARGE_MONTH]) {
return 'I was discharged before 1992.';
}
diff --git a/src/applications/discharge-wizard/tests/cypress/flows/discharge_form_update_DD215.cypress.spec.js b/src/applications/discharge-wizard/tests/cypress/flows/discharge_form_update_DD215.cypress.spec.js
index 133977b37a38..f8df8fe91929 100644
--- a/src/applications/discharge-wizard/tests/cypress/flows/discharge_form_update_DD215.cypress.spec.js
+++ b/src/applications/discharge-wizard/tests/cypress/flows/discharge_form_update_DD215.cypress.spec.js
@@ -19,11 +19,7 @@ describe('Discharge Upgrade Wizard: Discharge Update to form DD214 from DD215 Fl
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
diff --git a/src/applications/discharge-wizard/tests/cypress/flows/discharge_unjust_flow.cypress.spec.js b/src/applications/discharge-wizard/tests/cypress/flows/discharge_unjust_flow.cypress.spec.js
index 8600c76a1232..cec51b34c7e8 100644
--- a/src/applications/discharge-wizard/tests/cypress/flows/discharge_unjust_flow.cypress.spec.js
+++ b/src/applications/discharge-wizard/tests/cypress/flows/discharge_unjust_flow.cypress.spec.js
@@ -19,11 +19,7 @@ describe('Discharge Upgrade Wizard: Discharge Unjust Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -81,11 +77,7 @@ describe('Discharge Upgrade Wizard: Discharge Unjust Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -138,11 +130,7 @@ describe('Discharge Upgrade Wizard: Discharge Unjust Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
diff --git a/src/applications/discharge-wizard/tests/cypress/flows/mainflow.cypress.spec.js b/src/applications/discharge-wizard/tests/cypress/flows/mainflow.cypress.spec.js
index 0e8cfeaaaaeb..3c1a2fb20159 100644
--- a/src/applications/discharge-wizard/tests/cypress/flows/mainflow.cypress.spec.js
+++ b/src/applications/discharge-wizard/tests/cypress/flows/mainflow.cypress.spec.js
@@ -1,6 +1,5 @@
import * as h from '../helpers';
import { ROUTES } from '../../../constants';
-import { SHORT_NAME_MAP } from '../../../constants/question-data-map';
describe('Discharge Upgrade Wizard Main Flow', () => {
describe('Base navigation', () => {
@@ -19,11 +18,7 @@ describe('Discharge Upgrade Wizard Main Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -86,11 +81,7 @@ describe('Discharge Upgrade Wizard Main Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -196,11 +187,7 @@ describe('Discharge Upgrade Wizard Main Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
diff --git a/src/applications/discharge-wizard/tests/cypress/flows/sexual_orientation_flow.cypress.spec.js b/src/applications/discharge-wizard/tests/cypress/flows/sexual_orientation_flow.cypress.spec.js
index 28ff52188645..0d653700a3ec 100644
--- a/src/applications/discharge-wizard/tests/cypress/flows/sexual_orientation_flow.cypress.spec.js
+++ b/src/applications/discharge-wizard/tests/cypress/flows/sexual_orientation_flow.cypress.spec.js
@@ -19,11 +19,7 @@ describe('Discharge Upgrade Wizard: Sexual Orientation Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -91,11 +87,7 @@ describe('Discharge Upgrade Wizard: Sexual Orientation Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
@@ -157,11 +149,7 @@ describe('Discharge Upgrade Wizard: Sexual Orientation Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
@@ -233,11 +221,7 @@ describe('Discharge Upgrade Wizard: Sexual Orientation Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
diff --git a/src/applications/discharge-wizard/tests/cypress/flows/transgender_flow.cypress.spec.js b/src/applications/discharge-wizard/tests/cypress/flows/transgender_flow.cypress.spec.js
index 39742f1a59d8..d6a9c769694b 100644
--- a/src/applications/discharge-wizard/tests/cypress/flows/transgender_flow.cypress.spec.js
+++ b/src/applications/discharge-wizard/tests/cypress/flows/transgender_flow.cypress.spec.js
@@ -19,11 +19,7 @@ describe('Discharge Upgrade Wizard: Transgender Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
@@ -75,11 +71,7 @@ describe('Discharge Upgrade Wizard: Transgender Flow', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
diff --git a/src/applications/discharge-wizard/tests/cypress/helpers.js b/src/applications/discharge-wizard/tests/cypress/helpers.js
index fe3bc66207d6..13422a5a333b 100644
--- a/src/applications/discharge-wizard/tests/cypress/helpers.js
+++ b/src/applications/discharge-wizard/tests/cypress/helpers.js
@@ -13,6 +13,7 @@ export const PREV_APPLICATION_YEAR_INPUT = 'duw-prev_application_year';
export const PREV_APPLICATION_TYPE_INPUT = 'duw-prev_application_type';
export const FAILURE_TO_EXHAUST_INPUT = 'duw-failure_to_exhaust';
export const PRIOR_SERVICE_INPUT = 'duw-prior_service';
+export const YEAR = '2024';
export const clickStart = () =>
cy
@@ -72,3 +73,13 @@ export const clickContinue = () =>
.eq(1)
.should('be.visible')
.click();
+
+export const typeInInput = (selector, value) =>
+ cy
+ .findByTestId(selector)
+ .shadow()
+ .get('input')
+ .first()
+ .click()
+ .clear()
+ .type(value, { force: true });
diff --git a/src/applications/discharge-wizard/tests/cypress/review/review_edit_flows.cypress.spec.js b/src/applications/discharge-wizard/tests/cypress/review/review_edit_flows.cypress.spec.js
index e1c92ce6071b..cc4292d7bf56 100644
--- a/src/applications/discharge-wizard/tests/cypress/review/review_edit_flows.cypress.spec.js
+++ b/src/applications/discharge-wizard/tests/cypress/review/review_edit_flows.cypress.spec.js
@@ -18,11 +18,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
@@ -379,11 +375,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -407,11 +399,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- h.get15YearsPast(),
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, h.get15YearsPast());
h.clickContinue();
// DISCHARGE_MONTH
@@ -442,11 +430,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -525,11 +509,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -610,11 +590,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -699,11 +675,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -789,11 +761,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
@@ -881,11 +849,7 @@ describe('Review edit flows', () => {
// DISCHARGE_YEAR
h.verifyUrl(ROUTES.DISCHARGE_YEAR);
- h.selectDropdown(
- h.DISCHARGE_YEAR_INPUT,
- SHORT_NAME_MAP.DISCHARGE_YEAR,
- '2024',
- );
+ h.typeInInput(h.DISCHARGE_YEAR_INPUT, '2024');
h.clickContinue();
// DISCHARGE_REASON
diff --git a/src/applications/discharge-wizard/tests/helpers/index.unit.spec.js b/src/applications/discharge-wizard/tests/helpers/index.unit.spec.js
index 9e3e9542b022..f88f23073837 100644
--- a/src/applications/discharge-wizard/tests/helpers/index.unit.spec.js
+++ b/src/applications/discharge-wizard/tests/helpers/index.unit.spec.js
@@ -126,9 +126,9 @@ describe('Discharge Wizard helpers', () => {
formResponses,
);
- const oldDischargYearLabel = answerReviewLabel(
+ const oldDischargeYearLabel = answerReviewLabel(
SHORT_NAME_MAP.DISCHARGE_YEAR,
- { [SHORT_NAME_MAP.DISCHARGE_YEAR]: 'Before 1992' },
+ { [SHORT_NAME_MAP.DISCHARGE_YEAR]: '1990' },
);
const notSureCourtMartialLabel = answerReviewLabel(
@@ -151,7 +151,7 @@ describe('Discharge Wizard helpers', () => {
expect(serviceBranchLabel).to.equal('I served in the Army.');
expect(dischargeYearLabel).to.equal('I was discharged in 2022.');
- expect(oldDischargYearLabel).to.equal('I was discharged before 1992.');
+ expect(oldDischargeYearLabel).to.equal('I was discharged before 1992.');
expect(notSureCourtMartialLabel).to.equal(
`I'm not sure if my discharge was the outcome of a general court-martial.`,
);
diff --git a/src/applications/discharge-wizard/tests/utilities/shared.unit.spec.js b/src/applications/discharge-wizard/tests/utilities/shared.unit.spec.js
index df1a2bae4973..e008ebee4f94 100644
--- a/src/applications/discharge-wizard/tests/utilities/shared.unit.spec.js
+++ b/src/applications/discharge-wizard/tests/utilities/shared.unit.spec.js
@@ -27,7 +27,7 @@ describe('Shared Utilities', () => {
describe('determineErrorMessage', () => {
it('should correctly return the right error message', () => {
expect(determineErrorMessage(SHORT_NAME_MAP.DISCHARGE_YEAR)).to.be.equal(
- 'Select a year.',
+ 'Enter a valid 4 digit year.',
);
});
it('should correctly return the default error message if no mapped value', () => {
diff --git a/src/applications/discharge-wizard/utilities/shared.js b/src/applications/discharge-wizard/utilities/shared.js
index ac24ee3a943c..c2b0758d66e8 100644
--- a/src/applications/discharge-wizard/utilities/shared.js
+++ b/src/applications/discharge-wizard/utilities/shared.js
@@ -22,3 +22,7 @@ export const determineErrorMessage = shortName => {
export const determineLabel = shortName => {
return labelTextMap[shortName] ? labelTextMap[shortName] : '';
};
+
+export const isValidYear = value => {
+ return value < new Date().getFullYear() && value?.match(/^(19|20)\d{2}$/);
+};