diff --git a/src/applications/appeals/10182/actions/index.js b/src/applications/appeals/10182/actions/index.js index 8b6a8b19f277..653623c72a70 100644 --- a/src/applications/appeals/10182/actions/index.js +++ b/src/applications/appeals/10182/actions/index.js @@ -1,6 +1,11 @@ +import environment from 'platform/utilities/environment'; import { apiRequest } from 'platform/utilities/api'; -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { + NEW_API, + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../constants/apis'; import { FETCH_CONTESTABLE_ISSUES_INIT, @@ -8,11 +13,16 @@ import { FETCH_CONTESTABLE_ISSUES_FAILED, } from '../../shared/actions'; -export const getContestableIssues = () => { +export const getContestableIssues = props => { + const newApi = props?.[NEW_API]; return dispatch => { dispatch({ type: FETCH_CONTESTABLE_ISSUES_INIT }); - return apiRequest(CONTESTABLE_ISSUES_API, { apiVersion: 'v1' }) + const apiUrl = `${environment.API_URL}${ + newApi ? CONTESTABLE_ISSUES_API_NEW : CONTESTABLE_ISSUES_API + }`; + + return apiRequest(apiUrl) .then(response => dispatch({ type: FETCH_CONTESTABLE_ISSUES_SUCCEEDED, diff --git a/src/applications/appeals/10182/components/ContestableIssuesWidget.jsx b/src/applications/appeals/10182/components/ContestableIssuesWidget.jsx index 9e4a4c43216a..97e7338a43b2 100644 --- a/src/applications/appeals/10182/components/ContestableIssuesWidget.jsx +++ b/src/applications/appeals/10182/components/ContestableIssuesWidget.jsx @@ -6,6 +6,7 @@ import { setData } from 'platform/forms-system/src/js/actions'; import { getContestableIssues as getContestableIssuesAction } from '../actions'; import { APP_NAME } from '../constants'; +import { NEW_API } from '../constants/apis'; import { getEligibleContestableIssues } from '../../shared/utils/issues'; import { FETCH_CONTESTABLE_ISSUES_FAILED } from '../../shared/actions'; @@ -37,22 +38,24 @@ const ContestableIssuesWidget = props => { getContestableIssues, contestableIssues, setFormData, - formData, + formData = {}, } = props; const hasAttempted = useRef(false); + const newApi = formData[NEW_API]; useEffect( () => { if ( !hasAttempted.current && + typeof newApi !== 'undefined' && contestableIssues.status === FETCH_CONTESTABLE_ISSUES_FAILED ) { hasAttempted.current = true; // only call API once if previously failed - getContestableIssues(); + getContestableIssues({ [NEW_API]: newApi }); } }, - [contestableIssues.status, getContestableIssues], + [contestableIssues.status, newApi, getContestableIssues], ); useEffect(() => { diff --git a/src/applications/appeals/10182/config/form.js b/src/applications/appeals/10182/config/form.js index abea9bcd47a4..32a1b9a4eaf2 100644 --- a/src/applications/appeals/10182/config/form.js +++ b/src/applications/appeals/10182/config/form.js @@ -9,6 +9,8 @@ import prefillTransformer from './prefill-transformer'; import { transform } from './submit-transformer'; import submitForm from './submitForm'; +import { SUBMIT_URL } from '../constants/apis'; + import IntroductionPage from '../containers/IntroductionPage'; import ConfirmationPage from '../containers/ConfirmationPage'; import AddContestableIssue from '../components/AddContestableIssue'; @@ -64,7 +66,7 @@ import manifest from '../manifest.json'; const formConfig = { rootUrl: manifest.rootUrl, urlPrefix: '/', - submitUrl: 'notice_of_disagreements', + submitUrl: SUBMIT_URL, trackingPrefix: '10182-board-appeal-', downtime: { diff --git a/src/applications/appeals/10182/config/submitForm.js b/src/applications/appeals/10182/config/submitForm.js index fde040824bb5..84f57cb0427e 100644 --- a/src/applications/appeals/10182/config/submitForm.js +++ b/src/applications/appeals/10182/config/submitForm.js @@ -1,18 +1,22 @@ import { submitToUrl } from 'platform/forms-system/src/js/actions'; import environment from 'platform/utilities/environment'; +import { NEW_API, SUBMIT_URL_NEW } from '../constants/apis'; + // Analytics event export const buildEventData = () => {}; const submitForm = (form, formConfig) => { - const { trackingPrefix } = formConfig; - // v1 (add part III data) - const submitUrl = `${environment.API_URL}/v1/${formConfig.submitUrl}`; + const { submitUrl, trackingPrefix } = formConfig; const body = formConfig.transformForSubmit(formConfig, form); + const url = `${environment.API_URL}${ + form.data[NEW_API] ? SUBMIT_URL_NEW : submitUrl + }`; + // eventData for analytics const eventData = buildEventData(form.data); - return submitToUrl(body, submitUrl, trackingPrefix, eventData); + return submitToUrl(body, url, trackingPrefix, eventData); }; export default submitForm; diff --git a/src/applications/appeals/10182/constants/apis.js b/src/applications/appeals/10182/constants/apis.js new file mode 100644 index 000000000000..f99363520893 --- /dev/null +++ b/src/applications/appeals/10182/constants/apis.js @@ -0,0 +1,26 @@ +/** + * NOTE: If any of these API values change, please update the documentation: + * https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/products/decision-reviews/Notice-of-Disagreement/engineering/NOD_frontend_backend_interactions.md + */ +export const NEW_API = 'decisionReviewNodNewApi'; + +export const CONTESTABLE_ISSUES_API = + '/v1/notice_of_disagreements/contestable_issues'; + +// Evidence upload API - same endpoint as Supplemental Claim +export const EVIDENCE_UPLOAD_API = '/v1/decision_review_evidence'; + +export const SUBMIT_URL = '/v1/notice_of_disagreements'; + +/** + * New modularized API behind feature toggle: decision_review_nod_new_api + * The endpoint will be the same until the backend has completed modularization + */ +export const CONTESTABLE_ISSUES_API_NEW = + '/decision_reviews/v1/notice_of_disagreements/contestable_issues'; + +// Evidence upload API - same endpoint as Supplemental Claim +export const EVIDENCE_UPLOAD_API_NEW = + '/decision_reviews/v1/decision_review_evidence'; + +export const SUBMIT_URL_NEW = '/decision_reviews/v1/notice_of_disagreements'; diff --git a/src/applications/appeals/10182/constants.js b/src/applications/appeals/10182/constants/index.js similarity index 74% rename from src/applications/appeals/10182/constants.js rename to src/applications/appeals/10182/constants/index.js index fa76ca611bc9..0aa1d2ee623a 100644 --- a/src/applications/appeals/10182/constants.js +++ b/src/applications/appeals/10182/constants/index.js @@ -3,6 +3,3 @@ export const APP_NAME = 'Notice of Disagreement'; export const DATA_DOG_ID = 'cabce133-7a68-46ba-ac9b-68c57e8375eb'; export const DATA_DOG_TOKEN = 'pubb208973905b7f32eb100b1c27688ecc9'; export const DATA_DOG_SERVICE = 'benefits-notice-of-disagreement'; - -export const CONTESTABLE_ISSUES_API = - '/notice_of_disagreements/contestable_issues'; diff --git a/src/applications/appeals/10182/containers/FormApp.jsx b/src/applications/appeals/10182/containers/FormApp.jsx index 041853f0d0a1..3876faebeff5 100644 --- a/src/applications/appeals/10182/containers/FormApp.jsx +++ b/src/applications/appeals/10182/containers/FormApp.jsx @@ -10,6 +10,7 @@ import { getContestableIssues as getContestableIssuesAction } from '../actions'; import formConfig from '../config/form'; import { DATA_DOG_ID, DATA_DOG_TOKEN, DATA_DOG_SERVICE } from '../constants'; +import { NEW_API } from '../constants/apis'; import { FETCH_CONTESTABLE_ISSUES_SUCCEEDED } from '../../shared/actions'; import { wrapWithBreadcrumb } from '../../shared/components/Breadcrumbs'; @@ -32,6 +33,7 @@ export const FormApp = ({ setFormData, getContestableIssues, contestableIssues = {}, + toggles, }) => { const { pathname } = location || {}; @@ -77,7 +79,7 @@ export const FormApp = ({ // work properly is overly complicated (!isOutsideForm(pathname) || formData.internalTesting) ) { - getContestableIssues(); + getContestableIssues({ [NEW_API]: toggles[NEW_API] }); } else if ( // Checks if the API has returned contestable issues not already reflected // in `formData`. @@ -102,7 +104,25 @@ export const FormApp = ({ // `useEffect` (e.g. `setFormData`) never change, so we don't need to include // them in the dependency array. // eslint-disable-next-line react-hooks/exhaustive-deps - [loggedIn, contestableIssues, formData.contestedIssues, pathname], + [toggles, loggedIn, contestableIssues, formData.contestedIssues, pathname], + ); + + useEffect( + () => { + const isUpdatedApi = toggles[NEW_API] || false; + if ( + !toggles.loading && + (typeof formData[NEW_API] === 'undefined' || + formData[NEW_API] !== isUpdatedApi) + ) { + setFormData({ + ...formData, + [NEW_API]: toggles[NEW_API], + }); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [toggles, formData[NEW_API]], ); const content = isLoading ? ( @@ -156,6 +176,10 @@ FormApp.propTypes = { push: PropTypes.func, }), setFormData: PropTypes.func, + toggles: PropTypes.shape({ + [NEW_API]: PropTypes.bool, + loading: PropTypes.bool, + }), }; const mapStateToProps = state => ({ @@ -165,6 +189,7 @@ const mapStateToProps = state => ({ loggedIn: isLoggedIn(state), returnUrlFromSIPForm: state.form?.loadedData?.metadata?.returnUrl, isStartingOver: state.form.isStartingOver, + toggles: state.featureToggles || {}, }); const mapDispatchToProps = { diff --git a/src/applications/appeals/10182/tests/10182-contact-loop.cypress.spec.js b/src/applications/appeals/10182/tests/10182-contact-loop.cypress.spec.js index 508dfd8ac35c..bd967f9e5dc6 100644 --- a/src/applications/appeals/10182/tests/10182-contact-loop.cypress.spec.js +++ b/src/applications/appeals/10182/tests/10182-contact-loop.cypress.spec.js @@ -1,4 +1,7 @@ -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../constants/apis'; import mockData from './fixtures/data/maximal-test.json'; @@ -20,12 +23,12 @@ describe('NOD contact info loop', () => { cy.intercept( 'GET', - `/v0${CONTESTABLE_ISSUES_API}compensation`, + `${CONTESTABLE_ISSUES_API}/compensation`, mockContestableIssues, ); cy.intercept( 'GET', - `/v1${CONTESTABLE_ISSUES_API}compensation`, + `${CONTESTABLE_ISSUES_API_NEW}/compensation`, mockContestableIssues, ); diff --git a/src/applications/appeals/10182/tests/10182-downtime-notification.cypress.spec.js b/src/applications/appeals/10182/tests/10182-downtime-notification.cypress.spec.js index 73629292c33e..c5901776a7ee 100644 --- a/src/applications/appeals/10182/tests/10182-downtime-notification.cypress.spec.js +++ b/src/applications/appeals/10182/tests/10182-downtime-notification.cypress.spec.js @@ -1,4 +1,4 @@ -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { CONTESTABLE_ISSUES_API } from '../constants/apis'; import mockInProgress from './fixtures/mocks/in-progress-forms.json'; import mockData from './fixtures/data/minimal-test.json'; @@ -8,7 +8,7 @@ import downtimeTesting from '../../shared/tests/cypress.downtime'; downtimeTesting({ baseUrl: NOD_BASE_URL, - contestableApi: `/v1${CONTESTABLE_ISSUES_API}`, + contestableApi: CONTESTABLE_ISSUES_API, formId: '10182', inProgressVersion: 2, data: mockData.data, diff --git a/src/applications/appeals/10182/tests/10182-keyboard-only.cypress.spec.js b/src/applications/appeals/10182/tests/10182-keyboard-only.cypress.spec.js index dafc9bdb1b30..f90c47fc49b5 100644 --- a/src/applications/appeals/10182/tests/10182-keyboard-only.cypress.spec.js +++ b/src/applications/appeals/10182/tests/10182-keyboard-only.cypress.spec.js @@ -2,7 +2,11 @@ import formConfig from '../config/form'; import mockInProgress from './fixtures/mocks/in-progress-forms.json'; import mockSubmit from './fixtures/mocks/application-submit.json'; -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { + CONTESTABLE_ISSUES_API, + SUBMIT_URL, + SUBMIT_URL_NEW, +} from '../constants/apis'; import mockData from './fixtures/data/maximal-test.json'; import { CONTACT_INFO_PATH } from '../../shared/constants'; @@ -15,14 +19,14 @@ describe('Notice of Disagreement keyboard only navigation', () => { cy.wrap(mockData.data).as('testData'); - cy.intercept('PUT', 'v0/in_progress_forms/10182', mockInProgress); - cy.intercept('POST', `v0/${formConfig.submitUrl}`, mockSubmit); - cy.intercept('POST', `v1/${formConfig.submitUrl}`, mockSubmit); + cy.intercept('PUT', '/v0/in_progress_forms/10182', mockInProgress); + cy.intercept('POST', SUBMIT_URL, mockSubmit); + cy.intercept('POST', SUBMIT_URL_NEW, mockSubmit); cy.get('@testData').then(data => { const { chapters } = formConfig; - cy.intercept('GET', `/v1${CONTESTABLE_ISSUES_API}`, { + cy.intercept('GET', CONTESTABLE_ISSUES_API, { data: fixDecisionDates(data.contestedIssues, { unselected: true }), }).as('getIssues'); cy.visit( diff --git a/src/applications/appeals/10182/tests/10182-max-selections.cypress.spec.js b/src/applications/appeals/10182/tests/10182-max-selections.cypress.spec.js index 910e7dec7361..a39d412b863e 100644 --- a/src/applications/appeals/10182/tests/10182-max-selections.cypress.spec.js +++ b/src/applications/appeals/10182/tests/10182-max-selections.cypress.spec.js @@ -1,4 +1,4 @@ -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { CONTESTABLE_ISSUES_API } from '../constants/apis'; import mockInProgress from './fixtures/mocks/in-progress-forms.json'; import mockData from './fixtures/data/101-issues.json'; @@ -8,7 +8,7 @@ import preventMaxSelections from '../../shared/tests/cypress.max-selections'; preventMaxSelections({ baseUrl: NOD_BASE_URL, - contestableApi: `/v1${CONTESTABLE_ISSUES_API}`, + contestableApi: CONTESTABLE_ISSUES_API, formId: '10182', inProgressVersion: 2, data: mockData.data, diff --git a/src/applications/appeals/10182/tests/10182-notice-of-disagreement.cypress.spec.js b/src/applications/appeals/10182/tests/10182-notice-of-disagreement.cypress.spec.js index 506b48b486f9..cae36edb8dbe 100644 --- a/src/applications/appeals/10182/tests/10182-notice-of-disagreement.cypress.spec.js +++ b/src/applications/appeals/10182/tests/10182-notice-of-disagreement.cypress.spec.js @@ -10,7 +10,11 @@ import mockInProgress from './fixtures/mocks/in-progress-forms.json'; import mockPrefill from './fixtures/mocks/prefill.json'; import mockSubmit from './fixtures/mocks/application-submit.json'; import mockUpload from './fixtures/mocks/mock-upload.json'; -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { + CONTESTABLE_ISSUES_API, + SUBMIT_URL, + SUBMIT_URL_NEW, +} from '../constants/apis'; import { CONTESTABLE_ISSUES_PATH, @@ -119,13 +123,13 @@ const testConfig = createTestConfig( cypressSetup(); cy.intercept('POST', 'v1/decision_review_evidence', mockUpload); - cy.intercept('POST', `v0/${formConfig.submitUrl}`, mockSubmit); - cy.intercept('POST', `v1/${formConfig.submitUrl}`, mockSubmit); + cy.intercept('POST', SUBMIT_URL, mockSubmit); + cy.intercept('POST', SUBMIT_URL_NEW, mockSubmit); cy.get('@testData').then(data => { cy.intercept('GET', '/v0/in_progress_forms/10182', mockPrefill); cy.intercept('PUT', 'v0/in_progress_forms/10182', mockInProgress); - cy.intercept('GET', `/v1${CONTESTABLE_ISSUES_API}`, { + cy.intercept('GET', CONTESTABLE_ISSUES_API, { data: fixDecisionDates(data.contestedIssues, { unselected: true }), }).as('getIssues'); }); diff --git a/src/applications/appeals/10182/tests/actions/actions.unit.spec.js b/src/applications/appeals/10182/tests/actions/actions.unit.spec.js index 005d57bf8422..bdd772595fcc 100644 --- a/src/applications/appeals/10182/tests/actions/actions.unit.spec.js +++ b/src/applications/appeals/10182/tests/actions/actions.unit.spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { mockApiRequest } from 'platform/testing/unit/helpers'; +import * as apiUtils from 'platform/utilities/api'; import { getContestableIssues } from '../../actions'; import { @@ -9,10 +10,15 @@ import { FETCH_CONTESTABLE_ISSUES_SUCCEEDED, FETCH_CONTESTABLE_ISSUES_FAILED, } from '../../../shared/actions'; +import { + NEW_API, + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../../constants/apis'; describe('fetch contestable issues action', () => { + const mockData = { data: 'asdf' }; it('should dispatch an init action', () => { - const mockData = { data: 'asdf' }; mockApiRequest(mockData); const dispatch = sinon.spy(); return getContestableIssues()(dispatch).then(() => { @@ -29,7 +35,6 @@ describe('fetch contestable issues action', () => { }); it('should dispatch a failed action', () => { - const mockData = { data: 'asdf' }; mockApiRequest(mockData, false); const dispatch = sinon.spy(); return getContestableIssues()(dispatch).then(() => { @@ -41,4 +46,35 @@ describe('fetch contestable issues action', () => { ); }); }); + + describe('test apiRequest', () => { + let apiRequestSpy; + beforeEach(() => { + apiRequestSpy = sinon.stub(apiUtils, 'apiRequest').resolves(mockData); + }); + afterEach(() => { + apiRequestSpy.restore(); + }); + + it('should dispatch an init action', () => { + mockApiRequest(mockData); + const dispatch = sinon.spy(); + return getContestableIssues()(dispatch).then(() => { + // Original API + expect(apiRequestSpy.args[0][0]).to.contain(CONTESTABLE_ISSUES_API); + }); + }); + + it('should dispatch an init action with new API endpoint', () => { + mockApiRequest(mockData); + const dispatch = sinon.spy(); + return getContestableIssues({ [NEW_API]: true })(dispatch).then(() => { + return getContestableIssues()(dispatch).then(() => { + expect(apiRequestSpy.args[0][0]).to.contain( + CONTESTABLE_ISSUES_API_NEW, + ); + }); + }); + }); + }); }); diff --git a/src/applications/appeals/10182/tests/components/ContestableIssuesWidget.unit.spec.jsx b/src/applications/appeals/10182/tests/components/ContestableIssuesWidget.unit.spec.jsx index a785e1c8f9ef..65c08fffc4be 100644 --- a/src/applications/appeals/10182/tests/components/ContestableIssuesWidget.unit.spec.jsx +++ b/src/applications/appeals/10182/tests/components/ContestableIssuesWidget.unit.spec.jsx @@ -7,6 +7,8 @@ import sinon from 'sinon'; import { $, $$ } from 'platform/forms-system/src/js/utilities/ui'; import { ContestableIssuesWidget } from '../../components/ContestableIssuesWidget'; +import { NEW_API } from '../../constants/apis'; + import { FETCH_CONTESTABLE_ISSUES_SUCCEEDED, FETCH_CONTESTABLE_ISSUES_FAILED, @@ -92,6 +94,7 @@ describe('', () => { it('should call getContestableIssues only once, if there was a previous failure', async () => { const getContestableIssuesSpy = sinon.spy(); + const formData = { [NEW_API]: false }; const { props, mockStore } = getProps({ apiLoadStatus: FETCH_CONTESTABLE_ISSUES_FAILED, contestedIssues: [], @@ -99,12 +102,13 @@ describe('', () => { }); render( - + , ); await waitFor(() => { expect(getContestableIssuesSpy.called).to.be.true; + expect(getContestableIssuesSpy.args[0][0]).to.deep.equal(formData); }); }); @@ -123,4 +127,24 @@ describe('', () => { expect(getContestableIssuesSpy.called).to.be.false; }); + + it('should call getContestableIssues with new API', async () => { + const getContestableIssuesSpy = sinon.spy(); + const formData = { [NEW_API]: true }; + const { props, mockStore } = getProps({ + apiLoadStatus: FETCH_CONTESTABLE_ISSUES_FAILED, + contestedIssues: [], + getContestableIssues: getContestableIssuesSpy, + }); + render( + + + , + ); + + await waitFor(() => { + expect(getContestableIssuesSpy.called).to.be.true; + expect(getContestableIssuesSpy.args[0][0]).to.deep.equal(formData); + }); + }); }); diff --git a/src/applications/appeals/10182/tests/config/submitForm.unit.spec.js b/src/applications/appeals/10182/tests/config/submitForm.unit.spec.js index 8ccd4254fa42..a32cfd8f21bc 100644 --- a/src/applications/appeals/10182/tests/config/submitForm.unit.spec.js +++ b/src/applications/appeals/10182/tests/config/submitForm.unit.spec.js @@ -5,6 +5,7 @@ import formConfig from '../../config/form'; import maximalData from '../fixtures/data/maximal-test.json'; import submitForm from '../../config/submitForm'; +import { NEW_API, SUBMIT_URL_NEW } from '../../constants/apis'; describe('submitForm', () => { let xhr; @@ -24,7 +25,14 @@ describe('submitForm', () => { it('should use v1 endpoint', done => { const data = { data: maximalData.data }; submitForm(data, formConfig); - expect(requests[0].url).to.contain(`/v1/${formConfig.submitUrl}`); + expect(requests[0].url).to.contain(formConfig.submitUrl); + done(); + }); + + it('should use new v1 engine route endpoint', done => { + const data = { data: { ...maximalData.data, [NEW_API]: true } }; + submitForm(data, formConfig); + expect(requests[0].url).to.contain(SUBMIT_URL_NEW); done(); }); }); diff --git a/src/applications/appeals/10182/tests/containers/FormApp.unit.spec.jsx b/src/applications/appeals/10182/tests/containers/FormApp.unit.spec.jsx index 6e149e129fcc..07c84dfb7bc3 100644 --- a/src/applications/appeals/10182/tests/containers/FormApp.unit.spec.jsx +++ b/src/applications/appeals/10182/tests/containers/FormApp.unit.spec.jsx @@ -11,7 +11,11 @@ import { SET_DATA } from 'platform/forms-system/src/js/actions'; import { $ } from 'platform/forms-system/src/js/utilities/ui'; import FormApp from '../../containers/FormApp'; -import { CONTESTABLE_ISSUES_API } from '../../constants'; +import { + NEW_API, + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../../constants/apis'; import { FETCH_CONTESTABLE_ISSUES_SUCCEEDED, @@ -39,6 +43,7 @@ const getData = ({ data: { featureToggles: { loading: isLoading, + [NEW_API]: true, }, user: { login: { @@ -126,19 +131,17 @@ describe('FormApp', () => { }); }); - it('should call API when logged in', async () => { + it('should call new API if logged in', async () => { mockApiRequest(contestableIssuesResponse); - const { props, data } = getData({ - formData: { internalTesting: true }, - }); + const { props, data } = getData({ formData: { internalTesting: true } }); render( - + , ); await waitFor(() => { - expect(global.fetch.called).to.be.true; + expect(global.fetch.args[0][0]).to.contain(CONTESTABLE_ISSUES_API_NEW); resetFetch(); }); }); @@ -216,6 +219,7 @@ describe('FormApp', () => { }, ], areaOfDisagreement: [], + [NEW_API]: true, }, }); const store = mockStore(data); diff --git a/src/applications/appeals/10182/tests/pages/evidenceUpload.unit.spec.js b/src/applications/appeals/10182/tests/pages/evidenceUpload.unit.spec.js index f4085c112d30..4b22136832c7 100644 --- a/src/applications/appeals/10182/tests/pages/evidenceUpload.unit.spec.js +++ b/src/applications/appeals/10182/tests/pages/evidenceUpload.unit.spec.js @@ -5,10 +5,17 @@ import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import { uploadStore } from 'platform/forms-system/test/config/helpers'; +import environment from '~/platform/utilities/environment'; import { DefinitionTester, // selectCheckbox } from 'platform/testing/unit/schemaform-utils'; + import formConfig from '../../config/form'; +import { + NEW_API, + EVIDENCE_UPLOAD_API, + EVIDENCE_UPLOAD_API_NEW, +} from '../../constants/apis'; describe('Additional evidence upload', () => { const page = formConfig.chapters.boardReview.pages.evidenceUpload; @@ -81,6 +88,44 @@ describe('Additional evidence upload', () => { form.find('form').simulate('submit'); expect(form.find('.usa-input-error-message').length).to.equal(0); expect(onSubmit.called).to.be.true; + const { evidence } = onSubmit.args[0][0].uiSchema; + expect(evidence['ui:options'].fileUploadUrl).to.eq( + `${environment.API_URL}${EVIDENCE_UPLOAD_API}`, + ); + form.unmount(); + }); + + it('should submit to new URL with uploaded form', () => { + const onSubmit = sinon.spy(); + const form = mount( + + + , + ); + + form.find('form').simulate('submit'); + expect(form.find('.usa-input-error-message').length).to.equal(0); + expect(onSubmit.called).to.be.true; + const { evidence } = onSubmit.args[0][0].uiSchema; + expect(evidence['ui:options'].fileUploadUrl).to.eq( + `${environment.API_URL}${EVIDENCE_UPLOAD_API_NEW}`, + ); form.unmount(); }); }); diff --git a/src/applications/appeals/10182/utils/upload.js b/src/applications/appeals/10182/utils/upload.js index 475b903a4e1a..177001b6acce 100644 --- a/src/applications/appeals/10182/utils/upload.js +++ b/src/applications/appeals/10182/utils/upload.js @@ -5,6 +5,12 @@ import { EvidenceUploadDescription, } from '../content/EvidenceUpload'; +import { + NEW_API, + EVIDENCE_UPLOAD_API, + EVIDENCE_UPLOAD_API_NEW, +} from '../constants/apis'; + import { SUPPORTED_UPLOAD_TYPES, MAX_FILE_SIZE_BYTES, @@ -15,7 +21,7 @@ import { createPayload, parseResponse } from '../../shared/utils/upload'; export const evidenceUploadUI = { ...fileUiSchema(EvidenceUploadLabel, { - fileUploadUrl: `${environment.API_URL}/v1/decision_review_evidence`, + fileUploadUrl: `${environment.API_URL}${EVIDENCE_UPLOAD_API}`, fileTypes: SUPPORTED_UPLOAD_TYPES, maxSize: MAX_FILE_SIZE_BYTES, maxSizeText: `${MAX_FILE_SIZE_MB}MB`, @@ -26,6 +32,13 @@ export const evidenceUploadUI = { keepInPageOnReview: true, attachmentName: false, classNames: '', + updateUiSchema: formData => ({ + 'ui:options': { + fileUploadUrl: `${environment.API_URL}${ + formData[NEW_API] ? EVIDENCE_UPLOAD_API_NEW : EVIDENCE_UPLOAD_API + }`, + }, + }), }), 'ui:field': FileField, 'ui:description': EvidenceUploadDescription, diff --git a/src/applications/appeals/995/actions/index.js b/src/applications/appeals/995/actions/index.js index d9e11bca5604..9cd999005f27 100644 --- a/src/applications/appeals/995/actions/index.js +++ b/src/applications/appeals/995/actions/index.js @@ -1,5 +1,6 @@ import * as Sentry from '@sentry/browser'; +import environment from 'platform/utilities/environment'; import { apiRequest } from 'platform/utilities/api'; import { SUPPORTED_BENEFIT_TYPES, DEFAULT_BENEFIT_TYPE } from '../constants'; @@ -38,11 +39,11 @@ export const getContestableIssues = props => { ); } - const [apiVersion, baseApi] = newApi - ? CONTESTABLE_ISSUES_API_NEW - : CONTESTABLE_ISSUES_API; + const apiUrl = `${environment.API_URL}${ + newApi ? CONTESTABLE_ISSUES_API_NEW : CONTESTABLE_ISSUES_API + }/${benefitType}`; - return apiRequest(`${baseApi}/${benefitType}`, { apiVersion }) + return apiRequest(apiUrl) .then(response => dispatch({ type: FETCH_CONTESTABLE_ISSUES_SUCCEEDED, @@ -70,10 +71,10 @@ export const ITF_CREATION_SUCCEEDED = 'ITF_CREATION_SUCCEEDED'; export const ITF_CREATION_FAILED = 'ITF_CREATION_FAILED'; export function fetchITF({ accountUuid, inProgressFormId }) { - const [apiVersion, baseApi] = ITF_API; + const apiUrl = `${environment.API_URL}${ITF_API}`; return dispatch => { dispatch({ type: ITF_FETCH_INITIATED }); - return apiRequest(baseApi, { apiVersion }) + return apiRequest(apiUrl) .then(({ data }) => dispatch({ type: ITF_FETCH_SUCCEEDED, data })) .catch(() => { Sentry.withScope(scope => { @@ -91,14 +92,11 @@ export function createITF({ benefitType = DEFAULT_BENEFIT_TYPE, inProgressFormId, }) { - const [apiVersion, baseApi] = ITF_API; + const apiUrl = `${environment.API_URL}${ITF_API}/${benefitType}`; return dispatch => { dispatch({ type: ITF_CREATION_INITIATED }); - return apiRequest(`${baseApi}/${benefitType}`, { - method: 'POST', - apiVersion, - }) + return apiRequest(apiUrl, { method: 'POST' }) .then(({ data }) => dispatch({ type: ITF_CREATION_SUCCEEDED, data })) .catch(() => { Sentry.withScope(scope => { diff --git a/src/applications/appeals/995/config/form.js b/src/applications/appeals/995/config/form.js index 87be795eea47..0f6293b97cd1 100644 --- a/src/applications/appeals/995/config/form.js +++ b/src/applications/appeals/995/config/form.js @@ -116,7 +116,7 @@ const blankSchema = { type: 'object', properties: {} }; const formConfig = { rootUrl: manifest.rootUrl, urlPrefix: '/', - submitUrl: `/${SUBMIT_URL.join('')}`, + submitUrl: SUBMIT_URL, submit: submitForm, trackingPrefix: '995-supplemental-claim-', introduction: IntroductionPage, diff --git a/src/applications/appeals/995/config/submitForm.js b/src/applications/appeals/995/config/submitForm.js index 8795f0e87fa5..b013a064333a 100644 --- a/src/applications/appeals/995/config/submitForm.js +++ b/src/applications/appeals/995/config/submitForm.js @@ -12,7 +12,7 @@ const submitForm = (form, formConfig) => { const body = transform(formConfig, form); const url = `${environment.API_URL}${ - form.data[NEW_API] ? `/${SUBMIT_URL_NEW.join('')}` : submitUrl + form.data[NEW_API] ? SUBMIT_URL_NEW : submitUrl }`; // eventData for analytics diff --git a/src/applications/appeals/995/constants/apis.js b/src/applications/appeals/995/constants/apis.js index 1d1051d90e5b..3596005ce6a1 100644 --- a/src/applications/appeals/995/constants/apis.js +++ b/src/applications/appeals/995/constants/apis.js @@ -5,27 +5,28 @@ export const NEW_API = 'decisionReviewScNewApi'; // Not shown is the `{benefit_type}` suffix -export const CONTESTABLE_ISSUES_API = [ - 'v1', - '/supplemental_claims/contestable_issues', -]; +export const CONTESTABLE_ISSUES_API = + '/v1/supplemental_claims/contestable_issues'; + // Evidence upload API - same endpoint as NOD -export const EVIDENCE_UPLOAD_API = ['v1', '/decision_review_evidence']; -export const SUBMIT_URL = ['v1', '/supplemental_claims']; +export const EVIDENCE_UPLOAD_API = '/v1/decision_review_evidence'; + +export const SUBMIT_URL = '/v1/supplemental_claims'; // Not shown are the `v#` prefix and `{benefit_type}` suffix // Our team does not own this endpoint, so it's not included in the // modularization -export const ITF_API = ['v0', '/intent_to_file']; +export const ITF_API = '/v0/intent_to_file'; /** * New modularized API behind feature toggle: decision_review_sc_new_api * The endpoint will be the same until the backend has completed modularization */ -export const CONTESTABLE_ISSUES_API_NEW = [ - 'v1', - '/supplemental_claims/contestable_issues', -]; +export const CONTESTABLE_ISSUES_API_NEW = + '/decision_reviews/v1/supplemental_claims/contestable_issues'; + // Evidence upload API - same endpoint as NOD -export const EVIDENCE_UPLOAD_API_NEW = ['v1', '/decision_review_evidence']; -export const SUBMIT_URL_NEW = ['v1', '/supplemental_claims']; +export const EVIDENCE_UPLOAD_API_NEW = + '/decision_reviews/v1/decision_review_evidence'; + +export const SUBMIT_URL_NEW = '/decision_reviews/v1/supplemental_claims'; diff --git a/src/applications/appeals/995/tests/995-ITF.cypress.spec.js b/src/applications/appeals/995/tests/995-ITF.cypress.spec.js index a9a9aad04769..b7ca80f0d9d3 100644 --- a/src/applications/appeals/995/tests/995-ITF.cypress.spec.js +++ b/src/applications/appeals/995/tests/995-ITF.cypress.spec.js @@ -6,6 +6,7 @@ import { CONTESTABLE_ISSUES_API, ITF_API } from '../constants/apis'; import mockV2Data from './fixtures/data/maximal-test.json'; import { fetchItf, errorItf, postItf } from './995.cypress.helpers'; +import { mockContestableIssues } from '../../shared/tests/cypress.helpers'; import cypressSetup from '../../shared/tests/cypress.setup'; describe('995 ITF page', () => { @@ -18,8 +19,8 @@ describe('995 ITF page', () => { setStoredSubTask({ benefitType: 'compensation' }); cy.intercept( 'GET', - `/${CONTESTABLE_ISSUES_API.join('')}/compensation`, - [], + `${CONTESTABLE_ISSUES_API}/compensation`, + mockContestableIssues, ).as('getIssues'); cy.intercept('GET', '/v0/in_progress_forms/20-0995', mockV2Data); cy.intercept('PUT', '/v0/in_progress_forms/20-0995', mockV2Data); @@ -29,11 +30,10 @@ describe('995 ITF page', () => { cy.injectAxeThenAxeCheck(); }); - const itfApi = `/${ITF_API.join('')}`; - const postItfApi = `${itfApi}/compensation`; + const postItfApi = `${ITF_API}/compensation`; it('should show ITF found alert', () => { - cy.intercept('GET', itfApi, fetchItf()); + cy.intercept('GET', ITF_API, fetchItf()); cy.findAllByText(/start your claim/i, { selector: 'a' }) .first() @@ -54,7 +54,7 @@ describe('995 ITF page', () => { }); it('should show ITF created alert with too old active ITF', () => { - cy.intercept('GET', itfApi, fetchItf({ years: -2 })); + cy.intercept('GET', ITF_API, fetchItf({ years: -2 })); cy.intercept('POST', postItfApi, postItf()); cy.findAllByText(/start your claim/i, { selector: 'a' }) @@ -76,7 +76,7 @@ describe('995 ITF page', () => { }); it('should show ITF created alert if current ITF has already been used', () => { - cy.intercept('GET', itfApi, fetchItf({ months: -6 }, 'claim_recieved')); + cy.intercept('GET', ITF_API, fetchItf({ months: -6 }, 'claim_recieved')); cy.intercept('POST', postItfApi, postItf()); cy.findAllByText(/start your claim/i, { selector: 'a' }) @@ -98,7 +98,7 @@ describe('995 ITF page', () => { }); it('should show ITF created alert if current ITF is for pensions', () => { - cy.intercept('GET', itfApi, fetchItf({ months: 6 }, 'active', 'pension')); + cy.intercept('GET', ITF_API, fetchItf({ months: 6 }, 'active', 'pension')); cy.intercept('POST', postItfApi, postItf()); cy.findAllByText(/start your claim/i, { selector: 'a' }) @@ -120,7 +120,7 @@ describe('995 ITF page', () => { }); it('should show we can’t confirm error alert after creation error', () => { - cy.intercept('GET', itfApi, fetchItf({ years: -2 })); + cy.intercept('GET', ITF_API, fetchItf({ years: -2 })); cy.intercept('POST', postItfApi, errorItf()); cy.findAllByText(/start your claim/i, { selector: 'a' }) @@ -142,7 +142,7 @@ describe('995 ITF page', () => { }); it('should show we can’t confirm error alert after fetch & creation error', () => { - cy.intercept('GET', itfApi, errorItf()); + cy.intercept('GET', ITF_API, errorItf()); cy.intercept('POST', postItfApi, errorItf()); cy.findAllByText(/start your claim/i, { selector: 'a' }) diff --git a/src/applications/appeals/995/tests/995-contact-loop.cypress.spec.js b/src/applications/appeals/995/tests/995-contact-loop.cypress.spec.js index 5bfa1947157c..6f638ac9185c 100644 --- a/src/applications/appeals/995/tests/995-contact-loop.cypress.spec.js +++ b/src/applications/appeals/995/tests/995-contact-loop.cypress.spec.js @@ -23,13 +23,13 @@ describe('995 contact info loop', () => { setStoredSubTask({ benefitType: 'compensation' }); cy.intercept( 'GET', - `/${CONTESTABLE_ISSUES_API.join('')}/compensation`, + `${CONTESTABLE_ISSUES_API}/compensation`, mockContestableIssues, ).as('getIssues'); cy.intercept('GET', '/v0/in_progress_forms/20-0995', mockV2Data); cy.intercept('PUT', '/v0/in_progress_forms/20-0995', mockV2Data); - cy.intercept('GET', `/${ITF_API.join('')}`, fetchItf()); + cy.intercept('GET', ITF_API, fetchItf()); cy.intercept('PUT', '/v0/profile/telephones', mockTelephoneUpdate); cy.intercept('GET', '/v0/profile/status/*', mockTelephoneUpdateSuccess); diff --git a/src/applications/appeals/995/tests/995-downtime-notification.cypress.spec.js b/src/applications/appeals/995/tests/995-downtime-notification.cypress.spec.js index 8c4f92843bd9..1da028d24602 100644 --- a/src/applications/appeals/995/tests/995-downtime-notification.cypress.spec.js +++ b/src/applications/appeals/995/tests/995-downtime-notification.cypress.spec.js @@ -8,7 +8,7 @@ import downtimeTesting from '../../shared/tests/cypress.downtime'; downtimeTesting({ baseUrl: SC_BASE_URL, - contestableApi: `/${CONTESTABLE_ISSUES_API.join('')}/`, + contestableApi: CONTESTABLE_ISSUES_API, formId: '20-0995', data: mockData.data, inProgressVersion: 1, diff --git a/src/applications/appeals/995/tests/995-keyboard-only.cypress.spec.js b/src/applications/appeals/995/tests/995-keyboard-only.cypress.spec.js index fe5457dfd61c..e0e8670cf6ae 100644 --- a/src/applications/appeals/995/tests/995-keyboard-only.cypress.spec.js +++ b/src/applications/appeals/995/tests/995-keyboard-only.cypress.spec.js @@ -1,6 +1,7 @@ import path from 'path'; import formConfig from '../config/form'; +import mockPrefill from './fixtures/mocks/prefill.json'; import mockInProgress from './fixtures/mocks/in-progress-forms.json'; import mockSubmit from './fixtures/mocks/application-submit.json'; @@ -19,16 +20,17 @@ describe('Supplemental Claim keyboard only navigation', () => { 'testData', ); - cy.intercept('PUT', '/v0/in_progress_forms/995', mockInProgress); + cy.intercept('GET', '/v0/in_progress_forms/20-0995', mockPrefill); + cy.intercept('PUT', '/v0/in_progress_forms/20-0995', mockInProgress); cy.intercept('POST', formConfig.submitUrl, mockSubmit); - cy.intercept('GET', `/${ITF_API.join('')}`, fetchItf()); + cy.intercept('GET', ITF_API, fetchItf()); }); it('navigates through a maximal form', () => { cy.get('@testData').then(({ data }) => { const { chapters } = formConfig; - cy.intercept('GET', `/${CONTESTABLE_ISSUES_API.join('')}/compensation`, { + cy.intercept('GET', `${CONTESTABLE_ISSUES_API}/compensation`, { data: fixDecisionDates(data.contestedIssues, { unselected: true }), }); cy.visit( diff --git a/src/applications/appeals/995/tests/995-max-selections.cypress.spec.js b/src/applications/appeals/995/tests/995-max-selections.cypress.spec.js index cec407371398..37483116c0a8 100644 --- a/src/applications/appeals/995/tests/995-max-selections.cypress.spec.js +++ b/src/applications/appeals/995/tests/995-max-selections.cypress.spec.js @@ -8,7 +8,7 @@ import preventMaxSelections from '../../shared/tests/cypress.max-selections'; preventMaxSelections({ baseUrl: SC_BASE_URL, - contestableApi: `/${CONTESTABLE_ISSUES_API.join('')}/compensation`, + contestableApi: `${CONTESTABLE_ISSUES_API}/compensation`, formId: '20-0995', data: mockData.data, inProgressVersion: 1, diff --git a/src/applications/appeals/995/tests/995.cypress.helpers.js b/src/applications/appeals/995/tests/995.cypress.helpers.js index 97c3f9315543..b8332e590693 100644 --- a/src/applications/appeals/995/tests/995.cypress.helpers.js +++ b/src/applications/appeals/995/tests/995.cypress.helpers.js @@ -141,8 +141,8 @@ export const setupPerTest = (_testData, toggles = []) => { setStoredSubTask({ benefitType: 'compensation' }); - cy.intercept('POST', `/${EVIDENCE_UPLOAD_API.join('')}`, mockUpload); - cy.intercept('GET', `/${ITF_API.join('')}`, fetchItf()); + cy.intercept('POST', EVIDENCE_UPLOAD_API, mockUpload); + cy.intercept('GET', ITF_API, fetchItf()); cy.intercept('GET', '/v0/feature_toggles*', { data: { type: 'feature_toggles', @@ -154,13 +154,13 @@ export const setupPerTest = (_testData, toggles = []) => { const dataSet = Cypress.currentTest.titlePath[1]; cy.intercept( 'GET', - `/${CONTESTABLE_ISSUES_API.join('')}/compensation`, + `${CONTESTABLE_ISSUES_API}/compensation`, dataSet === 'maximal-test' ? mockContestableIssuesWithLegacyAppeals : mockContestableIssues, ).as('getIssues'); - cy.intercept('POST', `/${SUBMIT_URL.join('')}`, mockSubmit); + cy.intercept('POST', SUBMIT_URL, mockSubmit); cy.get('@testData').then(() => { cy.intercept('GET', '/v0/in_progress_forms/20-0995', mockPrefill); diff --git a/src/applications/appeals/995/tests/actions/actions.unit.spec.js b/src/applications/appeals/995/tests/actions/actions.unit.spec.js index 31c84aadad04..1fa1a34e888e 100644 --- a/src/applications/appeals/995/tests/actions/actions.unit.spec.js +++ b/src/applications/appeals/995/tests/actions/actions.unit.spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { testkit } from 'platform/testing/unit/sentry'; +import * as apiUtils from 'platform/utilities/api'; import { mockApiRequest } from 'platform/testing/unit/helpers'; @@ -15,6 +16,13 @@ import { ITF_CREATION_SUCCEEDED, ITF_CREATION_FAILED, } from '../../actions'; + +import { + NEW_API, + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../../constants/apis'; + import { FETCH_CONTESTABLE_ISSUES_INIT, FETCH_CONTESTABLE_ISSUES_SUCCEEDED, @@ -22,8 +30,8 @@ import { } from '../../../shared/actions'; describe('fetch contestable issues action', () => { + const mockData = { data: 'asdf' }; it('should dispatch an init action', () => { - const mockData = { data: 'asdf' }; const benefitType = 'compensation'; mockApiRequest(mockData); const dispatch = sinon.spy(); @@ -42,7 +50,6 @@ describe('fetch contestable issues action', () => { }); it('should dispatch an add person failed action', () => { - const mockData = { data: 'asdf' }; const props = { benefitType: 'compensation' }; mockApiRequest(mockData, false); const dispatch = sinon.spy(); @@ -57,7 +64,6 @@ describe('fetch contestable issues action', () => { }); it('should dispatch failed action from unsupported benefitType', () => { - const mockData = { data: 'asdf' }; mockApiRequest(mockData, false); const dispatch = sinon.spy(); return getContestableIssues({ benefitType: 'foo' })(dispatch).then(() => { @@ -69,6 +75,35 @@ describe('fetch contestable issues action', () => { ); }); }); + + describe('test new apiRequest', () => { + let apiRequestSpy; + beforeEach(() => { + apiRequestSpy = sinon.stub(apiUtils, 'apiRequest').resolves(mockData); + }); + afterEach(() => { + apiRequestSpy.restore(); + }); + it('should dispatch an init action', () => { + mockApiRequest(mockData); + const dispatch = sinon.spy(); + return getContestableIssues()(dispatch).then(() => { + // Original API + expect(apiRequestSpy.args[0][0]).to.contain(CONTESTABLE_ISSUES_API); + }); + }); + it('should dispatch an init action with new API endpoint', () => { + mockApiRequest(mockData); + const dispatch = sinon.spy(); + return getContestableIssues({ [NEW_API]: true })(dispatch).then(() => { + return getContestableIssues()(dispatch).then(() => { + expect(apiRequestSpy.args[0][0]).to.contain( + CONTESTABLE_ISSUES_API_NEW, + ); + }); + }); + }); + }); }); describe('ITF actions', () => { diff --git a/src/applications/appeals/995/tests/config/submitForm.unit.spec.js b/src/applications/appeals/995/tests/config/submitForm.unit.spec.js index 9740e181efff..c0bef3bc2235 100644 --- a/src/applications/appeals/995/tests/config/submitForm.unit.spec.js +++ b/src/applications/appeals/995/tests/config/submitForm.unit.spec.js @@ -3,7 +3,7 @@ import sinon from 'sinon'; import maximalTest from '../fixtures/data/maximal-test.json'; import formConfig from '../../config/form'; -import { SUBMIT_URL } from '../../constants/apis'; +import { NEW_API, SUBMIT_URL, SUBMIT_URL_NEW } from '../../constants/apis'; import submitForm from '../../config/submitForm'; @@ -24,7 +24,14 @@ describe('submitForm', () => { it('should use v1 endpoint with v2 data', done => { submitForm(maximalTest, formConfig); - expect(requests[0].url).to.contain(SUBMIT_URL.join('')); + expect(requests[0].url).to.contain(SUBMIT_URL); + done(); + }); + + it('should use new v1 engine route endpoint', done => { + const data = { data: { [NEW_API]: true } }; + submitForm(data, formConfig); + expect(requests[0].url).to.contain(SUBMIT_URL_NEW); done(); }); }); diff --git a/src/applications/appeals/995/tests/containers/App.unit.spec.jsx b/src/applications/appeals/995/tests/containers/App.unit.spec.jsx index fba7f53ee335..833786e8b34c 100644 --- a/src/applications/appeals/995/tests/containers/App.unit.spec.jsx +++ b/src/applications/appeals/995/tests/containers/App.unit.spec.jsx @@ -10,6 +10,7 @@ import * as Sentry from '@sentry/browser'; import { setStoredSubTask } from '@department-of-veterans-affairs/platform-forms/sub-task'; import { $ } from '@department-of-veterans-affairs/platform-forms-system/ui'; import { SET_DATA } from 'platform/forms-system/src/js/actions'; +import { mockApiRequest, resetFetch } from 'platform/testing/unit/helpers'; import App from '../../containers/App'; @@ -18,12 +19,19 @@ import { SC_NEW_FORM_TOGGLE, SC_NEW_FORM_DATA, } from '../../constants'; +import { + NEW_API, + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../../constants/apis'; + import { SELECTED } from '../../../shared/constants'; import { FETCH_CONTESTABLE_ISSUES_SUCCEEDED, FETCH_CONTESTABLE_ISSUES_FAILED, } from '../../../shared/actions'; -import { NEW_API } from '../../constants/apis'; + +import { contestableIssuesResponse } from '../../../shared/tests/fixtures/mocks/contestable-issues.json'; const hasComp = { benefitType: 'compensation' }; @@ -37,6 +45,7 @@ const getData = ({ push = () => {}, status = '', toggle = false, + toggleApi = false, } = {}) => { setStoredSubTask({ benefitType: data?.benefitType || '' }); return { @@ -72,7 +81,7 @@ const getData = ({ featureToggles: { loading: false, [SC_NEW_FORM_TOGGLE]: toggle, - [NEW_API]: toggle, + [NEW_API]: toggleApi, }, contestableIssues: { status, @@ -192,6 +201,41 @@ describe('App', () => { }); }); + it('should call contestable issues API if logged in', async () => { + mockApiRequest(contestableIssuesResponse); + const { props, data } = getData({ + data: { ...hasComp, internalTesting: true }, + }); + render( + + + , + ); + + await waitFor(() => { + expect(global.fetch.args[0][0]).to.contain(CONTESTABLE_ISSUES_API); + resetFetch(); + }); + }); + + it('should call new contestable issues API if logged in', async () => { + mockApiRequest(contestableIssuesResponse); + const { props, data } = getData({ + data: { ...hasComp, internalTesting: true }, + toggleApi: true, + }); + render( + + + , + ); + + await waitFor(() => { + expect(global.fetch.args[0][0]).to.contain(CONTESTABLE_ISSUES_API_NEW); + resetFetch(); + }); + }); + it('should update contested issues', async () => { const { props, data } = getData({ loggedIn: true, @@ -345,7 +389,7 @@ describe('App', () => { it('should set feature toggle in form data', async () => { setStoredSubTask(hasComp); - const { props, data } = getData({ toggle: true }); + const { props, data } = getData({ toggle: true, toggleApi: true }); const store = mockStore(data); render( diff --git a/src/applications/appeals/995/tests/utils/upload.unit.spec.js b/src/applications/appeals/995/tests/utils/upload.unit.spec.js new file mode 100644 index 000000000000..d28d1b371f25 --- /dev/null +++ b/src/applications/appeals/995/tests/utils/upload.unit.spec.js @@ -0,0 +1,38 @@ +import { expect } from 'chai'; + +import environment from 'platform/utilities/environment'; + +import formConfig from '../../config/form'; +import { + NEW_API, + EVIDENCE_UPLOAD_API, + EVIDENCE_UPLOAD_API_NEW, +} from '../../constants/apis'; + +describe('Supplemental Claims evidence upload page', () => { + const { uiSchema } = formConfig.chapters.evidence.pages.evidenceUpload; + + // Increase test coverage + it('should updateUiSchema for current API', () => { + window.location = { pathname: '/review-and-submit' }; + const result = uiSchema.additionalDocuments['ui:options'].updateUiSchema({ + [NEW_API]: false, + }); + expect(result).to.deep.equal({ + 'ui:options': { + fileUploadUrl: `${environment.API_URL}${EVIDENCE_UPLOAD_API}`, + }, + }); + }); + it('should updateUiSchema for new API', () => { + window.location = { pathname: '/review-and-submit' }; + const result = uiSchema.additionalDocuments['ui:options'].updateUiSchema({ + [NEW_API]: true, + }); + expect(result).to.deep.equal({ + 'ui:options': { + fileUploadUrl: `${environment.API_URL}${EVIDENCE_UPLOAD_API_NEW}`, + }, + }); + }); +}); diff --git a/src/applications/appeals/995/utils/evidence.js b/src/applications/appeals/995/utils/evidence.js index de5c96eb0ae5..ea18d79658c0 100644 --- a/src/applications/appeals/995/utils/evidence.js +++ b/src/applications/appeals/995/utils/evidence.js @@ -112,6 +112,6 @@ export const onFormLoaded = props => { return location; }); } - router.push(returnUrl); + router?.push(returnUrl); // return formData; // for testing only }; diff --git a/src/applications/appeals/995/utils/upload.js b/src/applications/appeals/995/utils/upload.js index 9e72441d98ee..a03dee15c85d 100644 --- a/src/applications/appeals/995/utils/upload.js +++ b/src/applications/appeals/995/utils/upload.js @@ -20,7 +20,7 @@ import { createPayload, parseResponse } from '../../shared/utils/upload'; export const fileUploadUi = content => ({ ...fileUiSchema(content.label, { itemDescription: content.description, - fileUploadUrl: `${environment.API_URL}/${EVIDENCE_UPLOAD_API.join('')}`, + fileUploadUrl: `${environment.API_URL}${EVIDENCE_UPLOAD_API}`, fileTypes: SUPPORTED_UPLOAD_TYPES, maxSize: MAX_FILE_SIZE_BYTES, maxSizeText: `${MAX_FILE_SIZE_MB}MB`, @@ -40,10 +40,9 @@ export const fileUploadUi = content => ({ attachmentName: false, updateUiSchema: formData => ({ 'ui:options': { - fileUploadUrl: `${environment.API_URL}/${(formData[NEW_API] - ? EVIDENCE_UPLOAD_API_NEW - : EVIDENCE_UPLOAD_API - ).join('')}`, + fileUploadUrl: `${environment.API_URL}${ + formData[NEW_API] ? EVIDENCE_UPLOAD_API_NEW : EVIDENCE_UPLOAD_API + }`, }, }), }), diff --git a/src/applications/appeals/996/actions/index.js b/src/applications/appeals/996/actions/index.js index 1d3795ce36e5..d3176e20cec9 100644 --- a/src/applications/appeals/996/actions/index.js +++ b/src/applications/appeals/996/actions/index.js @@ -1,10 +1,12 @@ +import environment from 'platform/utilities/environment'; import { apiRequest } from 'platform/utilities/api'; +import { SUPPORTED_BENEFIT_TYPES, DEFAULT_BENEFIT_TYPE } from '../constants'; import { - SUPPORTED_BENEFIT_TYPES, - DEFAULT_BENEFIT_TYPE, + NEW_API, CONTESTABLE_ISSUES_API, -} from '../constants'; + CONTESTABLE_ISSUES_API_NEW, +} from '../constants/apis'; import { FETCH_CONTESTABLE_ISSUES_INIT, @@ -14,6 +16,7 @@ import { export const getContestableIssues = props => { const benefitType = props?.benefitType || DEFAULT_BENEFIT_TYPE; + const newApi = props?.[NEW_API]; return dispatch => { dispatch({ type: FETCH_CONTESTABLE_ISSUES_INIT }); @@ -22,10 +25,8 @@ export const getContestableIssues = props => { ); if (!foundBenefitType || !foundBenefitType?.isSupported) { - return Promise.reject({ - error: 'invalidBenefitType', - type: foundBenefitType?.label || benefitType, - }).catch(errors => + // { error: 'invalidBenefitType', type: foundBenefitType?.label || benefitType } + return Promise.reject(new Error('invalidBenefitType')).catch(errors => dispatch({ type: FETCH_CONTESTABLE_ISSUES_FAILED, errors, @@ -33,9 +34,11 @@ export const getContestableIssues = props => { ); } - return apiRequest(`${CONTESTABLE_ISSUES_API}${benefitType}`, { - apiVersion: 'v1', - }) + const apiUrl = `${environment.API_URL}${ + newApi ? CONTESTABLE_ISSUES_API_NEW : CONTESTABLE_ISSUES_API + }/${benefitType}`; + + return apiRequest(apiUrl) .then(response => dispatch({ type: FETCH_CONTESTABLE_ISSUES_SUCCEEDED, diff --git a/src/applications/appeals/996/config/form.js b/src/applications/appeals/996/config/form.js index 394350d41566..d6420021861f 100644 --- a/src/applications/appeals/996/config/form.js +++ b/src/applications/appeals/996/config/form.js @@ -36,6 +36,7 @@ import informalConferenceTime from '../pages/informalConferenceTime'; import informalConferenceTimeRep from '../pages/informalConferenceTimeRep'; import { errorMessages, ADD_ISSUE_PATH } from '../constants'; +import { SUBMIT_URL } from '../constants/apis'; import { mayHaveLegacyAppeals, showNewHlrContent, @@ -68,7 +69,7 @@ import manifest from '../manifest.json'; const formConfig = { rootUrl: manifest.rootUrl, urlPrefix: '/', - submitUrl: 'higher_level_reviews', + submitUrl: SUBMIT_URL, submit: submitForm, trackingPrefix: 'decision-reviews-va20-0996-', downtime: { diff --git a/src/applications/appeals/996/config/submitForm.js b/src/applications/appeals/996/config/submitForm.js index 6cd423024964..da348d4a9baf 100644 --- a/src/applications/appeals/996/config/submitForm.js +++ b/src/applications/appeals/996/config/submitForm.js @@ -3,6 +3,8 @@ import { submitToUrl } from 'platform/forms-system/src/js/actions'; import { showNewHlrContent, hideNewHlrContent } from '../utils/helpers'; +import { NEW_API, SUBMIT_URL_NEW } from '../constants/apis'; + export const buildEventData = formData => { const { informalConference, informalConferenceChoice } = formData; let informalConf = 'no'; @@ -23,9 +25,10 @@ export const buildEventData = formData => { const submitForm = (form, formConfig) => { const { submitUrl, trackingPrefix } = formConfig; const body = formConfig.transformForSubmit(formConfig, form); - const version = form.data.hlrUpdatedContent ? 'v2' : 'v1'; - const url = [environment.API_URL, version, submitUrl].join('/'); + const url = `${environment.API_URL}${ + form.data[NEW_API] ? SUBMIT_URL_NEW : submitUrl + }`; // eventData for analytics const eventData = buildEventData(form.data); diff --git a/src/applications/appeals/996/constants/apis.js b/src/applications/appeals/996/constants/apis.js new file mode 100644 index 000000000000..3a5569fb6e97 --- /dev/null +++ b/src/applications/appeals/996/constants/apis.js @@ -0,0 +1,21 @@ +/** + * NOTE: If any of these API values change, please update the documentation: + * https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/products/decision-reviews/higher-level-review/engineering/HLR_frontend_backend_interactions.md + */ + +export const NEW_API = 'decisionReviewHlrNewApi'; + +// Not shown is the `{benefit_type}` suffix +export const CONTESTABLE_ISSUES_API = + '/v1/higher_level_reviews/contestable_issues'; + +export const SUBMIT_URL = '/v2/higher_level_reviews'; + +/** + * New modularized API behind feature toggle: decision_review_sc_new_api + * The endpoint will be the same until the backend has completed modularization + */ +export const CONTESTABLE_ISSUES_API_NEW = + '/decision_reviews/v1/higher_level_reviews/contestable_issues'; + +export const SUBMIT_URL_NEW = '/decision_reviews/v2/higher_level_reviews'; diff --git a/src/applications/appeals/996/constants.js b/src/applications/appeals/996/constants/index.js similarity index 97% rename from src/applications/appeals/996/constants.js rename to src/applications/appeals/996/constants/index.js index e04e82aa4004..2cdb0b569c2a 100644 --- a/src/applications/appeals/996/constants.js +++ b/src/applications/appeals/996/constants/index.js @@ -34,9 +34,6 @@ export const PROFILE_URL = '/profile'; // anchor (not an accordion) export const BENEFIT_OFFICES_URL = `${HLR_INFO_URL}#file-by-mail-in-person-or-with`; -export const CONTESTABLE_ISSUES_API = - '/higher_level_reviews/contestable_issues/'; - // Including a default until we determine how to get around the user restarting // the application after using the "Finish this application later" link // See https://dsva.slack.com/archives/C0113MPTGH5/p1600725048027200 diff --git a/src/applications/appeals/996/containers/Form0996App.jsx b/src/applications/appeals/996/containers/Form0996App.jsx index 6d85fe502280..0a71972e889f 100644 --- a/src/applications/appeals/996/containers/Form0996App.jsx +++ b/src/applications/appeals/996/containers/Form0996App.jsx @@ -15,6 +15,7 @@ import { DATA_DOG_SERVICE, SUPPORTED_BENEFIT_TYPES_LIST, } from '../constants'; +import { NEW_API } from '../constants/apis'; import { getContestableIssues as getContestableIssuesAction } from '../actions'; @@ -80,7 +81,10 @@ export const Form0996App = ({ if (!isLoadingIssues && (contestableIssues?.status || '') === '') { // load benefit type contestable issues setIsLoadingIssues(true); - getContestableIssues({ benefitType: formData.benefitType }); + getContestableIssues({ + benefitType: formData.benefitType, + [NEW_API]: toggles[NEW_API], + }); } else if ( contestableIssues.status === FETCH_CONTESTABLE_ISSUES_SUCCEEDED && (issuesNeedUpdating( @@ -132,30 +136,35 @@ export const Form0996App = ({ isLoadingIssues, legacyCount, loggedIn, + pathname, setFormData, subTaskBenefitType, - pathname, + toggles, ], ); useEffect( () => { const isUpdated = toggles.hlrUpdateedContnet || false; // expected typo + const isUpdatedApi = toggles[NEW_API] || false; if ( !toggles.loading && (typeof formData.hlrUpdatedContent === 'undefined' || - formData.hlrUpdatedContent !== isUpdated) + formData.hlrUpdatedContent !== isUpdated || + typeof formData[NEW_API] === 'undefined' || + formData[NEW_API] !== isUpdatedApi) ) { setFormData({ ...formData, hlrUpdatedContent: isUpdated, + [NEW_API]: toggles[NEW_API], }); // temp storage, used for homelessness page focus management sessionStorage.setItem('hlrUpdated', isUpdated); } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [toggles, formData.hlrUpdatedContent], + [toggles, formData.hlrUpdatedContent, formData[NEW_API]], ); let content = ( diff --git a/src/applications/appeals/996/tests/actions/actions.unit.spec.js b/src/applications/appeals/996/tests/actions/actions.unit.spec.js index 05226938a056..0a918d5f5fba 100644 --- a/src/applications/appeals/996/tests/actions/actions.unit.spec.js +++ b/src/applications/appeals/996/tests/actions/actions.unit.spec.js @@ -2,8 +2,16 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { mockApiRequest } from 'platform/testing/unit/helpers'; +import * as apiUtils from 'platform/utilities/api'; import { getContestableIssues } from '../../actions'; + +import { + NEW_API, + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../../constants/apis'; + import { FETCH_CONTESTABLE_ISSUES_INIT, FETCH_CONTESTABLE_ISSUES_SUCCEEDED, @@ -11,8 +19,8 @@ import { } from '../../../shared/actions'; describe('fetch contestable issues action', () => { + const mockData = { data: 'asdf' }; it('should dispatch an init action', () => { - const mockData = { data: 'asdf' }; const benefitType = 'compensation'; mockApiRequest(mockData); const dispatch = sinon.spy(); @@ -31,7 +39,6 @@ describe('fetch contestable issues action', () => { }); it('should dispatch an add person failed action', () => { - const mockData = { data: 'asdf' }; mockApiRequest(mockData, false); const dispatch = sinon.spy(); return getContestableIssues()(dispatch).then(() => { @@ -45,7 +52,6 @@ describe('fetch contestable issues action', () => { }); it('should dispatch failed action from unsupported benefitType', () => { - const mockData = { data: 'asdf' }; mockApiRequest(mockData, false); const dispatch = sinon.spy(); return getContestableIssues({ benefitType: 'foo' })(dispatch).then(() => { @@ -57,4 +63,33 @@ describe('fetch contestable issues action', () => { ); }); }); + + describe('test apiRequest', () => { + let apiRequestSpy; + beforeEach(() => { + apiRequestSpy = sinon.stub(apiUtils, 'apiRequest').resolves(mockData); + }); + afterEach(() => { + apiRequestSpy.restore(); + }); + it('should dispatch an init action', () => { + mockApiRequest(mockData); + const dispatch = sinon.spy(); + return getContestableIssues()(dispatch).then(() => { + // Original API + expect(apiRequestSpy.args[0][0]).to.contain(CONTESTABLE_ISSUES_API); + }); + }); + it('should dispatch an init action with new API endpoint', () => { + mockApiRequest(mockData); + const dispatch = sinon.spy(); + return getContestableIssues({ [NEW_API]: true })(dispatch).then(() => { + return getContestableIssues()(dispatch).then(() => { + expect(apiRequestSpy.args[0][0]).to.contain( + CONTESTABLE_ISSUES_API_NEW, + ); + }); + }); + }); + }); }); diff --git a/src/applications/appeals/996/tests/config/submitForm.unit.spec.js b/src/applications/appeals/996/tests/config/submitForm.unit.spec.js index 8962eed3707e..9d3bc103061a 100644 --- a/src/applications/appeals/996/tests/config/submitForm.unit.spec.js +++ b/src/applications/appeals/996/tests/config/submitForm.unit.spec.js @@ -4,6 +4,7 @@ import sinon from 'sinon'; import formConfig from '../../config/form'; import maximalTestV2 from '../fixtures/data/maximal-test-v2.json'; import maximalTestV25 from '../fixtures/data/maximal-test-v2.5.json'; +import { SUBMIT_URL, SUBMIT_URL_NEW } from '../../constants/apis'; import submitForm, { buildEventData } from '../../config/submitForm'; @@ -53,17 +54,15 @@ describe('submitForm', () => { xhr.restore(); }); - it('should use v1 endpoint with v1 data', done => { + it('should use v2 endpoint with v2 data', done => { submitForm(maximalTestV2, formConfig); - expect(requests[0].url).to.contain('/v1/higher_level_reviews'); - expect(requests[0].url.split('://')[1]).to.not.contain('//'); + expect(requests[0].url).to.contain(SUBMIT_URL); done(); }); - it('should use v1 endpoint with v2 data', done => { + it('should use v2 endpoint with v3 data', done => { submitForm(maximalTestV25, formConfig); - expect(requests[0].url).to.contain('/v2/higher_level_reviews'); - expect(requests[0].url.split('://')[1]).to.not.contain('//'); + expect(requests[0].url).to.contain(SUBMIT_URL_NEW); done(); }); }); diff --git a/src/applications/appeals/996/tests/containers/FormApp.unit.spec.jsx b/src/applications/appeals/996/tests/containers/FormApp.unit.spec.jsx index cb790549be60..6bbfd95e056e 100644 --- a/src/applications/appeals/996/tests/containers/FormApp.unit.spec.jsx +++ b/src/applications/appeals/996/tests/containers/FormApp.unit.spec.jsx @@ -12,7 +12,11 @@ import { mockApiRequest, resetFetch } from '~/platform/testing/unit/helpers'; import { SET_DATA } from '~/platform/forms-system/src/js/actions'; import Form0996App from '../../containers/Form0996App'; -import { CONTESTABLE_ISSUES_API } from '../../constants'; +import { + NEW_API, + CONTESTABLE_ISSUES_API, + CONTESTABLE_ISSUES_API_NEW, +} from '../../constants/apis'; import { SELECTED } from '../../../shared/constants'; import { @@ -63,6 +67,7 @@ const getData = ({ // eslint-disable-next-line camelcase hlr_updateed_contnet: true, hlrUpdateedContnet: true, + [NEW_API]: true, }, }, }; @@ -181,6 +186,24 @@ describe('Form0996App', () => { }); }); + it('should call API is logged in', async () => { + mockApiRequest(contestableIssuesResponse); + + const { props, data } = getData({ + formData: { benefitType: 'compensation', internalTesting: true }, + }); + render( + + + , + ); + + await waitFor(() => { + expect(global.fetch.args[0][0]).to.contain(CONTESTABLE_ISSUES_API_NEW); + resetFetch(); + }); + }); + it('should update benefit type in form data', async () => { const { props, data } = getData({ loggedIn: true, formData: {} }); const store = mockStore(data); @@ -286,6 +309,7 @@ describe('Form0996App', () => { legacyCount: 0, internalTesting: true, hlrUpdatedContent: true, + [NEW_API]: true, }, }); const store = mockStore(data); @@ -376,6 +400,7 @@ describe('Form0996App', () => { additionalIssues, legacyCount: 0, hlrUpdatedContent: true, + [NEW_API]: true, }, }); const store = mockStore(data); diff --git a/src/applications/appeals/996/tests/fixtures/data/maximal-test-v2.5.json b/src/applications/appeals/996/tests/fixtures/data/maximal-test-v2.5.json index 32796bc1f271..86355a4ff5cd 100644 --- a/src/applications/appeals/996/tests/fixtures/data/maximal-test-v2.5.json +++ b/src/applications/appeals/996/tests/fixtures/data/maximal-test-v2.5.json @@ -35,6 +35,7 @@ }, "socOptIn": true, "hlrUpdatedContent": true, + "decisionReviewHlrNewApi": true, "informalConferenceTime": "time0800to1200", "contestedIssues": [ { diff --git a/src/applications/appeals/996/tests/hlr-contact-loop.cypress.spec.js b/src/applications/appeals/996/tests/hlr-contact-loop.cypress.spec.js index 7a4bb53b9e44..f4fe0baecfce 100644 --- a/src/applications/appeals/996/tests/hlr-contact-loop.cypress.spec.js +++ b/src/applications/appeals/996/tests/hlr-contact-loop.cypress.spec.js @@ -7,11 +7,8 @@ */ import { setStoredSubTask } from '@department-of-veterans-affairs/platform-forms/sub-task'; -import { - BASE_URL, - CONTESTABLE_ISSUES_API, - CONTACT_INFO_PATH, -} from '../constants'; +import { BASE_URL, CONTACT_INFO_PATH } from '../constants'; +import { CONTESTABLE_ISSUES_API } from '../constants/apis'; import mockV2Data from './fixtures/data/maximal-test-v2.json'; import cypressSetup from '../../shared/tests/cypress.setup'; @@ -29,7 +26,7 @@ describe('HLR contact info loop', () => { window.dataLayer = []; setStoredSubTask({ benefitType: 'compensation' }); - cy.intercept('GET', `/v1${CONTESTABLE_ISSUES_API}compensation`, []).as( + cy.intercept('GET', `${CONTESTABLE_ISSUES_API}/compensation`, []).as( 'getIssues', ); cy.intercept('GET', '/v0/in_progress_forms/20-0996', mockV2Data); diff --git a/src/applications/appeals/996/tests/hlr-downtime-notification.cypress.spec.js b/src/applications/appeals/996/tests/hlr-downtime-notification.cypress.spec.js index 92f449802914..ece066e14f21 100644 --- a/src/applications/appeals/996/tests/hlr-downtime-notification.cypress.spec.js +++ b/src/applications/appeals/996/tests/hlr-downtime-notification.cypress.spec.js @@ -1,4 +1,4 @@ -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { CONTESTABLE_ISSUES_API } from '../constants/apis'; import mockData from './fixtures/data/minimal-test-v2.json'; import mockInProgress from './fixtures/mocks/in-progress-forms.json'; @@ -8,7 +8,7 @@ import downtimeTesting from '../../shared/tests/cypress.downtime'; downtimeTesting({ baseUrl: HLR_BASE_URL, - contestableApi: `/v0${CONTESTABLE_ISSUES_API}`, + contestableApi: `${CONTESTABLE_ISSUES_API}/`, formId: '20-0996', data: mockData.data, inProgressVersion: 3, diff --git a/src/applications/appeals/996/tests/hlr-keyboard-only.cypress.spec.js b/src/applications/appeals/996/tests/hlr-keyboard-only.cypress.spec.js index f24b428e98bf..ceaf0c206807 100644 --- a/src/applications/appeals/996/tests/hlr-keyboard-only.cypress.spec.js +++ b/src/applications/appeals/996/tests/hlr-keyboard-only.cypress.spec.js @@ -1,7 +1,7 @@ import { resetStoredSubTask } from '@department-of-veterans-affairs/platform-forms/sub-task'; import formConfig from '../config/form'; -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { CONTESTABLE_ISSUES_API } from '../constants/apis'; import mockV2p5Data from './fixtures/data/maximal-test-v2.5.json'; import mockInProgress from './fixtures/mocks/in-progress-forms.json'; @@ -29,7 +29,7 @@ describe('Higher-Level Review keyboard only navigation', () => { cy.get('@testData').then(data => { const { chapters } = formConfig; - cy.intercept('GET', `/v1${CONTESTABLE_ISSUES_API}compensation`, { + cy.intercept('GET', `${CONTESTABLE_ISSUES_API}/compensation`, { data: fixDecisionDates(data.contestedIssues, { unselected: true }), }).as('getIssues'); cy.visit( diff --git a/src/applications/appeals/996/tests/hlr-max-selections.cypress.spec.js b/src/applications/appeals/996/tests/hlr-max-selections.cypress.spec.js index b009f7521ab9..2db2891f07d3 100644 --- a/src/applications/appeals/996/tests/hlr-max-selections.cypress.spec.js +++ b/src/applications/appeals/996/tests/hlr-max-selections.cypress.spec.js @@ -1,4 +1,4 @@ -import { CONTESTABLE_ISSUES_API } from '../constants'; +import { CONTESTABLE_ISSUES_API } from '../constants/apis'; import mockData from './fixtures/data/101-issues.json'; import mockInProgress from './fixtures/mocks/in-progress-forms.json'; @@ -7,7 +7,7 @@ import preventMaxSelections from '../../shared/tests/cypress.max-selections'; preventMaxSelections({ baseUrl: HLR_BASE_URL, - contestableApi: `/v0${CONTESTABLE_ISSUES_API}compensation`, + contestableApi: `${CONTESTABLE_ISSUES_API}/compensation`, formId: '20-0996', data: mockData.data, inProgressVersion: 3, diff --git a/src/applications/appeals/996/tests/hlr-subtask.cypress.spec.js b/src/applications/appeals/996/tests/hlr-subtask.cypress.spec.js index 6a0f4229ea5a..e3211eb80b82 100644 --- a/src/applications/appeals/996/tests/hlr-subtask.cypress.spec.js +++ b/src/applications/appeals/996/tests/hlr-subtask.cypress.spec.js @@ -4,7 +4,7 @@ import { BASE_URL, FORM_URL } from '../constants'; import cypressSetup from '../../shared/tests/cypress.setup'; -describe('995 subtask', () => { +describe('996 subtask', () => { beforeEach(() => { cypressSetup(); window.dataLayer = []; diff --git a/src/applications/appeals/996/tests/hlr-v2.5.cypress.spec.js b/src/applications/appeals/996/tests/hlr-v2.5.cypress.spec.js index 119e19dc10fb..cf647172a266 100644 --- a/src/applications/appeals/996/tests/hlr-v2.5.cypress.spec.js +++ b/src/applications/appeals/996/tests/hlr-v2.5.cypress.spec.js @@ -10,7 +10,8 @@ import mockInProgress from './fixtures/mocks/in-progress-forms.json'; import mockPrefill from './fixtures/mocks/prefill.json'; import mockSubmit from './fixtures/mocks/application-submit.json'; -import { CONTESTABLE_ISSUES_API, BASE_URL } from '../constants'; +import { BASE_URL } from '../constants'; +import { CONTESTABLE_ISSUES_API } from '../constants/apis'; import { CONTESTABLE_ISSUES_PATH, SELECTED } from '../../shared/constants'; @@ -133,7 +134,7 @@ const testConfig = createTestConfig( cy.get('@testData').then(data => { cy.intercept('GET', '/v0/in_progress_forms/20-0996', mockPrefill); cy.intercept('PUT', '/v0/in_progress_forms/20-0996', mockInProgress); - cy.intercept('GET', `/v1${CONTESTABLE_ISSUES_API}compensation`, { + cy.intercept('GET', `${CONTESTABLE_ISSUES_API}/compensation`, { data: fixDecisionDates(data.contestedIssues, { unselected: true }), }).as('getIssues'); cy.intercept('GET', '/v0/feature_toggles*', { diff --git a/src/applications/appeals/996/tests/hlr.cypress.spec.js b/src/applications/appeals/996/tests/hlr.cypress.spec.js index 9d898cf77984..71b3b94e166c 100644 --- a/src/applications/appeals/996/tests/hlr.cypress.spec.js +++ b/src/applications/appeals/996/tests/hlr.cypress.spec.js @@ -10,7 +10,8 @@ import mockInProgress from './fixtures/mocks/in-progress-forms.json'; import mockPrefill from './fixtures/mocks/prefill.json'; import mockSubmit from './fixtures/mocks/application-submit.json'; -import { CONTESTABLE_ISSUES_API, BASE_URL } from '../constants'; +import { BASE_URL } from '../constants'; +import { CONTESTABLE_ISSUES_API, SUBMIT_URL } from '../constants/apis'; import { CONTESTABLE_ISSUES_PATH, SELECTED } from '../../shared/constants'; @@ -112,12 +113,12 @@ const testConfig = createTestConfig( setStoredSubTask({ benefitType: 'compensation' }); cy.intercept('PUT', '/v0/in_progress_forms/20-0996', mockInProgress); - cy.intercept('POST', '/v1/higher_level_reviews', mockSubmit); + cy.intercept('POST', SUBMIT_URL, mockSubmit); cy.get('@testData').then(data => { cy.intercept('GET', '/v0/in_progress_forms/20-0996', mockPrefill); cy.intercept('PUT', '/v0/in_progress_forms/20-0996', mockInProgress); - cy.intercept('GET', `/v1${CONTESTABLE_ISSUES_API}compensation`, { + cy.intercept('GET', `/${CONTESTABLE_ISSUES_API}/compensation`, { data: fixDecisionDates(data.contestedIssues, { unselected: true }), }).as('getIssues'); }); diff --git a/src/applications/appeals/shared/components/AreaOfDisagreement.jsx b/src/applications/appeals/shared/components/AreaOfDisagreement.jsx index 551475a6e5c4..480af8d66cc3 100644 --- a/src/applications/appeals/shared/components/AreaOfDisagreement.jsx +++ b/src/applications/appeals/shared/components/AreaOfDisagreement.jsx @@ -152,7 +152,7 @@ const AreaOfDisagreement = ({ ), )} { setStoredSubTask({ benefitType: 'compensation' }); // HLR & SC - cy.intercept('GET', `${contestableApi}compensation`, { + cy.intercept('GET', `${contestableApi}/compensation`, { data: fixDecisionDates(data.contestedIssues, { unselected: true }), }).as('getIssues'); cy.intercept('PUT', `/v0/in_progress_forms/${formId}`, mockInProgress); @@ -37,7 +37,7 @@ const downtimeTesting = ({ `/v0/in_progress_forms/${formId}`, inProgressMock({ data }), ); - cy.intercept('GET', `/${ITF_API.join('')}`, fetchItf()); // 995 only + cy.intercept('GET', ITF_API, fetchItf()); // 995 only cy.intercept('GET', '/data/cms/vamc-ehr.json', {}); cy.intercept('GET', '/v0/feature_toggles*', {}); diff --git a/src/applications/appeals/shared/tests/cypress.max-selections.js b/src/applications/appeals/shared/tests/cypress.max-selections.js index dd93b3e37bd5..2bd1d9e0f7b0 100644 --- a/src/applications/appeals/shared/tests/cypress.max-selections.js +++ b/src/applications/appeals/shared/tests/cypress.max-selections.js @@ -20,6 +20,7 @@ const preventMaxSelections = ({ describe('Max selections alert shows', () => { beforeEach(() => { setStoredSubTask({ benefitType: 'compensation' }); // HLR & SC + cypressSetup(); cy.intercept('GET', contestableApi, { data: [], diff --git a/src/applications/caregivers/components/FormFields/FacilitySearch.jsx b/src/applications/caregivers/components/FormFields/FacilitySearch.jsx index dae89bfcd262..025a203c168a 100644 --- a/src/applications/caregivers/components/FormFields/FacilitySearch.jsx +++ b/src/applications/caregivers/components/FormFields/FacilitySearch.jsx @@ -39,13 +39,21 @@ const FacilitySearch = props => { const ariaLiveMessage = () => { if (newFacilitiesCount === 0) return ''; - if (newFacilitiesCount === 1) - return content['facilities-aria-live-message']; - return replaceStrValues( - content['facilities-aria-live-message-multiple'], - newFacilitiesCount, + const newFacilitiesLoadedText = + newFacilitiesCount === 1 + ? content['facilities-aria-live-message-single'] + : replaceStrValues( + content['facilities-aria-live-message-multiple'], + newFacilitiesCount, + ); + + const totalFacilitiesLoadedText = replaceStrValues( + content['facilities-aria-live-message-total'], + facilities?.length, ); + + return `${newFacilitiesLoadedText} ${totalFacilitiesLoadedText}`; }; const isReviewPage = () => { diff --git a/src/applications/caregivers/locales/en/content.json b/src/applications/caregivers/locales/en/content.json index f5fe2879ee8d..3b41dc6ba4bb 100644 --- a/src/applications/caregivers/locales/en/content.json +++ b/src/applications/caregivers/locales/en/content.json @@ -2,7 +2,7 @@ "alert-download-message--500": "We\u2019re sorry. VA.gov is down right now. If you need help right now, please call the VA Help Desk at 800-698-2411. If you have hearing loss, call TTY:711.", "alert-download-message--generic": "We\u2019re sorry. Something went wrong on our end. If you need help right now, please call the VA Help Desk at 800-698-2411. If you have hearing loss, call TTY:711.", "alert-heading--generic": "Something went wrong", - "app-download--loading-text" : "Preparing your application for download...", + "app-download--loading-text": "Preparing your application for download...", "app-title": "Apply for the Program of Comprehensive Assistance for Family Caregivers", "app-subtitle": "Application for the Program of Comprehensive Assistance for Family Caregivers (VA Form 10-10CG)", "app-intro": "Use this form to apply for the Program of Comprehensive Assistance for Family Caregivers. Veterans can also use this application to add or change caregivers after they apply.", @@ -44,8 +44,9 @@ "error--fetching-coordinates": "Error fetching MapBox coordinates", "error--no-results-found": "No search results found.", "facilities-loading-text": "Loading available facilities...", - "facilities-aria-live-message": "1 new facility loaded", - "facilities-aria-live-message-multiple": "%s new facilities loaded", + "facilities-aria-live-message-single": "1 new facility loaded.", + "facilities-aria-live-message-multiple": "%s new facilities loaded.", + "facilities-aria-live-message-total": "Showing %s facilities matching your search criteria.", "form-address-same-as-label": "Is the %s mailing address the same as their home address? ", "form-address-street-label": "%s street address", "form-address-street2-label": "Street address line 2", diff --git a/src/applications/caregivers/tests/unit/components/FormFields/FacilitySearch.unit.spec.js b/src/applications/caregivers/tests/unit/components/FormFields/FacilitySearch.unit.spec.js index 6ef68076d685..eb9dcbe25855 100644 --- a/src/applications/caregivers/tests/unit/components/FormFields/FacilitySearch.unit.spec.js +++ b/src/applications/caregivers/tests/unit/components/FormFields/FacilitySearch.unit.spec.js @@ -427,7 +427,7 @@ describe('CG ', () => { expect(getByText(/Showing 1-2 of 2 facilities for/)).to.exist; expect(selectors().input).to.not.have.attr('error'); expect(selectors().ariaLiveStatus.textContent).to.eq( - '1 new facility loaded', + '1 new facility loaded. Showing 2 facilities matching your search criteria.', ); }); @@ -486,7 +486,7 @@ describe('CG ', () => { expect(getByText(/Showing 1-3 of 3 facilities for/)).to.exist; expect(selectors().input).to.not.have.attr('error'); expect(selectors().ariaLiveStatus.textContent).to.eq( - '2 new facilities loaded', + '2 new facilities loaded. Showing 3 facilities matching your search criteria.', ); }); diff --git a/src/applications/discharge-wizard/components/v2/Homepage.jsx b/src/applications/discharge-wizard/components/v2/Homepage.jsx index c7ab3d4aece5..ce353f96f9bd 100644 --- a/src/applications/discharge-wizard/components/v2/Homepage.jsx +++ b/src/applications/discharge-wizard/components/v2/Homepage.jsx @@ -7,6 +7,7 @@ import { updateIntroPageViewed } from '../../actions'; import { pageSetup } from '../../utilities/page-setup'; import { QUESTION_MAP } from '../../constants/question-data-map'; import { NeedHelp } from './NeedHelp'; +import VABenefitsAccordion from './VABenefitsAccordion'; const HomePage = ({ router, setIntroPageViewed }) => { const H1 = QUESTION_MAP.HOME; @@ -29,9 +30,9 @@ const HomePage = ({ router, setIntroPageViewed }) => {

Answer a series of questions to get customized step-by-step instructions on how to apply for a discharge upgrade or correction. - If your application goes through and your discharge is upgraded, - you’ll be eligible for the VA benefits you earned during your period - of service. + If we accept your application and upgrade your discharge, you’ll be + eligible for the VA benefits you earned during your period of + service.

@@ -66,60 +67,7 @@ const HomePage = ({ router, setIntroPageViewed }) => { />

- -

- Even with a less than honorable discharge, you may be able to - access some VA benefits through the Character of Discharge review - process. When you apply for VA benefits, we’ll review your record - to determine if your service was “honorable for VA purposes.” This - review can take up to a year. Please provide us with documents - supporting your case, similar to the evidence you’d send with an - application to upgrade your discharge. -

-

- You may want to consider finding someone to advocate on your - behalf, depending on the complexity of your case. A lawyer or - Veterans Service Organization (VSO) can collect and submit - supporting documents for you.{' '} - -

-

- Note: You can ask for a VA Character of Discharge - review while at the same time applying for a discharge upgrade - from the Department of Defense (DoD) or the Coast Guard. -

-

- If you need mental health services related to PTSD or other mental - health problems linked to your service (including conditions - related to an experience of military sexual trauma), you may - qualify for VA health benefits right away, even without a VA - Character of Discharge review or a discharge upgrade. -

-

Learn more about:

-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
+

If your previous upgrade application was denied, you can apply @@ -133,8 +81,8 @@ const HomePage = ({ router, setIntroPageViewed }) => { Applying again is most likely to be successful if your application is significantly different from when you last applied. For example, you may have additional evidence that wasn’t available to - you when you last applied, or the Department of Defense (DoD) may - have issued new rules regarding discharges. DoD rules changed for + you when you last applied, or the Department of Defense (DOD) may + have issued new rules regarding discharges. DOD rules changed for discharges related to PTSD, TBI, and mental health in 2014, military sexual harassment and assault in 2017, and sexual orientation in 2011. @@ -142,7 +90,7 @@ const HomePage = ({ router, setIntroPageViewed }) => {

- If the Department of Defense (DoD) or the Coast Guard determined + If the Department of Defense (DOD) or the Coast Guard determined you served honorably in one period of service, you may use that honorable characterization to establish eligibility for VA benefits, even if you later received a less than honorable @@ -174,7 +122,7 @@ const HomePage = ({ router, setIntroPageViewed }) => { to confirm your eligibility.

- You can also apply to the Department of Defense (DoD) or the Coast + You can also apply to the Department of Defense (DOD) or the Coast Guard for a second DD214 only for that honorable period of service. Select the Get Started link above and answer the questions based on your most recent discharge. When @@ -186,9 +134,9 @@ const HomePage = ({ router, setIntroPageViewed }) => {

- When the Department of Defense (DoD) or the Coast Guard upgrades a + When the Department of Defense (DOD) or the Coast Guard upgrades a Veteran’s discharge, it usually issues a DD215 showing corrections - to the DD214. The DoD or the Coast Guard attaches the DD215 to the + to the DD214. The DOD or the Coast Guard attaches the DD215 to the old DD214—which still shows the outdated discharge and related information. While the discharge on the DD215 is the correct discharge, a Veteran may still want a new DD214 that shows no diff --git a/src/applications/discharge-wizard/components/v2/RequestDD214.jsx b/src/applications/discharge-wizard/components/v2/RequestDD214.jsx index 8383fa0ee238..7258c3e7baff 100644 --- a/src/applications/discharge-wizard/components/v2/RequestDD214.jsx +++ b/src/applications/discharge-wizard/components/v2/RequestDD214.jsx @@ -41,14 +41,14 @@ const RequestDD214v2 = ({ router, formResponses, viewedIntroPage }) => {

To receive a second DD214 reflecting only your period of honorable service, you’ll need to complete Department of - Defense (DoD) Form 149 and send it to the {name}— + Defense (DOD) Form 149 and send it to the {name}— do not send it to the Discharge Review Board (DRB) for the {branchOfService}.

    diff --git a/src/applications/discharge-wizard/components/v2/ResultsPage.jsx b/src/applications/discharge-wizard/components/v2/ResultsPage.jsx index 4e06899aafdf..821ad3635597 100644 --- a/src/applications/discharge-wizard/components/v2/ResultsPage.jsx +++ b/src/applications/discharge-wizard/components/v2/ResultsPage.jsx @@ -8,7 +8,6 @@ import { ROUTES } from '../../constants'; import ResultsSummary from './resultsComponents/ResultsSummary'; import CarefulConsiderationStatement from './resultsComponents/CarefulConsiderationStatement'; import Warnings from './resultsComponents/Warnings'; -import OptionalStep from './resultsComponents/OptionalStep'; import StepOne from './resultsComponents/StepOne'; import AdditionalInstructions from './resultsComponents/AdditionalInstructions'; import StepTwo from './resultsComponents/StepTwo'; @@ -43,7 +42,6 @@ const ResultsPage = ({ formResponses, router, viewedIntroPage }) => { router={router} /> -
    diff --git a/src/applications/discharge-wizard/components/v2/VABenefitsAccordion.jsx b/src/applications/discharge-wizard/components/v2/VABenefitsAccordion.jsx new file mode 100644 index 000000000000..b0bf9041fae1 --- /dev/null +++ b/src/applications/discharge-wizard/components/v2/VABenefitsAccordion.jsx @@ -0,0 +1,99 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const VABenefitsAccordion = ({ isResultsPage = false }) => { + const links = [ + { + text: + 'VA health benefits for Veterans who’ve experienced military sexual trauma', + href: '/health-care/health-needs-conditions/military-sexual-trauma/', + }, + { + text: 'VA health benefits for Veterans with mental health conditions', + href: '/health-care/health-needs-conditions/mental-health/', + }, + { + text: 'VA health benefits for Veterans with PTSD', + href: '/health-care/health-needs-conditions/mental-health/ptsd/', + }, + ]; + + let learnMoreLinks = ( +
      + {links.map(link => ( +
    • + +
    • + ))} +
    + ); + + let vsoLink = ( + + ); + + if (isResultsPage) { + learnMoreLinks = ( + + ); + + vsoLink = ( + + Get help from an accredited representative (opens in a new tab) + + ); + } + + return ( + +

    + Even with a less than honorable discharge, you may be able to access + some VA benefits through the Character of Discharge review process. When + you apply for VA benefits, we’ll review your record to determine if your + service was “honorable for VA purposes.” This review can take up to a + year. Provide us with documents supporting your case, similar to the + evidence you’d send with an application to upgrade your discharge. +

    +

    + An accredited attorney, claims agent, or Veterans Service Organization + (VSO) representative can help you gather your evidence and submit your + application. {vsoLink}. +

    +

    + Note: You can ask for a VA Character of Discharge + review while at the same time applying for a discharge upgrade from the + Department of Defense (DOD) or the Coast Guard. +

    +

    + If you experienced sexual assault or harassment while in the military, + or need mental health services related to PTSD or other mental health + conditions linked to your service, you may qualify immediately for VA + health benefits, even without a VA Character of Discharge review or a + discharge upgrade. +

    +

    Learn more about:

    + {learnMoreLinks} +
    + ); +}; + +VABenefitsAccordion.propTypes = { + isResultsPage: PropTypes.bool, +}; + +export default VABenefitsAccordion; diff --git a/src/applications/discharge-wizard/components/v2/resultsComponents/AdditionalInstructions.jsx b/src/applications/discharge-wizard/components/v2/resultsComponents/AdditionalInstructions.jsx index b47006bd2350..5f8dc401d183 100644 --- a/src/applications/discharge-wizard/components/v2/resultsComponents/AdditionalInstructions.jsx +++ b/src/applications/discharge-wizard/components/v2/resultsComponents/AdditionalInstructions.jsx @@ -6,6 +6,7 @@ import { RESPONSES, } from '../../../constants/question-data-map'; import { BCMR, DRB } from '../../../constants'; +import VABenefitsAccordion from '../VABenefitsAccordion'; const AdditionalInstructions = ({ formResponses }) => { const serviceBranch = formResponses[SHORT_NAME_MAP.SERVICE_BRANCH]; @@ -49,74 +50,7 @@ const AdditionalInstructions = ({ formResponses }) => { .

    - -

    - Even with a less than honorable discharge, you may be able to access - some VA benefits through the Character of Discharge review process. - When you apply for VA benefits, we’ll review your record to - determine if your service was “honorable for VA purposes.” This - review can take up to a year. Please provide us with documents - supporting your case, similar to the evidence you’d send with an - application to upgrade your discharge. -

    -

    - You may want to consider finding someone to advocate on your behalf, - depending on the complexity of your case. A lawyer or Veterans - Service Organization (VSO) can collect and submit supporting - documents for you.{' '} - - Find a VSO near you (opens in a new tab). - -

    -

    - Note: You can ask for a VA Character of Discharge - review while at the same time applying for a discharge upgrade from - the Department of Defense (DoD) or the Coast Guard. -

    -

    - If you experienced sexual assault or harassment while in the - military, or need mental health services related to PTSD or other - mental health conditions linked to your service, you may qualify - immediately for VA health benefits, even without a VA Character of - Discharge review or a discharge upgrade. -

    -

    Learn more about:

    - -
    +

    Additional Resources


    @@ -246,9 +180,9 @@ const AdditionalInstructions = ({ formResponses }) => { )}

- Please note: This information was created based on how - you answered the questions on the previous page. This information will - not be specific to someone with different answers to the questions. + Note: This information was created based on how you + answered the questions on the previous page. This information will not + be specific to someone with different answers to the questions.

); diff --git a/src/applications/discharge-wizard/components/v2/resultsComponents/CarefulConsiderationStatement.jsx b/src/applications/discharge-wizard/components/v2/resultsComponents/CarefulConsiderationStatement.jsx index 28449d55d95d..816673c3f097 100644 --- a/src/applications/discharge-wizard/components/v2/resultsComponents/CarefulConsiderationStatement.jsx +++ b/src/applications/discharge-wizard/components/v2/resultsComponents/CarefulConsiderationStatement.jsx @@ -18,8 +18,8 @@ const CarefulConsiderationStatement = ({ formResponses, router }) => {

Because you answered that your discharge was related to posttraumatic stress disorder (PTSD) or other mental health - conditions, the DoD will apply “liberal consideration” to your case. - In 2014, the DoD recognized that many Veterans had received + conditions, the DOD will apply “liberal consideration” to your case. + In 2014, the DOD recognized that many Veterans had received discharges due to behavior connected to their previously undiagnosed or undocumented PTSD or mental health condition.

@@ -28,8 +28,8 @@ const CarefulConsiderationStatement = ({ formResponses, router }) => { return (

Because you answered that your discharge was related to a traumatic - brain injury (TBI), the DoD will apply “liberal consideration” to - your case. In 2014, the DoD recognized that many Veterans had + brain injury (TBI), the DOD will apply “liberal consideration” to + your case. In 2014, the DOD recognized that many Veterans had received discharges due to behavior connected to their previously undiagnosed or undocumented TBI.

@@ -39,8 +39,8 @@ const CarefulConsiderationStatement = ({ formResponses, router }) => { return (

Because you answered that your discharge was due to your sexual - orientation, the DoD encourages you to apply for an upgrade. In - 2011, the DoD recognized that many Veterans received discharges + orientation, the DOD encourages you to apply for an upgrade. In + 2011, the DOD recognized that many Veterans received discharges only because of their sexual orientation.
{' '} Note: You must prove that your discharge was solely due to your sexual orientation and events specifically @@ -65,8 +65,8 @@ const CarefulConsiderationStatement = ({ formResponses, router }) => { return (

Because you answered that your discharge was related to sexual - assault or harassment, the DoD will apply “liberal consideration” to - your case. In 2017, the DoD recognized that many Veterans had + assault or harassment, the DOD will apply “liberal consideration” to + your case. In 2017, the DOD recognized that many Veterans had received discharges due to sexual assault or harassment, and had unfairly received less than honorable discharges.{' '} Note: You must prove that your discharge was solely diff --git a/src/applications/discharge-wizard/components/v2/resultsComponents/OptionalStep.jsx b/src/applications/discharge-wizard/components/v2/resultsComponents/OptionalStep.jsx deleted file mode 100644 index 494358585c93..000000000000 --- a/src/applications/discharge-wizard/components/v2/resultsComponents/OptionalStep.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { - SHORT_NAME_MAP, - RESPONSES, -} from '../../../constants/question-data-map'; - -const OptionalStep = ({ formResponses }) => { - const validReason = [RESPONSES.REASON_PTSD, RESPONSES.REASON_TBI].includes( - formResponses[SHORT_NAME_MAP.REASON], - ); - const dischargeYear = parseInt( - formResponses[SHORT_NAME_MAP.DISCHARGE_YEAR], - 10, - ); - const validYear = dischargeYear >= 2001 && dischargeYear <= 2009; - - if (validReason && validYear) { - return ( -

- -

- (Optional): Apply to the Physical Disability Board of Review (PDBR) -

-

- If you believe your disability rating for TBI, PTSD, or mental - health conditions is too low, consider applying to the Physical - Disability Board of Review (PDBR). The DoD created the PDBR - specifically to review appeals about low disability ratings for - Veterans discharged between 2001 and 2009. Some Veterans discharged - during this period of time received lower disability ratings than - they deserved, especially if they suffered from TBI, PTSD, or other - mental health conditions. If you were discharged during this period - of time and previously received a disability rating of 20% or lower, - you’re eligible to apply to the PDBR for review. The PDBR does not - issue discharge upgrades and cannot review conditions not listed in - your military record before your separation. But, if the PDBR finds - that your disability rating was unjustly low, it may help you make - your case to upgrade your discharge.{' '} - - Learn more about PBDR reviews (opens in a new tab) - - .{' '} - - Apply for a PBDR review (opens in a new tab) - - . -

-
-
- ); - } - return null; -}; - -OptionalStep.propTypes = { - formResponses: PropTypes.object.isRequired, -}; - -export default OptionalStep; diff --git a/src/applications/discharge-wizard/components/v2/resultsComponents/ResultsSummary.jsx b/src/applications/discharge-wizard/components/v2/resultsComponents/ResultsSummary.jsx index 2a2fe515bf09..d214c9bd4998 100644 --- a/src/applications/discharge-wizard/components/v2/resultsComponents/ResultsSummary.jsx +++ b/src/applications/discharge-wizard/components/v2/resultsComponents/ResultsSummary.jsx @@ -24,22 +24,28 @@ const ResultsSummary = ({ formResponses }) => { const airForceAFRBAPortal = determineAirForceAFRBAPortal(formResponses); - const formNumber = determineFormData(formResponses).num; + const formData = determineFormData(formResponses); const dischargeBoard = determineBoardObj(formResponses).name; const serviceBranch = formResponses[SHORT_NAME_MAP.SERVICE_BRANCH]; const isReconsideration = forReconsideration ? ' for reconsideration' : ''; - let summary = `Based on your answers, you need to complete Department of Defense (DoD) Form ${formNumber} and send it to the ${dischargeBoard} for the ${determineBranchOfService( + const sendInstructions = `send it to the ${dischargeBoard} for the ${determineBranchOfService( serviceBranch, )}${isReconsideration}.`; + let summary = ''; + if (airForceAFRBAPortal) { - summary = - 'Based on your answers, you need to complete an Application for Correction of Military Record (DD 149). You can download this form from the Air Force Review Boards Agency Website and Portal.'; + summary = `Correction of Military Record (DD 149). You can download this form from the Air Force Review Boards Agency Website and Portal.`; + } else { + summary = `${formData?.formDescription} and ${sendInstructions}`; } return (
-

{summary}

+

+ Based on your answers, you’ll need to complete an Application for{' '} + {summary} +

); }; diff --git a/src/applications/discharge-wizard/components/v2/resultsComponents/StepOne.jsx b/src/applications/discharge-wizard/components/v2/resultsComponents/StepOne.jsx index f7ccd0b4f2fe..7ed759320e59 100644 --- a/src/applications/discharge-wizard/components/v2/resultsComponents/StepOne.jsx +++ b/src/applications/discharge-wizard/components/v2/resultsComponents/StepOne.jsx @@ -98,7 +98,7 @@ const StepOne = ({ formResponses }) => { Note: You’re generally only eligible for reconsideration if you have new evidence to present that wasn’t available when you applied last time. Make sure you’re clear about - exactly what that new evidence is. Additionally, changes in DoD + exactly what that new evidence is. Additionally, changes in DOD policy, like the new consideration guidelines for PTSD, TBI, and sexual assault or harassment, can qualify you for reconsideration. @@ -106,13 +106,13 @@ const StepOne = ({ formResponses }) => { {formResponses[SHORT_NAME_MAP.REASON] === RESPONSES.REASON_SEXUAL_ASSAULT && (
  • - Note: For upgrades related to sexual assault or harassment, you do not - need to prove the original assault or harassment occurred—meaning if - you didn’t file charges or report the incident, you can still apply - for an upgrade. The important part of your application is where you - explain the impact of the incident on your service. For example, - detail how the incident caused a decrease in your productivity, or was - the reason for PTSD. + Note: For upgrades related to sexual assault or + harassment, you do not need to prove the original assault or + harassment occurred—meaning if you didn’t file charges or report the + incident, you can still apply for an upgrade. The important part of + your application is where you explain the impact of the incident on + your service. For example, detail how the incident caused a decrease + in your productivity, or was the reason for PTSD.
  • )} {boardToSubmit.abbr !== DRB && ( @@ -167,12 +167,18 @@ const StepOne = ({ formResponses }) => { ); - const form = determineFormData(formResponses); - const header = `Download and fill out DoD Form ${form.num}`; + const { num: formNumber, link: formLink } = determineFormData(formResponses); + let formTitle = `Form ${formNumber}`; + + if ([293, 149].includes(formNumber)) { + formTitle = `DOD Form ${formNumber}`; + } + + const header = `Download and fill out DOD Form ${formNumber}`; return ( -

    Important tips for completing Form {form.num}:

    +

    Important tips for completing {formTitle}:

    {formResponses[SHORT_NAME_MAP.REASON] === RESPONSES.REASON_DD215_UPDATE_TO_DD214 ? dd214Tips @@ -181,7 +187,7 @@ const StepOne = ({ formResponses }) => { @@ -190,28 +196,23 @@ const StepOne = ({ formResponses }) => { size={3} className="vads-u-margin-top--0p5 vads-u-padding-right--1" /> - Download Form {form.num} (opens in a new tab) + Download {formTitle} (opens in a new tab)

    - Need help preparing your application? + If you need help filling out your form

    - The process of preparing a discharge upgrade or correction - application can be a lot of work and can take a long time. - Although many Veterans are successful on their own, you may want - to consider finding someone to advocate for you in this process. - Try a Veterans Service Organization (VSO), search online for a - lawyer who may provide services for low or no cost, or ask other - Veterans for recommendations.{' '} + You can appoint an accredited attorney, claims agent, or Veterans + Service Organization (VSO) representative.{' '} - Find a VSO near you (opens in a new tab) + Get help from an accredited representative (opens in a new tab) .

    diff --git a/src/applications/discharge-wizard/components/v2/resultsComponents/StepThree.jsx b/src/applications/discharge-wizard/components/v2/resultsComponents/StepThree.jsx index c1506175e6ac..05914800769a 100644 --- a/src/applications/discharge-wizard/components/v2/resultsComponents/StepThree.jsx +++ b/src/applications/discharge-wizard/components/v2/resultsComponents/StepThree.jsx @@ -97,7 +97,7 @@ const StepThree = ({ formResponses }) => { formResponses[SHORT_NAME_MAP.PREV_APPLICATION_YEAR], ) && [BCNR, BCMR].includes(boardToSubmit.abbr) ? (

    - Your last application was made before the release of DoD guidance + Your last application was made before the release of DOD guidance related to discharges like yours. As a result, the Board may treat your application as a new case. If possible, review the new policies and state in your application how the change in policy is diff --git a/src/applications/discharge-wizard/constants/index.js b/src/applications/discharge-wizard/constants/index.js index d28793298c7c..a5550f8ebd1b 100644 --- a/src/applications/discharge-wizard/constants/index.js +++ b/src/applications/discharge-wizard/constants/index.js @@ -1,10 +1,10 @@ export const DW_UPDATE_FIELD = 'discharge-wizard/UPDATE_FIELD'; export const venueWarning = - "You answered that you weren't sure where you applied for an upgrade before. The instructions below are for Veterans who have never applied for a discharge upgrade, so your process may be different. For more reliable information on your discharge upgrade process, please review your records to find out which board you sent your earlier application to, and complete the questions again."; + "You answered that you weren't sure where you applied for an upgrade before. The instructions below are for Veterans who have never applied for a discharge upgrade, so your process may be different. For more reliable information on your discharge upgrade process, review your records to find out which board you sent your earlier application to, and complete the questions again."; export const upgradeVenueWarning = - "You answered that you weren't sure where you applied for an upgrade before. The instructions below are for Veterans who had a successful upgrade application reviewed by the [branch of service] Discharge Review Board (DRB). For more reliable information, please review your records to find out which board you sent your earlier application to, and complete the questions again."; + "You answered that you weren't sure where you applied for an upgrade before. The instructions below are for Veterans who had a successful upgrade application reviewed by the [branch of service] Discharge Review Board (DRB). For more reliable information, review your records to find out which board you sent your earlier application to, and complete the questions again."; /* eslint-disable quote-props */ export const questionLabels = { diff --git a/src/applications/discharge-wizard/constants/question-data-map.js b/src/applications/discharge-wizard/constants/question-data-map.js index c5936fa836c6..c032e40b9b00 100644 --- a/src/applications/discharge-wizard/constants/question-data-map.js +++ b/src/applications/discharge-wizard/constants/question-data-map.js @@ -3,7 +3,8 @@ export const QUESTION_MAP = Object.freeze({ SERVICE_BRANCH: 'What was your branch of service?', DISCHARGE_YEAR: 'What year were you discharged from the military?', DISCHARGE_MONTH: 'What month were you discharged?', - REASON: 'Tell us why you want to change your discharge paperwork.', + REASON: + "Which of the following best describes why you want to change your discharge paperwork? Choose the one that's closest to your situation.", DISCHARGE_TYPE: "What's your character of discharge?", INTENTION: 'Do you want to change your name, discharge date, or something written in the "other remarks" section of your DD214?', diff --git a/src/applications/discharge-wizard/helpers/index.jsx b/src/applications/discharge-wizard/helpers/index.jsx index c3e9b541c469..ac2c4f26efde 100644 --- a/src/applications/discharge-wizard/helpers/index.jsx +++ b/src/applications/discharge-wizard/helpers/index.jsx @@ -529,15 +529,20 @@ export const determineVenueAddress = (formResponses, noDRB) => { // Determines specific form data Veterans will need to fill out based on form responses. export const determineFormData = formResponses => { const boardData = determineBoardObj(formResponses); + if ([DRB, AFDRB].includes(boardData?.abbr)) { return { num: 293, + formDescription: + 'the Review of Discharge From the Armed Forces of the United States (DOD Form 293)', link: 'http://www.esd.whs.mil/Portals/54/Documents/DD/forms/dd/dd0293.pdf', }; } + return { num: 149, + formDescription: 'Correction of Military Record (DOD Form 149)', link: 'https://www.esd.whs.mil/Portals/54/Documents/DD/forms/dd/dd0149.pdf', }; }; diff --git a/src/applications/discharge-wizard/tests/v2/helpers/index.unit.spec.js b/src/applications/discharge-wizard/tests/v2/helpers/index.unit.spec.js index 730c4c11d8d7..2264d3422d1c 100644 --- a/src/applications/discharge-wizard/tests/v2/helpers/index.unit.spec.js +++ b/src/applications/discharge-wizard/tests/v2/helpers/index.unit.spec.js @@ -38,6 +38,7 @@ describe('Discharge Wizard helpers', () => { }; const formNumber = determineFormData(formResponses); expect(formNumber).to.deep.equal({ + formDescription: 'Correction of Military Record (DOD Form 149)', num: 149, link: 'https://www.esd.whs.mil/Portals/54/Documents/DD/forms/dd/dd0149.pdf', diff --git a/src/applications/discover-your-benefits/constants/benefits.js b/src/applications/discover-your-benefits/constants/benefits.js index 0a3d80dd39a7..e2b10fa59bdb 100644 --- a/src/applications/discover-your-benefits/constants/benefits.js +++ b/src/applications/discover-your-benefits/constants/benefits.js @@ -558,7 +558,7 @@ export const BENEFITS_LIST = [ category: categories.LIFE_INSURANCE, id: 'VAL', description: - "Note: You must already have a VA service-connected disability rating to be approved for Veterans Affairs Life Insurance (VALife). VALife provides low-cost coverage to Veterans with service-connected disabilities. Find out if you're eligible and how to apply", + "Note: You must already have a VA service-connected disability rating to be approved for Veterans Affairs Life Insurance (VALife). VALife provides low-cost coverage to Veterans with service-connected disabilities. Find out if you're eligible and how to apply.", isTimeSensitive: false, mappings: { [mappingTypes.GOALS]: [goalTypes.RETIREMENT, goalTypes.UNDERSTAND], diff --git a/src/applications/ezr/config/chapters/householdInformation/spouseContactInformation.js b/src/applications/ezr/config/chapters/householdInformation/spouseContactInformation.js index 0eb14a8f7fa3..a06235379e98 100644 --- a/src/applications/ezr/config/chapters/householdInformation/spouseContactInformation.js +++ b/src/applications/ezr/config/chapters/householdInformation/spouseContactInformation.js @@ -16,7 +16,12 @@ const { export default { uiSchema: { ...titleUI(content['household-spouse-contact-info-title']), - spouseAddress: addressUI({ omit: ['isMilitary'] }), + spouseAddress: addressUI({ + omit: ['isMilitary'], + required: { + state: () => true, + }, + }), spousePhone: { ...phoneUI(content['household-sponse-phone-label']), 'ui:errorMessages': { diff --git a/src/applications/ezr/config/chapters/veteranInformation/homeAddress.js b/src/applications/ezr/config/chapters/veteranInformation/homeAddress.js index 6059ddcf80e3..b09a83658b28 100644 --- a/src/applications/ezr/config/chapters/veteranInformation/homeAddress.js +++ b/src/applications/ezr/config/chapters/veteranInformation/homeAddress.js @@ -15,7 +15,11 @@ const { export default { uiSchema: { 'view:pageTitle': titleUI(content['vet-home-address-title']), - veteranHomeAddress: addressUI(), + veteranHomeAddress: addressUI({ + required: { + state: () => true, + }, + }), }, schema: { type: 'object', diff --git a/src/applications/ezr/config/chapters/veteranInformation/mailingAddress.js b/src/applications/ezr/config/chapters/veteranInformation/mailingAddress.js index ef4183fc4148..7114290f4852 100644 --- a/src/applications/ezr/config/chapters/veteranInformation/mailingAddress.js +++ b/src/applications/ezr/config/chapters/veteranInformation/mailingAddress.js @@ -23,7 +23,11 @@ export default { content['vet-mailing-address-title'], content['vet-mailing-address-description'], ), - veteranAddress: addressUI(), + veteranAddress: addressUI({ + required: { + state: () => true, + }, + }), 'view:doesMailingMatchHomeAddress': yesNoUI( content['vet-address-match-title'], ), diff --git a/src/applications/ezr/tests/helpers.js b/src/applications/ezr/tests/helpers.js index 65af15535236..ba5411d473b3 100644 --- a/src/applications/ezr/tests/helpers.js +++ b/src/applications/ezr/tests/helpers.js @@ -1,4 +1,8 @@ +import React from 'react'; import ReactTestUtils from 'react-dom/test-utils'; +import { expect } from 'chai'; +import { render } from '@testing-library/react'; +import { DefinitionTester } from 'platform/testing/unit/schemaform-utils'; // simulate v1 forms library input change export const simulateInputChange = (formDOM, querySelectorElement, value) => { @@ -22,3 +26,35 @@ export const inputVaTextInput = (selector, value) => { }); vaTextInput.dispatchEvent(event); }; + +/* +Check to ensure that the address state field is required for non-US countries. It +must be present in order to pass Enrollment System validation. + */ +export const expectStateInputToBeRequired = ( + schema, + uiSchema, + definitions, + countryFieldName, + stateFieldName, +) => { + const { container } = render( + , + ); + + const country = container.querySelector(`[name="root_${countryFieldName}"]`); + const state = container.querySelector(`[name="root_${stateFieldName}"]`); + + country.__events.vaSelect({ + target: { + name: `root_${countryFieldName}`, + value: 'CAN', + }, + }); + + expect(state).to.have.attr('required', 'true'); +}; diff --git a/src/applications/ezr/tests/unit/config/householdInformation/spouseContactInformation.unit.spec.js b/src/applications/ezr/tests/unit/config/householdInformation/spouseContactInformation.unit.spec.js index d40ab73fac32..ef293e4d8d26 100644 --- a/src/applications/ezr/tests/unit/config/householdInformation/spouseContactInformation.unit.spec.js +++ b/src/applications/ezr/tests/unit/config/householdInformation/spouseContactInformation.unit.spec.js @@ -3,6 +3,7 @@ import { testNumberOfWebComponentFields, } from '../helpers.spec'; import formConfig from '../../../../config/form'; +import { expectStateInputToBeRequired } from '../../../helpers'; const { chapters: { @@ -24,7 +25,7 @@ testNumberOfWebComponentFields( ); // run test for correct number of error messages on submit -const expectedNumberOfErrors = 4; +const expectedNumberOfErrors = 5; testNumberOfErrorsOnSubmitForWebComponents( formConfig, schema, @@ -32,3 +33,17 @@ testNumberOfErrorsOnSubmitForWebComponents( expectedNumberOfErrors, pageTitle, ); + +describe('ezr VeteranHomeAddress config', () => { + const { defaultDefinitions: definitions } = formConfig; + + it('if the country selected is not the United States, the state field is still required', async () => { + expectStateInputToBeRequired( + schema, + uiSchema, + definitions, + 'spouseAddress_country', + 'spouseAddress_state', + ); + }); +}); diff --git a/src/applications/ezr/tests/unit/config/veteranInformation/homeAddress.unit.spec.js b/src/applications/ezr/tests/unit/config/veteranInformation/homeAddress.unit.spec.js index 6b23a9fdfc53..40712e9d71e1 100644 --- a/src/applications/ezr/tests/unit/config/veteranInformation/homeAddress.unit.spec.js +++ b/src/applications/ezr/tests/unit/config/veteranInformation/homeAddress.unit.spec.js @@ -3,6 +3,7 @@ import { testNumberOfWebComponentFields, } from '../helpers.spec'; import formConfig from '../../../../config/form'; +import { expectStateInputToBeRequired } from '../../../helpers'; const { chapters: { @@ -24,7 +25,7 @@ testNumberOfWebComponentFields( ); // run test for correct number of error messages on submit -const expectedNumberOfWebComponentErrors = 4; +const expectedNumberOfWebComponentErrors = 5; testNumberOfErrorsOnSubmitForWebComponents( formConfig, schema, @@ -32,3 +33,17 @@ testNumberOfErrorsOnSubmitForWebComponents( expectedNumberOfWebComponentErrors, pageTitle, ); + +describe('ezr VeteranHomeAddress config', () => { + const { defaultDefinitions: definitions } = formConfig; + + it('if the country selected is not the United States, the state field is still required', async () => { + expectStateInputToBeRequired( + schema, + uiSchema, + definitions, + 'veteranHomeAddress_country', + 'veteranHomeAddress_state', + ); + }); +}); diff --git a/src/applications/ezr/tests/unit/config/veteranInformation/mailingAddress.unit.spec.js b/src/applications/ezr/tests/unit/config/veteranInformation/mailingAddress.unit.spec.js index 5e92aa36d43e..f873aea70148 100644 --- a/src/applications/ezr/tests/unit/config/veteranInformation/mailingAddress.unit.spec.js +++ b/src/applications/ezr/tests/unit/config/veteranInformation/mailingAddress.unit.spec.js @@ -1,8 +1,9 @@ +import formConfig from '../../../../config/form'; import { testNumberOfErrorsOnSubmitForWebComponents, testNumberOfWebComponentFields, } from '../helpers.spec'; -import formConfig from '../../../../config/form'; +import { expectStateInputToBeRequired } from '../../../helpers'; const { chapters: { @@ -24,7 +25,7 @@ testNumberOfWebComponentFields( ); // run test for correct number of error messages on submit -const expectedNumberOfWebComponentErrors = 5; +const expectedNumberOfWebComponentErrors = 6; testNumberOfErrorsOnSubmitForWebComponents( formConfig, schema, @@ -32,3 +33,17 @@ testNumberOfErrorsOnSubmitForWebComponents( expectedNumberOfWebComponentErrors, pageTitle, ); + +describe('ezr VeteranMailingAddress config', () => { + const { defaultDefinitions: definitions } = formConfig; + + it('if the country selected is not the United States, the state field is still required', async () => { + expectStateInputToBeRequired( + schema, + uiSchema, + definitions, + 'veteranAddress_country', + 'veteranAddress_state', + ); + }); +}); diff --git a/src/applications/facility-locator/components/search-results-items/common/LocationPhoneLink.jsx b/src/applications/facility-locator/components/search-results-items/common/LocationPhoneLink.jsx index 9572f638f114..d077d125485b 100644 --- a/src/applications/facility-locator/components/search-results-items/common/LocationPhoneLink.jsx +++ b/src/applications/facility-locator/components/search-results-items/common/LocationPhoneLink.jsx @@ -15,8 +15,13 @@ export const renderPhoneNumber = ( return null; } - const { formattedPhoneNumber, extension, contact } = parsePhoneNumber(phone); - + const { + extension, + contact, + processed, + international, + countryCode, + } = parsePhoneNumber(phone); // The Telephone component will throw an error if passed an invalid phone number. // Since we can't use try/catch or componentDidCatch here, we'll just do this: if (contact.length !== 10) { @@ -30,17 +35,23 @@ export const renderPhoneNumber = ( {from === 'FacilityDetail' && } {title && {title}: } {subTitle} - - {formattedPhoneNumber} - + {processed ? ( + + ) : ( + // eslint-disable-next-line @department-of-veterans-affairs/prefer-telephone-component + + {contact} + + )}

    ); }; diff --git a/src/applications/facility-locator/tests/components/search-results/common/Covid19PhoneLink.unit.spec.jsx b/src/applications/facility-locator/tests/components/search-results/common/Covid19PhoneLink.unit.spec.jsx index 47a3df935049..42a3bc130c78 100644 --- a/src/applications/facility-locator/tests/components/search-results/common/Covid19PhoneLink.unit.spec.jsx +++ b/src/applications/facility-locator/tests/components/search-results/common/Covid19PhoneLink.unit.spec.jsx @@ -16,7 +16,7 @@ describe('Covid19PhoneLink', () => { .trim(), ).to.equal('Call to schedule:'); expect(wrapper.find('va-telephone').html()).to.equal( - '', + '', ); wrapper.unmount(); }); diff --git a/src/applications/facility-locator/tests/helpers/phoneNumbers.unit.spec.js b/src/applications/facility-locator/tests/helpers/phoneNumbers.unit.spec.js index ae107f8dafac..7a3cba06ce36 100644 --- a/src/applications/facility-locator/tests/helpers/phoneNumbers.unit.spec.js +++ b/src/applications/facility-locator/tests/helpers/phoneNumbers.unit.spec.js @@ -4,51 +4,129 @@ import { parsePhoneNumber } from '../../utils/phoneNumbers'; describe('parsePhoneNumber', () => { it('without extension, without dashes', () => { const phone = '1234567890'; - const { formattedPhoneNumber, extension, contact } = parsePhoneNumber( - phone, - ); - expect(formattedPhoneNumber).to.equal('123-456-7890'); - expect(extension).to.equal(''); + const { + contact, + extension, + processed, + international, + countryCode, + } = parsePhoneNumber(phone); + expect(processed).to.equal(true); + expect(extension).to.equal(undefined); expect(contact).to.equal('1234567890'); + expect(international).to.equal(false); + expect(countryCode).to.equal(undefined); }); it('with extension, without dashes', () => { const phone = '1234567890 x123'; - const { formattedPhoneNumber, extension, contact } = parsePhoneNumber( - phone, - ); - expect(formattedPhoneNumber).to.equal('123-456-7890 x123'); + const { + contact, + extension, + processed, + international, + countryCode, + } = parsePhoneNumber(phone); expect(extension).to.equal('123'); expect(contact).to.equal('1234567890'); + expect(processed).to.equal(true); + expect(international).to.equal(false); + expect(countryCode).to.equal(undefined); }); it('with dashes', () => { const phone = '123-456--7890'; - const { formattedPhoneNumber, extension, contact } = parsePhoneNumber( - phone, - ); - expect(formattedPhoneNumber).to.equal('123-456-7890'); - expect(extension).to.equal(''); + const { + contact, + extension, + processed, + international, + countryCode, + } = parsePhoneNumber(phone); + expect(extension).to.equal(undefined); expect(contact).to.equal('1234567890'); + expect(processed).to.equal(true); + expect(international).to.equal(false); + expect(countryCode).to.equal(undefined); }); it('with spaces and extension', () => { const phone = '123 456 7890 Extension 123'; - const { formattedPhoneNumber, extension, contact } = parsePhoneNumber( - phone, - ); - expect(formattedPhoneNumber).to.equal('123-456-7890 x123'); + const { + contact, + extension, + processed, + international, + countryCode, + } = parsePhoneNumber(phone); expect(extension).to.equal('123'); expect(contact).to.equal('1234567890'); + expect(processed).to.equal(true); + expect(international).to.equal(false); + expect(countryCode).to.equal(undefined); }); it('with 800 numbers', () => { const phone = '1-800-827-1000'; - const { formattedPhoneNumber, extension, contact } = parsePhoneNumber( - phone, - ); - expect(formattedPhoneNumber).to.equal('800-827-1000'); - expect(extension).to.equal(''); + const { + processed, + extension, + contact, + countryCode, + international, + } = parsePhoneNumber(phone); + expect(extension).to.equal(undefined); expect(contact).to.equal('8008271000'); + expect(processed).to.equal(true); + expect(international).to.equal(true); + expect(countryCode).to.equal(undefined); // because it is 1 + }); + + it('with +1-877 numbers', () => { + const phone = '+1-877-222-8387 ext. 123'; + const { + processed, + extension, + contact, + countryCode, + international, + } = parsePhoneNumber(phone); + expect(extension).to.equal('123'); + expect(contact).to.equal('8772228387'); + expect(processed).to.equal(true); + expect(international).to.equal(true); + expect(countryCode).to.equal(undefined); // because it is 1 + }); + + it('with +1(877) numbers', () => { + const phone = '+1(877) 222-8387 ext 123'; + const { + processed, + extension, + contact, + countryCode, + international, + } = parsePhoneNumber(phone); + expect(extension).to.equal('123'); + expect(contact).to.equal('8772228387'); + expect(processed).to.equal(true); + expect(international).to.equal(true); + expect(countryCode).to.equal(undefined); // because it is 1 + }); + + it('should process with extension from result', () => { + const phone = '573-475-4108 ext 1008'; + const { + processed, + extension, + contact, + countryCode, + international, + } = parsePhoneNumber(phone); + expect(extension).to.equal('1008'); + expect(contact).to.equal('5734754108'); + expect(processed).to.equal(true); + expect(international).to.equal(false); + expect(countryCode).to.equal(undefined); }); }); diff --git a/src/applications/facility-locator/utils/phoneNumbers.js b/src/applications/facility-locator/utils/phoneNumbers.js index 7502a25869c0..7a495c95dc30 100644 --- a/src/applications/facility-locator/utils/phoneNumbers.js +++ b/src/applications/facility-locator/utils/phoneNumbers.js @@ -14,13 +14,48 @@ */ export const parsePhoneNumber = phone => { - const phoneUS = phone.replace(/^1-/, ''); - const re = /^(\d{3})[ -]*?(\d{3})[ -]*?(\d{4})\s?(\D*)?[ ]?(\d*)?/; - const extension = phoneUS.replace(re, '$5').replace(/\D/g, ''); - const formattedPhoneNumber = extension - ? phoneUS.replace(re, '$1-$2-$3 x$5').replace(/x$/, '') - : phoneUS.replace(re, '$1-$2-$3'); - const contact = phoneUS.replace(re, '$1$2$3'); + // This regex has named capture groups for the international code (optional), area code, prefix, line number, and extension. + // in regular expressions (?...) is a named capture group, therefore you can reference the group by its name + // The international code (intl) is optional. Someone may write +1 ac, +1 (ac), 1 (ac), 1-ac, 1.ac or 1ac or just ac, (ac), or ac. + // the intl need not be separated by space, dash or period from the ac + // The ac is the area code and is expected to be 3 digits exactly + // The pfx is the prefix and is expected to be 3 digits exactly + // The linenum is the line number and is expected to be 4 digits exactly + // ac/pfx/linenum _can_ be separated by space, dash or period or repeats of those characters + // The extension is optional and is expected to be a number after the required capture groups + // The extension is at least separated by an "x" or "ext" or "extension" or "ext." or "x." or "ext." or "extension." + // Since the intl group is greedy, if there is a number like +63285503888 (the Philippines VA Medical Center) + // it will be captured as intl=6, ac=328, pfx=550, linenum=3888 because the ac, pfx, and linenum are required to be a certain length + // However, if it is entered as +063285503888, it will be captured as intl=06, ac=328, pfx=550, linenum=3888 + // This is because the intl group can expand to capture the 0, but the ac, pfx, and linenum groups are required to be a certain length + // Similarly if it is entered as: +01163285503888 it will be captured as intl=0116, ac=328, pfx=550, linenum=3888 + // At some point we may wish to remove the 0 and 011 from the intl group, but it is unlikely to be entered + const phoneRegex = /^(?:\+?(?\d{1,}?)[ -.]*)?\(?(?\d{3})\)?[- .]*(?\d{3})[- .]*(?\d{4}),?(?: ?e?xt?e?n?s?i?o?n?\.? ?(?\d*))?$/i; + const match = phoneRegex.exec(phone); - return { formattedPhoneNumber, extension, contact }; + const errorObject = { + contact: phone, + extension: undefined, + processed: false, + international: false, + countryCode: '', + }; + + // must have at least match + if (!match) { + return errorObject; + } + + const { intl, ac, pfx, linenum, ext } = match.groups; + if (!ac || !pfx || !linenum) { + return errorObject; + } + + return { + contact: `${ac}${pfx}${linenum}`, + extension: ext, + processed: true, + international: !!intl, + countryCode: intl && intl !== '1' ? intl : undefined, + }; }; diff --git a/src/applications/gi/components/profile/InstitutionProfile.jsx b/src/applications/gi/components/profile/InstitutionProfile.jsx index 25f1488e5a2d..c4819cbddfc0 100644 --- a/src/applications/gi/components/profile/InstitutionProfile.jsx +++ b/src/applications/gi/components/profile/InstitutionProfile.jsx @@ -209,12 +209,12 @@ export default function InstitutionProfile({ id="yellow-ribbon-program-information" >

    - The Yellow Ribbon program pays towards net tuition and fee costs - not covered by the Post-9/11 GI Bill at participating institutions - of higher learning (IHL). Schools that choose to participate in - the Yellow Ribbon program will contribute up to a certain dollar - amount toward the extra tuition. VA will match the participating - school’s contribution + The Yellow Ribbon program may pay towards net tuition and fee + costs not covered by the Post-9/11 GI Bill at participating + institutions of higher learning (IHL). Schools that choose to + participate in the Yellow Ribbon program will contribute up to a + certain dollar amount toward the extra tuition. VA will match the + participating school’s contribution {type === 'FOREIGN' && `${` `}in United States Dollars (USD)`}, up to the total cost of the tuition and fees. To confirm the number of students eligible for funding, contact the individual school. @@ -228,7 +228,7 @@ export default function InstitutionProfile({

    - What to know about the content displayed below + What to know about the content displayed

    diff --git a/src/applications/ivc-champva/10-10D/config/form.js b/src/applications/ivc-champva/10-10D/config/form.js index 1128df06b39d..d73f7e89d317 100644 --- a/src/applications/ivc-champva/10-10D/config/form.js +++ b/src/applications/ivc-champva/10-10D/config/form.js @@ -422,12 +422,13 @@ const formConfig = { sponsorInfoTitle: titleUI('Sponsor status details'), sponsorDOD: dateOfDeathUI('When did the sponsor die?'), sponsorDeathConditions: yesNoUI({ - title: 'Did sponsor die during active military service?', + title: 'Did the sponsor die during active military service?', hint: ADDITIONAL_FILES_HINT, labels: { - yes: 'Yes, sponsor passed away during active military service', + yes: + 'Yes, the sponsor passed away during active military service', no: - 'No, sponsor did not pass away during active military service', + 'No, the sponsor did not pass away during active military service', }, }), }, @@ -598,7 +599,7 @@ const formConfig = { undefined, false, )}. This includes social security number, mailing address, - contact information, relationship to sponsor, and health + contact information, relationship to the sponsor, and health insurance information.`, ), }, @@ -770,7 +771,8 @@ const formConfig = { path: 'applicant-relationship/:index', arrayPath: 'applicants', showPagePerItem: true, - title: item => `${applicantWording(item)} relationship to sponsor`, + title: item => + `${applicantWording(item)} relationship to the sponsor`, CustomPage: ApplicantRelationshipPage, CustomPageReview: ApplicantRelationshipReviewPage, schema: applicantListSchema([], { diff --git a/src/applications/ivc-champva/10-10D/pages/ApplicantSponsorMarriageDetailsPage.jsx b/src/applications/ivc-champva/10-10D/pages/ApplicantSponsorMarriageDetailsPage.jsx index 072c887b18d2..881db19e7a14 100644 --- a/src/applications/ivc-champva/10-10D/pages/ApplicantSponsorMarriageDetailsPage.jsx +++ b/src/applications/ivc-champva/10-10D/pages/ApplicantSponsorMarriageDetailsPage.jsx @@ -121,7 +121,7 @@ function marriageTitle(text, subtitle) { const dateOfMarriageToSponsor = { ...currentOrPastDateUI({ - title: 'Date of marriage to sponsor', + title: 'Date of marriage to the sponsor', errorMessages: { pattern: 'Please provide a valid date', required: 'Please provide the date of marriage', @@ -144,7 +144,7 @@ export const marriageDatesSchema = { 'ui:options': { viewField: ApplicantField }, items: { 'ui:options': marriageTitle( - ' date of marriage to sponsor', + ' date of marriage to the sponsor', 'If you don’t know the exact date, enter your best guess', ), dateOfMarriageToSponsor, diff --git a/src/applications/ivc-champva/10-10D/sass/10-10D.scss b/src/applications/ivc-champva/10-10D/sass/10-10D.scss index 30894f178e2c..a835fe027d94 100644 --- a/src/applications/ivc-champva/10-10D/sass/10-10D.scss +++ b/src/applications/ivc-champva/10-10D/sass/10-10D.scss @@ -16,3 +16,7 @@ va-accordion-item[data-chapter="uploadFiles"] { display: none; visibility: hidden; } + +.va-growable { + padding-left: 10px; +} \ No newline at end of file diff --git a/src/applications/ivc-champva/10-10D/tests/unit/config/form.unit.spec.jsx b/src/applications/ivc-champva/10-10D/tests/unit/config/form.unit.spec.jsx index eafd8f6e534e..8983f4c3bb24 100644 --- a/src/applications/ivc-champva/10-10D/tests/unit/config/form.unit.spec.jsx +++ b/src/applications/ivc-champva/10-10D/tests/unit/config/form.unit.spec.jsx @@ -178,7 +178,7 @@ testNumberOfWebComponentFields( formConfig.chapters.applicantInformation.pages.page18c.schema, formConfig.chapters.applicantInformation.pages.page18c.uiSchema, 0, - 'Applicant - relationship to sponsor', + 'Applicant - relationship to the sponsor', { ...mockData.data }, ); diff --git a/src/applications/letters/components/DownloadLetterLink.jsx b/src/applications/letters/components/DownloadLetterLink.jsx index d68acc723799..89262bf4d560 100644 --- a/src/applications/letters/components/DownloadLetterLink.jsx +++ b/src/applications/letters/components/DownloadLetterLink.jsx @@ -22,7 +22,7 @@ export class DownloadLetterLink extends React.Component { }); this.props.getLetterPdf( this.props.letterType, - this.props.letterName, + this.props.letterTitle, this.props.letterOptions, this.props.LH_MIGRATION__options, ); @@ -38,7 +38,7 @@ export class DownloadLetterLink extends React.Component { buttonDisabled = true; break; case DOWNLOAD_STATUSES.success: - buttonText = `${this.props.letterName} (PDF)`; + buttonText = `${this.props.letterTitle} (PDF)`; buttonDisabled = undefined; message = ( @@ -64,7 +64,7 @@ export class DownloadLetterLink extends React.Component { ); break; default: - buttonText = `${this.props.letterName} (PDF)`; + buttonText = `${this.props.letterTitle} (PDF)`; buttonDisabled = undefined; } @@ -101,7 +101,7 @@ export class DownloadLetterLink extends React.Component { function mapStateToProps(state, ownProps) { return { letterType: ownProps.letterType, - letterName: ownProps.letterName, + letterTitle: ownProps.letterTitle, downloadStatus: ownProps.downloadStatus, letterOptions: state.letters.requestOptions, shouldUseLighthouse: state.shouldUseLighthouse, @@ -109,7 +109,7 @@ function mapStateToProps(state, ownProps) { } DownloadLetterLink.propTypes = { - letterName: PropTypes.string.isRequired, + letterTitle: PropTypes.string.isRequired, letterType: PropTypes.string.isRequired, downloadStatus: PropTypes.string, }; diff --git a/src/applications/letters/containers/LetterList.jsx b/src/applications/letters/containers/LetterList.jsx index 261a0df3c6ae..24767fa70091 100644 --- a/src/applications/letters/containers/LetterList.jsx +++ b/src/applications/letters/containers/LetterList.jsx @@ -59,7 +59,7 @@ export class LetterList extends React.Component { conditionalDownloadButton = ( ', () => { expect(getLetterPdf.args[0]).to.eql([ defaultProps.letterType, - defaultProps.letterName, + defaultProps.letterTitle, undefined, undefined, ]); diff --git a/src/applications/mhv-medical-records/actions/vitals.js b/src/applications/mhv-medical-records/actions/vitals.js index c4b1b990e68a..c1e7304f1759 100644 --- a/src/applications/mhv-medical-records/actions/vitals.js +++ b/src/applications/mhv-medical-records/actions/vitals.js @@ -32,6 +32,12 @@ export const getVitals = ( } }; +/** + * Updates the list of vitals with the selected vital type, **will** make an API call to populate the data + * + * @param {string} vitalType a valid vital type + * @param {array} vitalList the list of vitals to check if it's empty + */ export const getVitalDetails = (vitalType, vitalList) => async dispatch => { try { if (!isArrayAndHasItems(vitalList)) { @@ -44,6 +50,15 @@ export const getVitalDetails = (vitalType, vitalList) => async dispatch => { } }; +/** + * Updates the list of vitals with the selected vital type, **will not** make an API call to populate the data + * + * @param {string} vitalType a valid vital type + */ +export const setVitalsList = vitalType => async dispatch => { + dispatch({ type: Actions.Vitals.GET, vitalType }); +}; + export const clearVitalDetails = () => async dispatch => { dispatch({ type: Actions.Vitals.CLEAR_DETAIL }); }; diff --git a/src/applications/mhv-medical-records/components/MrBreadcrumbs.jsx b/src/applications/mhv-medical-records/components/MrBreadcrumbs.jsx index d5debacfe2e8..358636146e21 100644 --- a/src/applications/mhv-medical-records/components/MrBreadcrumbs.jsx +++ b/src/applications/mhv-medical-records/components/MrBreadcrumbs.jsx @@ -5,12 +5,13 @@ import { Link, useHistory, useLocation, useParams } from 'react-router-dom'; import { Breadcrumbs, Paths } from '../util/constants'; import { setBreadcrumbs } from '../actions/breadcrumbs'; import { clearPageNumber, setPageNumber } from '../actions/pageTracker'; -import { handleDataDogAction } from '../util/helpers'; +import { handleDataDogAction, removeTrailingSlash } from '../util/helpers'; const MrBreadcrumbs = () => { const dispatch = useDispatch(); const location = useLocation(); const history = useHistory(); + const crumbsList = useSelector(state => state.mr.breadcrumbs.crumbsList); const pageNumber = useSelector(state => state.mr.pageTracker.pageNumber); const phase0p5Flag = useSelector( @@ -27,10 +28,12 @@ const MrBreadcrumbs = () => { ); const textContent = document.querySelector('h1')?.textContent; - const searchIndex = new URLSearchParams(window.location.search); + const searchIndex = new URLSearchParams(location.search); const page = searchIndex.get('page'); const { labId, vaccineId, summaryId, allergyId, conditionId } = useParams(); + const urlVitalsDate = searchIndex.get('timeFrame'); + useEffect( () => { if (page) dispatch(setPageNumber(+page)); @@ -56,12 +59,19 @@ const MrBreadcrumbs = () => { if (pageNumber) { backToPageNumCrumb = { ...Breadcrumbs[feature], - href: `${Breadcrumbs[feature].href.slice( - 0, - -1, + href: `${removeTrailingSlash( + Breadcrumbs[feature].href, )}?page=${pageNumber}`, }; dispatch(setBreadcrumbs([backToPageNumCrumb, detailCrumb])); + } else if (urlVitalsDate) { + const backToVitalsDateCrumb = { + ...Breadcrumbs[feature], + href: `${removeTrailingSlash( + Breadcrumbs[feature].href, + )}?timeFrame=${urlVitalsDate}`, + }; + dispatch(setBreadcrumbs([backToVitalsDateCrumb, detailCrumb])); } else { dispatch(setBreadcrumbs([Breadcrumbs[feature], detailCrumb])); } @@ -69,7 +79,14 @@ const MrBreadcrumbs = () => { dispatch(setBreadcrumbs([Breadcrumbs[feature]])); } }, - [dispatch, locationBasePath, locationChildPath, textContent, pageNumber], + [ + dispatch, + locationBasePath, + locationChildPath, + textContent, + pageNumber, + urlVitalsDate, + ], ); const handleRouteChange = ({ detail }) => { diff --git a/src/applications/mhv-medical-records/components/RecordList/VitalListItem.jsx b/src/applications/mhv-medical-records/components/RecordList/VitalListItem.jsx index eb7d4cd8f144..be3566b9abed 100644 --- a/src/applications/mhv-medical-records/components/RecordList/VitalListItem.jsx +++ b/src/applications/mhv-medical-records/components/RecordList/VitalListItem.jsx @@ -11,7 +11,7 @@ import { const VitalListItem = props => { const { record, options = {} } = props; - const { isAccelerating } = options; + const { isAccelerating, timeFrame } = options; const displayName = vitalTypeDisplayNames[record.type]; const ddLabelName = useMemo( @@ -61,6 +61,10 @@ const VitalListItem = props => { [updatedRecordType, isAccelerating], ); + const url = `/vitals/${kebabCase(updatedRecordType)}-history${ + isAccelerating ? `?timeFrame=${timeFrame}` : '' + }`; + return ( {
    { diff --git a/src/applications/mhv-medical-records/containers/VitalDetails.jsx b/src/applications/mhv-medical-records/containers/VitalDetails.jsx index f03c1be87f72..8859a6812fb3 100644 --- a/src/applications/mhv-medical-records/containers/VitalDetails.jsx +++ b/src/applications/mhv-medical-records/containers/VitalDetails.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useRef, useMemo } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; +import { useParams, useLocation } from 'react-router-dom'; import { chunk } from 'lodash'; import { VaPagination } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; import { focusElement } from '@department-of-veterans-affairs/platform-utilities/ui'; @@ -19,6 +19,7 @@ import { getVitalDetails, getVitals, reloadRecords, + setVitalsList, } from '../actions/vitals'; import PrintHeader from '../components/shared/PrintHeader'; import PrintDownload from '../components/shared/PrintDownload'; @@ -39,6 +40,7 @@ import { ALERT_TYPE_ERROR, accessAlertTypes, refreshExtractTypes, + loadStates as LOAD_STATES, } from '../util/constants'; import AccessTroubleAlertBox from '../components/shared/AccessTroubleAlertBox'; import useAlerts from '../hooks/use-alerts'; @@ -47,6 +49,7 @@ import { generateVitalsContent, generateVitalsIntro, } from '../util/pdfHelpers/vitals'; + import DownloadSuccessAlert from '../components/shared/DownloadSuccessAlert'; import NewRecordsIndicator from '../components/shared/NewRecordsIndicator'; import useListRefresh from '../hooks/useListRefresh'; @@ -58,6 +61,9 @@ import useAcceleratedData from '../hooks/useAcceleratedData'; const MAX_PAGE_LIST_LENGTH = 10; const VitalDetails = props => { const { runningUnitTest } = props; + + const location = useLocation(); + const records = useSelector(state => state.mr.vitals.vitalDetails); const vitalsList = useSelector(state => state.mr.vitals.vitalsList); const user = useSelector(state => state.user.profile); @@ -87,18 +93,19 @@ const VitalDetails = props => { state => state.mr.vitals.listCurrentAsOf, ); - const { isAcceleratingVitals } = useAcceleratedData(); + const { isAcceleratingVitals, isLoading } = useAcceleratedData(); - if (records?.length === 0 && isAcceleratingVitals) { - window.location.replace('/my-health/medical-records/vitals'); - } + const urlVitalsDate = new URLSearchParams(location.search).get('timeFrame'); + const dispatchAction = isCurrent => { + return getVitals(isCurrent, isAcceleratingVitals, urlVitalsDate); + }; useListRefresh({ listState, listCurrentAsOf: vitalsCurrentAsOf, refreshStatus: refresh.status, extractType: refreshExtractTypes.VPR, - dispatchAction: getVitals, + dispatchAction, dispatch, }); @@ -193,12 +200,24 @@ const VitalDetails = props => { useEffect( () => { - if (updatedRecordType) { + if (updatedRecordType && !isLoading) { const formattedVitalType = macroCase(updatedRecordType); - dispatch(getVitalDetails(formattedVitalType, vitalsList)); + + if (isAcceleratingVitals && vitalsList?.length) { + dispatch(setVitalsList(formattedVitalType)); + } else { + dispatch(getVitalDetails(formattedVitalType, vitalsList)); + } } }, - [vitalType, vitalsList, dispatch, updatedRecordType], + [ + vitalType, + vitalsList, + dispatch, + updatedRecordType, + isAcceleratingVitals, + isLoading, + ], ); const lastUpdatedText = getLastUpdatedText( @@ -426,6 +445,35 @@ Provider notes: ${vital.notes}\n\n`, ); } + if (!records?.length) { + if (isLoading || listState === LOAD_STATES.FETCHING) { + return ( +
    + +
    + ); + } + return ( +
    +

    + We don’t have any {vitalTypeDisplayNames[vitalType]} records for you + right now. Go back to the vitals page to select a different vital. +

    +

    + + Go back to the vitals page + +

    +
    + ); + } return (
    diff --git a/src/applications/mhv-medical-records/containers/Vitals.jsx b/src/applications/mhv-medical-records/containers/Vitals.jsx index 9b95225b152b..ba89ead028e0 100644 --- a/src/applications/mhv-medical-records/containers/Vitals.jsx +++ b/src/applications/mhv-medical-records/containers/Vitals.jsx @@ -7,6 +7,7 @@ import { updatePageTitle, usePrintTitle, } from '@department-of-veterans-affairs/mhv/exports'; +import { useHistory, useLocation } from 'react-router-dom'; import RecordList from '../components/RecordList/RecordList'; import { getVitals, reloadRecords } from '../actions/vitals'; import { @@ -31,17 +32,24 @@ import CernerFacilityAlert from '../components/shared/CernerFacilityAlert'; const Vitals = () => { const dispatch = useDispatch(); + const history = useHistory(); + const location = useLocation(); + const updatedRecordList = useSelector(state => state.mr.vitals.updatedList); const listState = useSelector(state => state.mr.vitals.listState); const vitals = useSelector(state => state.mr.vitals.vitalsList); const user = useSelector(state => state.user.profile); const refresh = useSelector(state => state.mr.refresh); const [cards, setCards] = useState(null); + const urlVitalsDate = new URLSearchParams(location.search).get('timeFrame'); const [acceleratedVitalsDate, setAcceleratedVitalsDate] = useState( - format(new Date(), 'yyyy-MM'), + urlVitalsDate || format(new Date(), 'yyyy-MM'), ); const [displayDate, setDisplayDate] = useState(acceleratedVitalsDate); + const activeAlert = useAlerts(dispatch); + const accessAlert = activeAlert && activeAlert.type === ALERT_TYPE_ERROR; + const vitalsCurrentAsOf = useSelector( state => state.mr.vitals.listCurrentAsOf, ); @@ -91,6 +99,22 @@ const Vitals = () => { [dispatch], ); + useEffect( + () => { + // Only update if there is no time frame. This is only for on initial page load. + const timeFrame = new URLSearchParams(location.search).get('timeFrame'); + if (!timeFrame) { + const searchParams = new URLSearchParams(location.search); + searchParams.set('timeFrame', acceleratedVitalsDate); + history.push({ + pathname: location.pathname, + search: searchParams.toString(), + }); + } + }, + [acceleratedVitalsDate, history, location.pathname, location.search], + ); + usePrintTitle( pageTitles.VITALS_PAGE_TITLE, user.userFullName, @@ -127,8 +151,6 @@ const Vitals = () => { [vitals, VITAL_TYPES], ); - const accessAlert = activeAlert && activeAlert.type === ALERT_TYPE_ERROR; - const content = () => { if (accessAlert) { return ( @@ -164,6 +186,7 @@ const Vitals = () => { ); } + if (cards?.length) { return ( <> @@ -233,6 +256,12 @@ const Vitals = () => { const triggerApiUpdate = e => { e.preventDefault(); + const searchParams = new URLSearchParams(location.search); + searchParams.set('timeFrame', acceleratedVitalsDate); + history.push({ + pathname: location.pathname, + search: searchParams.toString(), + }); setDisplayDate(acceleratedVitalsDate); dispatch({ type: Actions.Vitals.UPDATE_LIST_STATE, diff --git a/src/applications/mhv-medical-records/tests/actions/vitals.unit.spec.js b/src/applications/mhv-medical-records/tests/actions/vitals.unit.spec.js index a05e01e3127e..0ebca64db82c 100644 --- a/src/applications/mhv-medical-records/tests/actions/vitals.unit.spec.js +++ b/src/applications/mhv-medical-records/tests/actions/vitals.unit.spec.js @@ -8,6 +8,8 @@ import { clearVitalDetails, getVitalDetails, getVitals, + setVitalsList, + reloadRecords, } from '../../actions/vitals'; describe('Get vitals action', () => { @@ -74,6 +76,16 @@ describe('Get vital details action', () => { }); }); +describe('set vitals list action', () => { + it('should dispatch a get action', () => { + const dispatch = sinon.spy(); + return setVitalsList('vitalType')(dispatch).then(() => { + expect(dispatch.firstCall.args[0].type).to.equal(Actions.Vitals.GET); + expect(dispatch.firstCall.args[0].vitalType).to.equal('vitalType'); + }); + }); +}); + describe('Clear vital details action', () => { it('should dispatch a clear details action', () => { const dispatch = sinon.spy(); @@ -84,3 +96,14 @@ describe('Clear vital details action', () => { }); }); }); + +describe('reload records action', () => { + it('should dispatch a get action', () => { + const dispatch = sinon.spy(); + return reloadRecords()(dispatch).then(() => { + expect(dispatch.firstCall.args[0].type).to.equal( + Actions.Vitals.COPY_UPDATED_LIST, + ); + }); + }); +}); diff --git a/src/applications/mhv-medical-records/tests/components/MrBreadcrumbs.unit.spec.jsx b/src/applications/mhv-medical-records/tests/components/MrBreadcrumbs.unit.spec.jsx index 1c4fc793bfe9..45632f1d9290 100644 --- a/src/applications/mhv-medical-records/tests/components/MrBreadcrumbs.unit.spec.jsx +++ b/src/applications/mhv-medical-records/tests/components/MrBreadcrumbs.unit.spec.jsx @@ -7,6 +7,8 @@ import { renderInReduxProvider, } from '@department-of-veterans-affairs/platform-testing/react-testing-library-helpers'; import Sinon from 'sinon'; +import { Provider } from 'react-redux'; +import { render } from '@testing-library/react'; import reducer from '../../reducers'; import MrBreadcrumbs from '../../components/MrBreadcrumbs'; @@ -209,4 +211,75 @@ describe('MrBreadcrumbs component', () => { const { getByTestId } = screen; expect(getByTestId('mr-breadcrumbs')).to.exist; }); + + it('tests the time frame in the url logic', () => { + const initialState = { + mr: { + pageTracker: {}, + breadcrumbs: { + crumbsList: [ + { + href: '/', + label: 'VA.gov home', + }, + { + href: '/my-health', + label: 'My HealtheVet', + }, + { + href: '/', + label: 'Medical records', + isRouterLink: true, + }, + ], + }, + }, + featureToggles: { + // eslint-disable-next-line camelcase + mhv_integration_medical_records_to_phase_1: true, + }, + }; + window.document.querySelector = Sinon.stub().returns({ + textContent: 'test', + }); + const dispatchSpy = Sinon.spy(); + const mockStore = { + getState: () => ({ + ...initialState, + }), + subscribe: () => {}, + dispatch: action => { + action(dispatchSpy); + expect(dispatchSpy.called).to.be.true; + Sinon.assert.calledWith( + dispatchSpy, + Sinon.match(params => { + expect(params.type).to.equal('MR_SET_BREAD_CRUMBS'); + expect(params.payload).to.exist; + expect(params.payload.crumbs).to.exist; + expect(params.payload.crumbs.length).to.equal(2); + expect(params.payload.crumbs[0].href).to.equal( + '/vitals?timeFrame=2024-02', + ); + return true; + }), + ); + }, + }; + + const screen = render( + + + + + + + , + ); + + const header = screen.getByTestId('breadcrumbs'); + expect(header).to.exist; + }); }); diff --git a/src/applications/mhv-medical-records/tests/components/VitalListItem.unit.spec.jsx b/src/applications/mhv-medical-records/tests/components/VitalListItem.unit.spec.jsx index 7778a94429d3..9acd1f10e499 100644 --- a/src/applications/mhv-medical-records/tests/components/VitalListItem.unit.spec.jsx +++ b/src/applications/mhv-medical-records/tests/components/VitalListItem.unit.spec.jsx @@ -2,7 +2,10 @@ import { expect } from 'chai'; import React from 'react'; import { renderWithStoreAndRouter } from '@department-of-veterans-affairs/platform-testing/react-testing-library-helpers'; import { beforeEach } from 'mocha'; +import { render } from '@testing-library/react'; +import { MemoryRouter, Route } from 'react-router-dom'; import RecordListItem from '../../components/RecordList/RecordListItem'; +import VitalListItem from '../../components/RecordList/VitalListItem'; import reducer from '../../reducers'; import vitals from '../fixtures/vitals.json'; import { recordType, vitalTypes } from '../../util/constants'; @@ -121,3 +124,22 @@ describe('Vital list item component for a type with no records', () => { expect(recordDetailsLink, screen.container).to.not.exist; }); }); + +describe('Vital List item for OH work', () => { + it('should render the link should include a timeframe', () => { + const BLOOD_PRESSURE = vitals.entry[0].resource; + const record = convertVital(BLOOD_PRESSURE); + const options = { isAccelerating: true, timeFrame: '2024-01' }; + const { getByTestId } = render( + + + , + + , + ); + expect(getByTestId('vital-blood-pressure-review-over-time')).to.exist; + expect( + getByTestId('vital-blood-pressure-review-over-time').href, + ).to.contain('/vitals/blood-pressure-history?timeFrame=2024-01'); + }); +}); diff --git a/src/applications/mhv-medical-records/tests/e2e/accelerated/pages/Vitals.js b/src/applications/mhv-medical-records/tests/e2e/accelerated/pages/Vitals.js index c6b796ba8e78..139f620dada4 100644 --- a/src/applications/mhv-medical-records/tests/e2e/accelerated/pages/Vitals.js +++ b/src/applications/mhv-medical-records/tests/e2e/accelerated/pages/Vitals.js @@ -12,6 +12,8 @@ class Vitals { // check the correct param was used if (useOhData) { expect(req.url).to.contain('use_oh_data_path=1'); + expect(req.url).to.contain('from='); + expect(req.url).to.contain('to='); } else { expect(req.url).to.not.contain('use_oh_data_path=1'); } @@ -44,6 +46,10 @@ class Vitals { .click(); }; + checkUrl = ({ timeFrame }) => { + cy.url().should('include', `?timeFrame=${timeFrame}`); + }; + selectMonthAndYear = ({ month, year, submit = true }) => { cy.get('select[name="vitals-date-pickerMonth"]').select(month); cy.get('input[name="vitals-date-pickerYear"]').clear(); diff --git a/src/applications/mhv-medical-records/tests/e2e/accelerated/vitals/view-vitals-list.oracle-health.cypress.spec.js b/src/applications/mhv-medical-records/tests/e2e/accelerated/vitals/view-vitals-list.oracle-health.cypress.spec.js index 19c463fe632f..a9fda1f02f3f 100644 --- a/src/applications/mhv-medical-records/tests/e2e/accelerated/vitals/view-vitals-list.oracle-health.cypress.spec.js +++ b/src/applications/mhv-medical-records/tests/e2e/accelerated/vitals/view-vitals-list.oracle-health.cypress.spec.js @@ -23,6 +23,12 @@ describe('Medical Records View Vitals', () => { Vitals.goToVitalPage(); + const today = new Date(); + const timeFrame = `${today.getFullYear()}-${(today.getMonth() + 1) + .toString() + .padStart(2, '0')}`; + Vitals.checkUrl({ timeFrame }); + cy.injectAxeThenAxeCheck(); cy.get("[data-testid='current-date-display']").should('be.visible'); diff --git a/src/applications/mhv-medical-records/tests/e2e/accelerated/vitals/view-vitals-list.oracle-health.update-timeframe.cypress.spec.js b/src/applications/mhv-medical-records/tests/e2e/accelerated/vitals/view-vitals-list.oracle-health.update-timeframe.cypress.spec.js new file mode 100644 index 000000000000..4d1e180fb202 --- /dev/null +++ b/src/applications/mhv-medical-records/tests/e2e/accelerated/vitals/view-vitals-list.oracle-health.update-timeframe.cypress.spec.js @@ -0,0 +1,69 @@ +import MedicalRecordsSite from '../../mr_site/MedicalRecordsSite'; +import Vitals from '../pages/Vitals'; +import oracleHealthUser from '../fixtures/user/oracle-health.json'; +import vitalsData from '../fixtures/vitals/sample-lighthouse.json'; + +describe('Medical Records View Vitals', () => { + const site = new MedicalRecordsSite(); + + beforeEach(() => { + site.login(oracleHealthUser, false); + site.mockFeatureToggles({ + isAcceleratingEnabled: true, + isAcceleratingVitals: true, + }); + Vitals.setIntercepts({ vitalData: vitalsData }); + }); + + it('Visits View Vital List', () => { + site.loadPage(); + + // check for MY Va Health links + Vitals.checkLandingPageLinks(); + + Vitals.goToVitalPage(); + + const today = new Date(); + const timeFrame = `${today.getFullYear()}-${(today.getMonth() + 1) + .toString() + .padStart(2, '0')}`; + Vitals.checkUrl({ timeFrame }); + + Vitals.selectMonthAndYear({ + month: 'January', + year: '2020', + }); + Vitals.checkUrl({ timeFrame: '2020-01' }); + + cy.injectAxeThenAxeCheck(); + + cy.get("[data-testid='current-date-display']").should('be.visible'); + cy.get("[data-testid='current-date-display']").should('not.be.empty'); + + cy.get('[data-testid="vital-blood-pressure-review-over-time"]') + .should('be.visible') + .click(); + + Vitals.checkUrl({ timeFrame: '2020-01' }); + + // This is checking that the breadcrumbs are correct + cy.get('va-breadcrumbs') + .shadow() + .find('li') + .eq(3) + .find('a') + .should('be.visible') + .should('have.attr', 'href') + .and('include', '/vitals?timeFrame=2020-01'); + + cy.get('va-breadcrumbs') + .shadow() + .find('li') + .eq(3) + .find('a') + .click(); + + // Maintaining the same timeFrame across page clicks + Vitals.checkUrl({ timeFrame: '2020-01' }); + }); +}); diff --git a/src/applications/mhv-medical-records/tests/e2e/medical-records-view-labs-radiology-images.cypress.spec.js b/src/applications/mhv-medical-records/tests/e2e/medical-records-view-labs-radiology-images.cypress.spec.js index 213305e6932b..dd5216704eb6 100644 --- a/src/applications/mhv-medical-records/tests/e2e/medical-records-view-labs-radiology-images.cypress.spec.js +++ b/src/applications/mhv-medical-records/tests/e2e/medical-records-view-labs-radiology-images.cypress.spec.js @@ -1,24 +1,25 @@ import MedicalRecordsSite from './mr_site/MedicalRecordsSite'; import RadiologyDetailsPage from './pages/RadiologyDetailsPage'; import LabsAndTestsListPage from './pages/LabsAndTestsListPage'; +import defaultLabsAndTests from './fixtures/labs-and-tests/labsAndTests.json'; import statusResponseComplete from './fixtures/labs-and-tests/imaging-status-response-complete.json'; import viewImagesResponse from './fixtures/labs-and-tests/imaging-view-images-response.json'; import imagingStudies from './fixtures/labs-and-tests/radiologyCvix.json'; -describe('Medical Records Redirect Users to MHV Classic to view images', () => { +describe('Medical Records - Radiology images are shown when requested', () => { const site = new MedicalRecordsSite(); before(() => { site.login(); - // cy.visit('my-health/medical-records/labs-and-tests'); - LabsAndTestsListPage.goToLabsAndTests(); + LabsAndTestsListPage.goToLabsAndTests( + defaultLabsAndTests, + imagingStudies, + statusResponseComplete, + true, + ); }); it('View Radiology Images On Radiology Details Page', () => { - RadiologyDetailsPage.interceptImagingEndpoint(imagingStudies); - - RadiologyDetailsPage.interceptImagingStatus(statusResponseComplete); - LabsAndTestsListPage.clickRadiologyDetailsLink(0); const studyId = statusResponseComplete[0].studyIdUrn; @@ -30,10 +31,6 @@ describe('Medical Records Redirect Users to MHV Classic to view images', () => { RadiologyDetailsPage.verifyPaginationVisible(); - // RadiologyDetailsPage.verifyRadiologyImageLink( - // 'Request images on the My HealtheVet website', - // ); - cy.injectAxe(); cy.axeCheck('main', {}); }); diff --git a/src/applications/mhv-medical-records/tests/e2e/mr_site/MedicalRecordsSite.js b/src/applications/mhv-medical-records/tests/e2e/mr_site/MedicalRecordsSite.js index 867c6b163271..191c42479317 100644 --- a/src/applications/mhv-medical-records/tests/e2e/mr_site/MedicalRecordsSite.js +++ b/src/applications/mhv-medical-records/tests/e2e/mr_site/MedicalRecordsSite.js @@ -11,7 +11,6 @@ class MedicalRecordsSite { } this.mockVamcEhr(); this.mockMaintenanceWindow(); - cy.login(userFixture); cy.intercept('POST', '/my_health/v1/medical_records/session', { statusCode: 204, body: {}, @@ -20,9 +19,7 @@ class MedicalRecordsSite { statusCode: 200, body: sessionStatus, // status response copied from staging }).as('status'); - // src/platform/testing/e2e/cypress/support/commands/login.js handles the next two lines - // window.localStorage.setItem('isLoggedIn', true); - // cy.intercept('GET', '/v0/user', mockUser).as('mockUser'); + cy.login(userFixture); }; mockFeatureToggles = ({ diff --git a/src/applications/mhv-medical-records/tests/e2e/pages/LabsAndTestsListPage.js b/src/applications/mhv-medical-records/tests/e2e/pages/LabsAndTestsListPage.js index dd15131f7352..92cf0fa69c06 100644 --- a/src/applications/mhv-medical-records/tests/e2e/pages/LabsAndTestsListPage.js +++ b/src/applications/mhv-medical-records/tests/e2e/pages/LabsAndTestsListPage.js @@ -1,11 +1,12 @@ import defaultLabsAndTests from '../fixtures/labs-and-tests/labsAndTests.json'; import radiologyRecordsMhv from '../fixtures/labs-and-tests/radiologyRecordsMhv.json'; -// import radiologyRecordsMhv from '../../tests/fixtures/labs-and-tests/radiologyRecordsMhv.json'; import BaseListPage from './BaseListPage'; class LabsAndTestsListPage extends BaseListPage { goToLabsAndTests = ( labsAndTests = defaultLabsAndTests, + imaging = [], + imagingStatus = [], waitForLabsAndTests = false, ) => { cy.intercept( @@ -18,12 +19,14 @@ class LabsAndTestsListPage extends BaseListPage { '/my_health/v1/medical_records/radiology', radiologyRecordsMhv, ).as('RadiologyRecordsMhv'); - cy.intercept('GET', '/my_health/v1/medical_records/imaging', []).as( - 'CvixRadiologyRecordsMhv', - ); - cy.intercept('GET', '/my_health/v1/medical_records/imaging/status', []).as( + cy.intercept('GET', '/my_health/v1/medical_records/imaging', imaging).as( 'CvixRadiologyRecordsMhv', ); + cy.intercept( + 'GET', + '/my_health/v1/medical_records/imaging/status', + imagingStatus, + ).as('CvixRadiologyRecordsMhv'); cy.intercept( 'GET', '/my_health/v1/medical_records/bbmi_notification/status', diff --git a/src/applications/mhv-medical-records/tests/util/helpers.unit.spec.js b/src/applications/mhv-medical-records/tests/util/helpers.unit.spec.js index dbb3ad67c2bf..3e28ae264f00 100644 --- a/src/applications/mhv-medical-records/tests/util/helpers.unit.spec.js +++ b/src/applications/mhv-medical-records/tests/util/helpers.unit.spec.js @@ -20,6 +20,7 @@ import { getMonthFromSelectedDate, formatDateInLocalTimezone, handleDataDogAction, + removeTrailingSlash, } from '../../util/helpers'; import { refreshPhases } from '../../util/constants'; @@ -720,3 +721,26 @@ describe('formatDateInLocalTimezone', () => { expect(formattedDate).to.equal(expectedDate); }); }); + +describe('removeTrailingSlash', () => { + it('should remove the trailing slash from a string', () => { + const string = 'https://example.com/'; + const result = removeTrailingSlash(string); + expect(result).to.equal('https://example.com'); + }); + it('should return the string if there is no trailing slash', () => { + const string = 'https://example.com'; + const result = removeTrailingSlash(string); + expect(result).to.equal(string); + }); + it('should return the string if the string is empty', () => { + const string = ''; + const result = removeTrailingSlash(string); + expect(result).to.equal(string); + }); + it('should return the string if the string is null', () => { + const string = null; + const result = removeTrailingSlash(string); + expect(result).to.equal(string); + }); +}); diff --git a/src/applications/mhv-medical-records/util/helpers.js b/src/applications/mhv-medical-records/util/helpers.js index 9becdfdaac18..99bb36dae6fc 100644 --- a/src/applications/mhv-medical-records/util/helpers.js +++ b/src/applications/mhv-medical-records/util/helpers.js @@ -702,3 +702,14 @@ export const focusOnErrorField = () => { export const formatUserDob = userProfile => { return userProfile?.dob ? formatDateLong(userProfile.dob) : 'Not found'; }; + +/** + * Removes the trailing slash from a path + * + * @param {string} path path to remove trailing slash from + * @returns {string} path without trailing slash + */ +export const removeTrailingSlash = path => { + if (!path) return path; + return path.replace(/\/$/, ''); +}; diff --git a/src/applications/mhv-medications/components/PrescriptionDetails/GroupedMedications.jsx b/src/applications/mhv-medications/components/PrescriptionDetails/GroupedMedications.jsx index 757f87e25b10..e9fb427bd8af 100644 --- a/src/applications/mhv-medications/components/PrescriptionDetails/GroupedMedications.jsx +++ b/src/applications/mhv-medications/components/PrescriptionDetails/GroupedMedications.jsx @@ -6,7 +6,7 @@ import { EMPTY_FIELD } from '../../util/constants'; import { dateFormat, fromToNumbs } from '../../util/helpers'; import LastFilledInfo from '../shared/LastFilledInfo'; -const MAX_PAGE_LIST_LENGTH = 10; +const MAX_PAGE_LIST_LENGTH = 2; const MAX_GROUPED_LIST_LENGTH = 26; const GroupedMedications = props => { @@ -54,9 +54,11 @@ const GroupedMedications = props => { id="list-showing-info" data-testid="grouping-showing-info" > - {`Showing ${displayNums[0]} - ${ - displayNums[1] - } of ${totalListCount} prescriptions, from newest to oldest`} + {totalListCount === 1 + ? `Showing ${totalListCount} prescription` + : `Showing ${displayNums[0]} to ${ + displayNums[1] + } of ${totalListCount} prescriptions, from newest to oldest`}

    @@ -68,7 +70,7 @@ const GroupedMedications = props => { key={rx.prescriptionId} >
    - Prescription number: {rx.prescriptionNumber} +

    Prescription number: {rx.prescriptionNumber}

    @@ -87,7 +89,7 @@ const GroupedMedications = props => { {totalListCount > MAX_PAGE_LIST_LENGTH && ( onPageChange(e.detail.page)} - max-page-list-length={MAX_PAGE_LIST_LENGTH} + max-page-list-length={3} className="vads-u-justify-content--center no-print vads-u-margin-top--3" page={currentPage} pages={Math.ceil(totalListCount / MAX_PAGE_LIST_LENGTH)} diff --git a/src/applications/mhv-medications/tests/components/PrescriptionDetails/GroupedMedications.unit.spec.jsx b/src/applications/mhv-medications/tests/components/PrescriptionDetails/GroupedMedications.unit.spec.jsx index 4d597ad004e3..8d84e15d0ed5 100644 --- a/src/applications/mhv-medications/tests/components/PrescriptionDetails/GroupedMedications.unit.spec.jsx +++ b/src/applications/mhv-medications/tests/components/PrescriptionDetails/GroupedMedications.unit.spec.jsx @@ -27,7 +27,7 @@ describe('Grouped medications component', () => { await waitFor(() => { expect( screen.getByText( - `Showing 1 - 10 of ${ + `Showing 1 to 2 of ${ groupedMedicationsList.length } prescriptions, from newest to oldest`, ), diff --git a/src/applications/mhv-medications/tests/e2e/med_site/MedicationsSite.js b/src/applications/mhv-medications/tests/e2e/med_site/MedicationsSite.js index ce01f5bd5361..cb4d72dba773 100644 --- a/src/applications/mhv-medications/tests/e2e/med_site/MedicationsSite.js +++ b/src/applications/mhv-medications/tests/e2e/med_site/MedicationsSite.js @@ -14,17 +14,17 @@ class MedicationsSite { this.mockVamcEhr(); if (isMedicationsUser) { - cy.login(mockUser); - // src/platform/testing/e2e/cypress/support/commands/login.js handles the next two lines - // window.localStorage.setItem('isLoggedIn', true); - // cy.intercept('GET', '/v0/user', mockUser).as('mockUser'); - cy.intercept( 'GET', '/my_health/v1/prescriptions?page=1&per_page=999', prescriptions, ).as('prescriptions'); cy.intercept('GET', '/health-care/refill-track-prescriptions'); + + // src/platform/testing/e2e/cypress/support/commands/login.js handles the next two lines + // window.localStorage.setItem('isLoggedIn', true); + // cy.intercept('GET', '/v0/user', mockUser).as('mockUser'); + cy.login(mockUser); } else { // cy.login(); window.localStorage.setItem('isLoggedIn', false); diff --git a/src/applications/mhv-medications/tests/e2e/pages/MedicationsLandingPage.js b/src/applications/mhv-medications/tests/e2e/pages/MedicationsLandingPage.js index 89d65fc82624..1ecc1937f59d 100644 --- a/src/applications/mhv-medications/tests/e2e/pages/MedicationsLandingPage.js +++ b/src/applications/mhv-medications/tests/e2e/pages/MedicationsLandingPage.js @@ -27,8 +27,8 @@ class MedicationsLandingPage { }; visitLandingPageURL = () => { - cy.visit(medicationsUrls.MEDICATIONS_ABOUT); cy.intercept('GET', Paths.LANDING_LIST, rxList); + cy.visit(medicationsUrls.MEDICATIONS_ABOUT); }; verifyPrescriptionRefillRequestInformationAccordionDropDown = () => { @@ -128,8 +128,8 @@ class MedicationsLandingPage { }; visitLandingPageURLforEmptyMedicationsList = () => { - cy.visit(medicationsUrls.MEDICATIONS_ABOUT); cy.intercept('GET', Paths.LANDING_LIST, emptyPrescriptionsList); + cy.visit(medicationsUrls.MEDICATIONS_ABOUT); }; visitMedicationsListPage = prescriptionsList => { diff --git a/src/applications/mhv-medications/tests/e2e/pages/MedicationsRefillPage.js b/src/applications/mhv-medications/tests/e2e/pages/MedicationsRefillPage.js index f6c738aea799..650b3a159ec7 100644 --- a/src/applications/mhv-medications/tests/e2e/pages/MedicationsRefillPage.js +++ b/src/applications/mhv-medications/tests/e2e/pages/MedicationsRefillPage.js @@ -5,13 +5,13 @@ import { Paths } from '../utils/constants'; class MedicationsRefillPage { loadRefillPage = prescriptions => { - cy.visit(medicationsUrls.MEDICATIONS_REFILL); cy.intercept( 'GET', 'my_health/v1/prescriptions/list_refillable_prescriptions', prescriptions, ).as('refillList'); cy.intercept('GET', '/my_health/v1/medical_records/allergies', allergies); + cy.visit(medicationsUrls.MEDICATIONS_REFILL); }; loadRefillPageForApiCallFailure = () => { diff --git a/src/applications/mhv-medications/tests/e2e/utils/constants.js b/src/applications/mhv-medications/tests/e2e/utils/constants.js index 3b4856244333..e9d4059d96ae 100644 --- a/src/applications/mhv-medications/tests/e2e/utils/constants.js +++ b/src/applications/mhv-medications/tests/e2e/utils/constants.js @@ -13,11 +13,11 @@ export const Data = { ACTIVE_REFILL_IN_PROCESS: 'We expect to fill this prescription on', ACTIVE_NON_VA: 'You can’t manage this medication in this online tool.', PREVIOUS_PRESCRIPTION_PAGINATION: - 'Showing 1 - 10 of 26 prescriptions, from newest to oldest', + 'Showing 1 to 2 of 26 prescriptions, from newest to oldest', PREVIOUS_PRESCRIPTION_PAGINATION_SECOND: - 'Showing 11 - 20 of 26 prescriptions, from newest to oldest', + 'Showing 3 to 4 of 26 prescriptions, from newest to oldest', PREVIOUS_PRESCRIPTION_PAGINATION_THIRD: - 'Showing 21 - 26 of 26 prescriptions, from newest to oldest', + 'Showing 5 to 6 of 26 prescriptions, from newest to oldest', REFILL_HISTORY_INFO: 'Showing 12 refills, from newest to oldest', FILL_DATE_FIELD: 'Filled by pharmacy on', IMAGE_FIELD: 'Image', diff --git a/src/applications/pre-need-integration/config/transformForSubmit.js b/src/applications/pre-need-integration/config/transformForSubmit.js index 923b6e7a0f1f..76f60769a6b9 100644 --- a/src/applications/pre-need-integration/config/transformForSubmit.js +++ b/src/applications/pre-need-integration/config/transformForSubmit.js @@ -1,17 +1,5 @@ import { transformForSubmit as formsSystemTransformForSubmit } from 'platform/forms-system/src/js/helpers'; -const escapedCharacterReplacer = (_key, value) => { - if (typeof value === 'string') { - return value - .replaceAll('"', "'") - .replace(/(?:\r\n|\n\n|\r|\n)/g, '; ') - .replace(/(?:\t|\f|\b)/g, '') - .replace(/\\(?!(f|n|r|t|[u,U][\d,a-fA-F]{4}))/gm, '/'); - } - - return value; -}; - export default function transformForSubmit(formConfig, form) { const formCopy = { ...form, @@ -27,8 +15,12 @@ export default function transformForSubmit(formConfig, form) { }, }; delete formCopy.data.serviceRecords; + + /** @type {ReplacerOptions} */ + const options = { replaceEscapedCharacters: true }; + const transformedData = JSON.parse( - formsSystemTransformForSubmit(formConfig, formCopy), + formsSystemTransformForSubmit(formConfig, formCopy, options), ); if ( formCopy.data.application.applicant.applicantRelationshipToClaimant === @@ -37,12 +29,10 @@ export default function transformForSubmit(formConfig, form) { delete transformedData.application.applicant.name; delete transformedData.application.applicant.mailingAddress; } - return JSON.stringify( - { - ...transformedData, - formNumber: formConfig.formId, - version: 'int', - }, - escapedCharacterReplacer, - ); + + return JSON.stringify({ + ...transformedData, + formNumber: formConfig.formId, + version: 'int', + }); } diff --git a/src/applications/pre-need/config/transformForSubmit.js b/src/applications/pre-need/config/transformForSubmit.js index 8a877bb4ff2c..6c4c79047239 100644 --- a/src/applications/pre-need/config/transformForSubmit.js +++ b/src/applications/pre-need/config/transformForSubmit.js @@ -1,24 +1,12 @@ import { transformForSubmit as formsSystemTransformForSubmit } from 'platform/forms-system/src/js/helpers'; -const escapedCharacterReplacer = (_key, value) => { - if (typeof value === 'string') { - return value - .replaceAll('"', "'") - .replace(/(?:\r\n|\n\n|\r|\n)/g, '; ') - .replace(/(?:\t|\f|\b)/g, '') - .replace(/\\(?!(f|n|r|t|[u,U][\d,a-fA-F]{4}))/gm, '/'); - } - - return value; -}; - export default function transformForSubmit(formConfig, form) { + /** @type {ReplacerOptions} */ + const options = { replaceEscapedCharacters: true }; + const transformedData = JSON.parse( - formsSystemTransformForSubmit(formConfig, form), + formsSystemTransformForSubmit(formConfig, form, options), ); - return JSON.stringify( - { ...transformedData, formNumber: formConfig.formId }, - escapedCharacterReplacer, - ); + return JSON.stringify({ ...transformedData, formNumber: formConfig.formId }); } diff --git a/src/applications/representative-appoint/components/ClaimantTypeForm.jsx b/src/applications/representative-appoint/components/ClaimantTypeForm.jsx index ee02ac936567..3ba5932d0e25 100644 --- a/src/applications/representative-appoint/components/ClaimantTypeForm.jsx +++ b/src/applications/representative-appoint/components/ClaimantTypeForm.jsx @@ -4,6 +4,7 @@ import FormTitle from 'platform/forms-system/src/js/components/FormTitle'; import FormNavButtons from '~/platform/forms-system/src/js/components/FormNavButtons'; import SchemaForm from '~/platform/forms-system/src/js/components/SchemaForm'; import { schema, uiSchema } from '../pages/claimant/claimantType'; +import GetFormHelp from './GetFormHelp'; const ClaimantTypeForm = props => { const { data, onGoBack, onChange, onSubmit } = props; @@ -25,6 +26,10 @@ const ClaimantTypeForm = props => { data={data} > +
    +

    Need help?

    + +
    ); diff --git a/src/applications/representative-appoint/components/GetFormHelp.jsx b/src/applications/representative-appoint/components/GetFormHelp.jsx index 4c263224f341..e0f44a8d8905 100644 --- a/src/applications/representative-appoint/components/GetFormHelp.jsx +++ b/src/applications/representative-appoint/components/GetFormHelp.jsx @@ -2,17 +2,14 @@ import React from 'react'; import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts'; const GetFormHelp = () => ( -
    -

    - How to contact us if you have questions -

    -

    + <> +

    You can call us at{' '} ( ). We’re here 24/7.

    -
    + ); export default GetFormHelp; diff --git a/src/applications/representative-appoint/components/NeedHelp.jsx b/src/applications/representative-appoint/components/NeedHelp.jsx deleted file mode 100644 index c11883875122..000000000000 --- a/src/applications/representative-appoint/components/NeedHelp.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -export default function NeedHelp() { - return ( - <> -

    - Need help? -

    -

    - You can call us at {' '} - . We're here 24/7. -

    - - ); -} diff --git a/src/applications/representative-appoint/config/form.js b/src/applications/representative-appoint/config/form.js index 0a2fdc42d0a3..0add5f0207a0 100644 --- a/src/applications/representative-appoint/config/form.js +++ b/src/applications/representative-appoint/config/form.js @@ -2,6 +2,7 @@ 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'; import IntroductionPage from '../containers/IntroductionPage'; @@ -65,6 +66,7 @@ const formConfig = { introduction: IntroductionPage, confirmation: ConfirmationPage, footerContent: FormFooter, + getHelp: GetFormHelp, formId: '21-22', preSubmitInfo: { CustomComponent: PreSubmitInfo, diff --git a/src/applications/representative-appoint/containers/ConfirmationPage.jsx b/src/applications/representative-appoint/containers/ConfirmationPage.jsx index 81ad42bb31c9..5e7a11ddb577 100644 --- a/src/applications/representative-appoint/containers/ConfirmationPage.jsx +++ b/src/applications/representative-appoint/containers/ConfirmationPage.jsx @@ -4,10 +4,11 @@ import PropTypes from 'prop-types'; import { VaCheckbox } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; import scrollTo from 'platform/utilities/ui/scrollTo'; -import NeedHelp from '../components/NeedHelp'; import sendNextStepsEmail from '../api/sendNextStepsEmail'; import { getFormNumber, getFormName } from '../utilities/helpers'; +import GetFormHelp from '../components/GetFormHelp'; + export default function ConfirmationPage({ router }) { const [signedForm, setSignedForm] = useState(false); const [signedFormError, setSignedFormError] = useState(false); @@ -79,7 +80,11 @@ export default function ConfirmationPage({ router }) { onVaChange={handlers.onChangeSignedFormCheckbox} /> - + +
    +

    Need help?

    + +
    ); } diff --git a/src/applications/representative-appoint/containers/IntroductionPage.jsx b/src/applications/representative-appoint/containers/IntroductionPage.jsx index 170dcb62c768..3c6a74ba4140 100644 --- a/src/applications/representative-appoint/containers/IntroductionPage.jsx +++ b/src/applications/representative-appoint/containers/IntroductionPage.jsx @@ -6,7 +6,6 @@ import SaveInProgressIntro from 'platform/forms/save-in-progress/SaveInProgressI import repStatusLoader from 'applications/static-pages/representative-status'; import { useStore, connect } from 'react-redux'; import { isLoggedIn } from 'platform/user/selectors'; -import GetFormHelp from '../components/GetFormHelp'; const IntroductionPage = props => { const { route, loggedIn } = props; @@ -144,7 +143,6 @@ const IntroductionPage = props => { res-burden="5" />

    - ); }; diff --git a/src/applications/representative-appoint/containers/NextStepsPage.jsx b/src/applications/representative-appoint/containers/NextStepsPage.jsx index 3892994adc73..a4d08aa056c4 100644 --- a/src/applications/representative-appoint/containers/NextStepsPage.jsx +++ b/src/applications/representative-appoint/containers/NextStepsPage.jsx @@ -4,7 +4,8 @@ import FormTitle from 'platform/forms-system/src/js/components/FormTitle'; import scrollTo from 'platform/utilities/ui/scrollTo'; import ContactCard from '../components/ContactCard'; import AddressBlock from '../components/AddressBlock'; -import NeedHelp from '../components/NeedHelp'; +import GetFormHelp from '../components/GetFormHelp'; + import { addressExists, getFormSubtitle, @@ -70,7 +71,10 @@ export default function NextStepsPage() { Go back to VA.gov - +

    +

    Need help?

    + +
    ); } diff --git a/src/applications/simple-forms-form-engine/shared/actions/form-load/index.js b/src/applications/simple-forms-form-engine/shared/actions/form-load/index.js index 3bcac8080779..0125909e225d 100644 --- a/src/applications/simple-forms-form-engine/shared/actions/form-load/index.js +++ b/src/applications/simple-forms-form-engine/shared/actions/form-load/index.js @@ -6,7 +6,7 @@ export const INTEGRATION_DEPLOYMENT = 'https://pr18811-ps4nwwul37jtyembecv4bg0gafmyl3oj.ci.cms.va.gov'; import { fetchDrupalStaticDataFile } from 'platform/site-wide/drupal-static-data/connect/fetch'; -import mockForms from '../../_config/formConfig'; +import mockForms from '../../config/formConfig'; import { createFormConfig } from '../../utils/formConfig'; export const formLoadingInitiated = formId => { diff --git a/src/applications/simple-forms-form-engine/shared/_config/formConfig.js b/src/applications/simple-forms-form-engine/shared/config/formConfig.js similarity index 100% rename from src/applications/simple-forms-form-engine/shared/_config/formConfig.js rename to src/applications/simple-forms-form-engine/shared/config/formConfig.js diff --git a/src/applications/simple-forms-form-engine/shared/config/submitTransformer.js b/src/applications/simple-forms-form-engine/shared/config/submitTransformer.js new file mode 100644 index 000000000000..0ffc94b381a9 --- /dev/null +++ b/src/applications/simple-forms-form-engine/shared/config/submitTransformer.js @@ -0,0 +1,28 @@ +import { transformForSubmit as platformTransformForSubmit } from 'platform/forms-system/exportsFile'; + +/** + * @param formConfig {FormConfig} + * @param form {Object} + * @returns {string} The transformed form data as a JSON string + */ +const transformForSubmit = (formConfig, form) => { + // We need the form number to send it to the right model + const formData = { + ...form.data, + formNumber: formConfig.formId, + }; + + /** @type {ReplacerOptions} */ + const options = { replaceEscapedCharacters: true }; + + return platformTransformForSubmit( + formConfig, + { + ...form, + data: formData, + }, + options, + ); +}; + +export default transformForSubmit; diff --git a/src/applications/simple-forms-form-engine/shared/tests/unit/actions/form-load.unit.spec.js b/src/applications/simple-forms-form-engine/shared/tests/unit/actions/form-load.unit.spec.js index 1e7675924976..b44e07c20c9e 100644 --- a/src/applications/simple-forms-form-engine/shared/tests/unit/actions/form-load.unit.spec.js +++ b/src/applications/simple-forms-form-engine/shared/tests/unit/actions/form-load.unit.spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { DATA_FILES_PATH } from 'platform/site-wide/drupal-static-data/constants'; -import { normalizedForm } from '../../../_config/formConfig'; +import { normalizedForm } from '../../../config/formConfig'; import { DIGITAL_FORMS_FILENAME, FORM_LOADING_SUCCEEDED, diff --git a/src/applications/simple-forms-form-engine/shared/tests/unit/config/submitTransformer.unit.spec.js b/src/applications/simple-forms-form-engine/shared/tests/unit/config/submitTransformer.unit.spec.js new file mode 100644 index 000000000000..e5f98bf4ac41 --- /dev/null +++ b/src/applications/simple-forms-form-engine/shared/tests/unit/config/submitTransformer.unit.spec.js @@ -0,0 +1,34 @@ +import { expect } from 'chai'; +import * as helpers from 'platform/forms-system/src/js/helpers'; +import sinon from 'sinon'; +import transformForSubmit from '../../../config/submitTransformer'; + +describe('transformForSubmit', () => { + let spy; + let transformedString; + + const form = { data: {} }; + const formConfig = { formId: '1234' }; + + beforeEach(() => { + spy = sinon.spy(helpers, 'transformForSubmit'); + + transformedString = transformForSubmit(formConfig, form); + }); + + afterEach(() => { + spy.restore(); + }); + + it('calls the platform transformForSubmit', () => { + expect(spy.calledWith(formConfig)).to.eq(true); + + const options = spy.getCall(0).args[2]; + + expect(options.replaceEscapedCharacters).to.eq(true); + }); + + it('includes the form number', () => { + expect(JSON.parse(transformedString).formNumber).to.eq(formConfig.formId); + }); +}); diff --git a/src/applications/simple-forms-form-engine/shared/tests/unit/utils/digitalFormPatterns.unit.spec.js b/src/applications/simple-forms-form-engine/shared/tests/unit/utils/digitalFormPatterns.unit.spec.js index 484a308cc6db..33a91503fc3e 100644 --- a/src/applications/simple-forms-form-engine/shared/tests/unit/utils/digitalFormPatterns.unit.spec.js +++ b/src/applications/simple-forms-form-engine/shared/tests/unit/utils/digitalFormPatterns.unit.spec.js @@ -3,7 +3,7 @@ import sinon from 'sinon'; import * as webComponentPatterns from 'platform/forms-system/src/js/web-component-patterns'; import * as addressPatterns from 'platform/forms-system/src/js/web-component-patterns/addressPattern'; import * as digitalFormPatterns from '../../../utils/digitalFormPatterns'; -import { normalizedForm } from '../../../_config/formConfig'; +import { normalizedForm } from '../../../config/formConfig'; const { addressPages, diff --git a/src/applications/simple-forms-form-engine/shared/tests/unit/utils/formConfig.unit.spec.js b/src/applications/simple-forms-form-engine/shared/tests/unit/utils/formConfig.unit.spec.js index dea57b2f1497..4f80ab01ae50 100644 --- a/src/applications/simple-forms-form-engine/shared/tests/unit/utils/formConfig.unit.spec.js +++ b/src/applications/simple-forms-form-engine/shared/tests/unit/utils/formConfig.unit.spec.js @@ -7,7 +7,8 @@ import sinon from 'sinon'; import { render } from '@testing-library/react'; import * as digitalFormPatterns from '../../../utils/digitalFormPatterns'; import * as IntroductionPage from '../../../containers/IntroductionPage'; -import { normalizedForm } from '../../../_config/formConfig'; +import * as submitTransform from '../../../config/submitTransformer'; +import { normalizedForm } from '../../../config/formConfig'; import { createFormConfig, formatPages, @@ -24,6 +25,7 @@ const [ describe('createFormConfig', () => { let formConfig; let stub; + let transformSpy; beforeEach(() => { const FakeComponent = ({ ombInfo }) => ( @@ -42,6 +44,7 @@ describe('createFormConfig', () => { }), }; stub = sinon.stub(IntroductionPage, 'default').callsFake(FakeComponent); + transformSpy = sinon.spy(submitTransform, 'default'); formConfig = createFormConfig(normalizedForm, { rootUrl: '/root-url', @@ -51,6 +54,7 @@ describe('createFormConfig', () => { afterEach(() => { stub.restore(); + transformSpy.restore(); }); it('returns a properly formatted Form Config object', () => { @@ -97,6 +101,18 @@ describe('createFormConfig', () => { expect(statementOfTruth.body).to.eq(statementOfTruthBody); expect(statementOfTruth.fullNamePath).to.eq('fullName'); }); + + it('includes transformForSubmit', () => { + const form = { data: {} }; + + formConfig.transformForSubmit(formConfig, form); + + expect(transformSpy.calledWith(formConfig, form)).to.eq(true); + }); + + it('does not include a custom submit', () => { + expect(formConfig.submit).to.eq(undefined); + }); }); describe('formatPages', () => { diff --git a/src/applications/simple-forms-form-engine/shared/utils/formConfig.js b/src/applications/simple-forms-form-engine/shared/utils/formConfig.js index 65a2e16ac907..4257df6da224 100644 --- a/src/applications/simple-forms-form-engine/shared/utils/formConfig.js +++ b/src/applications/simple-forms-form-engine/shared/utils/formConfig.js @@ -7,6 +7,7 @@ import { personalInfoPages, phoneAndEmailPages, } from './digitalFormPatterns'; +import transformForSubmit from '../config/submitTransformer'; const getChapterKey = chapter => chapter.type === 'digital_form_your_personal_info' @@ -64,14 +65,13 @@ export const createFormConfig = (form, options) => { }, }, rootUrl, - urlPrefix: '/', - // eslint-disable-next-line no-console - submit: () => console.log(`Submitted ${subTitle}`), introduction: props => , confirmation: ConfirmationPage, formId, saveInProgress: {}, trackingPrefix, + transformForSubmit, + urlPrefix: '/', version: 0, prefillEnabled: true, savedFormMessages: { diff --git a/src/applications/simple-forms/20-10207/tests/unit/containers/ConfirmationPage.unit.spec.jsx b/src/applications/simple-forms/20-10207/tests/unit/containers/ConfirmationPage.unit.spec.jsx index 9e1c9fe80700..a47dbef6b629 100644 --- a/src/applications/simple-forms/20-10207/tests/unit/containers/ConfirmationPage.unit.spec.jsx +++ b/src/applications/simple-forms/20-10207/tests/unit/containers/ConfirmationPage.unit.spec.jsx @@ -3,7 +3,6 @@ import React from 'react'; import { expect } from 'chai'; import { mount } from 'enzyme'; import { createStore } from 'redux'; -import configureMockStore from 'redux-mock-store'; import { Provider } from 'react-redux'; import { cleanup } from '@testing-library/react'; import { format } from 'date-fns'; @@ -12,32 +11,35 @@ import formConfig from '../../../config/form'; import ConfirmationPage from '../../../containers/ConfirmationPage'; import testData from '../../e2e/fixtures/data/backend-mapping-support/veteran-maximal-2.json'; -describe('ConfirmationPage', () => { - let wrapper; - let store; - const mockStore = configureMockStore(); - const initialState = { - form: { - data: { - ...createInitialState(formConfig), - ...testData.data, - }, - submission: { - response: { - confirmationNumber: '1234567890', - }, - timestamp: '2022-01-01T00:00:00Z', +const submitDate = new Date(); +const initialState = { + form: { + ...createInitialState(formConfig), + testData, + submission: { + response: { + confirmationNumber: '1234567890', }, + timestamp: submitDate, }, - }; + }, +}; +const mockStore = state => createStore(() => state); + +const mountPage = (state = initialState) => { + const store = mockStore(state); + return mount( + + + , + ); +}; + +describe('ConfirmationPage', () => { + let wrapper; beforeEach(() => { - store = mockStore(initialState); - wrapper = mount( - - - , - ); + wrapper = mountPage(); }); afterEach(() => { @@ -50,52 +52,20 @@ describe('ConfirmationPage', () => { it('passes the correct props to ConfirmationPageView', () => { const confirmationViewProps = wrapper.find('ConfirmationView').props(); - expect(confirmationViewProps.submitDate).to.equal('2022-01-01T00:00:00Z'); + expect(confirmationViewProps.submitDate).to.equal(submitDate); expect(confirmationViewProps.confirmationNumber).to.equal('1234567890'); }); it('should select form from state when state.form is defined', () => { - const submitDate = new Date(); - const mockInitialState = { - form: { - submission: { - timestamp: submitDate, - response: { confirmationNumber: '1234' }, - }, - data: { - ...createInitialState(formConfig), - ...testData.data, - }, - }, - }; - const mockDefinedState = createStore(() => mockInitialState); - - const definedWrapper = mount( - - - , - ); - - expect(definedWrapper.text()).to.include( - format(submitDate, 'MMMM d, yyyy'), - ); - expect(definedWrapper.text()).to.include('1234'); - - definedWrapper.unmount(); + expect(wrapper.text()).to.include(format(submitDate, 'MMMM d, yyyy')); + expect(wrapper.text()).to.include('1234'); }); it('should throw error when state.form is undefined', () => { - const mockEmptyState = {}; - const mockEmptyStore = createStore(() => mockEmptyState); - let errorWrapper; expect(() => { - errorWrapper = mount( - - - , - ); + errorWrapper = mountPage({}); }).to.throw(); if (errorWrapper) { diff --git a/src/applications/simple-forms/21-4142/containers/ConfirmationPage.jsx b/src/applications/simple-forms/21-4142/containers/ConfirmationPage.jsx index 34f096bc1e99..1b9ae05017f5 100644 --- a/src/applications/simple-forms/21-4142/containers/ConfirmationPage.jsx +++ b/src/applications/simple-forms/21-4142/containers/ConfirmationPage.jsx @@ -2,15 +2,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect, useSelector } from 'react-redux'; -import { ConfirmationPageView } from '../../shared/components/ConfirmationPageView'; +import { ConfirmationView } from 'platform/forms-system/src/js/components/ConfirmationView'; -const content = { - headlineText: 'Thank you for submitting your authorization request', - nextStepsText: - 'After we review your authorization, we’ll contact the private provider or hospital to get the requested records. If we can’t get the records within 15 days we’ll send you a follow-up letter by mail.', -}; +const alertContent = confirmationNumber => ( + <> +

    Thank you for submitting your authorization request

    +

    + After we review your authorization, we’ll contact the private provider or + hospital to get the requested records. If we can’t get the records within + 15 days we’ll send you a follow-up letter by mail. +

    +

    Your confirmation number is {confirmationNumber}.

    + +); -export const ConfirmationPage = () => { +export const ConfirmationPage = props => { const form = useSelector(state => state.form || {}); const { submission, data } = form; const preparerNameDefined = @@ -23,12 +29,27 @@ export const ConfirmationPage = () => { const confirmationNumber = submission.response?.confirmationNumber; return ( - + submitterName={preparerName} + pdfUrl={submission.response?.pdfUrl} + devOnly={{ + showButtons: true, + }} + > + + + + + + + + + ); }; @@ -42,10 +63,18 @@ ConfirmationPage.propTypes = { }), formId: PropTypes.string, submission: PropTypes.shape({ + response: PropTypes.shape({ + attributes: PropTypes.shape({ + confirmationNumber: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, timestamp: PropTypes.string, }), }), name: PropTypes.string, + route: PropTypes.shape({ + formConfig: PropTypes.object, + }), }; function mapStateToProps(state) { diff --git a/src/applications/simple-forms/21-4142/tests/containers/ConfirmationPage.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/containers/ConfirmationPage.unit.spec.jsx deleted file mode 100644 index 6fddd6f83d2f..000000000000 --- a/src/applications/simple-forms/21-4142/tests/containers/ConfirmationPage.unit.spec.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import { render } from '@testing-library/react'; -import configureStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { expect } from 'chai'; -import formConfig from '../../config/form'; -import ConfirmationPage from '../../containers/ConfirmationPage'; - -const storeBase = { - form: { - formId: formConfig.formId, - submission: { - response: { - confirmationNumber: '123456', - }, - timestamp: Date.now(), - }, - data: { - veteran: { - fullName: { - first: 'Jack', - middle: 'W', - last: 'Witness', - }, - }, - }, - }, -}; - -describe('Confirmation page', () => { - const middleware = [thunk]; - const mockStore = configureStore(middleware); - - it('it should show status success and the correct name of person', () => { - const { container, getByText } = render( - - - , - ); - expect(container.querySelector('va-alert')).to.have.attr( - 'status', - 'success', - ); - getByText(/Jack W Witness/); - }); -}); diff --git a/src/applications/simple-forms/21-4142/tests/components/RecordField.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/components/RecordField.unit.spec.jsx similarity index 84% rename from src/applications/simple-forms/21-4142/tests/components/RecordField.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/components/RecordField.unit.spec.jsx index 7912e0316da6..135109a1c6b4 100644 --- a/src/applications/simple-forms/21-4142/tests/components/RecordField.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/components/RecordField.unit.spec.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { render } from '@testing-library/react'; -import RecordField from '../../components/RecordField'; +import RecordField from '../../../components/RecordField'; describe('RecordField', () => { it('should render', () => { diff --git a/src/applications/simple-forms/21-4142/tests/unit/containers/ConfirmationPage.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/containers/ConfirmationPage.unit.spec.jsx new file mode 100644 index 000000000000..f216902bf11e --- /dev/null +++ b/src/applications/simple-forms/21-4142/tests/unit/containers/ConfirmationPage.unit.spec.jsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { expect } from 'chai'; +import { mount } from 'enzyme'; +import { createStore } from 'redux'; +import { Provider } from 'react-redux'; +import { format } from 'date-fns'; +import { cleanup } from '@testing-library/react'; +import { createInitialState } from '@department-of-veterans-affairs/platform-forms-system/state/helpers'; +import ConfirmationPage from '../../../containers/ConfirmationPage'; +import formConfig from '../../../config/form'; +import testData from '../../e2e/fixtures/data/maximal-test.json'; + +const submitDate = new Date(); +const initialState = { + form: { + ...createInitialState(formConfig), + testData, + submission: { + response: { + confirmationNumber: '1234567890', + }, + timestamp: submitDate, + }, + }, +}; +const mockStore = state => createStore(() => state); + +const mountPage = state => { + const store = mockStore(state); + return mount( + + + , + ); +}; + +describe('ConfirmationPage', () => { + let wrapper = null; + + beforeEach(() => { + wrapper = mountPage(initialState); + }); + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + } + cleanup(); + }); + + it('passes the correct props to ConfirmationPageView', () => { + const confirmationViewProps = wrapper.find('ConfirmationView').props(); + + expect(confirmationViewProps.submitDate).to.equal(submitDate); + expect(confirmationViewProps.confirmationNumber).to.equal('1234567890'); + }); + + it('should select form from state when state.form is defined', () => { + expect(wrapper.text()).to.include(format(submitDate, 'MMMM d, yyyy')); + expect(wrapper.text()).to.include('1234'); + }); + + it('should throw error when state.form is undefined', () => { + let errorWrapper; + + expect(() => { + errorWrapper = mountPage({}); + }).to.throw(); + + if (errorWrapper) { + errorWrapper.unmount(); + } + }); +}); diff --git a/src/applications/simple-forms/21-4142/tests/containers/IntroductionPage.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/containers/IntroductionPage.unit.spec.jsx similarity index 91% rename from src/applications/simple-forms/21-4142/tests/containers/IntroductionPage.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/containers/IntroductionPage.unit.spec.jsx index 96236de1f574..7dd0732dc9b9 100644 --- a/src/applications/simple-forms/21-4142/tests/containers/IntroductionPage.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/containers/IntroductionPage.unit.spec.jsx @@ -2,8 +2,8 @@ import React from 'react'; import { Provider } from 'react-redux'; import { render } from '@testing-library/react'; import { expect } from 'chai'; -import formConfig from '../../config/form'; -import IntroductionPage from '../../containers/IntroductionPage'; +import formConfig from '../../../config/form'; +import IntroductionPage from '../../../containers/IntroductionPage'; const props = { route: { diff --git a/src/applications/simple-forms/21-4142/tests/pages/authorization.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/authorization.unit.spec.jsx similarity index 81% rename from src/applications/simple-forms/21-4142/tests/pages/authorization.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/authorization.unit.spec.jsx index 3bb05f2ff143..1f0d791757b7 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/authorization.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/authorization.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/contactInformation1.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/contactInformation1.unit.spec.jsx similarity index 82% rename from src/applications/simple-forms/21-4142/tests/pages/contactInformation1.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/contactInformation1.unit.spec.jsx index cea3816c717d..dc779164f0b2 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/contactInformation1.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/contactInformation1.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/contactInformation2.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/contactInformation2.unit.spec.jsx similarity index 89% rename from src/applications/simple-forms/21-4142/tests/pages/contactInformation2.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/contactInformation2.unit.spec.jsx index a936400e9885..63b9f3708429 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/contactInformation2.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/contactInformation2.unit.spec.jsx @@ -3,8 +3,8 @@ import { testNumberOfErrorsOnSubmitForWebComponents, testNumberOfFields, testNumberOfWebComponentFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/limitations.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/limitations.unit.spec.jsx similarity index 81% rename from src/applications/simple-forms/21-4142/tests/pages/limitations.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/limitations.unit.spec.jsx index a89a488d5196..d6eb0030747e 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/limitations.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/limitations.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, uiSchema } = formConfig.chapters.limitations.pages.limitations; diff --git a/src/applications/simple-forms/21-4142/tests/pages/patientIdentification1.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/patientIdentification1.unit.spec.jsx similarity index 82% rename from src/applications/simple-forms/21-4142/tests/pages/patientIdentification1.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/patientIdentification1.unit.spec.jsx index c6b480a31100..7446a69a5f4e 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/patientIdentification1.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/patientIdentification1.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/patientIdentification2.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/patientIdentification2.unit.spec.jsx similarity index 82% rename from src/applications/simple-forms/21-4142/tests/pages/patientIdentification2.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/patientIdentification2.unit.spec.jsx index 9137f2a9b43e..60a9e457987c 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/patientIdentification2.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/patientIdentification2.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/personalInformation1.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/personalInformation1.unit.spec.jsx similarity index 82% rename from src/applications/simple-forms/21-4142/tests/pages/personalInformation1.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/personalInformation1.unit.spec.jsx index 5107f797a1f3..4b65607939e2 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/personalInformation1.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/personalInformation1.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/personalInformation2.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/personalInformation2.unit.spec.jsx similarity index 82% rename from src/applications/simple-forms/21-4142/tests/pages/personalInformation2.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/personalInformation2.unit.spec.jsx index 7a8151458fc6..a8506ca23d73 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/personalInformation2.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/personalInformation2.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/preparerAddress1.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerAddress1.unit.spec.jsx similarity index 86% rename from src/applications/simple-forms/21-4142/tests/pages/preparerAddress1.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/preparerAddress1.unit.spec.jsx index 4ef071484d38..955bca7f68b3 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/preparerAddress1.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerAddress1.unit.spec.jsx @@ -1,13 +1,13 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; +} from '../../../../shared/tests/pages/pageTests.spec'; import { preparerIdentificationFields, veteranFields, veteranIsSelfText, -} from '../../definitions/constants'; -import formConfig from '../../config/form'; +} from '../../../definitions/constants'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/preparerAddress2.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerAddress2.unit.spec.jsx similarity index 84% rename from src/applications/simple-forms/21-4142/tests/pages/preparerAddress2.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/preparerAddress2.unit.spec.jsx index f620be00b455..cd0f835062ee 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/preparerAddress2.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerAddress2.unit.spec.jsx @@ -1,12 +1,12 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; +} from '../../../../shared/tests/pages/pageTests.spec'; import { preparerIdentificationFields, veteranIsSelfText, -} from '../../definitions/constants'; -import formConfig from '../../config/form'; +} from '../../../definitions/constants'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/preparerIdentification.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerIdentification.unit.spec.jsx similarity index 82% rename from src/applications/simple-forms/21-4142/tests/pages/preparerIdentification.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/preparerIdentification.unit.spec.jsx index dd58f60defb1..53398694382d 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/preparerIdentification.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerIdentification.unit.spec.jsx @@ -1,8 +1,8 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/preparerPersonalInformation.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerPersonalInformation.unit.spec.jsx similarity index 91% rename from src/applications/simple-forms/21-4142/tests/pages/preparerPersonalInformation.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/preparerPersonalInformation.unit.spec.jsx index 7f1298d8b95c..741c01bc62e0 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/preparerPersonalInformation.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/preparerPersonalInformation.unit.spec.jsx @@ -1,12 +1,12 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; +} from '../../../../shared/tests/pages/pageTests.spec'; import { preparerIdentificationFields, veteranDirectRelative, -} from '../../definitions/constants'; -import formConfig from '../../config/form'; +} from '../../../definitions/constants'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/21-4142/tests/pages/recordsRequested.unit.spec.jsx b/src/applications/simple-forms/21-4142/tests/unit/pages/recordsRequested.unit.spec.jsx similarity index 79% rename from src/applications/simple-forms/21-4142/tests/pages/recordsRequested.unit.spec.jsx rename to src/applications/simple-forms/21-4142/tests/unit/pages/recordsRequested.unit.spec.jsx index 0d7915762896..f9baf5eb2240 100644 --- a/src/applications/simple-forms/21-4142/tests/pages/recordsRequested.unit.spec.jsx +++ b/src/applications/simple-forms/21-4142/tests/unit/pages/recordsRequested.unit.spec.jsx @@ -1,9 +1,9 @@ import { testNumberOfErrorsOnSubmit, testNumberOfFields, -} from '../../../shared/tests/pages/pageTests.spec'; -import { patientIdentificationFields } from '../../definitions/constants'; -import formConfig from '../../config/form'; +} from '../../../../shared/tests/pages/pageTests.spec'; +import { patientIdentificationFields } from '../../../definitions/constants'; +import formConfig from '../../../config/form'; const { schema, diff --git a/src/applications/simple-forms/form-upload/pages/upload.jsx b/src/applications/simple-forms/form-upload/pages/upload.jsx index 4709a88559a1..d1a237e3b869 100644 --- a/src/applications/simple-forms/form-upload/pages/upload.jsx +++ b/src/applications/simple-forms/form-upload/pages/upload.jsx @@ -43,6 +43,8 @@ export const uploadPage = { title, formNumber, required: () => true, + // Disallow uploads greater than 25 MB + maxFileSize: 25000000, updateUiSchema: formData => { return { 'ui:title': warningsPresent(formData) diff --git a/src/applications/simple-forms/shared/config/submit-transformer.js b/src/applications/simple-forms/shared/config/submit-transformer.js index eb5b58e487f5..759056b1ac25 100644 --- a/src/applications/simple-forms/shared/config/submit-transformer.js +++ b/src/applications/simple-forms/shared/config/submit-transformer.js @@ -1,17 +1,5 @@ import { transformForSubmit as formsSystemTransformForSubmit } from 'platform/forms-system/src/js/helpers'; -const escapedCharacterReplacer = (_key, value) => { - if (typeof value === 'string') { - return value - .replaceAll('"', "'") - .replace(/(?:\r\n|\n\n|\r|\n)/g, '; ') - .replace(/(?:\t|\f|\b)/g, '') - .replace(/\\(?!(f|n|r|t|[u,U][\d,a-fA-F]{4}))/gm, '/'); - } - - return value; -}; - /** * Example: * ``` @@ -27,11 +15,11 @@ const escapedCharacterReplacer = (_key, value) => { */ export default function transformForSubmit(formConfig, form, options) { const transformedData = JSON.parse( - formsSystemTransformForSubmit(formConfig, form, options), + formsSystemTransformForSubmit(formConfig, form, { + ...options, + replaceEscapedCharacters: true, + }), ); - return JSON.stringify( - { ...transformedData, formNumber: formConfig.formId }, - escapedCharacterReplacer, - ); + return JSON.stringify({ ...transformedData, formNumber: formConfig.formId }); } diff --git a/src/applications/terms-of-use/helpers.js b/src/applications/terms-of-use/helpers.js index 1576a62ffcf5..5c71f59e0abe 100644 --- a/src/applications/terms-of-use/helpers.js +++ b/src/applications/terms-of-use/helpers.js @@ -2,6 +2,7 @@ import { environment, eauthEnvironmentPrefixes, cernerEnvPrefixes, + oracleHealthEnvPrefixes, logoutUrlSiS, } from '@department-of-veterans-affairs/platform-utilities/exports'; import { @@ -18,7 +19,10 @@ export const parseRedirectUrl = url => { const allowedDomains = [ `${new URL(environment.BASE_URL).hostname}`, // va.gov `${eauthEnvironmentPrefixes[environment.BUILDTYPE]}eauth.va.gov`, // eauth - `${cernerEnvPrefixes[environment.BUILDTYPE]}patientportal.myhealth.va.gov`, // cerner + `${cernerEnvPrefixes[environment.BUILDTYPE]}patientportal.myhealth.va.gov`, // oracle health staging + `${ + oracleHealthEnvPrefixes[environment.BUILDTYPE] + }patientportal.myhealth.va.gov`, // oracle health sandbox `${eauthEnvironmentPrefixes[environment.BUILDTYPE]}fed.eauth.va.gov`, // mobile `vamobile://login-success`, // mobile again ]; diff --git a/src/applications/terms-of-use/tests/MyVAHealth.unit.spec.jsx b/src/applications/terms-of-use/tests/MyVAHealth.unit.spec.jsx index e807be85d124..a47b431fcef8 100644 --- a/src/applications/terms-of-use/tests/MyVAHealth.unit.spec.jsx +++ b/src/applications/terms-of-use/tests/MyVAHealth.unit.spec.jsx @@ -13,6 +13,7 @@ const oldLocation = global.window.location; describe('MyVAHealth', () => { const ssoeTarget = `https://staging-patientportal.myhealth.va.gov`; + const altSsoeTarget = `https://sandbox-patientportal.myhealth.va.gov`; const server = setupServer(); before(() => server.listen()); @@ -32,20 +33,23 @@ describe('MyVAHealth', () => { expect(loadingIndicator).to.not.be.null; }); - it('should redirect formatted redirect url when api returns 200', async () => { - global.window.location = `https://dev.va.gov/terms-of-use/myvahealth/?ssoeTarget=${ssoeTarget}`; + [ssoeTarget, altSsoeTarget].forEach(targetUrl => { + it(`should redirect formatted redirect url (${targetUrl}) when api returns 200`, async () => { + global.window.location = `https://dev.va.gov/terms-of-use/myvahealth/?ssoeTarget=${targetUrl}`; - server.use( - rest.put( - `https://dev-api.va.gov/v0/terms_of_use_agreements/update_provisioning`, - (_, res, ctx) => res(ctx.status(200), ctx.json({ provisioned: true })), - ), - ); + server.use( + rest.put( + `https://dev-api.va.gov/v0/terms_of_use_agreements/update_provisioning`, + (_, res, ctx) => + res(ctx.status(200), ctx.json({ provisioned: true })), + ), + ); - render(); + render(); - await waitFor(() => { - expect(global.window.location).to.eql(ssoeTarget); + await waitFor(() => { + expect(global.window.location).to.eql(targetUrl); + }); }); }); diff --git a/src/applications/terms-of-use/tests/helpers.unit.spec.js b/src/applications/terms-of-use/tests/helpers.unit.spec.js index 2f9072d7f335..1cf6561a5811 100644 --- a/src/applications/terms-of-use/tests/helpers.unit.spec.js +++ b/src/applications/terms-of-use/tests/helpers.unit.spec.js @@ -14,6 +14,7 @@ describe('parseRedirectUrl', () => { [`https://int.eauth.va.gov/mhv-portal-web/eauth?deeplinking=home&postLogin=true`]: `https://int.eauth.va.gov/mhv-portal-web/eauth?deeplinking=home&postLogin=true`, [`https://google.com?q=https://va.gov`]: `https://dev.va.gov`, [`https%3A%2F%2Fint.eauth.va.gov%2Fisam%2Fsps%2Fauth%3FPartnerId%3Dhttps%3A%2F%2Fstaging-patientportal.myhealth.va.gov%2Fsession-api%2Fprotocol%2Fsaml2%2Fmetadata`]: `https://int.eauth.va.gov/isam/sps/auth?PartnerId=https://staging-patientportal.myhealth.va.gov/session-api/protocol/saml2/metadata`, + [`https%3A%2F%2Fint.eauth.va.gov%2Fisam%2Fsps%2Fauth%3FPartnerId%3Dhttps%3A%2F%2Fsandbox-patientportal.myhealth.va.gov%2Fsession-api%2Fprotocol%2Fsaml2%2Fmetadata`]: `https://int.eauth.va.gov/isam/sps/auth?PartnerId=https://sandbox-patientportal.myhealth.va.gov/session-api/protocol/saml2/metadata`, [`vamobile%3A%2F%2Flogin-success%3Fcode%3D39d23d80-b10d-4e8a-a7e1-7a33fb87211a%26type%3Didme&terms_code=de85bea0-0b00-42a3-b491-d0a834542490`]: `vamobile://login-success?code=39d23d80-b10d-4e8a-a7e1-7a33fb87211a&type=idme&terms_code=de85bea0-0b00-42a3-b491-d0a834542490`, }; diff --git a/src/applications/vaos/appointment-list/components/BackendAppointmentServiceAlert.unit.spec.js b/src/applications/vaos/appointment-list/components/BackendAppointmentServiceAlert.unit.spec.js index 104852f1d6fe..3193791b39da 100644 --- a/src/applications/vaos/appointment-list/components/BackendAppointmentServiceAlert.unit.spec.js +++ b/src/applications/vaos/appointment-list/components/BackendAppointmentServiceAlert.unit.spec.js @@ -13,7 +13,7 @@ import { getVAOSAppointmentMock, getVAOSRequestMock, } from '../../tests/mocks/mock'; -import RequestedAppointmentsListGroup from './RequestedAppointmentsListGroup'; +import RequestedAppointmentsPage from '../pages/RequestedAppointmentsPage/RequestedAppointmentsPage'; describe('VAOS Backend Service Alert', () => { const initialState = { @@ -359,18 +359,15 @@ describe('VAOS Backend Service Alert', () => { backendServiceFailures: true, }); - const screen = renderWithStoreAndRouter( - , - { - initialState: { - ...initialState, - featureToggles: { - ...initialState.featureToggles, - vaOnlineSchedulingVAOSServiceRequests: true, - }, + const screen = renderWithStoreAndRouter(, { + initialState: { + ...initialState, + featureToggles: { + ...initialState.featureToggles, + vaOnlineSchedulingVAOSServiceRequests: true, }, }, - ); + }); await waitFor(() => { expect(screen.baseElement).to.contain.text('Cheyenne VA Medical Center'); @@ -451,18 +448,15 @@ describe('VAOS Backend Service Alert', () => { backendServiceFailures: false, }); - const screen = renderWithStoreAndRouter( - , - { - initialState: { - ...initialState, - featureToggles: { - ...initialState.featureToggles, - vaOnlineSchedulingVAOSServiceRequests: true, - }, + const screen = renderWithStoreAndRouter(, { + initialState: { + ...initialState, + featureToggles: { + ...initialState.featureToggles, + vaOnlineSchedulingVAOSServiceRequests: true, }, }, - ); + }); await waitFor(() => { expect(screen.baseElement).to.contain.text('Cheyenne VA Medical Center'); diff --git a/src/applications/vaos/appointment-list/components/ConfirmedAppointmentDetailsPage/DetailsVA.jsx b/src/applications/vaos/appointment-list/components/ConfirmedAppointmentDetailsPage/DetailsVA.jsx index 4076cdfb0e70..7ee7d2617173 100644 --- a/src/applications/vaos/appointment-list/components/ConfirmedAppointmentDetailsPage/DetailsVA.jsx +++ b/src/applications/vaos/appointment-list/components/ConfirmedAppointmentDetailsPage/DetailsVA.jsx @@ -1,21 +1,21 @@ -import React from 'react'; +import { VaAlert } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; import PropTypes from 'prop-types'; +import React from 'react'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { shallowEqual } from 'recompose'; -import { VaAlert } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; import BackLink from '../../../components/BackLink'; -import VAFacilityLocation from '../../../components/VAFacilityLocation'; -import { getVAAppointmentLocationId } from '../../../services/appointment'; -import { getConfirmedAppointmentDetailsInfo } from '../../redux/selectors'; -import { FETCH_STATUS } from '../../../utils/constants'; -import InPersonLayout from '../../../components/layout/InPersonLayout'; -import CancelWarningPage from '../cancel/CancelWarningPage'; -import CancelConfirmationPage from '../cancel/CancelConfirmationPage'; import FacilityAddress from '../../../components/FacilityAddress'; +import FullWidthLayout from '../../../components/FullWidthLayout'; import ClaimExamLayout from '../../../components/layout/ClaimExamLayout'; +import InPersonLayout from '../../../components/layout/InPersonLayout'; import PhoneLayout from '../../../components/layout/PhoneLayout'; -import FullWidthLayout from '../../../components/FullWidthLayout'; +import VAFacilityLocation from '../../../components/VAFacilityLocation'; +import { getVAAppointmentLocationId } from '../../../services/appointment'; +import { FETCH_STATUS } from '../../../utils/constants'; +import CancelConfirmationPage from '../../pages/CancelAppointmentPage/CancelConfirmationPage'; +import CancelWarningPage from '../../pages/CancelAppointmentPage/CancelWarningPage'; +import { getConfirmedAppointmentDetailsInfo } from '../../redux/selectors'; export default function DetailsVA({ appointment, facilityData }) { const { id } = useParams(); diff --git a/src/applications/vaos/appointment-list/components/PastAppointmentsList/index.jsx b/src/applications/vaos/appointment-list/components/PastAppointmentsList/index.jsx index 2f038a61c6a5..0c5a76fabae5 100644 --- a/src/applications/vaos/appointment-list/components/PastAppointmentsList/index.jsx +++ b/src/applications/vaos/appointment-list/components/PastAppointmentsList/index.jsx @@ -2,23 +2,23 @@ import React, { useEffect, useState } from 'react'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports'; +import classNames from 'classnames'; import moment from 'moment'; import { useHistory } from 'react-router-dom'; -import classNames from 'classnames'; -import { getPastAppointmentListInfo } from '../../redux/selectors'; -import { FETCH_STATUS, GA_PREFIX } from '../../../utils/constants'; +import InfoAlert from '../../../components/InfoAlert'; +import { selectFeatureBreadcrumbUrlUpdate } from '../../../redux/selectors'; import { groupAppointmentByDay } from '../../../services/appointment'; -import NoAppointments from '../NoAppointments'; -import PastAppointmentsDateDropdown from './PastAppointmentsDateDropdown'; +import { FETCH_STATUS, GA_PREFIX } from '../../../utils/constants'; import { scrollAndFocus } from '../../../utils/scrollAndFocus'; -import InfoAlert from '../../../components/InfoAlert'; +import UpcomingAppointmentLayout from '../../pages/AppointmentsPage/UpcomingAppointmentLayout'; import { fetchPastAppointments, startNewAppointmentFlow, } from '../../redux/actions'; -import { selectFeatureBreadcrumbUrlUpdate } from '../../../redux/selectors'; -import UpcomingAppointmentLayout from '../AppointmentsPage/UpcomingAppointmentLayout'; +import { getPastAppointmentListInfo } from '../../redux/selectors'; import BackendAppointmentServiceAlert from '../BackendAppointmentServiceAlert'; +import NoAppointments from '../NoAppointments'; +import PastAppointmentsDateDropdown from './PastAppointmentsDateDropdown'; export function getPastAppointmentDateRangeOptions(today = moment()) { const startOfToday = today.clone().startOf('day'); diff --git a/src/applications/vaos/appointment-list/components/RequestedAppointmentsList.jsx b/src/applications/vaos/appointment-list/components/RequestedAppointmentsList.jsx index 58fc232739ee..daf8a888bd04 100644 --- a/src/applications/vaos/appointment-list/components/RequestedAppointmentsList.jsx +++ b/src/applications/vaos/appointment-list/components/RequestedAppointmentsList.jsx @@ -1,17 +1,17 @@ -import React, { useEffect } from 'react'; +import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports'; import PropTypes from 'prop-types'; +import React, { useEffect } from 'react'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; -import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports'; +import InfoAlert from '../../components/InfoAlert'; +import RequestAppointmentLayout from '../../components/RequestAppointmentLayout'; +import { FETCH_STATUS, GA_PREFIX } from '../../utils/constants'; +import { scrollAndFocus } from '../../utils/scrollAndFocus'; import { fetchPendingAppointments, startNewAppointmentFlow, } from '../redux/actions'; import { getRequestedAppointmentListInfo } from '../redux/selectors'; -import { FETCH_STATUS, GA_PREFIX } from '../../utils/constants'; import NoAppointments from './NoAppointments'; -import InfoAlert from '../../components/InfoAlert'; -import { scrollAndFocus } from '../../utils/scrollAndFocus'; -import RequestAppointmentLayout from './AppointmentsPage/RequestAppointmentLayout'; export default function RequestedAppointmentsList({ hasTypeChanged }) { const { diff --git a/src/applications/vaos/appointment-list/index.jsx b/src/applications/vaos/appointment-list/index.jsx index ee600d1a254c..dd6616284773 100644 --- a/src/applications/vaos/appointment-list/index.jsx +++ b/src/applications/vaos/appointment-list/index.jsx @@ -1,14 +1,14 @@ +import PageNotFound from '@department-of-veterans-affairs/platform-site-wide/PageNotFound'; import React from 'react'; -import { Switch, Route } from 'react-router-dom'; import { useSelector } from 'react-redux'; -import PageNotFound from '@department-of-veterans-affairs/platform-site-wide/PageNotFound'; -import AppointmentsPage from './components/AppointmentsPage/index'; -import RequestedAppointmentDetailsPage from './components/RequestedAppointmentDetailsPage'; -import ConfirmedAppointmentDetailsPage from './components/ConfirmedAppointmentDetailsPage'; +import { Route, Switch } from 'react-router-dom'; import useManualScrollRestoration from '../hooks/useManualScrollRestoration'; import { selectFeatureBreadcrumbUrlUpdate } from '../redux/selectors'; import { useIsInCCPilot } from '../referral-appointments/hooks/useIsInCCPilot'; import ReferralsAndRequests from '../referral-appointments/ReferralsAndRequests'; +import ConfirmedAppointmentDetailsPage from './components/ConfirmedAppointmentDetailsPage'; +import AppointmentsPage from './pages/AppointmentsPage/index'; +import RequestedAppointmentDetailsPage from './pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage'; function AppointmentListSection() { useManualScrollRestoration(); diff --git a/src/applications/vaos/appointment-list/index.unit.spec.js b/src/applications/vaos/appointment-list/index.unit.spec.js index 66f5c172d672..fd2f53061872 100644 --- a/src/applications/vaos/appointment-list/index.unit.spec.js +++ b/src/applications/vaos/appointment-list/index.unit.spec.js @@ -3,9 +3,9 @@ import { expect } from 'chai'; import Sinon from 'sinon'; import { AppointmentList } from './index'; import { renderWithStoreAndRouter } from '../tests/mocks/setup'; -import * as AppointmentsPage from './components/AppointmentsPage/index'; +import * as AppointmentsPage from './pages/AppointmentsPage/index'; import * as ConfirmedAppointmentDetailsPage from './components/ConfirmedAppointmentDetailsPage'; -import * as RequestedAppointmentDetailsPage from './components/RequestedAppointmentDetailsPage'; +import * as RequestedAppointmentDetailsPage from './pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage'; describe('VAOS Page: Appointment list routes', () => { const sandbox = Sinon.createSandbox(); diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/AppointmentColumnLayout.jsx b/src/applications/vaos/appointment-list/pages/AppointmentsPage/AppointmentColumnLayout.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/AppointmentColumnLayout.jsx rename to src/applications/vaos/appointment-list/pages/AppointmentsPage/AppointmentColumnLayout.jsx diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/AppointmentListItem.jsx b/src/applications/vaos/appointment-list/pages/AppointmentsPage/AppointmentListItem.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/AppointmentListItem.jsx rename to src/applications/vaos/appointment-list/pages/AppointmentsPage/AppointmentListItem.jsx diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/AppointmentListItemGroup.jsx b/src/applications/vaos/appointment-list/pages/AppointmentsPage/AppointmentListItemGroup.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/AppointmentListItemGroup.jsx rename to src/applications/vaos/appointment-list/pages/AppointmentsPage/AppointmentListItemGroup.jsx diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/RequestListItem.jsx b/src/applications/vaos/appointment-list/pages/AppointmentsPage/RequestListItem.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/RequestListItem.jsx rename to src/applications/vaos/appointment-list/pages/AppointmentsPage/RequestListItem.jsx diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/UpcomingAppointmentLayout.jsx b/src/applications/vaos/appointment-list/pages/AppointmentsPage/UpcomingAppointmentLayout.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/UpcomingAppointmentLayout.jsx rename to src/applications/vaos/appointment-list/pages/AppointmentsPage/UpcomingAppointmentLayout.jsx diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/index.jsx b/src/applications/vaos/appointment-list/pages/AppointmentsPage/index.jsx similarity index 86% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/index.jsx rename to src/applications/vaos/appointment-list/pages/AppointmentsPage/index.jsx index 603613e922ee..d2dc9a9f9441 100644 --- a/src/applications/vaos/appointment-list/components/AppointmentsPage/index.jsx +++ b/src/applications/vaos/appointment-list/pages/AppointmentsPage/index.jsx @@ -1,35 +1,29 @@ -import React, { useEffect, useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { Switch, Route, useLocation, useHistory } from 'react-router-dom'; -import classNames from 'classnames'; import DowntimeNotification, { externalServices, } from '@department-of-veterans-affairs/platform-monitoring/DowntimeNotification'; +import classNames from 'classnames'; import PropTypes from 'prop-types'; -import { - selectFeatureBreadcrumbUrlUpdate, - // selectFeatureBookingExclusion, -} from '../../../redux/selectors'; -import UpcomingAppointmentsList from '../UpcomingAppointmentsList'; -import PastAppointmentsList from '../PastAppointmentsList'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Route, Switch, useHistory, useLocation } from 'react-router-dom'; +import CernerAlert from '../../../components/CernerAlert'; import WarningNotification from '../../../components/WarningNotification'; -import ScheduleNewAppointment from '../ScheduleNewAppointment'; -import PageLayout from '../PageLayout'; -import { selectPendingAppointments } from '../../redux/selectors'; -import { - APPOINTMENT_STATUS, - // OH_TRANSITION_SITES, -} from '../../../utils/constants'; -import AppointmentListNavigation from '../AppointmentListNavigation'; +import { selectFeatureBreadcrumbUrlUpdate } from '../../../redux/selectors'; +import { APPOINTMENT_STATUS } from '../../../utils/constants'; import { scrollAndFocus } from '../../../utils/scrollAndFocus'; -import RequestedAppointmentsListGroup from '../RequestedAppointmentsListGroup'; -import CernerAlert from '../../../components/CernerAlert'; +import { selectPendingAppointments } from '../../redux/selectors'; +import RequestedAppointmentsPage from '../RequestedAppointmentsPage/RequestedAppointmentsPage'; // import CernerTransitionAlert from '../../../components/CernerTransitionAlert'; // import { selectPatientFacilities } from '~/platform/user/cerner-dsot/selectors'; import ReferralTaskCardWithReferral from '../../../referral-appointments/components/ReferralTaskCardWithReferral'; -import { setFormCurrentPage } from '../../../referral-appointments/redux/actions'; import { routeToCCPage } from '../../../referral-appointments/flow'; import { useIsInCCPilot } from '../../../referral-appointments/hooks/useIsInCCPilot'; +import { setFormCurrentPage } from '../../../referral-appointments/redux/actions'; +import AppointmentListNavigation from '../../components/AppointmentListNavigation'; +import PageLayout from '../../components/PageLayout'; +import PastAppointmentsListNew from '../../components/PastAppointmentsList'; +import ScheduleNewAppointment from '../../components/ScheduleNewAppointment'; +import UpcomingAppointmentsPage from '../UpcomingAppointmentsPage/UpcomingAppointmentsPage'; function renderWarningNotification() { return (props, childContent) => { @@ -192,13 +186,13 @@ export default function AppointmentsPage() { /> - + - + - + diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/index.unit.spec.js b/src/applications/vaos/appointment-list/pages/AppointmentsPage/index.unit.spec.js similarity index 100% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/index.unit.spec.js rename to src/applications/vaos/appointment-list/pages/AppointmentsPage/index.unit.spec.js diff --git a/src/applications/vaos/appointment-list/components/cancel/CancelConfirmationPage.jsx b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelConfirmationPage.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/cancel/CancelConfirmationPage.jsx rename to src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelConfirmationPage.jsx index 0779c3277f45..36145ceef5d3 100644 --- a/src/applications/vaos/appointment-list/components/cancel/CancelConfirmationPage.jsx +++ b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelConfirmationPage.jsx @@ -1,17 +1,17 @@ import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports'; -import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; +import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom/cjs/react-router-dom.min'; +import AppointmentCard from '../../../components/AppointmentCard'; +import BackLink from '../../../components/BackLink'; import { APPOINTMENT_TYPES, GA_PREFIX } from '../../../utils/constants'; import { startNewAppointmentFlow } from '../../redux/actions'; -import BackLink from '../../../components/BackLink'; // eslint-disable-next-line import/no-restricted-paths import getNewAppointmentFlow from '../../../new-appointment/newAppointmentFlow'; import { scrollAndFocus } from '../../../utils/scrollAndFocus'; import { selectAppointmentType } from '../../redux/selectors'; import CancelPageContent from './CancelPageContent'; -import AppointmentCard from '../../../components/AppointmentCard'; function handleClick(history, dispatch, url) { return e => { diff --git a/src/applications/vaos/appointment-list/components/cancel/CancelPageContent.jsx b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageContent.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/cancel/CancelPageContent.jsx rename to src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageContent.jsx index 177a39003ab2..20eddd826709 100644 --- a/src/applications/vaos/appointment-list/components/cancel/CancelPageContent.jsx +++ b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageContent.jsx @@ -1,8 +1,8 @@ -import React from 'react'; import PropTypes from 'prop-types'; +import React from 'react'; import { APPOINTMENT_TYPES } from '../../../utils/constants'; -import CancelPageLayoutRequest from './CancelPageLayoutRequest'; import CancelPageLayout from './CancelPageLayout'; +import CancelPageLayoutRequest from './CancelPageLayoutRequest'; export default function CancelPageContent({ type }) { if ( diff --git a/src/applications/vaos/appointment-list/components/cancel/CancelPageLayout.jsx b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageLayout.jsx similarity index 98% rename from src/applications/vaos/appointment-list/components/cancel/CancelPageLayout.jsx rename to src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageLayout.jsx index 03df536b2845..d774e91de186 100644 --- a/src/applications/vaos/appointment-list/components/cancel/CancelPageLayout.jsx +++ b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageLayout.jsx @@ -1,13 +1,9 @@ import React from 'react'; +import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { shallowEqual } from 'recompose'; -import { useSelector } from 'react-redux'; -import { - getConfirmedAppointmentDetailsInfo, - selectIsCanceled, - selectIsInPerson, - selectIsPhone, -} from '../../redux/selectors'; +import Address from '../../../components/Address'; +import FacilityDirectionsLink from '../../../components/FacilityDirectionsLink'; import { ClinicOrFacilityPhone, Details, @@ -16,12 +12,19 @@ import { Where, Who, } from '../../../components/layout/DetailPageLayout'; -import Section from '../../../components/Section'; -import { AppointmentDate, AppointmentTime } from '../AppointmentDateTime'; import NewTabAnchor from '../../../components/NewTabAnchor'; +import Section from '../../../components/Section'; import { getRealFacilityId } from '../../../utils/appointment'; -import FacilityDirectionsLink from '../../../components/FacilityDirectionsLink'; -import Address from '../../../components/Address'; +import { + AppointmentDate, + AppointmentTime, +} from '../../components/AppointmentDateTime'; +import { + getConfirmedAppointmentDetailsInfo, + selectIsCanceled, + selectIsInPerson, + selectIsPhone, +} from '../../redux/selectors'; function getHeading(appointment) { const isCanceled = selectIsCanceled(appointment); diff --git a/src/applications/vaos/appointment-list/components/cancel/CancelPageLayoutRequest.jsx b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageLayoutRequest.jsx similarity index 98% rename from src/applications/vaos/appointment-list/components/cancel/CancelPageLayoutRequest.jsx rename to src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageLayoutRequest.jsx index 42179dd2db07..0de9c692f212 100644 --- a/src/applications/vaos/appointment-list/components/cancel/CancelPageLayoutRequest.jsx +++ b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelPageLayoutRequest.jsx @@ -5,16 +5,16 @@ import { useParams } from 'react-router-dom/cjs/react-router-dom.min'; import { shallowEqual } from 'recompose'; import Address from '../../../components/Address'; import FacilityPhone from '../../../components/FacilityPhone'; -import { selectRequestedAppointmentDetails } from '../../redux/selectors'; -import ListBestTimeToCall from '../ListBestTimeToCall'; import { CCDetails, Details, } from '../../../components/layout/DetailPageLayout'; +import NewTabAnchor from '../../../components/NewTabAnchor'; import Section from '../../../components/Section'; -import { APPOINTMENT_STATUS } from '../../../utils/constants'; import { getRealFacilityId } from '../../../utils/appointment'; -import NewTabAnchor from '../../../components/NewTabAnchor'; +import { APPOINTMENT_STATUS } from '../../../utils/constants'; +import ListBestTimeToCall from '../../components/ListBestTimeToCall'; +import { selectRequestedAppointmentDetails } from '../../redux/selectors'; export default function CancelPageLayoutRequest() { const { id } = useParams(); diff --git a/src/applications/vaos/appointment-list/components/cancel/CancelWarningPage.jsx b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelWarningPage.jsx similarity index 100% rename from src/applications/vaos/appointment-list/components/cancel/CancelWarningPage.jsx rename to src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelWarningPage.jsx index 2bba17708a06..bb1cce26b457 100644 --- a/src/applications/vaos/appointment-list/components/cancel/CancelWarningPage.jsx +++ b/src/applications/vaos/appointment-list/pages/CancelAppointmentPage/CancelWarningPage.jsx @@ -1,17 +1,17 @@ /* eslint-disable @department-of-veterans-affairs/prefer-button-component */ -import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; +import React, { useEffect } from 'react'; import { useDispatch } from 'react-redux'; +import AppointmentCard from '../../../components/AppointmentCard'; import BackLink from '../../../components/BackLink'; +import { APPOINTMENT_TYPES } from '../../../utils/constants'; import { scrollAndFocus } from '../../../utils/scrollAndFocus'; import { closeCancelAppointment, confirmCancelAppointment, } from '../../redux/actions'; import { selectAppointmentType } from '../../redux/selectors'; -import { APPOINTMENT_TYPES } from '../../../utils/constants'; import CancelPageContent from './CancelPageContent'; -import AppointmentCard from '../../../components/AppointmentCard'; function handleConfirm(dispatch) { return () => dispatch(confirmCancelAppointment()); diff --git a/src/applications/vaos/appointment-list/components/RequestedAppointmentDetailsPage.jsx b/src/applications/vaos/appointment-list/pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage.jsx similarity index 81% rename from src/applications/vaos/appointment-list/components/RequestedAppointmentDetailsPage.jsx rename to src/applications/vaos/appointment-list/pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage.jsx index c9b718d6d31c..9b08c1e1f772 100644 --- a/src/applications/vaos/appointment-list/components/RequestedAppointmentDetailsPage.jsx +++ b/src/applications/vaos/appointment-list/pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage.jsx @@ -1,23 +1,26 @@ +import { VaAlert } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; import React, { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; -import { VaAlert } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; -import BackLink from '../../components/BackLink'; -import { scrollAndFocus } from '../../utils/scrollAndFocus'; -import VAFacilityLocation from '../../components/VAFacilityLocation'; -import { selectRequestedAppointmentDetails } from '../redux/selectors'; -import { selectFeatureBreadcrumbUrlUpdate } from '../../redux/selectors'; -import ErrorMessage from '../../components/ErrorMessage'; -import PageLayout from './PageLayout'; -import FullWidthLayout from '../../components/FullWidthLayout'; -import { closeCancelAppointment, fetchRequestDetails } from '../redux/actions'; -import CancelWarningPage from './cancel/CancelWarningPage'; -import CancelConfirmationPage from './cancel/CancelConfirmationPage'; -import { FETCH_STATUS } from '../../utils/constants'; -import FacilityAddress from '../../components/FacilityAddress'; -import FacilityPhone from '../../components/FacilityPhone'; -import VARequestLayout from '../../components/layout/VARequestLayout'; -import CCRequestLayout from '../../components/layout/CCRequestLayout'; +import { useParams } from 'react-router-dom'; +import BackLink from '../../../components/BackLink'; +import ErrorMessage from '../../../components/ErrorMessage'; +import FacilityAddress from '../../../components/FacilityAddress'; +import FacilityPhone from '../../../components/FacilityPhone'; +import FullWidthLayout from '../../../components/FullWidthLayout'; +import CCRequestLayout from '../../../components/layout/CCRequestLayout'; +import VARequestLayout from '../../../components/layout/VARequestLayout'; +import VAFacilityLocation from '../../../components/VAFacilityLocation'; +import { selectFeatureBreadcrumbUrlUpdate } from '../../../redux/selectors'; +import { FETCH_STATUS } from '../../../utils/constants'; +import { scrollAndFocus } from '../../../utils/scrollAndFocus'; +import PageLayout from '../../components/PageLayout'; +import { + closeCancelAppointment, + fetchRequestDetails, +} from '../../redux/actions'; +import { selectRequestedAppointmentDetails } from '../../redux/selectors'; +import CancelConfirmationPage from '../CancelAppointmentPage/CancelConfirmationPage'; +import CancelWarningPage from '../CancelAppointmentPage/CancelWarningPage'; export default function RequestedAppointmentDetailsPage() { const { id } = useParams(); diff --git a/src/applications/vaos/appointment-list/components/RequestedAppointmentDetailsPage.unit.spec.js b/src/applications/vaos/appointment-list/pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage.unit.spec.js similarity index 97% rename from src/applications/vaos/appointment-list/components/RequestedAppointmentDetailsPage.unit.spec.js rename to src/applications/vaos/appointment-list/pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage.unit.spec.js index 914ac9ba6af6..46267efb34e2 100644 --- a/src/applications/vaos/appointment-list/components/RequestedAppointmentDetailsPage.unit.spec.js +++ b/src/applications/vaos/appointment-list/pages/RequestedAppointmentDetailsPage/RequestedAppointmentDetailsPage.unit.spec.js @@ -6,21 +6,21 @@ import { expect } from 'chai'; import MockDate from 'mockdate'; import moment from 'moment'; import React from 'react'; -import { AppointmentList } from '..'; -import { APPOINTMENT_STATUS, FETCH_STATUS } from '../../utils/constants'; -import MockAppointmentResponse from '../../tests/e2e/fixtures/MockAppointmentResponse'; -import MockFacilityResponse from '../../tests/e2e/fixtures/MockFacilityResponse'; -import { mockFacilityFetch } from '../../tests/mocks/fetch'; +import { AppointmentList } from '../..'; +import MockAppointmentResponse from '../../../tests/e2e/fixtures/MockAppointmentResponse'; +import MockFacilityResponse from '../../../tests/e2e/fixtures/MockFacilityResponse'; +import { mockFacilityFetch } from '../../../tests/mocks/fetch'; import { mockAppointmentApi, mockAppointmentUpdateApi, mockAppointmentsApi, -} from '../../tests/mocks/helpers'; +} from '../../../tests/mocks/helpers'; import { createTestStore, getTestDate, renderWithStoreAndRouter, -} from '../../tests/mocks/setup'; +} from '../../../tests/mocks/setup'; +import { APPOINTMENT_STATUS, FETCH_STATUS } from '../../../utils/constants'; describe('VAOS Page: RequestedAppointmentDetailsPage', () => { const testDate = getTestDate(); diff --git a/src/applications/vaos/appointment-list/components/RequestedAppointmentsListGroup.jsx b/src/applications/vaos/appointment-list/pages/RequestedAppointmentsPage/RequestedAppointmentsPage.jsx similarity index 88% rename from src/applications/vaos/appointment-list/components/RequestedAppointmentsListGroup.jsx rename to src/applications/vaos/appointment-list/pages/RequestedAppointmentsPage/RequestedAppointmentsPage.jsx index d2c1ace289ba..2dddd704b3be 100644 --- a/src/applications/vaos/appointment-list/components/RequestedAppointmentsListGroup.jsx +++ b/src/applications/vaos/appointment-list/pages/RequestedAppointmentsPage/RequestedAppointmentsPage.jsx @@ -1,26 +1,26 @@ -import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; +import React, { useEffect } from 'react'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports'; import classNames from 'classnames'; -import { - fetchPendingAppointments, - startNewAppointmentFlow, -} from '../redux/actions'; -import { getRequestedAppointmentListInfo } from '../redux/selectors'; +import InfoAlert from '../../../components/InfoAlert'; import { APPOINTMENT_STATUS, FETCH_STATUS, GA_PREFIX, -} from '../../utils/constants'; -import NoAppointments from './NoAppointments'; -import InfoAlert from '../../components/InfoAlert'; -import { scrollAndFocus } from '../../utils/scrollAndFocus'; -import RequestAppointmentLayout from './AppointmentsPage/RequestAppointmentLayout'; -import BackendAppointmentServiceAlert from './BackendAppointmentServiceAlert'; +} from '../../../utils/constants'; +import { scrollAndFocus } from '../../../utils/scrollAndFocus'; +import RequestAppointmentLayout from '../../../components/RequestAppointmentLayout'; +import BackendAppointmentServiceAlert from '../../components/BackendAppointmentServiceAlert'; +import NoAppointments from '../../components/NoAppointments'; +import { + fetchPendingAppointments, + startNewAppointmentFlow, +} from '../../redux/actions'; +import { getRequestedAppointmentListInfo } from '../../redux/selectors'; -export default function RequestedAppointmentsListGroup({ hasTypeChanged }) { +export default function RequestedAppointmentsPage({ hasTypeChanged }) { const { pendingAppointments, pendingStatus, @@ -157,6 +157,6 @@ export default function RequestedAppointmentsListGroup({ hasTypeChanged }) { ); } -RequestedAppointmentsListGroup.propTypes = { +RequestedAppointmentsPage.propTypes = { hasTypeChanged: PropTypes.bool, }; diff --git a/src/applications/vaos/appointment-list/components/RequestedAppointmentsListGroup.unit.spec.js b/src/applications/vaos/appointment-list/pages/RequestedAppointmentsPage/RequestedAppointmentsPage.unit.spec.js similarity index 88% rename from src/applications/vaos/appointment-list/components/RequestedAppointmentsListGroup.unit.spec.js rename to src/applications/vaos/appointment-list/pages/RequestedAppointmentsPage/RequestedAppointmentsPage.unit.spec.js index 1bbeafbaa5fb..52b75e87479f 100644 --- a/src/applications/vaos/appointment-list/components/RequestedAppointmentsListGroup.unit.spec.js +++ b/src/applications/vaos/appointment-list/pages/RequestedAppointmentsPage/RequestedAppointmentsPage.unit.spec.js @@ -1,13 +1,16 @@ -import React from 'react'; -import moment from 'moment'; -import MockDate from 'mockdate'; import { mockFetch } from '@department-of-veterans-affairs/platform-testing/helpers'; import { expect } from 'chai'; -import RequestedAppointmentsListGroup from './RequestedAppointmentsListGroup'; -import { getVAOSRequestMock } from '../../tests/mocks/mock'; -import reducers from '../../redux/reducer'; -import { mockVAOSAppointmentsFetch } from '../../tests/mocks/helpers'; -import { getTestDate, renderWithStoreAndRouter } from '../../tests/mocks/setup'; +import MockDate from 'mockdate'; +import moment from 'moment'; +import React from 'react'; +import reducers from '../../../redux/reducer'; +import { mockVAOSAppointmentsFetch } from '../../../tests/mocks/helpers'; +import { getVAOSRequestMock } from '../../../tests/mocks/mock'; +import { + getTestDate, + renderWithStoreAndRouter, +} from '../../../tests/mocks/setup'; +import RequestedAppointmentsPage from './RequestedAppointmentsPage'; const initialStateVAOSService = { featureToggles: { @@ -104,15 +107,12 @@ describe('VAOS Component: RequestedAppointmentsList', () => { }); // When veteran selects requested appointments - const screen = renderWithStoreAndRouter( - , - { - initialState: { - ...initialStateVAOSService, - }, - reducers, + const screen = renderWithStoreAndRouter(, { + initialState: { + ...initialStateVAOSService, }, - ); + reducers, + }); // Then it should display the requested appointments expect(await screen.findByText('Primary care')).to.be.ok; @@ -194,15 +194,12 @@ describe('VAOS Component: RequestedAppointmentsList', () => { }); // When veteran selects requested appointments - const screen = renderWithStoreAndRouter( - , - { - initialState: { - ...initialStateVAOSService, - }, - reducers, + const screen = renderWithStoreAndRouter(, { + initialState: { + ...initialStateVAOSService, }, - ); + reducers, + }); // Then it should display the requested appointments expect(await screen.findByText('Primary care')).to.be.ok; @@ -238,15 +235,12 @@ describe('VAOS Component: RequestedAppointmentsList', () => { }); // When veteran selects requested appointments - const screen = renderWithStoreAndRouter( - , - { - initialState: { - ...initialStateVAOSService, - }, - reducers, + const screen = renderWithStoreAndRouter(, { + initialState: { + ...initialStateVAOSService, }, - ); + reducers, + }); // Then it should display the no appointments alert message expect( @@ -327,15 +321,12 @@ describe('VAOS Component: RequestedAppointmentsList', () => { }); // When veteran selects requested appointments - const screen = renderWithStoreAndRouter( - , - { - initialState: { - ...initialStateVAOSService, - }, - reducers, + const screen = renderWithStoreAndRouter(, { + initialState: { + ...initialStateVAOSService, }, - ); + reducers, + }); // Then it should display the requested appointments expect( diff --git a/src/applications/vaos/appointment-list/components/UpcomingAppointmentsList.jsx b/src/applications/vaos/appointment-list/pages/UpcomingAppointmentsPage/UpcomingAppointmentsPage.jsx similarity index 84% rename from src/applications/vaos/appointment-list/components/UpcomingAppointmentsList.jsx rename to src/applications/vaos/appointment-list/pages/UpcomingAppointmentsPage/UpcomingAppointmentsPage.jsx index 6f2171febe13..1bc60557b34f 100644 --- a/src/applications/vaos/appointment-list/components/UpcomingAppointmentsList.jsx +++ b/src/applications/vaos/appointment-list/pages/UpcomingAppointmentsPage/UpcomingAppointmentsPage.jsx @@ -1,24 +1,24 @@ -import React, { useEffect } from 'react'; -import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports'; -import moment from 'moment'; import classNames from 'classnames'; +import moment from 'moment'; +import React, { useEffect } from 'react'; +import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import InfoAlert from '../../components/InfoAlert'; -import { getUpcomingAppointmentListInfo } from '../redux/selectors'; -import { FETCH_STATUS, GA_PREFIX } from '../../utils/constants'; -import { groupAppointmentByDay } from '../../services/appointment'; -import NoAppointments from './NoAppointments'; -import { scrollAndFocus } from '../../utils/scrollAndFocus'; +import InfoAlert from '../../../components/InfoAlert'; +import { selectFeatureBreadcrumbUrlUpdate } from '../../../redux/selectors'; +import { groupAppointmentByDay } from '../../../services/appointment'; +import { FETCH_STATUS, GA_PREFIX } from '../../../utils/constants'; +import { scrollAndFocus } from '../../../utils/scrollAndFocus'; +import BackendAppointmentServiceAlert from '../../components/BackendAppointmentServiceAlert'; +import NoAppointments from '../../components/NoAppointments'; import { fetchFutureAppointments, startNewAppointmentFlow, -} from '../redux/actions'; -import { selectFeatureBreadcrumbUrlUpdate } from '../../redux/selectors'; -import UpcomingAppointmentLayout from './AppointmentsPage/UpcomingAppointmentLayout'; -import BackendAppointmentServiceAlert from './BackendAppointmentServiceAlert'; +} from '../../redux/actions'; +import { getUpcomingAppointmentListInfo } from '../../redux/selectors'; +import UpcomingAppointmentLayout from '../AppointmentsPage/UpcomingAppointmentLayout'; -export default function UpcomingAppointmentsList() { +export default function UpcomingAppointmentsPage() { const history = useHistory(); const dispatch = useDispatch(); const { diff --git a/src/applications/vaos/appointment-list/components/UpcomingAppointmentsList.unit.spec.js b/src/applications/vaos/appointment-list/pages/UpcomingAppointmentsPage/UpcomingAppointmentsPage.unit.spec.js similarity index 96% rename from src/applications/vaos/appointment-list/components/UpcomingAppointmentsList.unit.spec.js rename to src/applications/vaos/appointment-list/pages/UpcomingAppointmentsPage/UpcomingAppointmentsPage.unit.spec.js index 7e0b4a4d6647..40eaa1144238 100644 --- a/src/applications/vaos/appointment-list/components/UpcomingAppointmentsList.unit.spec.js +++ b/src/applications/vaos/appointment-list/pages/UpcomingAppointmentsPage/UpcomingAppointmentsPage.unit.spec.js @@ -1,16 +1,19 @@ -import React from 'react'; -import MockDate from 'mockdate'; +import { mockFetch } from '@department-of-veterans-affairs/platform-testing/helpers'; import { expect } from 'chai'; +import MockDate from 'mockdate'; import moment from 'moment'; -import { mockFetch } from '@department-of-veterans-affairs/platform-testing/helpers'; -import reducers from '../../redux/reducer'; -import { getTestDate, renderWithStoreAndRouter } from '../../tests/mocks/setup'; -import UpcomingAppointmentsList from './UpcomingAppointmentsList'; +import React from 'react'; +import reducers from '../../../redux/reducer'; import { - mockVAOSAppointmentsFetch, mockAppointmentsApi, -} from '../../tests/mocks/helpers'; -import { getVAOSAppointmentMock } from '../../tests/mocks/mock'; + mockVAOSAppointmentsFetch, +} from '../../../tests/mocks/helpers'; +import { getVAOSAppointmentMock } from '../../../tests/mocks/mock'; +import { + getTestDate, + renderWithStoreAndRouter, +} from '../../../tests/mocks/setup'; +import UpcomingAppointmentsPage from './UpcomingAppointmentsPage'; const initialState = { featureToggles: { @@ -85,7 +88,7 @@ describe('VAOS Component: UpcomingAppointmentsList', () => { statuses: ['booked', 'arrived', 'fulfilled', 'cancelled'], }); - const screen = renderWithStoreAndRouter(, { + const screen = renderWithStoreAndRouter(, { initialState: myInitialState, reducers, }); @@ -136,7 +139,7 @@ describe('VAOS Component: UpcomingAppointmentsList', () => { statuses: ['booked', 'arrived', 'fulfilled', 'cancelled'], }); - const screen = renderWithStoreAndRouter(, { + const screen = renderWithStoreAndRouter(, { initialState: myInitialState, reducers, }); @@ -188,7 +191,7 @@ describe('VAOS Component: UpcomingAppointmentsList', () => { statuses: ['booked', 'arrived', 'fulfilled', 'cancelled'], }); - const screen = renderWithStoreAndRouter(, { + const screen = renderWithStoreAndRouter(, { initialState: myInitialState, reducers, }); @@ -238,7 +241,7 @@ describe('VAOS Component: UpcomingAppointmentsList', () => { statuses: ['booked', 'arrived', 'fulfilled', 'cancelled'], }); - const screen = renderWithStoreAndRouter(, { + const screen = renderWithStoreAndRouter(, { initialState: myInitialState, reducers, }); @@ -291,7 +294,7 @@ describe('VAOS Component: UpcomingAppointmentsList', () => { statuses: ['booked', 'arrived', 'fulfilled', 'cancelled'], }); - const screen = renderWithStoreAndRouter(, { + const screen = renderWithStoreAndRouter(, { initialState: myInitialState, reducers, }); diff --git a/src/applications/vaos/appointment-list/components/AppointmentsPage/RequestAppointmentLayout.jsx b/src/applications/vaos/components/RequestAppointmentLayout.jsx similarity index 93% rename from src/applications/vaos/appointment-list/components/AppointmentsPage/RequestAppointmentLayout.jsx rename to src/applications/vaos/components/RequestAppointmentLayout.jsx index 3a98b9aae70c..7ae29b775190 100644 --- a/src/applications/vaos/appointment-list/components/AppointmentsPage/RequestAppointmentLayout.jsx +++ b/src/applications/vaos/components/RequestAppointmentLayout.jsx @@ -1,23 +1,23 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; import { useSelector } from 'react-redux'; -import AppointmentFlexGrid from '../../../components/AppointmentFlexGrid'; -import ListItem from '../../../components/ListItem'; -import AppointmentRow from '../../../components/AppointmentRow'; import { selectAppointmentLocality, + selectApptDetailAriaText, selectIsCanceled, + selectIsCommunityCare, selectModalityIcon, selectTypeOfCareName, - selectApptDetailAriaText, - selectIsCommunityCare, -} from '../../redux/selectors'; +} from '../appointment-list/redux/selectors'; import { selectFeatureBreadcrumbUrlUpdate, selectFeatureCCDirectScheduling, -} from '../../../redux/selectors'; -import AppointmentColumn from '../../../components/AppointmentColumn'; +} from '../redux/selectors'; +import AppointmentColumn from './AppointmentColumn'; +import AppointmentFlexGrid from './AppointmentFlexGrid'; +import AppointmentRow from './AppointmentRow'; +import ListItem from './ListItem'; export default function RequestAppointmentLayout({ appointment, index }) { const appointmentLocality = useSelector(() => diff --git a/src/applications/vaos/new-appointment/components/ContactInfoPage.jsx b/src/applications/vaos/new-appointment/components/ContactInfoPage.jsx index eb40bd672285..af1354fc804c 100644 --- a/src/applications/vaos/new-appointment/components/ContactInfoPage.jsx +++ b/src/applications/vaos/new-appointment/components/ContactInfoPage.jsx @@ -110,28 +110,6 @@ function ContactInformationParagraph() { const phoneConfig = phoneUI('Your phone number'); const pageKey = 'contactInfo'; -function Description() { - const flowType = useSelector(getFlowType); - - if (FLOW_TYPES.DIRECT === flowType) - return ( - <> - -

    - Want to update your contact information for more VA benefits and - services? -
    - - Go to your VA.gov profile (opens in new tab) - - . -

    - - ); - - return ; -} - export default function ContactInfoPage() { const pageTitle = useSelector(state => getPageTitle(state, pageKey)); @@ -151,7 +129,7 @@ export default function ContactInfoPage() { }, []); const uiSchema = { - 'ui:description': , + 'ui:description': , phoneNumber: { ...phoneConfig, 'ui:errorMessages': { @@ -234,22 +212,21 @@ export default function ContactInfoPage() { onChange={newData => setData(newData)} data={data} > - {FLOW_TYPES.REQUEST === flowType && ( - -
    - You can update your contact information for most of your - benefits and services in your VA.gov profile. -
    - - Go to your VA.gov profile (opens in new tab) - -
    -
    - )} + +
    + You can update your contact information for most of your benefits + and services in your VA.gov profile. +
    + + Go to your VA.gov profile (opens in new tab) + +
    +
    + dispatch(routeToPreviousAppointmentPage(history, pageKey, data)) 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 1cf5b6b40fad..f998c93ba07f 100644 --- a/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js +++ b/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js @@ -12,7 +12,7 @@ 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: { @@ -40,7 +40,7 @@ describe('VAOS Page: ContactInfoPage', () => { // it should display page heading and description await waitFor(() => { - expect(screen.history.push.called).to.be.true; + expect(screen.getByText(/How should we contact you\?/i)).to.be.ok; }); expect( screen.getByText( @@ -50,7 +50,7 @@ describe('VAOS Page: ContactInfoPage', () => { expect( screen.getByText( - /Want to update your contact information for more VA benefits and services\?/, + /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/); diff --git a/src/applications/vaos/new-appointment/newAppointmentFlow.js b/src/applications/vaos/new-appointment/newAppointmentFlow.js index ff7546089f95..8cc23a8b6e16 100644 --- a/src/applications/vaos/new-appointment/newAppointmentFlow.js +++ b/src/applications/vaos/new-appointment/newAppointmentFlow.js @@ -312,7 +312,7 @@ const flow = { }, contactInfo: { url: '/new-appointment/contact-info', - label: 'Confirm your contact information', + label: 'How should we contact you?', next: 'review', }, review: { @@ -375,10 +375,6 @@ export default function getNewAppointmentFlow(state) { }, contactInfo: { ...flow.contactInfo, - label: - FLOW_TYPES.DIRECT === flowType - ? 'Confirm your contact information' - : 'How should we contact you?', url: featureBreadcrumbUrlUpdate ? 'contact-information' : '/new-appointment/contact-info', diff --git a/src/applications/vaos/new-appointment/redux/reducer.js b/src/applications/vaos/new-appointment/redux/reducer.js index 270f483f606b..ef3c2556c484 100644 --- a/src/applications/vaos/new-appointment/redux/reducer.js +++ b/src/applications/vaos/new-appointment/redux/reducer.js @@ -71,9 +71,7 @@ import { distanceBetween } from '../../utils/address'; import { isTypeOfCareSupported } from '../../services/location'; export const REASON_ADDITIONAL_INFO_TITLES = { - request: 'Add any details you’d like to share with your provider.', - direct: - 'Please provide any additional details you’d like to share with your provider about this appointment.', + va: 'Add any details you’d like to share with your provider.', ccRequest: 'Share any information that you think will help the provider prepare for your appointment. You don’t have to share anything if you don’t want to.', }; @@ -657,10 +655,7 @@ export default function formReducer(state = initialState, action) { let additionalInfoTitle = REASON_ADDITIONAL_INFO_TITLES.ccRequest; if (formData.facilityType !== FACILITY_TYPES.COMMUNITY_CARE) { - additionalInfoTitle = - state.flowType === FLOW_TYPES.DIRECT - ? REASON_ADDITIONAL_INFO_TITLES.direct - : REASON_ADDITIONAL_INFO_TITLES.request; + additionalInfoTitle = REASON_ADDITIONAL_INFO_TITLES.va; } else { delete formData.reasonForAppointment; } diff --git a/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx b/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx index d2b23dd9b43e..24b172a4758b 100644 --- a/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx +++ b/src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx @@ -51,7 +51,7 @@ export const ChooseDateAndTime = props => { ) { setLoading(false); setFailed(true); - scrollAndFocus('h2'); + scrollAndFocus('h1'); } }, [currentReferral.providerId, dispatch, providerFetchStatus, futureStatus], @@ -71,19 +71,12 @@ export const ChooseDateAndTime = props => { ); } - if (failed) { - return ( - -

    We’re sorry. We’ve run into a problem

    -

    - We’re having trouble getting your upcoming appointments. Please try - again later. -

    -
    - ); - } return ( - + { sandbox.restore(); }); it('should fetch provider or appointments from store if it exists and not call API', async () => { - const screen = renderWithStoreAndRouter( + renderWithStoreAndRouter( , @@ -149,7 +149,6 @@ describe('VAOS ChoseDateAndTime component', () => { store: createTestStore(initialFullState), }, ); - expect(await screen.getByTestId('pick-heading')).to.exist; sandbox.assert.notCalled(getProviderByIdModule.getProviderById); sandbox.assert.notCalled(fetchAppointmentsModule.fetchAppointments); }); diff --git a/src/applications/vaos/referral-appointments/ConfirmReferral.jsx b/src/applications/vaos/referral-appointments/ConfirmReferral.jsx index b2fdcb841267..1f862894d675 100644 --- a/src/applications/vaos/referral-appointments/ConfirmReferral.jsx +++ b/src/applications/vaos/referral-appointments/ConfirmReferral.jsx @@ -6,8 +6,10 @@ import ReferralLayout from './components/ReferralLayout'; export default function ConfirmReferral(props) { const { currentReferral } = props; return ( - -

    Confirm Referral for {currentReferral.CategoryOfCare}

    +

    {currentReferral.UUID}

    - -

    Referrals and requests

    +

    Find your requested appointments and community care referrals.

    Community care referrals

    diff --git a/src/applications/vaos/referral-appointments/ReviewAndConfirm.jsx b/src/applications/vaos/referral-appointments/ReviewAndConfirm.jsx index afcd381f5561..a5df14f6b684 100644 --- a/src/applications/vaos/referral-appointments/ReviewAndConfirm.jsx +++ b/src/applications/vaos/referral-appointments/ReviewAndConfirm.jsx @@ -94,6 +94,15 @@ const ReviewAndConfirm = props => { ], ); + const handleGoBack = e => { + e.preventDefault(); + routeToPreviousReferralPage( + history, + 'reviewAndConfirm', + currentReferral.UUID, + ); + }; + if (loading) { return (

    @@ -101,24 +110,15 @@ const ReviewAndConfirm = props => {
    ); } - - if (failed) { - return ( - -

    We’re sorry. We’ve run into a problem

    -

    - We’re having trouble getting your upcoming appointments. Please try - again later. -

    -
    - ); - } const headingStyles = 'vads-u-margin--0 vads-u-font-family--sans vads-u-font-weight--bold vads-u-font-size--source-sans-normalized'; return ( - +
    -

    Review your appointment details


    @@ -152,12 +152,7 @@ const ReviewAndConfirm = props => { text="Edit" data-testid="edit-when-information-link" onClick={e => { - e.preventDefault(); - routeToPreviousReferralPage( - history, - 'reviewAndConfirm', - currentReferral.UUID, - ); + handleGoBack(e); }} />
    @@ -204,12 +199,7 @@ const ReviewAndConfirm = props => { secondary uswds onClick={e => { - e.preventDefault(); - routeToPreviousReferralPage( - history, - 'confirmAppointment', - currentReferral.UUID, - ); + handleGoBack(e); }} /> { providerFetchStatus: FETCH_STATUS.notStarted, }, }; - const failedState = { - featureToggles: { - vaOnlineSchedulingCCDirectScheduling: true, - }, - referral: { - selectedProvider: {}, - providerFetchStatus: FETCH_STATUS.failed, - }, - }; beforeEach(() => { global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); sandbox @@ -63,7 +54,7 @@ describe('VAOS Component: ReviewAndConfirm', () => { store: createTestStore(initialFullState), }, ); - expect(await screen.getByTestId('review-heading')).to.exist; + expect(await screen.getByTestId('referral-layout-heading')).to.exist; sandbox.assert.notCalled(getProviderByIdModule.getProviderById); }); it('should fetch provider if not in redux', async () => { @@ -80,20 +71,6 @@ describe('VAOS Component: ReviewAndConfirm', () => { expect(await screen.getByTestId('loading')).to.exist; sandbox.assert.calledOnce(getProviderByIdModule.getProviderById); }); - it('should show the error message if fetch fails', async () => { - const selectedSlotKey = getReferralSlotKey('UUID'); - sessionStorage.setItem(selectedSlotKey, '0'); - - const screen = renderWithStoreAndRouter( - , - { - store: createTestStore(failedState), - }, - ); - expect(await screen.getByTestId('error')).to.exist; - }); it('should get selected slot from session storage if not in redux', async () => { const selectedSlotKey = getReferralSlotKey('UUID'); sessionStorage.setItem(selectedSlotKey, '0'); @@ -109,7 +86,7 @@ describe('VAOS Component: ReviewAndConfirm', () => { store: createTestStore(noSelectState), }, ); - expect(await screen.getByTestId('review-heading')).to.exist; + expect(await screen.getByTestId('referral-layout-heading')).to.exist; expect(await screen.getByTestId('slot-day-time')).to.contain.text( 'Monday, September 9, 2024', ); diff --git a/src/applications/vaos/referral-appointments/ScheduleReferral.jsx b/src/applications/vaos/referral-appointments/ScheduleReferral.jsx index 45568b3230c7..0ea785402858 100644 --- a/src/applications/vaos/referral-appointments/ScheduleReferral.jsx +++ b/src/applications/vaos/referral-appointments/ScheduleReferral.jsx @@ -26,9 +26,11 @@ export default function ScheduleReferral(props) { ? '1 appointment' : `${currentReferral.numberOfAppointments} appointments`; return ( - +
    -

    Referral for {currentReferral.CategoryOfCare}

    {`Your referring VA facility approved you for ${appointmentCountString} with a community care provider. You can now schedule your appointment with a community care provider.`}

    diff --git a/src/applications/vaos/referral-appointments/components/DateAndTimeContent.jsx b/src/applications/vaos/referral-appointments/components/DateAndTimeContent.jsx index bb274634c02f..b2cbbf480946 100644 --- a/src/applications/vaos/referral-appointments/components/DateAndTimeContent.jsx +++ b/src/applications/vaos/referral-appointments/components/DateAndTimeContent.jsx @@ -98,9 +98,6 @@ export const DateAndTimeContent = props => { return ( <>
    -

    - Schedule an appointment with your provider -

    You or your referring VA facility selected to schedule an appointment online with this provider: diff --git a/src/applications/vaos/referral-appointments/components/DateAndTimeContent.unit.spec.js b/src/applications/vaos/referral-appointments/components/DateAndTimeContent.unit.spec.js index 7aaa1ae1658a..2d67215a2535 100644 --- a/src/applications/vaos/referral-appointments/components/DateAndTimeContent.unit.spec.js +++ b/src/applications/vaos/referral-appointments/components/DateAndTimeContent.unit.spec.js @@ -65,8 +65,6 @@ describe('VAOS Component: DateAndTimeContent', () => { initialState, }, ); - - expect(screen.getByTestId('pick-heading')).to.exist; expect(screen.getByTestId('cal-widget')).to.exist; }); it('should show error if no date selected', async () => { diff --git a/src/applications/vaos/referral-appointments/components/ErrorAlert.jsx b/src/applications/vaos/referral-appointments/components/ErrorAlert.jsx new file mode 100644 index 000000000000..df3bfdcaf9fe --- /dev/null +++ b/src/applications/vaos/referral-appointments/components/ErrorAlert.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const ErrorAlert = () => { + return ( + +

    We’re sorry. We’ve run into a problem.

    +

    + Something went wrong on our end. Please refresh the page and try again. +

    + + ); +}; + +export default ErrorAlert; diff --git a/src/applications/vaos/referral-appointments/components/ReferralLayout.jsx b/src/applications/vaos/referral-appointments/components/ReferralLayout.jsx index a26d35651d26..17a1a55af55c 100644 --- a/src/applications/vaos/referral-appointments/components/ReferralLayout.jsx +++ b/src/applications/vaos/referral-appointments/components/ReferralLayout.jsx @@ -11,6 +11,7 @@ import ErrorBoundary from '../../components/ErrorBoundary'; import WarningNotification from '../../components/WarningNotification'; import { selectCurrentPage } from '../redux/selectors'; import { routeToPreviousReferralPage } from '../flow'; +import ErrorAlert from './ErrorAlert'; function BreadCrumbNav() { const history = useHistory(); @@ -41,7 +42,12 @@ function BreadCrumbNav() { ); } -export default function ReferralLayout({ children, hasEyebrow }) { +export default function ReferralLayout({ + children, + hasEyebrow, + apiFailure, + heading, +}) { const location = useLocation(); return ( @@ -62,11 +68,18 @@ export default function ReferralLayout({ children, hasEyebrow }) {
    {hasEyebrow && ( - - New Appointment - + <> + + New Appointment + + {heading && ( +

    {heading}

    + )} + )} - {children} + + {apiFailure ? : children} +
    @@ -76,6 +89,8 @@ export default function ReferralLayout({ children, hasEyebrow }) { } ReferralLayout.propTypes = { + apiFailure: PropTypes.bool, children: PropTypes.node, hasEyebrow: PropTypes.bool, + heading: PropTypes.string, }; diff --git a/src/applications/vaos/referral-appointments/components/ReferralLayout.unit.spec.js b/src/applications/vaos/referral-appointments/components/ReferralLayout.unit.spec.js new file mode 100644 index 000000000000..1d3581aa1298 --- /dev/null +++ b/src/applications/vaos/referral-appointments/components/ReferralLayout.unit.spec.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { expect } from 'chai'; +import ReferralLayout from './ReferralLayout'; +import { + renderWithStoreAndRouter, + createTestStore, +} from '../../tests/mocks/setup'; + +describe('VAOS Component: ReferralLayout', () => { + const initialFullState = { + featureToggles: { + vaOnlineSchedulingCCDirectScheduling: true, + }, + }; + it('should render the layout with the correct header and children', () => { + const screen = renderWithStoreAndRouter( + +
    I'm a child
    +
    , + { + store: createTestStore(initialFullState), + }, + ); + + expect(screen.getByTestId('child')).to.exist; + expect(screen.getByText('A tribute to the best heading in the world')).to + .exist; + expect(screen.getByText('New Appointment')).to.exist; + }); + it('should render the error with the correct header and children', () => { + const screen = renderWithStoreAndRouter( + +
    I'm a child
    +
    , + { + store: createTestStore(initialFullState), + }, + ); + + expect(screen.getByTestId('error')).to.exist; + expect(screen.queryByTestId('child')).to.not.exist; + expect(screen.getByText('A tribute to the best heading in the world')).to + .exist; + expect(screen.getByText('New Appointment')).to.exist; + }); +}); diff --git a/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.jsx b/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.jsx index 5fff15787726..52d3b2b0d830 100644 --- a/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.jsx +++ b/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.jsx @@ -3,6 +3,7 @@ import { useLocation } from 'react-router-dom'; import { useGetReferralById } from '../hooks/useGetReferralById'; import ReferralTaskCard from './ReferralTaskCard'; +import { FETCH_STATUS } from '../../utils/constants'; export default function ReferralTaskCardWithReferral() { const { search } = useLocation(); @@ -10,10 +11,26 @@ export default function ReferralTaskCardWithReferral() { const params = new URLSearchParams(search); const id = params.get('id'); - const { currentReferral } = useGetReferralById(id); + const { currentReferral, referralFetchStatus } = useGetReferralById(id); - if (!currentReferral) { - return null; + if ( + !currentReferral && + (referralFetchStatus === FETCH_STATUS.succeeded || + referralFetchStatus === FETCH_STATUS.failed) + ) { + return ( + +

    We’re sorry. We’ve run into a problem

    +

    + We’re having trouble getting your referral information. Please try + again later. +

    +
    + ); } return ; diff --git a/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.unit.spec.js b/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.unit.spec.js index c78bf9017773..28b144d1285f 100644 --- a/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.unit.spec.js +++ b/src/applications/vaos/referral-appointments/components/ReferralTaskCardWithReferral.unit.spec.js @@ -86,4 +86,28 @@ describe('VAOS Component: ReferralTaskCardWithReferral', () => { const taskCard = screen.queryByTestId('referral-task-card'); expect(taskCard).to.be.null; }); + + it('should display the error alert when referral is not found', async () => { + const store = createTestStore(initialState); + const screen = renderWithStoreAndRouter(, { + store, + path: '/?id=thisisareferralthatwontbefound', + }); + expect(await screen.getByTestId('referral-error')).to.exist; + }); + + it('should display the error alert when fetch fails', async () => { + const store = createTestStore({ + ...initialState, + referral: { + referrals: [], + referralFetchStatus: FETCH_STATUS.failed, + }, + }); + const screen = renderWithStoreAndRouter(, { + store, + path: '/?id=add2f0f4-a1ea-4dea-a504-a54ab57c6801', + }); + expect(await screen.getByTestId('referral-error')).to.exist; + }); }); diff --git a/src/applications/vaos/referral-appointments/components/RequestsList.jsx b/src/applications/vaos/referral-appointments/components/RequestsList.jsx index 79de8bb67552..d0dac5e7c89a 100644 --- a/src/applications/vaos/referral-appointments/components/RequestsList.jsx +++ b/src/applications/vaos/referral-appointments/components/RequestsList.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import RequestAppointmentLayout from '../../appointment-list/components/AppointmentsPage/RequestAppointmentLayout'; +import RequestAppointmentLayout from '../../components/RequestAppointmentLayout'; import { APPOINTMENT_STATUS } from '../../utils/constants'; import InfoAlert from '../../components/InfoAlert'; import ScheduleAppointmentLink from '../../appointment-list/components/ScheduleAppointmentLink'; diff --git a/src/applications/vaos/referral-appointments/flow.js b/src/applications/vaos/referral-appointments/flow.js index 31edb1c15c9f..41bd74110d98 100644 --- a/src/applications/vaos/referral-appointments/flow.js +++ b/src/applications/vaos/referral-appointments/flow.js @@ -50,7 +50,11 @@ export default function getPageFlow(referralId) { export function routeToPageInFlow(history, current, action, referralId) { const pageFlow = getPageFlow(referralId); - const nextPageString = pageFlow[current][action]; + // if there is no current page meaning there was an error fetching referral data + // then we are on an error state in the form and back should go back to appointments. + const nextPageString = current + ? pageFlow[current][action] + : 'referralsAndRequests'; const nextPage = pageFlow[nextPageString]; if (action === 'next' && nextPageString === 'scheduleReferral') { startReferralTimer(referralId); diff --git a/src/applications/vaos/referral-appointments/hooks/useGetReferralById.jsx b/src/applications/vaos/referral-appointments/hooks/useGetReferralById.jsx index 424c7c1443a6..49bfe7c9b2fb 100644 --- a/src/applications/vaos/referral-appointments/hooks/useGetReferralById.jsx +++ b/src/applications/vaos/referral-appointments/hooks/useGetReferralById.jsx @@ -14,17 +14,6 @@ const useGetReferralById = id => { state => getReferral(state), shallowEqual, ); - useEffect( - () => { - if ( - isInCCPilot && - (referralFetchStatus === FETCH_STATUS.succeeded && !referrals.length) - ) { - setReferralNotFound(true); - } - }, - [isInCCPilot, referralFetchStatus, referrals], - ); useEffect( () => { if (isInCCPilot && (referrals.length && id)) { @@ -41,6 +30,7 @@ const useGetReferralById = id => { useEffect( () => { if ( + id && isInCCPilot && !referrals.length && referralFetchStatus === FETCH_STATUS.notStarted diff --git a/src/applications/vaos/referral-appointments/index.jsx b/src/applications/vaos/referral-appointments/index.jsx index 5b31b9c58730..b2f40ae3a21d 100644 --- a/src/applications/vaos/referral-appointments/index.jsx +++ b/src/applications/vaos/referral-appointments/index.jsx @@ -6,7 +6,6 @@ import { Redirect, useLocation, } from 'react-router-dom'; -import { VaAlert } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; import ScheduleReferral from './ScheduleReferral'; import ReviewAndConfirm from './ReviewAndConfirm'; import ConfirmReferral from './ConfirmReferral'; @@ -17,6 +16,7 @@ import { useIsInCCPilot } from './hooks/useIsInCCPilot'; import { FETCH_STATUS } from '../utils/constants'; import FormLayout from '../new-appointment/components/FormLayout'; import { scrollAndFocus } from '../utils/scrollAndFocus'; +import ReferralLayout from './components/ReferralLayout'; export default function ReferralAppointments() { useManualScrollRestoration(); @@ -48,13 +48,17 @@ export default function ReferralAppointments() { }, [referralFetchStatus], ); + if (referralNotFound || !isInCCPilot) { return ; } - if ( - (!referral || referralFetchStatus === FETCH_STATUS.loading) && - !referralNotFound - ) { + + if (referralFetchStatus === FETCH_STATUS.failed) { + // Referral Layout shows the error component is apiFailure is true + return ; + } + + if (!referral && referralFetchStatus !== FETCH_STATUS.failed) { return ( @@ -62,16 +66,6 @@ export default function ReferralAppointments() { ); } - if (referralFetchStatus === FETCH_STATUS.failed) { - return ( - -

    - There was an error trying to get your referral data -

    -

    Please try again later, or contact your VA facility for help.

    -
    - ); - } return ( <> diff --git a/src/applications/vaos/referral-appointments/redux/actions.js b/src/applications/vaos/referral-appointments/redux/actions.js index e3c0bdc6f9e1..042dc6ea6eee 100644 --- a/src/applications/vaos/referral-appointments/redux/actions.js +++ b/src/applications/vaos/referral-appointments/redux/actions.js @@ -80,7 +80,7 @@ export function fetchReferralById(id) { const referrals = await getPatientReferralById(id); dispatch({ type: FETCH_REFERRAL_SUCCEEDED, - data: Object.keys(referrals).length ? [referrals] : [], + data: [referrals], }); return referrals; } catch (error) { diff --git a/src/applications/vaos/referral-appointments/redux/reducers.js b/src/applications/vaos/referral-appointments/redux/reducers.js index 4a40359af160..2ed66d5e8d6f 100644 --- a/src/applications/vaos/referral-appointments/redux/reducers.js +++ b/src/applications/vaos/referral-appointments/redux/reducers.js @@ -58,6 +58,7 @@ function ccAppointmentReducer(state = initialState, action) { return { ...state, referralsFetchStatus: FETCH_STATUS.succeeded, + referralFetchStatus: FETCH_STATUS.succeeded, referrals: action.data, }; case FETCH_REFERRALS_FAILED: diff --git a/src/applications/vaos/referral-appointments/tests/hooks/useGetReferralById/useGetReferralById.unit.spec.js b/src/applications/vaos/referral-appointments/tests/hooks/useGetReferralById/useGetReferralById.unit.spec.js index 543c86f52f55..445d23210349 100644 --- a/src/applications/vaos/referral-appointments/tests/hooks/useGetReferralById/useGetReferralById.unit.spec.js +++ b/src/applications/vaos/referral-appointments/tests/hooks/useGetReferralById/useGetReferralById.unit.spec.js @@ -1,6 +1,5 @@ import React from 'react'; import { expect } from 'chai'; -import { waitFor } from '@testing-library/react'; import sinon from 'sinon'; import * as getPatientReferralByIdModule from '../../../../services/referral'; import { renderWithStoreAndRouter } from '../../../../tests/mocks/setup'; @@ -104,34 +103,5 @@ describe('Community Care Referrals', () => { getPatientReferralByIdModule.getPatientReferralById, ); }); - it('Returns the referral not found when referral not in fetch', async () => { - sandbox - .stub(getPatientReferralByIdModule, 'getPatientReferralById') - .resolves({}); - const initialState = { - featureToggles: { - vaOnlineSchedulingCCDirectScheduling: true, - }, - referral: { - facility: null, - referrals: [], - referralFetchStatus: FETCH_STATUS.notStarted, - }, - user: { - profile: { - facilities: [{ facilityId: '983' }], - }, - }, - }; - const screen = renderWithStoreAndRouter(, { - initialState, - }); - await waitFor(() => { - expect(screen.getByText('Referral not found: true')).to.exist; - }); - sandbox.assert.calledOnce( - getPatientReferralByIdModule.getPatientReferralById, - ); - }); }); }); diff --git a/src/applications/vaos/services/mocks/index.js b/src/applications/vaos/services/mocks/index.js index 484c873c4d9e..7a477018ddda 100644 --- a/src/applications/vaos/services/mocks/index.js +++ b/src/applications/vaos/services/mocks/index.js @@ -328,6 +328,9 @@ const responses = { }); }, 'GET /vaos/v2/epsApi/referralDetails/:referralId': (req, res) => { + if (req.params.referralId === 'error') { + return res.status(500).json({ error: true }); + } const referrals = referralUtils.createReferrals(3, '2024-12-02'); const singleReferral = referrals.find( referral => referral?.UUID === req.params.referralId, diff --git a/src/applications/vaos/services/referral/index.js b/src/applications/vaos/services/referral/index.js index 66a04a44faa2..a772219d2ca7 100644 --- a/src/applications/vaos/services/referral/index.js +++ b/src/applications/vaos/services/referral/index.js @@ -9,17 +9,13 @@ export async function getPatientReferrals() { } export async function getPatientReferralById(referralId) { - try { - const response = await apiRequestWithUrl( - `/vaos/v2/epsApi/referralDetails/${referralId}`, - { - method: 'GET', - }, - ); - return response.data; - } catch (error) { - return null; - } + const response = await apiRequestWithUrl( + `/vaos/v2/epsApi/referralDetails/${referralId}`, + { + method: 'GET', + }, + ); + return response.data; } export async function getProviderById(providerId) { diff --git a/src/applications/vaos/tests/e2e/workflows/direct-schedule-workflow/primary-care.cypress.spec.js b/src/applications/vaos/tests/e2e/workflows/direct-schedule-workflow/primary-care.cypress.spec.js index d205052fe2cf..692689da6e97 100644 --- a/src/applications/vaos/tests/e2e/workflows/direct-schedule-workflow/primary-care.cypress.spec.js +++ b/src/applications/vaos/tests/e2e/workflows/direct-schedule-workflow/primary-care.cypress.spec.js @@ -278,7 +278,7 @@ describe('VAOS direct schedule flow - Primary care', () => { .clickNextButton(); ContactInfoPageObject.assertUrl() - .assertHeading({ name: /Confirm your contact information/i }) + .assertHeading({ name: /How should we contact you/i }) .typeEmailAddress('veteran@va.gov') .typePhoneNumber('5555555555') .clickNextButton(); diff --git a/src/platform/forms-system/src/js/components/BackLink.jsx b/src/platform/forms-system/src/js/components/BackLink.jsx index fb5312415cf9..7888e6b1ea6a 100644 --- a/src/platform/forms-system/src/js/components/BackLink.jsx +++ b/src/platform/forms-system/src/js/components/BackLink.jsx @@ -70,7 +70,10 @@ const BackLink = ({ router, routes, location, form, setData }) => { } return ( -