From e7f37bea0fd926f8ccc3f8418d6c9b190e13bf45 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Fri, 15 Nov 2024 14:33:33 -0500 Subject: [PATCH 01/28] Adds initial mhv-supply-reorder form app --- .../mhv-supply-reordering/README.md | 6 + .../mhv-supply-reordering/app-entry.jsx | 15 +++ .../mhv-supply-reordering/config/form.js | 59 ++++++++++ .../mhv-supply-reordering/constants.js | 2 + .../mhv-supply-reordering/containers/App.jsx | 12 ++ .../containers/ConfirmationPage.jsx | 100 ++++++++++++++++ .../containers/IntroductionPage.jsx | 111 ++++++++++++++++++ .../mhv-supply-reordering/manifest.json | 7 ++ .../pages/nameAndDateOfBirth.js | 24 ++++ .../mhv-supply-reordering/reducers/index.js | 6 + .../mhv-supply-reordering/routes.jsx | 12 ++ .../sass/mhv-supply-reordering.scss | 6 + .../containers/ConfirmationPage.unit.spec.jsx | 45 +++++++ .../containers/IntroductionPage.unit.spec.jsx | 68 +++++++++++ .../tests/fixtures/data/minimal-test.json | 9 ++ .../fixtures/mocks/local-mock-responses.js | 8 ++ .../tests/fixtures/mocks/user.json | 56 +++++++++ .../mhv-supply-reordering.cypress.spec.js | 37 ++++++ 18 files changed, 583 insertions(+) create mode 100644 src/applications/mhv-supply-reordering/README.md create mode 100644 src/applications/mhv-supply-reordering/app-entry.jsx create mode 100644 src/applications/mhv-supply-reordering/config/form.js create mode 100644 src/applications/mhv-supply-reordering/constants.js create mode 100644 src/applications/mhv-supply-reordering/containers/App.jsx create mode 100644 src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx create mode 100644 src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx create mode 100644 src/applications/mhv-supply-reordering/manifest.json create mode 100644 src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js create mode 100644 src/applications/mhv-supply-reordering/reducers/index.js create mode 100644 src/applications/mhv-supply-reordering/routes.jsx create mode 100644 src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss create mode 100644 src/applications/mhv-supply-reordering/tests/containers/ConfirmationPage.unit.spec.jsx create mode 100644 src/applications/mhv-supply-reordering/tests/containers/IntroductionPage.unit.spec.jsx create mode 100644 src/applications/mhv-supply-reordering/tests/fixtures/data/minimal-test.json create mode 100644 src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js create mode 100644 src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json create mode 100644 src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress.spec.js diff --git a/src/applications/mhv-supply-reordering/README.md b/src/applications/mhv-supply-reordering/README.md new file mode 100644 index 000000000000..6c73f872b0c4 --- /dev/null +++ b/src/applications/mhv-supply-reordering/README.md @@ -0,0 +1,6 @@ +# mhv-supply-reordering + +Form app generated with `yarn app:new`. Changes to the following files were reverted, since `VA_FORM_IDS.FORM_VA_2346A` already exists. + +- `src/platform/forms/constants.js` +- `src/platform/forms/tests/forms.unit.spec.js` diff --git a/src/applications/mhv-supply-reordering/app-entry.jsx b/src/applications/mhv-supply-reordering/app-entry.jsx new file mode 100644 index 000000000000..dc4f15fc3ceb --- /dev/null +++ b/src/applications/mhv-supply-reordering/app-entry.jsx @@ -0,0 +1,15 @@ +import '@department-of-veterans-affairs/platform-polyfills'; +import './sass/mhv-supply-reordering.scss'; + +import { startAppFromIndex } from '@department-of-veterans-affairs/platform-startup/exports'; + +import routes from './routes'; +import reducer from './reducers'; +import manifest from './manifest.json'; + +startAppFromIndex({ + entryName: manifest.entryName, + url: manifest.rootUrl, + reducer, + routes, +}); diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js new file mode 100644 index 000000000000..002fcb2a77b4 --- /dev/null +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -0,0 +1,59 @@ +import environment from 'platform/utilities/environment'; +import footerContent from 'platform/forms/components/FormFooter'; +import { VA_FORM_IDS } from 'platform/forms/constants'; +import { TITLE, SUBTITLE } from '../constants'; +import manifest from '../manifest.json'; +import IntroductionPage from '../containers/IntroductionPage'; +import ConfirmationPage from '../containers/ConfirmationPage'; + +import nameAndDateOfBirth from '../pages/nameAndDateOfBirth'; + +/** @type {FormConfig} */ +const formConfig = { + rootUrl: manifest.rootUrl, + urlPrefix: '/', + submitUrl: `${environment.API_URL}/v0/mdot/supplies`, + submit: () => + Promise.resolve({ attributes: { confirmationNumber: '123123123' } }), + trackingPrefix: 'mhv-supply-reordering-', + introduction: IntroductionPage, + confirmation: ConfirmationPage, + dev: { + showNavLinks: true, + collapsibleNavLinks: true, + }, + formId: VA_FORM_IDS.FORM_VA_2346A, + saveInProgress: { + // messages: { + // inProgress: 'Your benefits application (MDOT) is in progress.', + // expired: 'Your saved benefits application (MDOT) has expired. If you want to apply for benefits, please start a new application.', + // saved: 'Your benefits application has been saved.', + // }, + }, + version: 0, + prefillEnabled: true, + savedFormMessages: { + notFound: 'Please start over to apply for benefits.', + noAuth: 'Please sign in again to continue your application for benefits.', + }, + title: TITLE, + subTitle: SUBTITLE, + defaultDefinitions: {}, + chapters: { + personalInformationChapter: { + title: 'Your personal information', + pages: { + nameAndDateOfBirth: { + path: 'name-and-date-of-birth', + title: 'Name and date of birth', + uiSchema: nameAndDateOfBirth.uiSchema, + schema: nameAndDateOfBirth.schema, + }, + }, + }, + }, + // getHelp, + footerContent, +}; + +export default formConfig; diff --git a/src/applications/mhv-supply-reordering/constants.js b/src/applications/mhv-supply-reordering/constants.js new file mode 100644 index 000000000000..0998bfb074c7 --- /dev/null +++ b/src/applications/mhv-supply-reordering/constants.js @@ -0,0 +1,2 @@ +export const TITLE = 'Order Medical Supplies'; +export const SUBTITLE = 'benefits (VA Form MDOT)'; diff --git a/src/applications/mhv-supply-reordering/containers/App.jsx b/src/applications/mhv-supply-reordering/containers/App.jsx new file mode 100644 index 000000000000..797306ee1e20 --- /dev/null +++ b/src/applications/mhv-supply-reordering/containers/App.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import RoutedSavableApp from 'platform/forms/save-in-progress/RoutedSavableApp'; +import formConfig from '../config/form'; + +export default function App({ location, children }) { + return ( + + {children} + + ); +} diff --git a/src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx b/src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx new file mode 100644 index 000000000000..5d5e0099643f --- /dev/null +++ b/src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx @@ -0,0 +1,100 @@ +import React, { useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; +import { format, isValid } from 'date-fns'; +import { useSelector } from 'react-redux'; +import { scrollTo, waitForRenderThenFocus } from 'platform/utilities/ui'; + +export const ConfirmationPage = () => { + const alertRef = useRef(null); + const form = useSelector(state => state.form || {}); + const { submission, formId, data = {} } = form; + const { fullName } = data; + const submitDate = submission?.timestamp; + const confirmationNumber = submission?.response?.confirmationNumber; + + useEffect( + () => { + if (alertRef?.current) { + scrollTo('topScrollElement'); + waitForRenderThenFocus('h2', alertRef.current); + } + }, + [alertRef], + ); + + return ( +
+
+ VA logo +
+ + +

Your application has been submitted

+
+ +

We may contact you for more information or documents.

+

Please print this page for your records.

+
+

+ Order Medical Supplies Claim{' '} + (Form {formId}) +

+ {fullName ? ( + + for {fullName.first} {fullName.middle} {fullName.last} + {fullName.suffix ? `, ${fullName.suffix}` : null} + + ) : null} + + {confirmationNumber ? ( + <> +

Confirmation number

+

{confirmationNumber}

+ + ) : null} + + {isValid(submitDate) ? ( +

+ Date submitted +
+ {format(submitDate, 'MMMM d, yyyy')} +

+ ) : null} + + +
+ + Go back to VA.gov + + + {/*
+

Need help?

+ +
*/} +
+ ); +}; + +ConfirmationPage.propTypes = { + form: PropTypes.shape({ + data: PropTypes.shape({ + fullName: { + first: PropTypes.string, + middle: PropTypes.string, + last: PropTypes.string, + suffix: PropTypes.string, + }, + }), + formId: PropTypes.string, + submission: PropTypes.shape({ + timestamp: PropTypes.string, + }), + }), + name: PropTypes.string, +}; + +export default ConfirmationPage; diff --git a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx new file mode 100644 index 000000000000..2e004f5cadd3 --- /dev/null +++ b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx @@ -0,0 +1,111 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { focusElement, scrollToTop } from 'platform/utilities/ui'; +import FormTitle from 'platform/forms-system/src/js/components/FormTitle'; +import SaveInProgressIntro from 'platform/forms/save-in-progress/SaveInProgressIntro'; +import { useSelector } from 'react-redux'; +import { isLOA3, isLoggedIn } from 'platform/user/selectors'; +import { TITLE, SUBTITLE } from '../constants'; + +const OMB_RES_BURDEN = 30; +const OMB_NUMBER = '2346A'; +const OMB_EXP_DATE = '12/31/2025'; + +const ProcessList = () => { + return ( + + +

To fill out this application, you’ll need your:

+
    +
  • Social Security number (required)
  • +
+

+ What if I need help filling out my application? An + accredited representative, like a Veterans Service Officer (VSO), can + help you fill out your claim.{' '} + + Get help filing your claim + +

+
+ +

Complete this benefits form.

+

+ After submitting the form, you’ll get a confirmation message. You can + print this for your records. +

+
+ +

+ We process claims within a week. If more than a week has passed since + you submitted your application and you haven’t heard back, please + don’t apply again. Call us at. +

+
+ +

+ Once we’ve processed your claim, you’ll get a notice in the mail with + our decision. +

+
+
+ ); +}; + +export const IntroductionPage = props => { + const userLoggedIn = useSelector(state => isLoggedIn(state)); + const userIdVerified = useSelector(state => isLOA3(state)); + const { route } = props; + const { formConfig, pageList } = route; + const showVerifyIdentify = userLoggedIn && !userIdVerified; + + useEffect(() => { + scrollToTop(); + focusElement('h1'); + }, []); + + return ( +
+ +

+ Follow the steps below to apply for benefits. +

+ + {showVerifyIdentify ? ( +
{/* add verify identity alert if applicable */}
+ ) : ( + + )} +

+ +

+ ); +}; + +IntroductionPage.propTypes = { + route: PropTypes.shape({ + formConfig: PropTypes.shape({ + prefillEnabled: PropTypes.bool.isRequired, + savedFormMessages: PropTypes.object.isRequired, + }).isRequired, + pageList: PropTypes.arrayOf(PropTypes.object).isRequired, + }).isRequired, + location: PropTypes.shape({ + basename: PropTypes.string, + }), +}; + +export default IntroductionPage; diff --git a/src/applications/mhv-supply-reordering/manifest.json b/src/applications/mhv-supply-reordering/manifest.json new file mode 100644 index 000000000000..cb9ba6357389 --- /dev/null +++ b/src/applications/mhv-supply-reordering/manifest.json @@ -0,0 +1,7 @@ +{ + "appName": "Order Medical Supplies", + "entryFile": "./app-entry.jsx", + "entryName": "mhv-supply-reordering", + "rootUrl": "/my-health/order-supplies", + "productId": "9f2025da-09d6-469f-8d6e-811e7afa7582" +} diff --git a/src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js b/src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js new file mode 100644 index 000000000000..90b487fb8a6e --- /dev/null +++ b/src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js @@ -0,0 +1,24 @@ +import { + dateOfBirthSchema, + dateOfBirthUI, + fullNameNoSuffixSchema, + fullNameNoSuffixUI, + titleUI, +} from 'platform/forms-system/src/js/web-component-patterns'; + +/** @type {PageSchema} */ +export default { + uiSchema: { + ...titleUI('Name and date of birth'), + fullName: fullNameNoSuffixUI(), + dateOfBirth: dateOfBirthUI(), + }, + schema: { + type: 'object', + properties: { + fullName: fullNameNoSuffixSchema, + dateOfBirth: dateOfBirthSchema, + }, + required: ['fullName', 'dateOfBirth'], + }, +}; diff --git a/src/applications/mhv-supply-reordering/reducers/index.js b/src/applications/mhv-supply-reordering/reducers/index.js new file mode 100644 index 000000000000..fa616ca0da4b --- /dev/null +++ b/src/applications/mhv-supply-reordering/reducers/index.js @@ -0,0 +1,6 @@ +import { createSaveInProgressFormReducer } from 'platform/forms/save-in-progress/reducers'; +import formConfig from '../config/form'; + +export default { + form: createSaveInProgressFormReducer(formConfig), +}; diff --git a/src/applications/mhv-supply-reordering/routes.jsx b/src/applications/mhv-supply-reordering/routes.jsx new file mode 100644 index 000000000000..8b56e82cbf9a --- /dev/null +++ b/src/applications/mhv-supply-reordering/routes.jsx @@ -0,0 +1,12 @@ +import { createRoutesWithSaveInProgress } from 'platform/forms/save-in-progress/helpers'; +import formConfig from './config/form'; +import App from './containers/App'; + +const route = { + path: '/', + component: App, + indexRoute: { onEnter: (nextState, replace) => replace('/introduction') }, + childRoutes: createRoutesWithSaveInProgress(formConfig), +}; + +export default route; diff --git a/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss b/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss new file mode 100644 index 000000000000..80433ce82d02 --- /dev/null +++ b/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss @@ -0,0 +1,6 @@ +@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-process-list"; +@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-form-process"; +@import "../../../platform/forms/sass/m-schemaform"; +@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-modal"; +@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-omb-info"; +@import "../../../platform/forms/sass/m-form-confirmation"; diff --git a/src/applications/mhv-supply-reordering/tests/containers/ConfirmationPage.unit.spec.jsx b/src/applications/mhv-supply-reordering/tests/containers/ConfirmationPage.unit.spec.jsx new file mode 100644 index 000000000000..5bfb439956cb --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/containers/ConfirmationPage.unit.spec.jsx @@ -0,0 +1,45 @@ +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: { + fullName: { + first: 'John', + middle: '', + last: 'Doe', + }, + }, + }, +}; + +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(/John Doe/); + }); +}); diff --git a/src/applications/mhv-supply-reordering/tests/containers/IntroductionPage.unit.spec.jsx b/src/applications/mhv-supply-reordering/tests/containers/IntroductionPage.unit.spec.jsx new file mode 100644 index 000000000000..96236de1f574 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/containers/IntroductionPage.unit.spec.jsx @@ -0,0 +1,68 @@ +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'; + +const props = { + route: { + path: 'introduction', + pageList: [], + formConfig, + }, + userLoggedIn: false, + userIdVerified: true, +}; + +const mockStore = { + getState: () => ({ + user: { + login: { + currentlyLoggedIn: false, + }, + profile: { + savedForms: [], + prefillsAvailable: [], + loa: { + current: 3, + highest: 3, + }, + verified: true, + dob: '2000-01-01', + claims: { + appeals: false, + }, + }, + }, + form: { + formId: formConfig.formId, + loadedStatus: 'success', + savedStatus: '', + loadedData: { + metadata: {}, + }, + data: {}, + }, + scheduledDowntime: { + globalDowntime: null, + isReady: true, + isPending: false, + serviceMap: { get() {} }, + dismissedDowntimeWarnings: [], + }, + }), + subscribe: () => {}, + dispatch: () => {}, +}; + +describe('IntroductionPage', () => { + it('should render', () => { + const { container } = render( + + + , + ); + expect(container).to.exist; + }); +}); diff --git a/src/applications/mhv-supply-reordering/tests/fixtures/data/minimal-test.json b/src/applications/mhv-supply-reordering/tests/fixtures/data/minimal-test.json new file mode 100644 index 000000000000..b57dee5b87cf --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/fixtures/data/minimal-test.json @@ -0,0 +1,9 @@ +{ + "data": { + "fullName": { + "first": "John", + "last": "Doe" + }, + "dateOfBirth": "1980-01-01" + } +} diff --git a/src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js new file mode 100644 index 000000000000..e6c6e27268da --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js @@ -0,0 +1,8 @@ +// yarn mock-api --responses ./src/applications/{application}/tests/e2e/fixtures/mocks/local-mock-responses.js +const mockUser = require('./user.json'); + +const responses = { + 'GET /v0/user': mockUser, +}; + +module.exports = responses; diff --git a/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json new file mode 100644 index 000000000000..c707324756e1 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json @@ -0,0 +1,56 @@ +{ + "data": { + "attributes": { + "profile": { + "sign_in": { + "service_name": "idme" + }, + "email": "john.doe@example.com", + "loa": { "current": 3 }, + "first_name": "John", + "middle_name": "", + "last_name": "Doe", + "gender": "M", + "birth_date": "1985-01-01", + "verified": true + }, + "veteran_status": { + "status": "OK", + "is_veteran": true, + "served_in_military": true + }, + "in_progress_forms": [], + "prefills_available": [], + "services": [ + "facilities", + "hca", + "edu-benefits", + "evss-claims", + "form526", + "user-profile", + "health-records", + "rx", + "messaging" + ], + "va_profile": { + "status": "OK", + "birth_date": "19850101", + "family_name": "Doe", + "gender": "M", + "given_names": ["John", ""], + "active_status": "active", + "facilities": [ + { + "facility_id": "983", + "is_cerner": false + }, + { + "facility_id": "984", + "is_cerner": false + } + ] + } + } + }, + "meta": { "errors": null } +} diff --git a/src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress.spec.js b/src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress.spec.js new file mode 100644 index 000000000000..906270a8aace --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress.spec.js @@ -0,0 +1,37 @@ +import path from 'path'; +import testForm from 'platform/testing/e2e/cypress/support/form-tester'; +import { createTestConfig } from 'platform/testing/e2e/cypress/support/form-tester/utilities'; +import mockUser from './fixtures/mocks/user.json'; +import formConfig from '../config/form'; +import manifest from '../manifest.json'; + +const testConfig = createTestConfig( + { + dataPrefix: 'data', + dataDir: path.join(__dirname, 'fixtures', 'data'), + dataSets: ['minimal-test'], + pageHooks: { + introduction: ({ afterHook }) => { + afterHook(() => { + cy.findAllByText(/^start/i, { selector: 'a[href="#start"]' }) + .last() + .click({ force: true }); + }); + }, + }, + + setupPerTest: () => { + cy.intercept('GET', '/v0/user', mockUser); + cy.intercept('POST', formConfig.submitUrl, { status: 200 }); + cy.login(mockUser); + }, + + // Skip tests in CI until the form is released. + // Remove this setting when the form has a content page in production. + skip: Cypress.env('CI'), + }, + manifest, + formConfig, +); + +testForm(testConfig); From e1ef873d7b5bf3cf7d06f4f8da6945b3d9727894 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Fri, 15 Nov 2024 17:40:39 -0500 Subject: [PATCH 02/28] Adds mocks --- .../mhv-supply-reordering/README.md | 80 +++++++++++ .../mocks/feature-toggles/index.js | 33 +++++ .../mocks/in-progress-forms/mdot/index.js | 134 ++++++++++++++++++ .../mhv-supply-reordering/mocks/index.js | 19 +++ .../mocks/maintenance-windows/index.js | 4 + .../mocks/mdot/supplies/index.js | 8 ++ .../mhv-supply-reordering/mocks/user/index.js | 68 +++++++++ 7 files changed, 346 insertions(+) create mode 100644 src/applications/mhv-supply-reordering/mocks/feature-toggles/index.js create mode 100644 src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js create mode 100644 src/applications/mhv-supply-reordering/mocks/index.js create mode 100644 src/applications/mhv-supply-reordering/mocks/maintenance-windows/index.js create mode 100644 src/applications/mhv-supply-reordering/mocks/mdot/supplies/index.js create mode 100644 src/applications/mhv-supply-reordering/mocks/user/index.js diff --git a/src/applications/mhv-supply-reordering/README.md b/src/applications/mhv-supply-reordering/README.md index 6c73f872b0c4..f2f6efd47f4a 100644 --- a/src/applications/mhv-supply-reordering/README.md +++ b/src/applications/mhv-supply-reordering/README.md @@ -1,6 +1,86 @@ # mhv-supply-reordering +## Background Info + +About: This app provides an interface to re-order Hearing Aid and Sleep Apnea accessories +Slack Channel: [#va-cto-supply-reordering](https://dsva.slack.com/archives/C05DFSM57FW/p1689711688225089) + +## App + Form app generated with `yarn app:new`. Changes to the following files were reverted, since `VA_FORM_IDS.FORM_VA_2346A` already exists. - `src/platform/forms/constants.js` - `src/platform/forms/tests/forms.unit.spec.js` + +## Quick start to get running locally + +Before you get started check [this page](https://depo-platform-documentation.scrollhelp.site/developer-docs/setting-up-your-local-frontend-environment) first to make sure you are setup to use the correct version of Node and Yarn. + +- clone vets-website repo `git clone git@github.com:department-of-veterans-affairs/vets-website.git` +- run `yarn install` +- turn on local mocks `yarn mock-api --responses src/applications/mhv-supply-reordering/mocks/index.js` +- start app `yarn watch --env entry=mhv-supply-reordering` +- Run this in your browser console to simulate being logged in `localStorage.setItem('hasSession', true);` +- visit the app: `http://localhost:3001/my-health/order-supplies` + +Note: The application fetches supply data from `/v0/in_progress_forms/mdot`. This endpoint is mocked in the local development environment. + +## Running tests + +Unit tests can be run using this command: `yarn test:unit --app-folder mhv-supply-reordering`. To get detailed errors, run this command with `--log-level=error`. To get coverage reports run this command `yarn test:unit --app-folder mhv-supply-reordering --coverage --coverage-html`. View the report at `/coverage/index.html` + +Cypress tests can be run with the GUI using this command: `yarn cy:open`. From there you can filter by `mhv-supply-reordering` to run end to end tests for this app. + +Run Cypress from command line: + +- Run all `yarn cy:run --spec "src/applications/mhv-supply-reordering/**/**/*"` +- Specify browser `-b electron` + +## VA Forms - Web Component Fields and Patterns + +[[docs](https://depo-platform-documentation.scrollhelp.site/developer-docs/va-forms-library-web-component-fields-and-patterns)] + +[[examples](https://staging.va.gov/mock-form-patterns/introduction)] + +A web-component-field is a design system web component for use in forms. These can be found at `src/platform/forms-system/src/js/web-component-fields`. + +A web-component-pattern is a group of web-component-fields that can span one or more pages (e.g. - a multi-page form). These can be found at `src/platform/forms-system/src/js/web-component-patterns`. + +## Form Flow + +- IntroductionPage +- SelectSupplies +- ContactInformation (optionally: EditEmail, EditAddress) +- ReviewPage +- ConfirmationPage + +## API Responses + +[[mocker-api](https://github.com/jaywcjlove/mocker-api/tree/v2.9.0?tab=readme-ov-file#usage)] + +[[vets-api OpenAPI documentation](https://department-of-veterans-affairs.github.io/va-digital-services-platform-docs/api-reference/#/in_progress_forms)] + +`GET /v0/in_progress_forms/MDOT` returns the following... (note the lack of a `data` property) + +```json +{ + "formData": { + "fullName": {}, + "permanentAddress": {}, + "temporaryAddress": {}, + "ssnLastFour": "", + "gender": "", + "vetEmail": "", + "dateOfBirth": "", + "eligibility": {}, + "supplies": [] + }, + "metadata": { + "version": 0, + "prefill": true, + "returnUrl": "" + } +} +``` + +When requesting `GET /v0/in_progress_forms/MDOT`, the MDOT client in vets-api will make a request to the system of record for veteran details and supplies available to the veteran. See `V0::InProgressFormsController.camelized_prefill_for_user` and `FormProfiles::MDOT#prefill`. Test against the possible responses for `MDOT::Client.new(user).get_supplies` as these are mapped to `mdot.exceptions` values in `vets-api/config/locales/exceptions.en.yml` and then passed along in the response. Also, see `vets-api/spec/support/vcr_cassettes/mdot/get_supplies*.yml`. diff --git a/src/applications/mhv-supply-reordering/mocks/feature-toggles/index.js b/src/applications/mhv-supply-reordering/mocks/feature-toggles/index.js new file mode 100644 index 000000000000..32e3a43c565e --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/feature-toggles/index.js @@ -0,0 +1,33 @@ +const { snakeCase } = require('lodash'); + +const APPLICATION_FEATURE_TOGGLES = Object.freeze({ + mhvVaHealthChatEnabled: false, + mhvLandingPagePersonalization: false, + mhvIntegrationMedicalRecordsToPhase1: false, + travelPayPowerSwitch: false, +}); + +const generateFeatureToggles = ({ + toggles = APPLICATION_FEATURE_TOGGLES, +} = {}) => { + const snakeCaseToggles = Object.entries(toggles).map(([key, value]) => ({ + name: key, + value, + })); + + const camelCaseToggles = Object.entries(toggles).map(([key, value]) => ({ + name: snakeCase(key), + value, + })); + + return { + data: { + type: 'feature_toggles', + features: [...snakeCaseToggles, ...camelCaseToggles], + }, + }; +}; + +module.exports = { + 'GET /v0/feature_toggles': generateFeatureToggles(), +}; diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js new file mode 100644 index 000000000000..b33770f77b19 --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js @@ -0,0 +1,134 @@ +const getOk = { + formData: { + fullName: { + first: 'Greg', + middle: 'A', + last: 'Anderson', + }, + permanentAddress: { + street: '101 EXAMPLE STREET', + street2: 'APT 2', + city: 'KANSAS CITY', + state: 'MO', + country: 'UNITED STATES', + postalCode: '64117', + }, + temporaryAddress: { + street: 'PSC 1234 BOX 12345', + street2: ', ', + city: 'APO', + state: 'AE', + country: 'ARMED FORCES AF,EU,ME,CA', + postalCode: '09324', + }, + ssnLastFour: '1200', + gender: 'M', + vetEmail: 'vets.gov.user+1@gmail.com', + dateOfBirth: '1933-04-05', + eligibility: { + accessories: true, + apneas: true, + batteries: true, + }, + supplies: [ + { + productName: 'ERHK HE11 680 MINI', + productGroup: 'Accessory', + productId: 6584, + availableForReorder: true, + lastOrderDate: '2022-05-16', + nextAvailabilityDate: '2022-10-16', + quantity: 5, + }, + { + productName: 'AIRFIT F10 M', + productGroup: 'Apnea', + productId: 6641, + availableForReorder: true, + lastOrderDate: '2022-07-05', + nextAvailabilityDate: '2022-12-05', + quantity: 1, + }, + { + productName: 'AIRFIT P10', + productGroup: 'Apnea', + productId: 6650, + availableForReorder: true, + lastOrderDate: '2022-07-05', + nextAvailabilityDate: '2022-12-05', + quantity: 1, + }, + { + productName: 'AIRCURVE10-ASV-CLIMATELINE', + productGroup: 'Apnea', + productId: 8467, + lastOrderDate: '2022-07-06', + nextAvailabilityDate: '2022-12-06', + quantity: 1, + }, + ], + }, + metadata: { + version: 0, + prefill: true, + returnUrl: '/veteran-information', + }, +}; + +// eslint-disable-next-line no-unused-vars +const unauthenticated = { + errors: [ + { + title: 'Not authorized', + detail: 'Not authorized', + code: '401', + status: '401', + }, + ], +}; + +// eslint-disable-next-line no-unused-vars +const internalServerError = { + errors: [ + { + title: 'Internal server error', + detail: 'Internal server error', + code: '500', + status: '500', + }, + ], +}; + +// eslint-disable-next-line no-unused-vars +const notFound = { + errors: [ + { + title: 'Veteran Not Found', + detail: 'The veteran could not be found', + code: 'MDOT_invalid', + source: 'MDOT::Client', + status: '404', + }, + ], +}; + +const putOk = { + data: { + id: '12345', + type: 'in_progress_forms', + attributes: { + formId: 'MDOT', + createdAt: '', + updatedAt: '', + metadata: {}, + }, + }, +}; + +module.exports = { + // `GET /v0/in_progress_forms/${VA_FORM_IDS.FORM_VA_2346A}` + 'GET /v0/in_progress_forms/MDOT': getOk, + // 'GET /v0/in_progress_forms/MDOT': (_, res) => res.status(404).json(notFound), + // 'GET /v0/in_progress_forms/MDOT': (_, res) => res.status(500).json(internalServerError), + 'PUT /v0/in_progress_forms/MDOT': putOk, +}; diff --git a/src/applications/mhv-supply-reordering/mocks/index.js b/src/applications/mhv-supply-reordering/mocks/index.js new file mode 100644 index 000000000000..a94019766b5e --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/index.js @@ -0,0 +1,19 @@ +// eslint-disable-next-line no-unused-vars +const delay = require('mocker-api/lib/delay'); + +const featureToggles = require('./feature-toggles'); +const inProgressFormsMdot = require('./in-progress-forms/mdot'); +const maintenanceWindows = require('./maintenance-windows'); +const mdotSupplies = require('./mdot/supplies'); +const user = require('./user'); + +const mockApiResponses = { + ...featureToggles, + ...inProgressFormsMdot, + ...maintenanceWindows, + ...mdotSupplies, + ...user, +}; + +// module.exports = mockApiResponses; +module.exports = delay(mockApiResponses, 1000); diff --git a/src/applications/mhv-supply-reordering/mocks/maintenance-windows/index.js b/src/applications/mhv-supply-reordering/mocks/maintenance-windows/index.js new file mode 100644 index 000000000000..e81bc0bc3a80 --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/maintenance-windows/index.js @@ -0,0 +1,4 @@ +module.exports = { + 'GET /v0/maintenance_windows': { data: [] }, + 'OPTIONS /v0/maintenance_windows': 'OK', +}; diff --git a/src/applications/mhv-supply-reordering/mocks/mdot/supplies/index.js b/src/applications/mhv-supply-reordering/mocks/mdot/supplies/index.js new file mode 100644 index 000000000000..7b410a2596ef --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/mdot/supplies/index.js @@ -0,0 +1,8 @@ +const ok = { + status: 'Order Processed', + orderId: '12345', +}; + +module.exports = { + 'POST /v0/mdot/supplies': ok, +}; diff --git a/src/applications/mhv-supply-reordering/mocks/user/index.js b/src/applications/mhv-supply-reordering/mocks/user/index.js new file mode 100644 index 000000000000..37196982358c --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/user/index.js @@ -0,0 +1,68 @@ +/* eslint-disable camelcase */ +const ok = { + data: { + attributes: { + profile: { + sign_in: { + service_name: 'idme', + auth_broker: 'iam', + ssoe: true, + }, + email: 'vets.gov.user+1@gmail.com', + loa: { current: 3 }, + first_name: 'Greg', + middle_name: '', + last_name: 'Anderson', + gender: 'F', + birth_date: '1933-04-05', + verified: true, + }, + session: { + auth_broker: 'iam', + ssoe: true, + transactionid: 'sf8mUOpuAoxkx8uWxI6yrBAS/t0yrsjDKqktFz255P0=', + }, + veteran_status: { + status: 'OK', + is_veteran: true, + served_in_military: true, + }, + in_progress_forms: [], + prefills_available: ['21-526EZ', 'MDOT'], + services: [ + 'facilities', + 'hca', + 'edu-benefits', + 'evss-claims', + 'form526', + 'user-profile', + 'health-records', + 'rx', + 'messaging', + ], + va_profile: { + status: 'OK', + birth_date: '19330405', + family_name: 'Anderson', + gender: 'M', + given_names: ['Greg', ''], + active_status: 'active', + facilities: [ + { + facility_id: '983', + is_cerner: false, + }, + { + facility_id: '984', + is_cerner: false, + }, + ], + }, + }, + }, + meta: { errors: null }, +}; + +module.exports = { + 'GET /v0/user': ok, +}; From 2fc3d8d678441e335a3db117b84297801569f5cf Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Fri, 15 Nov 2024 18:11:54 -0500 Subject: [PATCH 03/28] Additions --- .../components/DlcEmailLink.jsx | 6 +++ .../components/DlcTelephoneLink.jsx | 12 +++++ .../mhv-supply-reordering/components/Help.jsx | 24 ++++++++++ .../components/UnsavedFieldNote.jsx | 21 ++++++++ .../components/VerifiedPrefillAlert.jsx | 12 +++++ .../mhv-supply-reordering/config/form.js | 48 +++++++++++-------- .../mhv-supply-reordering/constants.js | 5 ++ 7 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/components/DlcEmailLink.jsx create mode 100644 src/applications/mhv-supply-reordering/components/DlcTelephoneLink.jsx create mode 100644 src/applications/mhv-supply-reordering/components/Help.jsx create mode 100644 src/applications/mhv-supply-reordering/components/UnsavedFieldNote.jsx create mode 100644 src/applications/mhv-supply-reordering/components/VerifiedPrefillAlert.jsx diff --git a/src/applications/mhv-supply-reordering/components/DlcEmailLink.jsx b/src/applications/mhv-supply-reordering/components/DlcEmailLink.jsx new file mode 100644 index 000000000000..13a4f15ae411 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/DlcEmailLink.jsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { DLC_EMAIL } from '../constants'; + +const DlcEmailLink = () => {DLC_EMAIL}; + +export default DlcEmailLink; diff --git a/src/applications/mhv-supply-reordering/components/DlcTelephoneLink.jsx b/src/applications/mhv-supply-reordering/components/DlcTelephoneLink.jsx new file mode 100644 index 000000000000..d4dc96a88af8 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/DlcTelephoneLink.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts'; +import { DLC_TELEPHONE } from '../constants'; + +const DlcTelephoneLink = () => ( + <> + ( + ) + +); + +export default DlcTelephoneLink; diff --git a/src/applications/mhv-supply-reordering/components/Help.jsx b/src/applications/mhv-supply-reordering/components/Help.jsx new file mode 100644 index 000000000000..cb2e1dad81f7 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/Help.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import DlcTelephoneLink from './DlcTelephoneLink'; +import { HEALTH_FACILITIES_URL } from '../constants'; + +const Help = () => { + return ( + <> +

+ If you have trouble using your supplies,{' '} + + find the phone number for your local VA health facility + + . +

+

+ If you have questions about your supplies, call our VA + Denver Logistics Center at . We’re here Monday + through Friday, 8:15 a.m. to 5:00 p.m. ET. +

+ + ); +}; + +export default Help; diff --git a/src/applications/mhv-supply-reordering/components/UnsavedFieldNote.jsx b/src/applications/mhv-supply-reordering/components/UnsavedFieldNote.jsx new file mode 100644 index 000000000000..c24d5005d4a2 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/UnsavedFieldNote.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DlcTelephoneLink from './DlcTelephoneLink'; + +const UnsavedFieldNote = ({ fieldName }) => ( +

+ Note: Any updates you make to your {fieldName} will only apply to this + order. If you’d like to update for all future orders, you can either call us + at or change in your{' '} + + VA.gov profile + + . +

+); + +UnsavedFieldNote.propTypes = { + fieldName: PropTypes.string.isRequired, +}; + +export default UnsavedFieldNote; diff --git a/src/applications/mhv-supply-reordering/components/VerifiedPrefillAlert.jsx b/src/applications/mhv-supply-reordering/components/VerifiedPrefillAlert.jsx new file mode 100644 index 000000000000..8337f4832679 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/VerifiedPrefillAlert.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const VerifiedPrefillAlert = ( + +
+ Since you’re signed in to your account, you can save your order in + progress and come back later to finish filling it out. +
+
+); + +export default VerifiedPrefillAlert; diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 002fcb2a77b4..f5d81fc2e774 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -1,12 +1,31 @@ import environment from 'platform/utilities/environment'; import footerContent from 'platform/forms/components/FormFooter'; import { VA_FORM_IDS } from 'platform/forms/constants'; -import { TITLE, SUBTITLE } from '../constants'; +import { TITLE as title, SUBTITLE as subTitle } from '../constants'; import manifest from '../manifest.json'; -import IntroductionPage from '../containers/IntroductionPage'; -import ConfirmationPage from '../containers/ConfirmationPage'; +import introduction from '../containers/IntroductionPage'; +import confirmation from '../containers/ConfirmationPage'; import nameAndDateOfBirth from '../pages/nameAndDateOfBirth'; +import getHelp from '../components/Help'; + +const blankSchema = { type: 'object', properties: {} }; + +const savedFormMessages = { + notFound: 'Please start over to reorder health care supplies.', + noAuth: + 'Please sign in again to continue your application for health care supply reordering.', +}; + +const saveInProgress = { + messages: { + inProgress: + 'Your health care supply reordering application (2346) is in progress.', + expired: + 'Your saved health care supply reordering application (2346) has expired. If you want to reorder supplies, please start a new application.', + saved: 'Your health care supply reordering application has been saved.', + }, +}; /** @type {FormConfig} */ const formConfig = { @@ -16,28 +35,19 @@ const formConfig = { submit: () => Promise.resolve({ attributes: { confirmationNumber: '123123123' } }), trackingPrefix: 'mhv-supply-reordering-', - introduction: IntroductionPage, - confirmation: ConfirmationPage, + introduction, + confirmation, dev: { showNavLinks: true, collapsibleNavLinks: true, }, formId: VA_FORM_IDS.FORM_VA_2346A, - saveInProgress: { - // messages: { - // inProgress: 'Your benefits application (MDOT) is in progress.', - // expired: 'Your saved benefits application (MDOT) has expired. If you want to apply for benefits, please start a new application.', - // saved: 'Your benefits application has been saved.', - // }, - }, + savedFormMessages, + saveInProgress, version: 0, prefillEnabled: true, - savedFormMessages: { - notFound: 'Please start over to apply for benefits.', - noAuth: 'Please sign in again to continue your application for benefits.', - }, - title: TITLE, - subTitle: SUBTITLE, + title, + subTitle, defaultDefinitions: {}, chapters: { personalInformationChapter: { @@ -52,7 +62,7 @@ const formConfig = { }, }, }, - // getHelp, + getHelp, footerContent, }; diff --git a/src/applications/mhv-supply-reordering/constants.js b/src/applications/mhv-supply-reordering/constants.js index 0998bfb074c7..a84474cb2fb5 100644 --- a/src/applications/mhv-supply-reordering/constants.js +++ b/src/applications/mhv-supply-reordering/constants.js @@ -1,2 +1,7 @@ export const TITLE = 'Order Medical Supplies'; export const SUBTITLE = 'benefits (VA Form MDOT)'; + +export const DLC_EMAIL = 'dalc.css@va.gov'; +export const DLC_TELEPHONE = '3032736200'; + +export const HEALTH_FACILITIES_URL = '/find-locations/?facilityType=health'; From 6f0f61d934ec9be9d3f09d4211cafd8e7ac00f58 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 18 Nov 2024 15:39:53 -0500 Subject: [PATCH 04/28] Adds alerts --- .../mhv-supply-reordering/README.md | 2 +- .../mhv-supply-reordering/actions/index.js | 13 +++ .../actions/mdotInProgressForm.js | 35 +++++++ .../components/alerts/AlertDeceased.jsx | 19 ++++ .../alerts/AlertNoRecordForUser.jsx | 29 ++++++ .../alerts/AlertNoSuppliesForReorder.jsx | 38 ++++++++ .../alerts/AlertReorderAccessExpired.jsx | 25 +++++ .../alerts/AlertSomethingWentWrong.jsx | 25 +++++ .../components/alerts/index.js | 13 +++ .../mhv-supply-reordering/config/form.js | 23 ++++- .../config/prefillTransformer.js | 10 ++ .../mhv-supply-reordering/constants.js | 9 +- .../containers/Alerts.jsx | 58 ++++++++++++ .../mhv-supply-reordering/containers/App.jsx | 52 ++++++++++- .../containers/IntroductionPage.jsx | 92 +++++++------------ .../mocks/in-progress-forms/mdot/index.js | 2 +- .../mhv-supply-reordering/mocks/index.js | 3 +- .../mhv-supply-reordering/reducers/index.js | 2 + .../reducers/mdotInProgressFormReducer.js | 30 ++++++ .../mhv-supply-reordering/selectors/index.js | 42 +++++++++ .../tests/containers/Alerts.unit.spec.jsx | 73 +++++++++++++++ 21 files changed, 524 insertions(+), 71 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/actions/index.js create mode 100644 src/applications/mhv-supply-reordering/actions/mdotInProgressForm.js create mode 100644 src/applications/mhv-supply-reordering/components/alerts/AlertDeceased.jsx create mode 100644 src/applications/mhv-supply-reordering/components/alerts/AlertNoRecordForUser.jsx create mode 100644 src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx create mode 100644 src/applications/mhv-supply-reordering/components/alerts/AlertReorderAccessExpired.jsx create mode 100644 src/applications/mhv-supply-reordering/components/alerts/AlertSomethingWentWrong.jsx create mode 100644 src/applications/mhv-supply-reordering/components/alerts/index.js create mode 100644 src/applications/mhv-supply-reordering/config/prefillTransformer.js create mode 100644 src/applications/mhv-supply-reordering/containers/Alerts.jsx create mode 100644 src/applications/mhv-supply-reordering/reducers/mdotInProgressFormReducer.js create mode 100644 src/applications/mhv-supply-reordering/selectors/index.js create mode 100644 src/applications/mhv-supply-reordering/tests/containers/Alerts.unit.spec.jsx diff --git a/src/applications/mhv-supply-reordering/README.md b/src/applications/mhv-supply-reordering/README.md index f2f6efd47f4a..ab8a80963aa6 100644 --- a/src/applications/mhv-supply-reordering/README.md +++ b/src/applications/mhv-supply-reordering/README.md @@ -83,4 +83,4 @@ A web-component-pattern is a group of web-component-fields that can span one or } ``` -When requesting `GET /v0/in_progress_forms/MDOT`, the MDOT client in vets-api will make a request to the system of record for veteran details and supplies available to the veteran. See `V0::InProgressFormsController.camelized_prefill_for_user` and `FormProfiles::MDOT#prefill`. Test against the possible responses for `MDOT::Client.new(user).get_supplies` as these are mapped to `mdot.exceptions` values in `vets-api/config/locales/exceptions.en.yml` and then passed along in the response. Also, see `vets-api/spec/support/vcr_cassettes/mdot/get_supplies*.yml`. +When requesting `GET /v0/in_progress_forms/MDOT`, the MDOT client in vets-api will make a request to the system of record for veteran details and supplies available to the veteran. See `V0::InProgressFormsController.camelized_prefill_for_user` and `FormProfiles::MDOT#prefill`. On the front-end, test against the possible responses for `MDOT::Client.new(user).get_supplies` which are mapped to `mdot.exceptions` values in `vets-api/config/locales/exceptions.en.yml` and then passed along in the response. Also, see `vets-api/spec/support/vcr_cassettes/mdot/get_supplies*.yml`. diff --git a/src/applications/mhv-supply-reordering/actions/index.js b/src/applications/mhv-supply-reordering/actions/index.js new file mode 100644 index 000000000000..6dd774350f49 --- /dev/null +++ b/src/applications/mhv-supply-reordering/actions/index.js @@ -0,0 +1,13 @@ +import { + GET_MDOT_IN_PROGRESS_FORM_STARTED, + GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED, + GET_MDOT_IN_PROGRESS_FORM_FAILED, + getMdotInProgressForm, +} from './mdotInProgressForm'; + +export { + GET_MDOT_IN_PROGRESS_FORM_STARTED, + GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED, + GET_MDOT_IN_PROGRESS_FORM_FAILED, + getMdotInProgressForm, +}; diff --git a/src/applications/mhv-supply-reordering/actions/mdotInProgressForm.js b/src/applications/mhv-supply-reordering/actions/mdotInProgressForm.js new file mode 100644 index 000000000000..1d72132d7935 --- /dev/null +++ b/src/applications/mhv-supply-reordering/actions/mdotInProgressForm.js @@ -0,0 +1,35 @@ +import { apiRequest } from 'platform/utilities/api'; + +export const GET_MDOT_IN_PROGRESS_FORM_STARTED = + 'GET_MDOT_IN_PROGRESS_FORM_STARTED'; +export const GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED = + 'GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED'; +export const GET_MDOT_IN_PROGRESS_FORM_FAILED = + 'GET_MDOT_IN_PROGRESS_FORM_FAILED'; + +const getMdotInProgressFormStarted = () => ({ + type: GET_MDOT_IN_PROGRESS_FORM_STARTED, +}); + +const getMdotInProgressFormSucceeded = payload => ({ + type: GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED, + payload, +}); + +const getMdotInProgressFormFailed = payload => ({ + type: GET_MDOT_IN_PROGRESS_FORM_FAILED, + payload, +}); + +const MDOT_IN_PROGRESS_FORM_PATH = '/in_progress_forms/MDOT'; + +export const getMdotInProgressForm = () => async dispatch => { + await dispatch(getMdotInProgressFormStarted()); + return apiRequest(MDOT_IN_PROGRESS_FORM_PATH) + .then(payload => { + return dispatch(getMdotInProgressFormSucceeded(payload)); + }) + .catch(err => { + return dispatch(getMdotInProgressFormFailed(err)); + }); +}; diff --git a/src/applications/mhv-supply-reordering/components/alerts/AlertDeceased.jsx b/src/applications/mhv-supply-reordering/components/alerts/AlertDeceased.jsx new file mode 100644 index 000000000000..1b0014a16197 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/alerts/AlertDeceased.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts'; + +const AlertDeceased = () => ( + +

Our records show that this Veteran is deceased

+
+ We can’t fulfill an order for this Veteran + + If this information is incorrect, please call Veterans Benefits + Assistance at + , Monday through Friday, + 8:00 a.m. to 9:00 p.m. E.T. + +
+
+); + +export default AlertDeceased; diff --git a/src/applications/mhv-supply-reordering/components/alerts/AlertNoRecordForUser.jsx b/src/applications/mhv-supply-reordering/components/alerts/AlertNoRecordForUser.jsx new file mode 100644 index 000000000000..a1e647cb074a --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/alerts/AlertNoRecordForUser.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { HEALTH_FACILITIES_URL } from '../../constants'; + +const AlertNoRecordForUser = () => ( + +

We can’t find your records in our system

+
+ + You can’t order hearing aid or CPAP supplies at this time because we + find your records in our system or we’re missing some information needed + for you to order. + + + + If you think this is incorrect, call your health care provider to update + your record.{' '} + + Find contact information for your local medical center. + + +
+
+); + +export default AlertNoRecordForUser; diff --git a/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx b/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx new file mode 100644 index 000000000000..660c64a917bc --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { format } from 'date-fns'; +import DlcEmailLink from '../DlcEmailLink'; +import DlcTelephoneLink from '../DlcTelephoneLink'; + +/** + * Generates an alert for a veteran that is not eligible to order supplies. + * @param {string} reorderDate the date supplies will be available to reorder + * @returns the alert + */ +const AlertNoSuppliesForReorder = ({ reorderDate }) => { + const formattedDate = format(new Date(reorderDate), 'MMMM d, yyyy'); + return ( + +

You can’t reorder your items at this time

+
+ + Our records show that your items aren’t available for reorder until{' '} + {formattedDate}. You can only order items once every 5 months. + + + If you need an item sooner, call the DLC Customer Service Section at{' '} + or email . + +
+
+ ); +}; + +AlertNoSuppliesForReorder.propTypes = { + reorderDate: PropTypes.string.isRequired, +}; + +export default AlertNoSuppliesForReorder; diff --git a/src/applications/mhv-supply-reordering/components/alerts/AlertReorderAccessExpired.jsx b/src/applications/mhv-supply-reordering/components/alerts/AlertReorderAccessExpired.jsx new file mode 100644 index 000000000000..985119dd6d95 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/alerts/AlertReorderAccessExpired.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import DlcEmailLink from '../DlcEmailLink'; +import DlcTelephoneLink from '../DlcTelephoneLink'; + +const AlertReorderAccessExpired = () => ( + +

You can’t reorder your items at this time

+
+ + You can’t order hearing aid or CPAP supplies online at this time because + you haven’t placed an order within the past two years. + + + + If you need to place an order, call the DLC Customer Service Section at{' '} + or email . + +
+
+); + +export default AlertReorderAccessExpired; diff --git a/src/applications/mhv-supply-reordering/components/alerts/AlertSomethingWentWrong.jsx b/src/applications/mhv-supply-reordering/components/alerts/AlertSomethingWentWrong.jsx new file mode 100644 index 000000000000..3a551fb1da40 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/alerts/AlertSomethingWentWrong.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import DlcEmailLink from '../DlcEmailLink'; +import DlcTelephoneLink from '../DlcTelephoneLink'; + +const AlertSomethingWentWrong = () => ( + +

We’re sorry. Something went wrong on our end.

+
+

+ You can’t place an order for hearing aid or CPAP supplies because + something went wrong on our end. +

+

+ What you can do +

+

+ For help ordering hearing aid or CPAP supplies, please call the DLC + Customer Service Section at or email{' '} + . +

+
+
+); + +export default AlertSomethingWentWrong; diff --git a/src/applications/mhv-supply-reordering/components/alerts/index.js b/src/applications/mhv-supply-reordering/components/alerts/index.js new file mode 100644 index 000000000000..3b9c9ebd51e8 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/alerts/index.js @@ -0,0 +1,13 @@ +import AlertDeceased from './AlertDeceased'; +import AlertNoRecordForUser from './AlertNoRecordForUser'; +import AlertNoSuppliesForReorder from './AlertNoSuppliesForReorder'; +import AlertReorderAccessExpired from './AlertReorderAccessExpired'; +import AlertSomethingWentWrong from './AlertSomethingWentWrong'; + +export { + AlertDeceased, + AlertNoRecordForUser, + AlertNoSuppliesForReorder, + AlertReorderAccessExpired, + AlertSomethingWentWrong, +}; diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index f5d81fc2e774..576a3057c9d1 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -9,7 +9,9 @@ import confirmation from '../containers/ConfirmationPage'; import nameAndDateOfBirth from '../pages/nameAndDateOfBirth'; import getHelp from '../components/Help'; -const blankSchema = { type: 'object', properties: {} }; +import prefillTransformer from './prefillTransformer'; + +const blankSchema = { type: 'object', properties: {} }; // eslint-disable-line no-unused-vars const savedFormMessages = { notFound: 'Please start over to reorder health care supplies.', @@ -27,6 +29,22 @@ const saveInProgress = { }, }; +const customText = { + // appSavedSuccessfullyMessage: '', + appType: 'order', + // continueAppButtonText: '', + // reviewPageTitle: '', + // startNewAppButtonText: '', + // submitButtonText: '', +}; + +// const formOptions = { +// noTitle: true, +// noTopNav: true, +// noBottomNav: true, +// fullWidth: true, +// }; + /** @type {FormConfig} */ const formConfig = { rootUrl: manifest.rootUrl, @@ -42,12 +60,15 @@ const formConfig = { collapsibleNavLinks: true, }, formId: VA_FORM_IDS.FORM_VA_2346A, + // formOptions, savedFormMessages, saveInProgress, version: 0, prefillEnabled: true, + prefillTransformer, title, subTitle, + customText, defaultDefinitions: {}, chapters: { personalInformationChapter: { diff --git a/src/applications/mhv-supply-reordering/config/prefillTransformer.js b/src/applications/mhv-supply-reordering/config/prefillTransformer.js new file mode 100644 index 000000000000..37629222b34e --- /dev/null +++ b/src/applications/mhv-supply-reordering/config/prefillTransformer.js @@ -0,0 +1,10 @@ +const prefillTransformer = (pages, formData, metadata) => ({ + pages, + formData: { + ...formData, + emailAddress: formData?.vetEmail || formData?.emailAddress, + }, + metadata, +}); + +export default prefillTransformer; diff --git a/src/applications/mhv-supply-reordering/constants.js b/src/applications/mhv-supply-reordering/constants.js index a84474cb2fb5..04993f7d2155 100644 --- a/src/applications/mhv-supply-reordering/constants.js +++ b/src/applications/mhv-supply-reordering/constants.js @@ -1,7 +1,14 @@ export const TITLE = 'Order Medical Supplies'; -export const SUBTITLE = 'benefits (VA Form MDOT)'; +export const SUBTITLE = + 'Use this form to order hearing aid batteries and CPAP supplies'; export const DLC_EMAIL = 'dalc.css@va.gov'; export const DLC_TELEPHONE = '3032736200'; export const HEALTH_FACILITIES_URL = '/find-locations/?facilityType=health'; + +export const MDOT_ERROR_CODES = { + DECEASED: 'MDOT_deceased', + INVALID: 'MDOT_invalid', + SUPPLIES_NOT_FOUND: 'MDOT_supplies_not_found', +}; diff --git a/src/applications/mhv-supply-reordering/containers/Alerts.jsx b/src/applications/mhv-supply-reordering/containers/Alerts.jsx new file mode 100644 index 000000000000..a2fe7474735d --- /dev/null +++ b/src/applications/mhv-supply-reordering/containers/Alerts.jsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; + +import { + AlertDeceased, + AlertNoRecordForUser, + AlertNoSuppliesForReorder, + AlertReorderAccessExpired, + AlertSomethingWentWrong, +} from '../components/alerts'; + +import { + canReorderOn, + showAlertDeceased, + showAlertNoRecordForUser, + showAlertNoSuppliesForReorder, + showAlertReorderAccessExpired, + showAlertSomethingWentWrong, +} from '../selectors'; + +const Alerts = () => { + const reorderDate = useSelector(canReorderOn); + const renderAlertDeceased = useSelector(showAlertDeceased); + const renderAlertNoRecordForUser = useSelector(showAlertNoRecordForUser); + const renderAlertNoSuppliesForReorder = useSelector( + showAlertNoSuppliesForReorder, + ); + const renderAlertReorderAccessExpired = useSelector( + showAlertReorderAccessExpired, + ); + const renderAlertSomethingWentWrong = useSelector( + showAlertSomethingWentWrong, + ); + + if (renderAlertDeceased) { + return ; + } + + if (renderAlertNoRecordForUser) { + return ; + } + + if (renderAlertReorderAccessExpired) { + return ; + } + + if (renderAlertSomethingWentWrong) { + return ; + } + + if (renderAlertNoSuppliesForReorder) { + return ; + } + + return null; +}; + +export default Alerts; diff --git a/src/applications/mhv-supply-reordering/containers/App.jsx b/src/applications/mhv-supply-reordering/containers/App.jsx index 797306ee1e20..2811ac8c94c8 100644 --- a/src/applications/mhv-supply-reordering/containers/App.jsx +++ b/src/applications/mhv-supply-reordering/containers/App.jsx @@ -1,12 +1,54 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import { VaBreadcrumbs } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; +import { RequiredLoginView } from '@department-of-veterans-affairs/platform-user/RequiredLoginView'; +import backendServices from '@department-of-veterans-affairs/platform-user/profile/backendServices'; import RoutedSavableApp from 'platform/forms/save-in-progress/RoutedSavableApp'; + import formConfig from '../config/form'; +import manifest from '../manifest.json'; +import { signInServiceEnabled } from '../selectors'; + +const breadcrumbList = [ + { + href: '/', + label: 'VA.gov Home', + }, + { + href: '/my-health', + label: 'Health care', + }, + { + href: manifest.rootUrl, + label: manifest.appName, + }, +]; + +const serviceRequired = [ + // backendServices.FACILITIES, + backendServices.FORM_PREFILL, + // backendServices.IDENTITY_PROOFED, + backendServices.SAVE_IN_PROGRESS, + backendServices.USER_PROFILE, +]; + +const App = ({ location, children }) => { + const { user } = useSelector(state => state); + const useSiS = useSelector(signInServiceEnabled); -export default function App({ location, children }) { return ( - - {children} - + + + + {children} + + ); -} +}; + +export default App; diff --git a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx index 2e004f5cadd3..21d285b0918f 100644 --- a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx +++ b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx @@ -1,68 +1,43 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; -import { focusElement, scrollToTop } from 'platform/utilities/ui'; +import { useDispatch, useSelector } from 'react-redux'; + +import { focusElement } from 'platform/utilities/ui'; import FormTitle from 'platform/forms-system/src/js/components/FormTitle'; import SaveInProgressIntro from 'platform/forms/save-in-progress/SaveInProgressIntro'; -import { useSelector } from 'react-redux'; -import { isLOA3, isLoggedIn } from 'platform/user/selectors'; + +import { getMdotInProgressForm } from '../actions'; import { TITLE, SUBTITLE } from '../constants'; +import Alerts from './Alerts'; + const OMB_RES_BURDEN = 30; const OMB_NUMBER = '2346A'; const OMB_EXP_DATE = '12/31/2025'; -const ProcessList = () => { - return ( - - -

To fill out this application, you’ll need your:

-
    -
  • Social Security number (required)
  • -
-

- What if I need help filling out my application? An - accredited representative, like a Veterans Service Officer (VSO), can - help you fill out your claim.{' '} - - Get help filing your claim - -

-
- -

Complete this benefits form.

-

- After submitting the form, you’ll get a confirmation message. You can - print this for your records. -

-
- -

- We process claims within a week. If more than a week has passed since - you submitted your application and you haven’t heard back, please - don’t apply again. Call us at. -

-
- -

- Once we’ve processed your claim, you’ll get a notice in the mail with - our decision. -

-
-
- ); -}; +const Loading = () => ( +
+ +
+); + +export const IntroductionPage = ({ route }) => { + const dispatch = useDispatch(); + const state = useSelector(s => s); + const loading = + state?.mdotInProgressForm?.loading || + state?.user?.profile?.loading || + false; + + useEffect(() => focusElement('h1'), [loading]); + + useEffect(() => dispatch(getMdotInProgressForm()), [dispatch]); -export const IntroductionPage = props => { - const userLoggedIn = useSelector(state => isLoggedIn(state)); - const userIdVerified = useSelector(state => isLOA3(state)); - const { route } = props; - const { formConfig, pageList } = route; - const showVerifyIdentify = userLoggedIn && !userIdVerified; + const alerts = ; - useEffect(() => { - scrollToTop(); - focusElement('h1'); - }, []); + if (loading) { + return ; + } return (
@@ -70,15 +45,12 @@ export const IntroductionPage = props => {

Follow the steps below to apply for benefits.

- - {showVerifyIdentify ? ( -
{/* add verify identity alert if applicable */}
- ) : ( + {alerts || ( res.status(404).json(notFound), // 'GET /v0/in_progress_forms/MDOT': (_, res) => res.status(500).json(internalServerError), diff --git a/src/applications/mhv-supply-reordering/mocks/index.js b/src/applications/mhv-supply-reordering/mocks/index.js index a94019766b5e..d03a25cf2c47 100644 --- a/src/applications/mhv-supply-reordering/mocks/index.js +++ b/src/applications/mhv-supply-reordering/mocks/index.js @@ -1,5 +1,4 @@ -// eslint-disable-next-line no-unused-vars -const delay = require('mocker-api/lib/delay'); +const delay = require('mocker-api/lib/delay'); // eslint-disable-line no-unused-vars const featureToggles = require('./feature-toggles'); const inProgressFormsMdot = require('./in-progress-forms/mdot'); diff --git a/src/applications/mhv-supply-reordering/reducers/index.js b/src/applications/mhv-supply-reordering/reducers/index.js index fa616ca0da4b..2c68ea6d4dbf 100644 --- a/src/applications/mhv-supply-reordering/reducers/index.js +++ b/src/applications/mhv-supply-reordering/reducers/index.js @@ -1,6 +1,8 @@ import { createSaveInProgressFormReducer } from 'platform/forms/save-in-progress/reducers'; import formConfig from '../config/form'; +import { mdotInProgressFormReducer } from './mdotInProgressFormReducer'; export default { form: createSaveInProgressFormReducer(formConfig), + mdotInProgressForm: mdotInProgressFormReducer, }; diff --git a/src/applications/mhv-supply-reordering/reducers/mdotInProgressFormReducer.js b/src/applications/mhv-supply-reordering/reducers/mdotInProgressFormReducer.js new file mode 100644 index 000000000000..f7ba3662db2d --- /dev/null +++ b/src/applications/mhv-supply-reordering/reducers/mdotInProgressFormReducer.js @@ -0,0 +1,30 @@ +import { + GET_MDOT_IN_PROGRESS_FORM_STARTED, + GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED, + GET_MDOT_IN_PROGRESS_FORM_FAILED, +} from '../actions'; + +export const initialState = { + formData: {}, + error: false, + loading: false, +}; + +export const mdotInProgressFormReducer = (state = initialState, action) => { + const { payload, type } = action; + switch (type) { + case GET_MDOT_IN_PROGRESS_FORM_STARTED: + return { ...state, loading: true }; + case GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED: + return { ...state, formData: payload.formData, loading: false }; + case GET_MDOT_IN_PROGRESS_FORM_FAILED: + return { + ...state, + formData: {}, + loading: false, + error: payload?.errors?.at(0), + }; + default: + return state; + } +}; diff --git a/src/applications/mhv-supply-reordering/selectors/index.js b/src/applications/mhv-supply-reordering/selectors/index.js new file mode 100644 index 000000000000..ba9fba841f71 --- /dev/null +++ b/src/applications/mhv-supply-reordering/selectors/index.js @@ -0,0 +1,42 @@ +import { signInServiceEnabled } from '~/platform/user/authentication/selectors'; + +import { isLOA3, isLoggedIn, isVAPatient } from '~/platform/user/selectors'; + +import { MDOT_ERROR_CODES } from '../constants'; + +const canReorderOn = state => + state?.mdotInProgressForm?.formData?.supplies + ?.map(s => s?.nextAvailabilityDate) + ?.sort() + ?.at(0); + +const showAlertDeceased = state => + state?.mdotInProgressForm?.error?.code === MDOT_ERROR_CODES.DECEASED || false; + +const showAlertNoRecordForUser = state => + state?.mdotInProgressForm?.error?.code === MDOT_ERROR_CODES.INVALID || false; + +const showAlertNoSuppliesForReorder = state => + state?.mdotInProgressForm?.formData?.supplies?.every( + supply => supply?.availableForReorder === undefined, + ) || false; + +const showAlertReorderAccessExpired = state => + state?.mdotInProgressForm?.error?.code === + MDOT_ERROR_CODES.SUPPLIES_NOT_FOUND || false; + +const showAlertSomethingWentWrong = state => + Math.trunc(+state?.mdotInProgressForm?.error?.status / 500) === 1; + +export { + canReorderOn, + isLOA3, + isLoggedIn, + isVAPatient, + signInServiceEnabled, + showAlertDeceased, + showAlertNoRecordForUser, + showAlertNoSuppliesForReorder, + showAlertReorderAccessExpired, + showAlertSomethingWentWrong, +}; diff --git a/src/applications/mhv-supply-reordering/tests/containers/Alerts.unit.spec.jsx b/src/applications/mhv-supply-reordering/tests/containers/Alerts.unit.spec.jsx new file mode 100644 index 000000000000..d4d7b4614bcb --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/containers/Alerts.unit.spec.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { expect } from 'chai'; + +import { renderInReduxProvider } from '@department-of-veterans-affairs/platform-testing/react-testing-library-helpers'; + +import Alerts from '../../containers/Alerts'; + +import { MDOT_ERROR_CODES } from '../../constants'; +import reducers from '../../reducers'; + +const stateFn = ({ + supplies = [{ availableForReorder: true }], + error = false, +} = {}) => ({ + mdotInProgressForm: { + formData: { + supplies, + }, + error, + loading: false, + }, +}); + +const setup = ({ initialState = stateFn() } = {}) => + renderInReduxProvider(, { initialState, reducers }); + +describe(' container', () => { + it('renders nothing', () => { + const { container } = setup(); + expect(container).to.be.empty; + }); + + it('renders ', () => { + const initialState = stateFn({ + error: { code: MDOT_ERROR_CODES.DECEASED }, + }); + const { getAllByTestId, getByTestId } = setup({ initialState }); + getByTestId('reorder-alert--deceased'); + expect(getAllByTestId(/^reorder-alert--/).length).to.eq(1); + }); + + it('renders ', () => { + const initialState = stateFn({ error: { code: MDOT_ERROR_CODES.INVALID } }); + const { getAllByTestId, getByTestId } = setup({ initialState }); + getByTestId('reorder-alert--no-record-for-user'); + expect(getAllByTestId(/^reorder-alert--/).length).to.eq(1); + }); + + it('renders ', () => { + const initialState = stateFn({ + error: { code: MDOT_ERROR_CODES.SUPPLIES_NOT_FOUND }, + }); + const { getAllByTestId, getByTestId } = setup({ initialState }); + getByTestId('reorder-alert--reorder-access-expired'); + expect(getAllByTestId(/^reorder-alert--/).length).to.eq(1); + }); + + it('renders ', () => { + const initialState = stateFn({ error: { status: 500 } }); + const { getAllByTestId, getByTestId } = setup({ initialState }); + getByTestId('reorder-alert--something-went-wrong'); + expect(getAllByTestId(/^reorder-alert--/).length).to.eq(1); + }); + + it('renders ', () => { + const initialState = stateFn({ + supplies: [{ nextAvailabilityDate: '2199-01-01' }], + }); + const { getAllByTestId, getByTestId } = setup({ initialState }); + getByTestId('reorder-alert--no-supplies-for-reorder'); + expect(getAllByTestId(/^reorder-alert--/).length).to.eq(1); + }); +}); From 1640844cae95212cf4c3b6d86ae9992319212520 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 18 Nov 2024 17:45:57 -0500 Subject: [PATCH 05/28] Save point --- .../components/EditAddress.jsx | 213 ++++++++++++++++++ .../components/EditEmail.jsx | 46 ++++ .../components/SelectArrayItemsWidget.jsx | 177 +++++++++++++++ .../components/SuppliesAvailable.jsx | 23 ++ .../components/SuppliesUnavailable.jsx | 74 ++++++ .../components/SupplyField.jsx | 20 ++ .../mhv-supply-reordering/config/form.js | 82 ++++--- .../containers/ConfirmationPage.jsx | 5 - .../containers/IntroductionPage.jsx | 48 ++-- .../pages/contactInformation.jsx | 56 +++++ .../pages/mailingAddress.js | 26 +++ .../pages/nameAndDateOfBirth.js | 24 -- .../pages/selectSupplies.jsx | 70 ++++++ .../mhv-supply-reordering/selectors/index.js | 12 + .../mhv-supply-reordering/utils/validators.js | 12 + 15 files changed, 803 insertions(+), 85 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/components/EditAddress.jsx create mode 100644 src/applications/mhv-supply-reordering/components/EditEmail.jsx create mode 100644 src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx create mode 100644 src/applications/mhv-supply-reordering/components/SuppliesAvailable.jsx create mode 100644 src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx create mode 100644 src/applications/mhv-supply-reordering/components/SupplyField.jsx create mode 100644 src/applications/mhv-supply-reordering/pages/contactInformation.jsx create mode 100644 src/applications/mhv-supply-reordering/pages/mailingAddress.js delete mode 100644 src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js create mode 100644 src/applications/mhv-supply-reordering/pages/selectSupplies.jsx create mode 100644 src/applications/mhv-supply-reordering/utils/validators.js diff --git a/src/applications/mhv-supply-reordering/components/EditAddress.jsx b/src/applications/mhv-supply-reordering/components/EditAddress.jsx new file mode 100644 index 000000000000..b82c4521005e --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/EditAddress.jsx @@ -0,0 +1,213 @@ +import React, { useState } from 'react'; +import { + VaCheckbox, + VaSelect, + VaTextInput, + VaButton, +} from '@department-of-veterans-affairs/component-library/dist/react-bindings'; +import constants from 'vets-json-schema/dist/constants.json'; +import UnsavedFieldNote from './UnsavedFieldNote'; + +/** + * PATTERNS + * NONBLANK_PATTERN - rejects white space only + * POSTAL_CODE_PATTERNS - Matches US/Mexican/Canadian codes + */ +const COUNTRY_VALUES = constants.countries.map(country => country.value); +const COUNTRY_NAMES = constants.countries.map(country => country.label); + +const MILITARY_STATE_VALUES = constants.militaryStates.map( + state => state.value, +); +// const MILITARY_STATE_NAMES = constants.militaryStates.map(state => state.label); + +// filtered States that include US territories +const filteredStates = constants.states.USA.filter( + state => !MILITARY_STATE_VALUES.includes(state.value), +); + +const STATE_VALUES = filteredStates.map(state => state.value); +const STATE_NAMES = filteredStates.map(state => state.label); + +/* +const NONBLANK_PATTERN = '^.*\\S.*'; +const POSTAL_CODE_PATTERNS = { + CAN: + '^(?=[^DdFfIiOoQqUu\\d\\s])[A-Za-z]\\d(?=[^DdFfIiOoQqUu\\d\\s])[A-Za-z]\\s{0,1}\\d(?=[^DdFfIiOoQqUu\\d\\s])[A-Za-z]\\d$', + MEX: '^\\d{5}$', + USA: '^\\d{5}$', +}; +const POSTAL_CODE_PATTERN_ERROR_MESSAGES = { + CAN: 'Enter a valid 6-character postal code', + MEX: 'Enter a valid 5-digit postal code', + USA: 'Enter a valid 5-digit ZIP code', +}; +const CITY_ERROR_MESSAGES = { + default: 'City is required', + military: 'Select a post office type: APO, FPO, or DPO', +}; +const MILITARY_CITY_TITLE = 'APO/FPO/DPO'; +const MILITARY_CITY_VALUES = constants.militaryCities.map(city => city.value); +const MILITARY_CITY_NAMES = constants.militaryCities.map(city => city.label); +const CAN_STATE_VALUES = constants.states.CAN.map(state => state.value); +const CAN_STATE_NAMES = constants.states.CAN.map(state => state.label); +const MEX_STATE_VALUES = constants.states.MEX.map(state => state.value); +const MEX_STATE_NAMES = constants.states.MEX.map(state => state.label); +const schemaCrossXRef = { + isMilitary: 'isMilitary', + 'view:militaryBaseDescription': 'view:militaryBaseDescription', + country: 'country', + street: 'street', + street2: 'street2', + street3: 'street3', + city: 'city', + state: 'state', + postalCode: 'postalCode', +}; +const USA = { + value: 'USA', + label: 'United States', +}; +// TODO: Refactor for dynamic content +const MilitaryBaseInfo = () => ( +
+ + + The United States is automatically chosen as your country if you live on + a military base outside of the country. + + +
+); +*/ + +const EditAddress = ({ data, goToPath, setFormData }) => { + const [address, setAddress] = useState(data.permanentAddress || {}); + + const handleSubmit = event => { + event.preventDefault(); + setFormData({ ...data, permanentAddress: address }); + goToPath('/contact-information'); + }; + + const handleInputChange = event => { + const { name, value } = event.target; + setAddress(prevAddress => ({ ...prevAddress, [name]: value })); + }; + + return ( +
+

Order medical supplies

+

Contact information

+ +
+ + setAddress(prevAddress => ({ + ...prevAddress, + isMilitary: e.detail.checked, + })) + } + checked={address.isMilitary} + /> + {/* Learn more about military base addresses */} + + + handleInputChange({ + target: { name: 'country', value: e.detail.value }, + }) + } + required + > + {COUNTRY_VALUES.map((value, index) => ( + + ))} + + + + handleInputChange({ + target: { name: 'street', value: e.target.value }, + }) + } + required + /> + + + handleInputChange({ + target: { name: 'street2', value: e.target.value }, + }) + } + /> + + + handleInputChange({ + target: { name: 'city', value: e.target.value }, + }) + } + required + /> + + + handleInputChange({ + target: { name: 'state', value: e.detail.value }, + }) + } + required + > + {STATE_VALUES.map((value, index) => ( + + ))} + + + + handleInputChange({ + target: { name: 'postalCode', value: e.target.value }, + }) + } + required + /> + +
+ + goToPath('/contact-information')} + uswds + /> +
+ +
+ ); +}; + +export default EditAddress; diff --git a/src/applications/mhv-supply-reordering/components/EditEmail.jsx b/src/applications/mhv-supply-reordering/components/EditEmail.jsx new file mode 100644 index 000000000000..c68a81c1cf7a --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/EditEmail.jsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import UnsavedFieldNote from './UnsavedFieldNote'; + +const EditEmail = ({ data, goToPath, setFormData }) => { + const [email, setEmail] = useState(data.emailAddress || ''); + + const handleSubmit = event => { + event.preventDefault(); + setFormData({ ...data, emailAddress: email }); + goToPath('/contact-information'); + }; + + return ( +
+

Order medical supplies

+

Contact information

+ +
+ + setEmail(e.target.value)} + required + /> +
+ + goToPath('/contact-information')} + /> +
+
+
+ ); +}; + +export default EditEmail; diff --git a/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx b/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx new file mode 100644 index 000000000000..0f58175cafcb --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx @@ -0,0 +1,177 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { VaCheckbox } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; + +import get from 'platform/utilities/data/get'; +import set from 'platform/utilities/data/set'; +import { setData } from 'platform/forms-system/src/js/actions'; +import { autoSaveForm } from 'platform/forms/save-in-progress/actions'; + +class SelectArrayItemsWidget extends React.Component { + defaultSelectedPropName = 'view:selected'; + + keyValue = 'supplies'; + + onChange = (index, checked) => { + const items = set( + `[${index}].${this.props.formData.supplies['view:selected'] || + this.defaultSelectedPropName}`, + checked, + this.props.value, + ); + this.props.onChange(items); + }; + + render() { + const { value: supplies, options, formContext, formData } = this.props; + + // Need customTitle to set error message above title. + const { label: selectedPropName, customTitle } = options; + + // inReviewMode = true (review page view, not in edit mode) + // inReviewMode = false (in edit mode) + const { onReviewPage } = formContext; + const inReviewMode = onReviewPage && formContext.reviewMode; + + const hasSelections = formData.supplies?.reduce( + (result, item) => + result || !!get(selectedPropName || this.defaultSelectedPropName, item), + false, + ); + + const itemsList = + formData.supplies?.length > 0 && + formData.supplies.map((item, index) => { + const itemDescription = `Quantity: ${item.quantity} + Last ordered on: ${item.lastOrderDate}`; + + const itemIsSelected = !!get( + selectedPropName || this.defaultSelectedPropName, + item, + ); + + // Don't show un-selected ratings in review mode + if (inReviewMode && !itemIsSelected) { + return null; + } + + const checkboxVisible = + !onReviewPage || (onReviewPage && !inReviewMode); + + const widgetClasses = classNames( + 'form-checkbox', + options.widgetClassNames, + { selected: itemIsSelected }, + ); + + // When a `customTitle` option is included, the ObjectField is set to + // wrap its contents in a div instead of a dl, so we don't need a + // include dt and dd elements in the markup; this change fixes an + // accessibility issue + return ( +
+ {checkboxVisible && ( + this.onChange(index, event.target.checked)} + /> + )} +
+ ); + }); + + const hasCustomTitle = !!customTitle?.trim(); + const Tag = formContext.onReviewPage ? 'h4' : 'h3'; + + const content = + itemsList && (!inReviewMode || (inReviewMode && hasSelections)) ? ( + itemsList + ) : ( +

+ + {`No rated disabilities ${ + supplies?.length > 0 ? 'selected' : 'found' + }`} + +

+ ); + + return hasCustomTitle ? ( +
+ + {customTitle} + + {content} +
+ ) : ( + <>{content} + ); + } +} + +SelectArrayItemsWidget.propTypes = { + formContext: PropTypes.object.isRequired, + formId: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + options: PropTypes.shape({ + title: PropTypes.string, + customTitle: PropTypes.string, + field: PropTypes.string, + label: PropTypes.func, + showFieldLabel: PropTypes.bool, + validations: PropTypes.array, + }).isRequired, + value: PropTypes.arrayOf( + PropTypes.shape({ + productName: PropTypes.string.isRequired, + productId: PropTypes.number.isRequired, + }), + ).isRequired, + autoSaveForm: PropTypes.func, + formData: PropTypes.shape({}), + metadata: PropTypes.shape({ + version: PropTypes.number, + returnUrl: PropTypes.string, + submission: PropTypes.shape({}), + }), + required: PropTypes.bool, + setData: PropTypes.func, +}; + +SelectArrayItemsWidget.defaultProps = { + value: [], + formData: {}, + loadedData: { + metadata: {}, + }, + setData: () => {}, + autoSaveForm: () => {}, +}; + +const mapDispatchToProps = { + setData, + autoSaveForm, +}; + +const mapStateToProps = state => { + const { form } = state; + return { + formId: form.formId, + formData: form.data, + metadata: form.loadedData?.metadata || {}, + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(SelectArrayItemsWidget); + +export { SelectArrayItemsWidget }; diff --git a/src/applications/mhv-supply-reordering/components/SuppliesAvailable.jsx b/src/applications/mhv-supply-reordering/components/SuppliesAvailable.jsx new file mode 100644 index 000000000000..964399b78f90 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/SuppliesAvailable.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const SuppliesAvailable = ({ supplies }) => ( + +

Available for reorder

+

+ You have {supplies.length} {supplies.length > 1 ? 'supplies' : 'supply'}{' '} + available for reorder. +

+
    + {supplies.map(({ productId, productName }) => ( +
  • {productName}
  • + ))} +
+
+); + +SuppliesAvailable.propTypes = { + supplies: PropTypes.array.isRequired, +}; + +export default SuppliesAvailable; diff --git a/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx b/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx new file mode 100644 index 000000000000..326461ff9f33 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx @@ -0,0 +1,74 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { format } from 'date-fns'; +import classNames from 'classnames'; +import DlcTelephoneLink from './DlcTelephoneLink'; +import { HEALTH_FACILITIES_URL } from '../constants'; + +const SuppliesUnavailable = ({ supplies }) => { + const cards = supplies + ?.sort((a, b) => a.productName.localeCompare(b.productName)) + .map((supply, index) => ( +
+ +
+ {supply.productGroup}
+ Device: {supply.productName}
+ Quantity: {supply.quantity}
+ Last ordered on{' '} + {format(new Date(supply.lastOrderDate), 'MMMM d, yyyy')} +
+ {supply.availableForReorder && ( +

+ You can’t order this supply online until{' '} + {format(new Date(supply.nextAvailabilityDate), 'MMMM d, yyyy')}. + If you need this supply now call us at . +

+ )} + {!supply.availableForReorder && ( +

+ This item is not available for reordering. To reorder, you can + call your VA healthcare team{' '} + or{' '} + + send them a message + + . +

+ )} +
+
+ )); + + return ( + <> + {supplies?.length > 0 && ( +
+

Unavailable for reorder

+

+ Showing {supplies.length} medical{' '} + {supplies.length > 1 + ? 'supplies, alphabetically by name' + : 'supply'} +

+
+ {cards} +
+ )} + + ); +}; + +SuppliesUnavailable.propTypes = { + supplies: PropTypes.array.isRequired, +}; + +export default SuppliesUnavailable; diff --git a/src/applications/mhv-supply-reordering/components/SupplyField.jsx b/src/applications/mhv-supply-reordering/components/SupplyField.jsx new file mode 100644 index 000000000000..c41b4dcff0c8 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/SupplyField.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default function SupplyField({ formData }) { + const { supplyName } = formData.supplies; + + return ( +
+ {supplyName} +
+ ); +} + +SupplyField.propTypes = { + formData: PropTypes.shape({ + supplies: PropTypes.shape({ + supplyName: PropTypes.string, + }), + }), +}; diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 576a3057c9d1..8366e76bcbf5 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -3,15 +3,20 @@ import footerContent from 'platform/forms/components/FormFooter'; import { VA_FORM_IDS } from 'platform/forms/constants'; import { TITLE as title, SUBTITLE as subTitle } from '../constants'; import manifest from '../manifest.json'; -import introduction from '../containers/IntroductionPage'; -import confirmation from '../containers/ConfirmationPage'; -import nameAndDateOfBirth from '../pages/nameAndDateOfBirth'; +import contactInformation from '../pages/contactInformation'; +import selectSupplies from '../pages/selectSupplies'; + +import EditAddress from '../components/EditAddress'; +import EditEmail from '../components/EditEmail'; import getHelp from '../components/Help'; +import introduction from '../containers/IntroductionPage'; +import confirmation from '../containers/ConfirmationPage'; + import prefillTransformer from './prefillTransformer'; -const blankSchema = { type: 'object', properties: {} }; // eslint-disable-line no-unused-vars +const blankSchema = { type: 'object', properties: {} }; const savedFormMessages = { notFound: 'Please start over to reorder health care supplies.', @@ -38,12 +43,50 @@ const customText = { // submitButtonText: '', }; -// const formOptions = { -// noTitle: true, -// noTopNav: true, -// noBottomNav: true, -// fullWidth: true, -// }; +const chapters = { + contactInformationChapter: { + title: 'Contact information', + pages: { + contactInformation: { + path: 'contact-information', + title: 'Contact information', + uiSchema: contactInformation.uiSchema, + schema: contactInformation.schema, + }, + editEmailAddress: { + title: 'Edit email address', + taskListHide: true, + path: 'edit-email-address', + CustomPage: EditEmail, + CustomPageReview: EditEmail, + depends: () => false, + uiSchema: {}, + schema: blankSchema, + }, + editMailingAddress: { + title: 'Edit mailing address', + taskListHide: true, + path: 'edit-mailing-address', + CustomPage: EditAddress, + CustomPageReview: EditAddress, + depends: () => false, + uiSchema: {}, + schema: blankSchema, + }, + }, + }, + selectSuppliesChapter: { + title: 'Select supplies', + pages: { + selectSupplies: { + path: 'select-supplies', + title: 'Select supplies', + uiSchema: selectSupplies.uiSchema, + schema: selectSupplies.schema, + }, + }, + }, +}; /** @type {FormConfig} */ const formConfig = { @@ -55,12 +98,7 @@ const formConfig = { trackingPrefix: 'mhv-supply-reordering-', introduction, confirmation, - dev: { - showNavLinks: true, - collapsibleNavLinks: true, - }, formId: VA_FORM_IDS.FORM_VA_2346A, - // formOptions, savedFormMessages, saveInProgress, version: 0, @@ -70,19 +108,7 @@ const formConfig = { subTitle, customText, defaultDefinitions: {}, - chapters: { - personalInformationChapter: { - title: 'Your personal information', - pages: { - nameAndDateOfBirth: { - path: 'name-and-date-of-birth', - title: 'Name and date of birth', - uiSchema: nameAndDateOfBirth.uiSchema, - schema: nameAndDateOfBirth.schema, - }, - }, - }, - }, + chapters, getHelp, footerContent, }; diff --git a/src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx b/src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx index 5d5e0099643f..3705a1823795 100644 --- a/src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx +++ b/src/applications/mhv-supply-reordering/containers/ConfirmationPage.jsx @@ -70,11 +70,6 @@ export const ConfirmationPage = () => { Go back to VA.gov - - {/*
-

Need help?

- -
*/} ); }; diff --git a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx index 21d285b0918f..0843695e53da 100644 --- a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx +++ b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx @@ -9,11 +9,11 @@ import SaveInProgressIntro from 'platform/forms/save-in-progress/SaveInProgressI import { getMdotInProgressForm } from '../actions'; import { TITLE, SUBTITLE } from '../constants'; +import VerifiedPrefillAlert from '../components/VerifiedPrefillAlert'; import Alerts from './Alerts'; - -const OMB_RES_BURDEN = 30; -const OMB_NUMBER = '2346A'; -const OMB_EXP_DATE = '12/31/2025'; +import SuppliesAvailable from '../components/SuppliesAvailable'; +import SuppliesUnavailable from '../components/SuppliesUnavailable'; +import { selectSupplies, selectUnavailableSupplies } from '../selectors'; const Loading = () => (
@@ -29,41 +29,33 @@ export const IntroductionPage = ({ route }) => { state?.user?.profile?.loading || false; + const supplies = useSelector(selectSupplies); + const unavailableSupplies = useSelector(selectUnavailableSupplies); + useEffect(() => focusElement('h1'), [loading]); useEffect(() => dispatch(getMdotInProgressForm()), [dispatch]); - const alerts = ; - if (loading) { return ; } return ( -
+ <> -

- Follow the steps below to apply for benefits. -

- {alerts || ( - - )} -

- + + -

+ + ); }; diff --git a/src/applications/mhv-supply-reordering/pages/contactInformation.jsx b/src/applications/mhv-supply-reordering/pages/contactInformation.jsx new file mode 100644 index 000000000000..63e915a3fb24 --- /dev/null +++ b/src/applications/mhv-supply-reordering/pages/contactInformation.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { Link } from 'react-router'; +import { titleUI } from 'platform/forms-system/src/js/web-component-patterns'; +import AddressViewField from '@department-of-veterans-affairs/platform-forms-system/AddressViewField'; +import UnsavedFieldNote from '../components/UnsavedFieldNote'; + +const Description = ({ formData }) => { + const { emailAddress, permanentAddress } = formData; + + return ( + <> + +

+ We’ve prefilled some of your information from VA Denver Logistics + Center’s record. If you need to correct anything, you can edit the + contact information. +

+
+ +

Email address

+ {emailAddress || ''} +

+ + Edit + +

+ +

Mailing address

+ +

+ + Edit + +

+ + + + ); +}; + +/** @type {PageSchema} */ +export default { + uiSchema: { + ...titleUI('Contact information'), + 'ui:description': Description, + 'ui:required': () => true, // don't allow progressing without all contact info + 'ui:options': { + hideOnReview: true, + forceDivWrapper: true, + }, + }, + schema: { + type: 'object', + properties: {}, + }, +}; diff --git a/src/applications/mhv-supply-reordering/pages/mailingAddress.js b/src/applications/mhv-supply-reordering/pages/mailingAddress.js new file mode 100644 index 000000000000..0c0c1fcb31ec --- /dev/null +++ b/src/applications/mhv-supply-reordering/pages/mailingAddress.js @@ -0,0 +1,26 @@ +import { + addressSchema, + addressUI, + titleUI, +} from 'platform/forms-system/src/js/web-component-patterns'; + +/** @type {PageSchema} */ +export default { + uiSchema: { + ...titleUI( + 'Mailing address', + 'We’ll send any important information about your application to this address.', + ), + address: addressUI({ + omit: ['street3'], + }), + }, + schema: { + type: 'object', + properties: { + address: addressSchema({ + omit: ['street3'], + }), + }, + }, +}; diff --git a/src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js b/src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js deleted file mode 100644 index 90b487fb8a6e..000000000000 --- a/src/applications/mhv-supply-reordering/pages/nameAndDateOfBirth.js +++ /dev/null @@ -1,24 +0,0 @@ -import { - dateOfBirthSchema, - dateOfBirthUI, - fullNameNoSuffixSchema, - fullNameNoSuffixUI, - titleUI, -} from 'platform/forms-system/src/js/web-component-patterns'; - -/** @type {PageSchema} */ -export default { - uiSchema: { - ...titleUI('Name and date of birth'), - fullName: fullNameNoSuffixUI(), - dateOfBirth: dateOfBirthUI(), - }, - schema: { - type: 'object', - properties: { - fullName: fullNameNoSuffixSchema, - dateOfBirth: dateOfBirthSchema, - }, - required: ['fullName', 'dateOfBirth'], - }, -}; diff --git a/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx b/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx new file mode 100644 index 000000000000..de6b4d95db1b --- /dev/null +++ b/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { titleUI } from 'platform/forms-system/src/js/web-component-patterns'; +import SupplyField from '../components/SupplyField'; +import SelectArrayItemsWidget from '../components/SelectArrayItemsWidget'; +import { validateAtLeastOneSelected } from '../utils/validators'; + +const Description = () => { + return ( + <> +

Available for reorder

+

You have 3 supplies that are available for reorder.

+

+ Note: For CPAP supplies, each order is a 12-month + supply. You can only order each item once every 12 months. +

+

+ For hearing aid supplies, each order is a 6-month supply. You can only + order each item once every 6 months. +

+ + ); +}; + +export default { + uiSchema: { + ...titleUI('Select supplies'), + 'ui:description': Description, + supplies: { + 'ui:field': 'StringField', + 'ui:widget': SelectArrayItemsWidget, + 'ui:options': { + showFieldLabel: true, + viewField: SupplyField, + }, + 'ui:required': () => true, + 'ui:validations': [validateAtLeastOneSelected], + }, + }, + schema: { + type: 'object', + properties: { + supplies: { + type: 'array', + minItems: 1, + items: { + type: 'object', + required: ['supply'], + properties: { + supply: { + type: 'object', + properties: { + productId: { + type: 'string', + }, + productName: { + type: 'string', + }, + }, + }, + 'view:selected': { + type: 'boolean', + default: false, + }, + 'view:descriptionInfo': { type: 'object', properties: {} }, + }, + }, + }, + }, + }, +}; diff --git a/src/applications/mhv-supply-reordering/selectors/index.js b/src/applications/mhv-supply-reordering/selectors/index.js index ba9fba841f71..1d49a5f2e313 100644 --- a/src/applications/mhv-supply-reordering/selectors/index.js +++ b/src/applications/mhv-supply-reordering/selectors/index.js @@ -4,6 +4,16 @@ import { isLOA3, isLoggedIn, isVAPatient } from '~/platform/user/selectors'; import { MDOT_ERROR_CODES } from '../constants'; +const selectSupplies = state => + state?.mdotInProgressForm?.formData?.supplies?.filter( + s => !!s?.availableForReorder, + ) || []; + +const selectUnavailableSupplies = state => + state?.mdotInProgressForm?.formData?.supplies?.filter( + s => !s?.availableForReorder, + ) || []; + const canReorderOn = state => state?.mdotInProgressForm?.formData?.supplies ?.map(s => s?.nextAvailabilityDate) @@ -33,6 +43,8 @@ export { isLOA3, isLoggedIn, isVAPatient, + selectSupplies, + selectUnavailableSupplies, signInServiceEnabled, showAlertDeceased, showAlertNoRecordForUser, diff --git a/src/applications/mhv-supply-reordering/utils/validators.js b/src/applications/mhv-supply-reordering/utils/validators.js new file mode 100644 index 000000000000..26ec96b8fa22 --- /dev/null +++ b/src/applications/mhv-supply-reordering/utils/validators.js @@ -0,0 +1,12 @@ +export function validateAtLeastOneSelected(errors, fieldData, formData) { + if (!formData.supplies || formData.supplies.length === 0) { + errors.addError('Please select at least one supply item.'); + return; + } + + const selectedCount = formData.supplies.filter(item => item['view:selected']) + .length; + if (selectedCount < 1) { + errors.addError('Please select at least one supply item.'); + } +} From a320df024a057a1718a2140c7333801354d569f8 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 19 Nov 2024 10:04:54 -0500 Subject: [PATCH 06/28] Moves breadcrumbs to IntroductionPage --- .../components/Breadcrumbs.jsx | 22 +++++++++++++++++++ .../mhv-supply-reordering/containers/App.jsx | 18 --------------- .../containers/IntroductionPage.jsx | 2 ++ 3 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/components/Breadcrumbs.jsx diff --git a/src/applications/mhv-supply-reordering/components/Breadcrumbs.jsx b/src/applications/mhv-supply-reordering/components/Breadcrumbs.jsx new file mode 100644 index 000000000000..e31031728179 --- /dev/null +++ b/src/applications/mhv-supply-reordering/components/Breadcrumbs.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { VaBreadcrumbs } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; +import manifest from '../manifest.json'; + +const breadcrumbList = [ + { + href: '/', + label: 'VA.gov Home', + }, + { + href: '/my-health', + label: 'Health care', + }, + { + href: manifest.rootUrl, + label: manifest.appName, + }, +]; + +const Breadcrumbs = () => ; + +export default Breadcrumbs; diff --git a/src/applications/mhv-supply-reordering/containers/App.jsx b/src/applications/mhv-supply-reordering/containers/App.jsx index 2811ac8c94c8..d4d2bd9d3480 100644 --- a/src/applications/mhv-supply-reordering/containers/App.jsx +++ b/src/applications/mhv-supply-reordering/containers/App.jsx @@ -1,30 +1,13 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { VaBreadcrumbs } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; import { RequiredLoginView } from '@department-of-veterans-affairs/platform-user/RequiredLoginView'; import backendServices from '@department-of-veterans-affairs/platform-user/profile/backendServices'; import RoutedSavableApp from 'platform/forms/save-in-progress/RoutedSavableApp'; import formConfig from '../config/form'; -import manifest from '../manifest.json'; import { signInServiceEnabled } from '../selectors'; -const breadcrumbList = [ - { - href: '/', - label: 'VA.gov Home', - }, - { - href: '/my-health', - label: 'Health care', - }, - { - href: manifest.rootUrl, - label: manifest.appName, - }, -]; - const serviceRequired = [ // backendServices.FACILITIES, backendServices.FORM_PREFILL, @@ -44,7 +27,6 @@ const App = ({ location, children }) => { serviceRequired={serviceRequired} > - {children} diff --git a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx index 0843695e53da..fcd49129d2d3 100644 --- a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx +++ b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx @@ -9,6 +9,7 @@ import SaveInProgressIntro from 'platform/forms/save-in-progress/SaveInProgressI import { getMdotInProgressForm } from '../actions'; import { TITLE, SUBTITLE } from '../constants'; +import Breadcrumbs from '../components/Breadcrumbs'; import VerifiedPrefillAlert from '../components/VerifiedPrefillAlert'; import Alerts from './Alerts'; import SuppliesAvailable from '../components/SuppliesAvailable'; @@ -42,6 +43,7 @@ export const IntroductionPage = ({ route }) => { return ( <> + From 3192cb4939e749706830fd54bc5f28a9266cf4cb Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 19 Nov 2024 10:06:05 -0500 Subject: [PATCH 07/28] Sets useTopBackLink: true in formConfig (large continue button) --- src/applications/mhv-supply-reordering/config/form.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 8366e76bcbf5..532871d426c5 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -88,6 +88,13 @@ const chapters = { }, }; +// const formOptions = { +// noTitle: true, +// noTopNav: true, +// fullWidth: true, +// noBottomNav: true, +// }; + /** @type {FormConfig} */ const formConfig = { rootUrl: manifest.rootUrl, @@ -111,6 +118,8 @@ const formConfig = { chapters, getHelp, footerContent, + // formOptions, + useTopBackLink: true, }; export default formConfig; From 7c05b664def0bc14f9ad5d723f7d7e4a10a74f4b Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 19 Nov 2024 10:18:41 -0500 Subject: [PATCH 08/28] Show subtitle on IntroductionPage, only --- src/applications/mhv-supply-reordering/config/form.js | 4 ++-- .../mhv-supply-reordering/containers/IntroductionPage.jsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 532871d426c5..59913d9aa9c8 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -1,7 +1,7 @@ import environment from 'platform/utilities/environment'; import footerContent from 'platform/forms/components/FormFooter'; import { VA_FORM_IDS } from 'platform/forms/constants'; -import { TITLE as title, SUBTITLE as subTitle } from '../constants'; +import { TITLE as title } from '../constants'; import manifest from '../manifest.json'; import contactInformation from '../pages/contactInformation'; @@ -112,7 +112,7 @@ const formConfig = { prefillEnabled: true, prefillTransformer, title, - subTitle, + // subTitle, customText, defaultDefinitions: {}, chapters, diff --git a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx index fcd49129d2d3..d9e569707e00 100644 --- a/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx +++ b/src/applications/mhv-supply-reordering/containers/IntroductionPage.jsx @@ -44,7 +44,8 @@ export const IntroductionPage = ({ route }) => { return ( <> - + +

{SUBTITLE}

Date: Tue, 19 Nov 2024 10:19:23 -0500 Subject: [PATCH 09/28] Sets 'Finish this order later' content for save link --- src/applications/mhv-supply-reordering/config/form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 59913d9aa9c8..827e9bf0a4f3 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -38,6 +38,7 @@ const customText = { // appSavedSuccessfullyMessage: '', appType: 'order', // continueAppButtonText: '', + finishAppLaterMessage: 'Finish this order later', // reviewPageTitle: '', // startNewAppButtonText: '', // submitButtonText: '', From 256a65da914825a27c0784b3f422d8c4d5eb9179 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 19 Nov 2024 10:24:13 -0500 Subject: [PATCH 10/28] Removes margin from paragraph in informative va-alert --- .../mhv-supply-reordering/pages/contactInformation.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/mhv-supply-reordering/pages/contactInformation.jsx b/src/applications/mhv-supply-reordering/pages/contactInformation.jsx index 63e915a3fb24..ca9783c63775 100644 --- a/src/applications/mhv-supply-reordering/pages/contactInformation.jsx +++ b/src/applications/mhv-supply-reordering/pages/contactInformation.jsx @@ -10,7 +10,7 @@ const Description = ({ formData }) => { return ( <> -

+

We’ve prefilled some of your information from VA Denver Logistics Center’s record. If you need to correct anything, you can edit the contact information. From 9ecef23a3a23eaa76439140b222d936373a3502d Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 19 Nov 2024 10:25:51 -0500 Subject: [PATCH 11/28] Sets Select supplies as first page --- .../mhv-supply-reordering/config/form.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 827e9bf0a4f3..cb8b9468aff3 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -45,6 +45,17 @@ const customText = { }; const chapters = { + selectSuppliesChapter: { + title: 'Select supplies', + pages: { + selectSupplies: { + path: 'select-supplies', + title: 'Select supplies', + uiSchema: selectSupplies.uiSchema, + schema: selectSupplies.schema, + }, + }, + }, contactInformationChapter: { title: 'Contact information', pages: { @@ -76,17 +87,6 @@ const chapters = { }, }, }, - selectSuppliesChapter: { - title: 'Select supplies', - pages: { - selectSupplies: { - path: 'select-supplies', - title: 'Select supplies', - uiSchema: selectSupplies.uiSchema, - schema: selectSupplies.schema, - }, - }, - }, }; // const formOptions = { From e67788ca952d40f0b0be2eddf6931eb29d9b92c2 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 19 Nov 2024 11:11:27 -0500 Subject: [PATCH 12/28] Sets supply count, 'No supplies selected/found' text --- .../components/SelectArrayItemsWidget.jsx | 4 +--- .../mhv-supply-reordering/pages/selectSupplies.jsx | 12 ++++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx b/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx index 0f58175cafcb..fee064d32508 100644 --- a/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx +++ b/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx @@ -96,9 +96,7 @@ class SelectArrayItemsWidget extends React.Component { ) : (

- {`No rated disabilities ${ - supplies?.length > 0 ? 'selected' : 'found' - }`} + {`No supplies ${supplies?.length > 0 ? 'selected' : 'found'}`}

); diff --git a/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx b/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx index de6b4d95db1b..54f9e74c808c 100644 --- a/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx +++ b/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx @@ -1,14 +1,18 @@ import React from 'react'; -import { titleUI } from 'platform/forms-system/src/js/web-component-patterns'; import SupplyField from '../components/SupplyField'; import SelectArrayItemsWidget from '../components/SelectArrayItemsWidget'; import { validateAtLeastOneSelected } from '../utils/validators'; -const Description = () => { +const Description = ({ formData }) => { + const count = formData?.supplies?.length || 0; + return ( <>

Available for reorder

-

You have 3 supplies that are available for reorder.

+

+ You have {count} {count > 1 ? 'supplies' : 'supply'} that are available + for reorder. +

Note: For CPAP supplies, each order is a 12-month supply. You can only order each item once every 12 months. @@ -23,9 +27,9 @@ const Description = () => { export default { uiSchema: { - ...titleUI('Select supplies'), 'ui:description': Description, supplies: { + 'ui:title': 'Select available supplies for reorder', 'ui:field': 'StringField', 'ui:widget': SelectArrayItemsWidget, 'ui:options': { From 217d6ec3af0380038a2bfff7a8baa8aaab6f2bfa Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Thu, 21 Nov 2024 13:07:09 -0500 Subject: [PATCH 13/28] Adds formatDate helper --- .../components/SuppliesUnavailable.jsx | 13 ++++++------- .../components/alerts/AlertNoSuppliesForReorder.jsx | 6 +++--- .../mhv-supply-reordering/utils/helpers.js | 5 +++++ 3 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/utils/helpers.js diff --git a/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx b/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx index 326461ff9f33..dc2f80be29e7 100644 --- a/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx +++ b/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { format } from 'date-fns'; import classNames from 'classnames'; import DlcTelephoneLink from './DlcTelephoneLink'; import { HEALTH_FACILITIES_URL } from '../constants'; +import { formatDate } from '../utils/helpers'; const SuppliesUnavailable = ({ supplies }) => { const cards = supplies @@ -20,17 +20,16 @@ const SuppliesUnavailable = ({ supplies }) => { })} >

- {supply.productGroup}
- Device: {supply.productName}
+ {supply.productName}
+ Device: {supply.deviceName}
Quantity: {supply.quantity}
- Last ordered on{' '} - {format(new Date(supply.lastOrderDate), 'MMMM d, yyyy')} + Last ordered on {formatDate(supply.lastOrderDate)}
{supply.availableForReorder && (

You can’t order this supply online until{' '} - {format(new Date(supply.nextAvailabilityDate), 'MMMM d, yyyy')}. - If you need this supply now call us at . + {formatDate(supply.nextAvailabilityDate)}. If you need this supply + now call us at .

)} {!supply.availableForReorder && ( diff --git a/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx b/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx index 660c64a917bc..0655fc9d4e45 100644 --- a/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx +++ b/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { format } from 'date-fns'; import DlcEmailLink from '../DlcEmailLink'; import DlcTelephoneLink from '../DlcTelephoneLink'; +import { formatDate } from '../../utils/helpers'; /** * Generates an alert for a veteran that is not eligible to order supplies. @@ -10,7 +10,7 @@ import DlcTelephoneLink from '../DlcTelephoneLink'; * @returns the alert */ const AlertNoSuppliesForReorder = ({ reorderDate }) => { - const formattedDate = format(new Date(reorderDate), 'MMMM d, yyyy'); + const date = formatDate(reorderDate); return ( {
Our records show that your items aren’t available for reorder until{' '} - {formattedDate}. You can only order items once every 5 months. + {date}. You can only order items once every 5 months. If you need an item sooner, call the DLC Customer Service Section at{' '} diff --git a/src/applications/mhv-supply-reordering/utils/helpers.js b/src/applications/mhv-supply-reordering/utils/helpers.js new file mode 100644 index 000000000000..2a3e245e0731 --- /dev/null +++ b/src/applications/mhv-supply-reordering/utils/helpers.js @@ -0,0 +1,5 @@ +import { format } from 'date-fns'; + +const formatDate = dateString => format(new Date(dateString), 'MMMM d, yyyy'); + +export { formatDate }; From b94d005ca23fbc5131678e6268b434984b1175ba Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Fri, 22 Nov 2024 09:10:35 -0500 Subject: [PATCH 14/28] Updates --- .../mhv-supply-reordering/README.md | 20 +++++++++++++++++++ .../components/SelectArrayItemsWidget.jsx | 6 ++++-- .../mhv-supply-reordering/config/form.js | 12 +++++++++++ .../mhv-supply-reordering/constants.js | 12 +++++++++-- .../mocks/in-progress-forms/mdot/index.js | 1 + .../pages/chooseSupplies.jsx | 8 ++++++++ 6 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx diff --git a/src/applications/mhv-supply-reordering/README.md b/src/applications/mhv-supply-reordering/README.md index ab8a80963aa6..699ae22eb982 100644 --- a/src/applications/mhv-supply-reordering/README.md +++ b/src/applications/mhv-supply-reordering/README.md @@ -84,3 +84,23 @@ A web-component-pattern is a group of web-component-fields that can span one or ``` When requesting `GET /v0/in_progress_forms/MDOT`, the MDOT client in vets-api will make a request to the system of record for veteran details and supplies available to the veteran. See `V0::InProgressFormsController.camelized_prefill_for_user` and `FormProfiles::MDOT#prefill`. On the front-end, test against the possible responses for `MDOT::Client.new(user).get_supplies` which are mapped to `mdot.exceptions` values in `vets-api/config/locales/exceptions.en.yml` and then passed along in the response. Also, see `vets-api/spec/support/vcr_cassettes/mdot/get_supplies*.yml`. + +## Dynamic Forms + +[[using update and replace schema funcs](https://depo-platform-documentation.scrollhelp.site/developer-docs/va-forms-library-how-to-use-updateschema-and-repla)] + +see `src/applications/disability-benefits/all-claims/pages/toxicExposure/toxicExposureConditions.js` for an example. + +## Device Types + +How do we access other device types? (e.g. - assistive devices, nebulizers)... It sounds like these are included in the request for supplies. + +(I believe) The `productGroup` property of a supply can be one of the following values: `['accessories', 'batteries', 'apnea']`. + +## Thoughts & Improvements + +Should there be a `GET /v0/mdot/supplies` endpoint that returns available supplies? Current implementation of fetching supplies from `/v0/in_progres_forms/MDOT` feels strange. + +Need to understand the relationship between backend forms and frontend forms. + +Just build the frontend. diff --git a/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx b/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx index fee064d32508..61cc1719d501 100644 --- a/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx +++ b/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx @@ -44,8 +44,9 @@ class SelectArrayItemsWidget extends React.Component { const itemsList = formData.supplies?.length > 0 && formData.supplies.map((item, index) => { - const itemDescription = `Quantity: ${item.quantity} - Last ordered on: ${item.lastOrderDate}`; + let itemDescription = item?.deviceName ? `Device: ${item.deviceName}\n` : ''; + itemDescription += `Quantity: ${item.quantity}\n`; + itemDescription += `Last ordered on ${item.lastOrderDate}\n`; const itemIsSelected = !!get( selectedPropName || this.defaultSelectedPropName, @@ -81,6 +82,7 @@ class SelectArrayItemsWidget extends React.Component { typeof itemIsSelected === 'undefined' ? false : itemIsSelected } onVaChange={event => this.onChange(index, event.target.checked)} + style={{ whiteSpace: 'pre-line' }} /> )}
diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index cb8b9468aff3..7b3ade7b9596 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -4,6 +4,7 @@ import { VA_FORM_IDS } from 'platform/forms/constants'; import { TITLE as title } from '../constants'; import manifest from '../manifest.json'; +import chooseSupplies from '../pages/chooseSupplies'; import contactInformation from '../pages/contactInformation'; import selectSupplies from '../pages/selectSupplies'; @@ -45,6 +46,17 @@ const customText = { }; const chapters = { + // chooseSuppliesChapter: { + // title: 'Choose supplies', + // pages: { + // chooseSupplies: { + // path: 'choose-supplies', + // title: 'Choose supplies', + // uiSchema: chooseSupplies.uiSchema, + // schema: chooseSupplies.schema, + // }, + // }, + // }, selectSuppliesChapter: { title: 'Select supplies', pages: { diff --git a/src/applications/mhv-supply-reordering/constants.js b/src/applications/mhv-supply-reordering/constants.js index 04993f7d2155..e2ee6254885f 100644 --- a/src/applications/mhv-supply-reordering/constants.js +++ b/src/applications/mhv-supply-reordering/constants.js @@ -1,3 +1,5 @@ +const { freeze } = Object; + export const TITLE = 'Order Medical Supplies'; export const SUBTITLE = 'Use this form to order hearing aid batteries and CPAP supplies'; @@ -7,8 +9,14 @@ export const DLC_TELEPHONE = '3032736200'; export const HEALTH_FACILITIES_URL = '/find-locations/?facilityType=health'; -export const MDOT_ERROR_CODES = { +export const MDOT_ERROR_CODES = freeze({ DECEASED: 'MDOT_deceased', INVALID: 'MDOT_invalid', SUPPLIES_NOT_FOUND: 'MDOT_supplies_not_found', -}; +}); + +export const PRODUCT_GROUPS = freeze({ + ACCESSORIES: 'accessories', + BATTERIES: 'batteries', + APNEA: 'apnea', +}); diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js index 8bd54a3ebd29..a8778a443b16 100644 --- a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js @@ -128,6 +128,7 @@ const putOk = { module.exports = { // `GET /v0/in_progress_forms/${VA_FORM_IDS.FORM_VA_2346A}`: getOk, 'GET /v0/in_progress_forms/MDOT': getOk, + 'OPTIONS /v0/in_progress_forms/MDOT': 'OK', // 'GET /v0/in_progress_forms/MDOT': (_, res) => res.status(404).json(notFound), // 'GET /v0/in_progress_forms/MDOT': (_, res) => res.status(500).json(internalServerError), 'PUT /v0/in_progress_forms/MDOT': putOk, diff --git a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx new file mode 100644 index 000000000000..ea5b0c4938e5 --- /dev/null +++ b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx @@ -0,0 +1,8 @@ +/** @type {PageSchema} */ +export default { + uiSchema: {}, + schema: { + type: 'object', + properties: {}, + }, +}; From 7c96130b410b29a785c80d3401ced85ef108daf3 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 25 Nov 2024 10:20:08 -0500 Subject: [PATCH 15/28] Save point --- .../mhv-supply-reordering/config/form.js | 38 +++++++-------- .../pages/chooseSupplies.jsx | 46 ++++++++++++++++++- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 7b3ade7b9596..15956057c002 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -6,7 +6,7 @@ import manifest from '../manifest.json'; import chooseSupplies from '../pages/chooseSupplies'; import contactInformation from '../pages/contactInformation'; -import selectSupplies from '../pages/selectSupplies'; +// import selectSupplies from '../pages/selectSupplies'; import EditAddress from '../components/EditAddress'; import EditEmail from '../components/EditEmail'; @@ -46,28 +46,28 @@ const customText = { }; const chapters = { - // chooseSuppliesChapter: { - // title: 'Choose supplies', - // pages: { - // chooseSupplies: { - // path: 'choose-supplies', - // title: 'Choose supplies', - // uiSchema: chooseSupplies.uiSchema, - // schema: chooseSupplies.schema, - // }, - // }, - // }, - selectSuppliesChapter: { - title: 'Select supplies', + chooseSuppliesChapter: { + title: 'Choose supplies', pages: { - selectSupplies: { - path: 'select-supplies', - title: 'Select supplies', - uiSchema: selectSupplies.uiSchema, - schema: selectSupplies.schema, + chooseSupplies: { + path: 'choose-supplies', + title: 'Choose supplies', + uiSchema: chooseSupplies.uiSchema, + schema: chooseSupplies.schema, }, }, }, + // selectSuppliesChapter: { + // title: 'Select supplies', + // pages: { + // selectSupplies: { + // path: 'select-supplies', + // title: 'Select supplies', + // uiSchema: selectSupplies.uiSchema, + // schema: selectSupplies.schema, + // }, + // }, + // }, contactInformationChapter: { title: 'Contact information', pages: { diff --git a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx index ea5b0c4938e5..fd334f8bb28e 100644 --- a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx +++ b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx @@ -1,8 +1,50 @@ +import React from 'react'; +// import { validateAtLeastOneSelected } from '../utils/validators'; +import { checkboxGroupSchema } from '@department-of-veterans-affairs/platform-forms-system/web-component-patterns'; + +const Description = ({ formData }) => { + const count = formData?.supplies?.length || 0; + + return ( + <> +

Available for reorder

+

+ You have {count} {count > 1 ? 'supplies' : 'supply'} that are available + for reorder. +

+

+ Note: For CPAP supplies, each order is a 12-month + supply. You can only order each item once every 12 months. +

+

+ For hearing aid supplies, each order is a 6-month supply. You can only + order each item once every 6 months. +

+ + ); +}; + /** @type {PageSchema} */ export default { - uiSchema: {}, + uiSchema: { + 'ui:description': Description, + chosenSupplies: { + title: 'Select available supplies for reorder', + labels: {}, + required: false, + replaceSchema: formData => + checkboxGroupSchema(formData?.supplies?.map(s => s.productId)), + updateUiSchema: formData => + formData?.supplies?.reduce( + (acc, s) => ({ ...acc, [s.productId]: s.productName }), + {}, + ), + }, + }, schema: { type: 'object', - properties: {}, + properties: { + chosenSupplies: checkboxGroupSchema([]), + }, }, }; From af807bec997bbc2890cdb0bca1bcc1aa6215a5ae Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 25 Nov 2024 12:46:23 -0500 Subject: [PATCH 16/28] Checkboxes! --- .../pages/chooseSupplies.jsx | 30 +++---- .../sass/mhv-supply-reordering.scss | 13 ++-- .../tests/utils/helpers.unit.spec.js | 78 +++++++++++++++++++ .../mhv-supply-reordering/utils/helpers.js | 37 ++++++++- 4 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js diff --git a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx index fd334f8bb28e..7457b87174f9 100644 --- a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx +++ b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx @@ -2,6 +2,18 @@ import React from 'react'; // import { validateAtLeastOneSelected } from '../utils/validators'; import { checkboxGroupSchema } from '@department-of-veterans-affairs/platform-forms-system/web-component-patterns'; +import { + suppliesReplaceSchema, + suppliesUpdateUiSchema, + suppliesUi, +} from '../utils/helpers'; + +const numberOfSuppliesPhrase = count => { + if (count > 1) return `${count} supplies`; + if (count < 1) return 'no supplies'; + return `${count} supply`; +}; + const Description = ({ formData }) => { const count = formData?.supplies?.length || 0; @@ -9,8 +21,7 @@ const Description = ({ formData }) => { <>

Available for reorder

- You have {count} {count > 1 ? 'supplies' : 'supply'} that are available - for reorder. + You have {numberOfSuppliesPhrase(count)} that are available for reorder.

Note: For CPAP supplies, each order is a 12-month @@ -28,18 +39,11 @@ const Description = ({ formData }) => { export default { uiSchema: { 'ui:description': Description, - chosenSupplies: { + chosenSupplies: suppliesUi({ title: 'Select available supplies for reorder', - labels: {}, - required: false, - replaceSchema: formData => - checkboxGroupSchema(formData?.supplies?.map(s => s.productId)), - updateUiSchema: formData => - formData?.supplies?.reduce( - (acc, s) => ({ ...acc, [s.productId]: s.productName }), - {}, - ), - }, + replaceSchema: suppliesReplaceSchema, + updateUiSchema: suppliesUpdateUiSchema, + }), }, schema: { type: 'object', diff --git a/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss b/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss index 80433ce82d02..87de3550f6e3 100644 --- a/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss +++ b/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss @@ -1,6 +1,7 @@ -@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-process-list"; -@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-form-process"; -@import "../../../platform/forms/sass/m-schemaform"; -@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-modal"; -@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-omb-info"; -@import "../../../platform/forms/sass/m-form-confirmation"; +// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-process-list"; +// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-form-process"; +// @import "../../../platform/forms/sass/m-schemaform"; +// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-modal"; +// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-omb-info"; +// @import "../../../platform/forms/sass/m-form-confirmation"; +@import "~@department-of-veterans-affairs/css-library/dist/tokens/scss/variables"; diff --git a/src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js b/src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js new file mode 100644 index 000000000000..8cd8e584c7e8 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js @@ -0,0 +1,78 @@ +import { expect } from 'chai'; +import { + suppliesReplaceSchema, + suppliesUpdateUiSchema, +} from '../../utils/helpers'; + +describe('suppliesReplaceSchema', () => { + it('handles an empty supplies array', () => { + const result = suppliesReplaceSchema({ supplies: [] }); + expect(result).to.deep.equal({ type: 'object', properties: {} }); + }); + + it('handles a null supplies value', () => { + const result = suppliesReplaceSchema({ supplies: null }); + expect(result).to.deep.equal({ type: 'object', properties: {} }); + }); + + it('handles an undefined supplies value', () => { + const result = suppliesReplaceSchema({ supplies: undefined }); + expect(result).to.deep.equal({ type: 'object', properties: {} }); + }); + + it('handles an empty object', () => { + const result = suppliesReplaceSchema({}); + expect(result).to.deep.equal({ type: 'object', properties: {} }); + }); + + it('handles an array of supply objects', () => { + const supplies = [ + { productId: 123, productName: 'Product 123' }, + { productId: 456, productName: 'Product 456' }, + ]; + const expected = { + type: 'object', + properties: { + '123': { type: 'boolean' }, + '456': { type: 'boolean' }, + }, + }; + const result = suppliesReplaceSchema({ supplies }); + expect(result).to.deep.equal(expected); + }); +}); + +describe('suppliesUpdateUiSchema', () => { + it('handles an empty supplies array', () => { + const result = suppliesUpdateUiSchema({ supplies: [] }); + expect(result).to.deep.equal({}); + }); + + it('handles a null supplies value', () => { + const result = suppliesUpdateUiSchema({ supplies: null }); + expect(result).to.deep.equal({}); + }); + + it('handles an undefined supplies value', () => { + const result = suppliesUpdateUiSchema({ supplies: undefined }); + expect(result).to.deep.equal({}); + }); + + it('handles an empty object', () => { + const result = suppliesUpdateUiSchema({}); + expect(result).to.deep.equal({}); + }); + + it('handles an array of supply objects', () => { + const supplies = [ + { productId: 123, productName: 'Product 123' }, + { productId: 456, productName: 'Product 456' }, + ]; + const expected = { + '123': 'Product 123', + '456': 'Product 456', + }; + const result = suppliesUpdateUiSchema({ supplies }); + expect(result).to.deep.equal(expected); + }); +}); diff --git a/src/applications/mhv-supply-reordering/utils/helpers.js b/src/applications/mhv-supply-reordering/utils/helpers.js index 2a3e245e0731..5c076f17a401 100644 --- a/src/applications/mhv-supply-reordering/utils/helpers.js +++ b/src/applications/mhv-supply-reordering/utils/helpers.js @@ -1,5 +1,40 @@ import { format } from 'date-fns'; +import { + checkboxGroupSchema, + checkboxGroupUI, +} from '@department-of-veterans-affairs/platform-forms-system/web-component-patterns'; const formatDate = dateString => format(new Date(dateString), 'MMMM d, yyyy'); -export { formatDate }; +const suppliesReplaceSchema = formData => + checkboxGroupSchema((formData?.supplies || []).map(s => s.productId)); + +const suppliesUpdateUiSchema = formData => + (formData?.supplies || []).reduce( + (acc, s) => ({ ...acc, [s.productId]: { 'ui:title': s.productName } }), + {}, + ); + +const suppliesUi = ({ + title, + description, + hint, + replaceSchema, + updateUiSchema, +}) => + checkboxGroupUI({ + title, + description, + hint, + labels: {}, + required: false, + replaceSchema, + updateUiSchema, + }); + +export { + formatDate, + suppliesReplaceSchema, + suppliesUpdateUiSchema, + suppliesUi, +}; From f62ece4c7eac73100d949ab2d5157ba0414072b9 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 25 Nov 2024 13:58:15 -0500 Subject: [PATCH 17/28] Adds product description --- .../mhv-supply-reordering/config/form.js | 12 ------------ .../sass/mhv-supply-reordering.scss | 6 ------ .../mhv-supply-reordering/utils/helpers.js | 10 +++++++++- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 15956057c002..8cffd4e274e8 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -6,7 +6,6 @@ import manifest from '../manifest.json'; import chooseSupplies from '../pages/chooseSupplies'; import contactInformation from '../pages/contactInformation'; -// import selectSupplies from '../pages/selectSupplies'; import EditAddress from '../components/EditAddress'; import EditEmail from '../components/EditEmail'; @@ -57,17 +56,6 @@ const chapters = { }, }, }, - // selectSuppliesChapter: { - // title: 'Select supplies', - // pages: { - // selectSupplies: { - // path: 'select-supplies', - // title: 'Select supplies', - // uiSchema: selectSupplies.uiSchema, - // schema: selectSupplies.schema, - // }, - // }, - // }, contactInformationChapter: { title: 'Contact information', pages: { diff --git a/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss b/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss index 87de3550f6e3..822eeaedb6c2 100644 --- a/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss +++ b/src/applications/mhv-supply-reordering/sass/mhv-supply-reordering.scss @@ -1,7 +1 @@ -// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-process-list"; -// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-form-process"; -// @import "../../../platform/forms/sass/m-schemaform"; -// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-modal"; -// @import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-omb-info"; -// @import "../../../platform/forms/sass/m-form-confirmation"; @import "~@department-of-veterans-affairs/css-library/dist/tokens/scss/variables"; diff --git a/src/applications/mhv-supply-reordering/utils/helpers.js b/src/applications/mhv-supply-reordering/utils/helpers.js index 5c076f17a401..7deb5088b19b 100644 --- a/src/applications/mhv-supply-reordering/utils/helpers.js +++ b/src/applications/mhv-supply-reordering/utils/helpers.js @@ -11,7 +11,15 @@ const suppliesReplaceSchema = formData => const suppliesUpdateUiSchema = formData => (formData?.supplies || []).reduce( - (acc, s) => ({ ...acc, [s.productId]: { 'ui:title': s.productName } }), + (acc, { deviceName, lastOrderDate, productId, productName, quantity }) => ({ + ...acc, + [productId]: { + 'ui:title': productName, + 'ui:description': `Device: ${deviceName}\nQuantity: ${quantity}\nLast ordered on ${formatDate( + lastOrderDate, + )}`, + }, + }), {}, ); From ae5e3792623d95e105ba88f99691197f21241b22 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 25 Nov 2024 14:08:57 -0500 Subject: [PATCH 18/28] Updates readme --- src/applications/mhv-supply-reordering/README.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/applications/mhv-supply-reordering/README.md b/src/applications/mhv-supply-reordering/README.md index 699ae22eb982..b720b0670065 100644 --- a/src/applications/mhv-supply-reordering/README.md +++ b/src/applications/mhv-supply-reordering/README.md @@ -49,7 +49,7 @@ A web-component-pattern is a group of web-component-fields that can span one or ## Form Flow - IntroductionPage -- SelectSupplies +- ChooseSupplies - ContactInformation (optionally: EditEmail, EditAddress) - ReviewPage - ConfirmationPage @@ -85,7 +85,7 @@ A web-component-pattern is a group of web-component-fields that can span one or When requesting `GET /v0/in_progress_forms/MDOT`, the MDOT client in vets-api will make a request to the system of record for veteran details and supplies available to the veteran. See `V0::InProgressFormsController.camelized_prefill_for_user` and `FormProfiles::MDOT#prefill`. On the front-end, test against the possible responses for `MDOT::Client.new(user).get_supplies` which are mapped to `mdot.exceptions` values in `vets-api/config/locales/exceptions.en.yml` and then passed along in the response. Also, see `vets-api/spec/support/vcr_cassettes/mdot/get_supplies*.yml`. -## Dynamic Forms +## Dynamic Form Fields [[using update and replace schema funcs](https://depo-platform-documentation.scrollhelp.site/developer-docs/va-forms-library-how-to-use-updateschema-and-repla)] @@ -93,14 +93,6 @@ see `src/applications/disability-benefits/all-claims/pages/toxicExposure/toxicEx ## Device Types -How do we access other device types? (e.g. - assistive devices, nebulizers)... It sounds like these are included in the request for supplies. +How do we access other device types? (e.g. - assistive devices, nebulizers). Are these included in the request for supplies? -(I believe) The `productGroup` property of a supply can be one of the following values: `['accessories', 'batteries', 'apnea']`. - -## Thoughts & Improvements - -Should there be a `GET /v0/mdot/supplies` endpoint that returns available supplies? Current implementation of fetching supplies from `/v0/in_progres_forms/MDOT` feels strange. - -Need to understand the relationship between backend forms and frontend forms. - -Just build the frontend. +The `productGroup` property of a supply can be one of the following values: `['accessories', 'batteries', 'apnea']`. From 46080d33bbd8026a71dd4759996a74f8ff34c25c Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 25 Nov 2024 14:19:05 -0500 Subject: [PATCH 19/28] Removes unused code --- .../components/SelectArrayItemsWidget.jsx | 177 ------------------ .../components/SupplyField.jsx | 20 -- .../pages/selectSupplies.jsx | 74 -------- .../fixtures/mocks/local-mock-responses.js | 8 - .../tests/fixtures/mocks/user.json | 56 ------ 5 files changed, 335 deletions(-) delete mode 100644 src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx delete mode 100644 src/applications/mhv-supply-reordering/components/SupplyField.jsx delete mode 100644 src/applications/mhv-supply-reordering/pages/selectSupplies.jsx delete mode 100644 src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js delete mode 100644 src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json diff --git a/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx b/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx deleted file mode 100644 index 61cc1719d501..000000000000 --- a/src/applications/mhv-supply-reordering/components/SelectArrayItemsWidget.jsx +++ /dev/null @@ -1,177 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { VaCheckbox } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; - -import get from 'platform/utilities/data/get'; -import set from 'platform/utilities/data/set'; -import { setData } from 'platform/forms-system/src/js/actions'; -import { autoSaveForm } from 'platform/forms/save-in-progress/actions'; - -class SelectArrayItemsWidget extends React.Component { - defaultSelectedPropName = 'view:selected'; - - keyValue = 'supplies'; - - onChange = (index, checked) => { - const items = set( - `[${index}].${this.props.formData.supplies['view:selected'] || - this.defaultSelectedPropName}`, - checked, - this.props.value, - ); - this.props.onChange(items); - }; - - render() { - const { value: supplies, options, formContext, formData } = this.props; - - // Need customTitle to set error message above title. - const { label: selectedPropName, customTitle } = options; - - // inReviewMode = true (review page view, not in edit mode) - // inReviewMode = false (in edit mode) - const { onReviewPage } = formContext; - const inReviewMode = onReviewPage && formContext.reviewMode; - - const hasSelections = formData.supplies?.reduce( - (result, item) => - result || !!get(selectedPropName || this.defaultSelectedPropName, item), - false, - ); - - const itemsList = - formData.supplies?.length > 0 && - formData.supplies.map((item, index) => { - let itemDescription = item?.deviceName ? `Device: ${item.deviceName}\n` : ''; - itemDescription += `Quantity: ${item.quantity}\n`; - itemDescription += `Last ordered on ${item.lastOrderDate}\n`; - - const itemIsSelected = !!get( - selectedPropName || this.defaultSelectedPropName, - item, - ); - - // Don't show un-selected ratings in review mode - if (inReviewMode && !itemIsSelected) { - return null; - } - - const checkboxVisible = - !onReviewPage || (onReviewPage && !inReviewMode); - - const widgetClasses = classNames( - 'form-checkbox', - options.widgetClassNames, - { selected: itemIsSelected }, - ); - - // When a `customTitle` option is included, the ObjectField is set to - // wrap its contents in a div instead of a dl, so we don't need a - // include dt and dd elements in the markup; this change fixes an - // accessibility issue - return ( -

- {checkboxVisible && ( - this.onChange(index, event.target.checked)} - style={{ whiteSpace: 'pre-line' }} - /> - )} -
- ); - }); - - const hasCustomTitle = !!customTitle?.trim(); - const Tag = formContext.onReviewPage ? 'h4' : 'h3'; - - const content = - itemsList && (!inReviewMode || (inReviewMode && hasSelections)) ? ( - itemsList - ) : ( -

- - {`No supplies ${supplies?.length > 0 ? 'selected' : 'found'}`} - -

- ); - - return hasCustomTitle ? ( -
- - {customTitle} - - {content} -
- ) : ( - <>{content} - ); - } -} - -SelectArrayItemsWidget.propTypes = { - formContext: PropTypes.object.isRequired, - formId: PropTypes.string.isRequired, - id: PropTypes.string.isRequired, - options: PropTypes.shape({ - title: PropTypes.string, - customTitle: PropTypes.string, - field: PropTypes.string, - label: PropTypes.func, - showFieldLabel: PropTypes.bool, - validations: PropTypes.array, - }).isRequired, - value: PropTypes.arrayOf( - PropTypes.shape({ - productName: PropTypes.string.isRequired, - productId: PropTypes.number.isRequired, - }), - ).isRequired, - autoSaveForm: PropTypes.func, - formData: PropTypes.shape({}), - metadata: PropTypes.shape({ - version: PropTypes.number, - returnUrl: PropTypes.string, - submission: PropTypes.shape({}), - }), - required: PropTypes.bool, - setData: PropTypes.func, -}; - -SelectArrayItemsWidget.defaultProps = { - value: [], - formData: {}, - loadedData: { - metadata: {}, - }, - setData: () => {}, - autoSaveForm: () => {}, -}; - -const mapDispatchToProps = { - setData, - autoSaveForm, -}; - -const mapStateToProps = state => { - const { form } = state; - return { - formId: form.formId, - formData: form.data, - metadata: form.loadedData?.metadata || {}, - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(SelectArrayItemsWidget); - -export { SelectArrayItemsWidget }; diff --git a/src/applications/mhv-supply-reordering/components/SupplyField.jsx b/src/applications/mhv-supply-reordering/components/SupplyField.jsx deleted file mode 100644 index c41b4dcff0c8..000000000000 --- a/src/applications/mhv-supply-reordering/components/SupplyField.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default function SupplyField({ formData }) { - const { supplyName } = formData.supplies; - - return ( -
- {supplyName} -
- ); -} - -SupplyField.propTypes = { - formData: PropTypes.shape({ - supplies: PropTypes.shape({ - supplyName: PropTypes.string, - }), - }), -}; diff --git a/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx b/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx deleted file mode 100644 index 54f9e74c808c..000000000000 --- a/src/applications/mhv-supply-reordering/pages/selectSupplies.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import SupplyField from '../components/SupplyField'; -import SelectArrayItemsWidget from '../components/SelectArrayItemsWidget'; -import { validateAtLeastOneSelected } from '../utils/validators'; - -const Description = ({ formData }) => { - const count = formData?.supplies?.length || 0; - - return ( - <> -

Available for reorder

-

- You have {count} {count > 1 ? 'supplies' : 'supply'} that are available - for reorder. -

-

- Note: For CPAP supplies, each order is a 12-month - supply. You can only order each item once every 12 months. -

-

- For hearing aid supplies, each order is a 6-month supply. You can only - order each item once every 6 months. -

- - ); -}; - -export default { - uiSchema: { - 'ui:description': Description, - supplies: { - 'ui:title': 'Select available supplies for reorder', - 'ui:field': 'StringField', - 'ui:widget': SelectArrayItemsWidget, - 'ui:options': { - showFieldLabel: true, - viewField: SupplyField, - }, - 'ui:required': () => true, - 'ui:validations': [validateAtLeastOneSelected], - }, - }, - schema: { - type: 'object', - properties: { - supplies: { - type: 'array', - minItems: 1, - items: { - type: 'object', - required: ['supply'], - properties: { - supply: { - type: 'object', - properties: { - productId: { - type: 'string', - }, - productName: { - type: 'string', - }, - }, - }, - 'view:selected': { - type: 'boolean', - default: false, - }, - 'view:descriptionInfo': { type: 'object', properties: {} }, - }, - }, - }, - }, - }, -}; diff --git a/src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js deleted file mode 100644 index e6c6e27268da..000000000000 --- a/src/applications/mhv-supply-reordering/tests/fixtures/mocks/local-mock-responses.js +++ /dev/null @@ -1,8 +0,0 @@ -// yarn mock-api --responses ./src/applications/{application}/tests/e2e/fixtures/mocks/local-mock-responses.js -const mockUser = require('./user.json'); - -const responses = { - 'GET /v0/user': mockUser, -}; - -module.exports = responses; diff --git a/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json deleted file mode 100644 index c707324756e1..000000000000 --- a/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "data": { - "attributes": { - "profile": { - "sign_in": { - "service_name": "idme" - }, - "email": "john.doe@example.com", - "loa": { "current": 3 }, - "first_name": "John", - "middle_name": "", - "last_name": "Doe", - "gender": "M", - "birth_date": "1985-01-01", - "verified": true - }, - "veteran_status": { - "status": "OK", - "is_veteran": true, - "served_in_military": true - }, - "in_progress_forms": [], - "prefills_available": [], - "services": [ - "facilities", - "hca", - "edu-benefits", - "evss-claims", - "form526", - "user-profile", - "health-records", - "rx", - "messaging" - ], - "va_profile": { - "status": "OK", - "birth_date": "19850101", - "family_name": "Doe", - "gender": "M", - "given_names": ["John", ""], - "active_status": "active", - "facilities": [ - { - "facility_id": "983", - "is_cerner": false - }, - { - "facility_id": "984", - "is_cerner": false - } - ] - } - } - }, - "meta": { "errors": null } -} From 9a0848ce706d52749c7296626e266f67831a899f Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 25 Nov 2024 14:28:02 -0500 Subject: [PATCH 20/28] Readd user.json --- .../tests/fixtures/mocks/user.json | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json diff --git a/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json new file mode 100644 index 000000000000..c707324756e1 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/fixtures/mocks/user.json @@ -0,0 +1,56 @@ +{ + "data": { + "attributes": { + "profile": { + "sign_in": { + "service_name": "idme" + }, + "email": "john.doe@example.com", + "loa": { "current": 3 }, + "first_name": "John", + "middle_name": "", + "last_name": "Doe", + "gender": "M", + "birth_date": "1985-01-01", + "verified": true + }, + "veteran_status": { + "status": "OK", + "is_veteran": true, + "served_in_military": true + }, + "in_progress_forms": [], + "prefills_available": [], + "services": [ + "facilities", + "hca", + "edu-benefits", + "evss-claims", + "form526", + "user-profile", + "health-records", + "rx", + "messaging" + ], + "va_profile": { + "status": "OK", + "birth_date": "19850101", + "family_name": "Doe", + "gender": "M", + "given_names": ["John", ""], + "active_status": "active", + "facilities": [ + { + "facility_id": "983", + "is_cerner": false + }, + { + "facility_id": "984", + "is_cerner": false + } + ] + } + } + }, + "meta": { "errors": null } +} From 5a6bab3e8192b3cf07bfd9e1e087425deaac0086 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Nov 2024 09:53:08 -0500 Subject: [PATCH 21/28] Updates --- .../alerts/AlertNoSuppliesForReorder.jsx | 12 +-- .../mhv-supply-reordering/constants.js | 2 +- .../mocks/in-progress-forms/mdot/index.js | 78 ++++++++++--------- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx b/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx index 0655fc9d4e45..f8faaa9f3586 100644 --- a/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx +++ b/src/applications/mhv-supply-reordering/components/alerts/AlertNoSuppliesForReorder.jsx @@ -10,7 +10,7 @@ import { formatDate } from '../../utils/helpers'; * @returns the alert */ const AlertNoSuppliesForReorder = ({ reorderDate }) => { - const date = formatDate(reorderDate); + const date = reorderDate ? formatDate(reorderDate) : undefined; return ( { >

You can’t reorder your items at this time

- - Our records show that your items aren’t available for reorder until{' '} - {date}. You can only order items once every 5 months. - + {date && ( + + Our records show that your items aren’t available for reorder until{' '} + {date}. You can only order items once every 5 months. + + )} If you need an item sooner, call the DLC Customer Service Section at{' '} or email . diff --git a/src/applications/mhv-supply-reordering/constants.js b/src/applications/mhv-supply-reordering/constants.js index e2ee6254885f..355f49f85dc6 100644 --- a/src/applications/mhv-supply-reordering/constants.js +++ b/src/applications/mhv-supply-reordering/constants.js @@ -2,7 +2,7 @@ const { freeze } = Object; export const TITLE = 'Order Medical Supplies'; export const SUBTITLE = - 'Use this form to order hearing aid batteries and CPAP supplies'; + 'Use this form to order hearing aid batteries and accessories and CPAP supplies'; export const DLC_EMAIL = 'dalc.css@va.gov'; export const DLC_TELEPHONE = '3032736200'; diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js index a8778a443b16..53bb015eb52e 100644 --- a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js @@ -1,3 +1,43 @@ +const supplies = [ + { + productName: 'ERHK HE11 680 MINI', + productGroup: 'Accessory', + productId: 6584, + availableForReorder: true, + lastOrderDate: '2022-05-16', + nextAvailabilityDate: '2022-10-16', + quantity: 5, + }, + { + productName: 'AIRFIT F10 M', + productGroup: 'Apnea', + productId: 6641, + availableForReorder: true, + lastOrderDate: '2022-07-05', + nextAvailabilityDate: '2022-12-05', + quantity: 1, + }, + { + productName: 'AIRFIT P10', + productGroup: 'Apnea', + productId: 6650, + availableForReorder: true, + lastOrderDate: '2022-07-05', + nextAvailabilityDate: '2022-12-05', + quantity: 1, + }, + { + productName: 'AIRCURVE10-ASV-CLIMATELINE', + productGroup: 'Apnea', + productId: 8467, + lastOrderDate: '2022-07-06', + nextAvailabilityDate: '2022-12-06', + quantity: 1, + }, +]; + +// const supplies = []; + const getOk = { formData: { fullName: { @@ -30,43 +70,7 @@ const getOk = { apneas: true, batteries: true, }, - supplies: [ - { - productName: 'ERHK HE11 680 MINI', - productGroup: 'Accessory', - productId: 6584, - availableForReorder: true, - lastOrderDate: '2022-05-16', - nextAvailabilityDate: '2022-10-16', - quantity: 5, - }, - { - productName: 'AIRFIT F10 M', - productGroup: 'Apnea', - productId: 6641, - availableForReorder: true, - lastOrderDate: '2022-07-05', - nextAvailabilityDate: '2022-12-05', - quantity: 1, - }, - { - productName: 'AIRFIT P10', - productGroup: 'Apnea', - productId: 6650, - availableForReorder: true, - lastOrderDate: '2022-07-05', - nextAvailabilityDate: '2022-12-05', - quantity: 1, - }, - { - productName: 'AIRCURVE10-ASV-CLIMATELINE', - productGroup: 'Apnea', - productId: 8467, - lastOrderDate: '2022-07-06', - nextAvailabilityDate: '2022-12-06', - quantity: 1, - }, - ], + supplies, }, metadata: { version: 0, From 26bafcdb68150ea4633932154bce007ebf2ffbef Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Nov 2024 10:27:10 -0500 Subject: [PATCH 22/28] Fixes supply phrase --- .../mhv-supply-reordering/pages/chooseSupplies.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx index 7457b87174f9..2c9a59703d49 100644 --- a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx +++ b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx @@ -10,8 +10,8 @@ import { const numberOfSuppliesPhrase = count => { if (count > 1) return `${count} supplies`; - if (count < 1) return 'no supplies'; - return `${count} supply`; + if (count === 1) return `${count} supply`; + return 'no supplies'; }; const Description = ({ formData }) => { From f4b9e2e7f8375491d9298b822997c73ff75b572a Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Nov 2024 11:22:43 -0500 Subject: [PATCH 23/28] No e2e specs, yet --- ...g.cypress.spec.js => mhv-supply-reordering.cypress._spec.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/applications/mhv-supply-reordering/tests/{mhv-supply-reordering.cypress.spec.js => mhv-supply-reordering.cypress._spec.js} (94%) diff --git a/src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress.spec.js b/src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress._spec.js similarity index 94% rename from src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress.spec.js rename to src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress._spec.js index 906270a8aace..27beb5a90b5b 100644 --- a/src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress.spec.js +++ b/src/applications/mhv-supply-reordering/tests/mhv-supply-reordering.cypress._spec.js @@ -28,7 +28,7 @@ const testConfig = createTestConfig( // Skip tests in CI until the form is released. // Remove this setting when the form has a content page in production. - skip: Cypress.env('CI'), + skip: Cypress.env('CI'), // doesn't work as advertised. }, manifest, formConfig, From eb480c3a43fed2f7b901a52cb98e9d2b2ae701e6 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Nov 2024 13:54:21 -0500 Subject: [PATCH 24/28] Removes commented code, adds unit specs --- .../components/EditAddress.jsx | 59 ----------- .../components/SuppliesAvailable.jsx | 5 +- .../components/SuppliesUnavailable.jsx | 78 +++++++-------- .../mhv-supply-reordering/config/form.js | 8 -- .../mocks/in-progress-forms/mdot/errors.js | 39 ++++++++ .../mocks/in-progress-forms/mdot/index.js | 40 +------- .../pages/chooseSupplies.jsx | 7 +- .../mhv-supply-reordering/selectors/index.js | 2 +- .../mdotInProgressFormsReducer.unit.spec.js | 58 +++++++++++ .../tests/selectors/showAlert.unit.spec.js | 98 +++++++++++++++++++ .../tests/utils/helpers.unit.spec.js | 7 +- .../mhv-supply-reordering/utils/helpers.js | 14 ++- 12 files changed, 254 insertions(+), 161 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/errors.js create mode 100644 src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js create mode 100644 src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js diff --git a/src/applications/mhv-supply-reordering/components/EditAddress.jsx b/src/applications/mhv-supply-reordering/components/EditAddress.jsx index b82c4521005e..546789da5c49 100644 --- a/src/applications/mhv-supply-reordering/components/EditAddress.jsx +++ b/src/applications/mhv-supply-reordering/components/EditAddress.jsx @@ -8,18 +8,12 @@ import { import constants from 'vets-json-schema/dist/constants.json'; import UnsavedFieldNote from './UnsavedFieldNote'; -/** - * PATTERNS - * NONBLANK_PATTERN - rejects white space only - * POSTAL_CODE_PATTERNS - Matches US/Mexican/Canadian codes - */ const COUNTRY_VALUES = constants.countries.map(country => country.value); const COUNTRY_NAMES = constants.countries.map(country => country.label); const MILITARY_STATE_VALUES = constants.militaryStates.map( state => state.value, ); -// const MILITARY_STATE_NAMES = constants.militaryStates.map(state => state.label); // filtered States that include US territories const filteredStates = constants.states.USA.filter( @@ -29,58 +23,6 @@ const filteredStates = constants.states.USA.filter( const STATE_VALUES = filteredStates.map(state => state.value); const STATE_NAMES = filteredStates.map(state => state.label); -/* -const NONBLANK_PATTERN = '^.*\\S.*'; -const POSTAL_CODE_PATTERNS = { - CAN: - '^(?=[^DdFfIiOoQqUu\\d\\s])[A-Za-z]\\d(?=[^DdFfIiOoQqUu\\d\\s])[A-Za-z]\\s{0,1}\\d(?=[^DdFfIiOoQqUu\\d\\s])[A-Za-z]\\d$', - MEX: '^\\d{5}$', - USA: '^\\d{5}$', -}; -const POSTAL_CODE_PATTERN_ERROR_MESSAGES = { - CAN: 'Enter a valid 6-character postal code', - MEX: 'Enter a valid 5-digit postal code', - USA: 'Enter a valid 5-digit ZIP code', -}; -const CITY_ERROR_MESSAGES = { - default: 'City is required', - military: 'Select a post office type: APO, FPO, or DPO', -}; -const MILITARY_CITY_TITLE = 'APO/FPO/DPO'; -const MILITARY_CITY_VALUES = constants.militaryCities.map(city => city.value); -const MILITARY_CITY_NAMES = constants.militaryCities.map(city => city.label); -const CAN_STATE_VALUES = constants.states.CAN.map(state => state.value); -const CAN_STATE_NAMES = constants.states.CAN.map(state => state.label); -const MEX_STATE_VALUES = constants.states.MEX.map(state => state.value); -const MEX_STATE_NAMES = constants.states.MEX.map(state => state.label); -const schemaCrossXRef = { - isMilitary: 'isMilitary', - 'view:militaryBaseDescription': 'view:militaryBaseDescription', - country: 'country', - street: 'street', - street2: 'street2', - street3: 'street3', - city: 'city', - state: 'state', - postalCode: 'postalCode', -}; -const USA = { - value: 'USA', - label: 'United States', -}; -// TODO: Refactor for dynamic content -const MilitaryBaseInfo = () => ( -
- - - The United States is automatically chosen as your country if you live on - a military base outside of the country. - - -
-); -*/ - const EditAddress = ({ data, goToPath, setFormData }) => { const [address, setAddress] = useState(data.permanentAddress || {}); @@ -111,7 +53,6 @@ const EditAddress = ({ data, goToPath, setFormData }) => { } checked={address.isMilitary} /> - {/* Learn more about military base addresses */} (

Available for reorder

- You have {supplies.length} {supplies.length > 1 ? 'supplies' : 'supply'}{' '} - available for reorder. + You have {numberOfSuppliesPhrase(supplies?.length)} available for reorder.

    {supplies.map(({ productId, productName }) => ( diff --git a/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx b/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx index dc2f80be29e7..61fff63c37aa 100644 --- a/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx +++ b/src/applications/mhv-supply-reordering/components/SuppliesUnavailable.jsx @@ -3,53 +3,47 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import DlcTelephoneLink from './DlcTelephoneLink'; import { HEALTH_FACILITIES_URL } from '../constants'; -import { formatDate } from '../utils/helpers'; +import { formatDate, sortSupplies } from '../utils/helpers'; -const SuppliesUnavailable = ({ supplies }) => { - const cards = supplies - ?.sort((a, b) => a.productName.localeCompare(b.productName)) - .map((supply, index) => ( -
    { + const cards = sortSupplies(supplies).map((supply, index) => ( +
    + - -
    - {supply.productName}
    - Device: {supply.deviceName}
    - Quantity: {supply.quantity}
    - Last ordered on {formatDate(supply.lastOrderDate)} -
    - {supply.availableForReorder && ( -

    - You can’t order this supply online until{' '} - {formatDate(supply.nextAvailabilityDate)}. If you need this supply - now call us at . -

    - )} - {!supply.availableForReorder && ( -

    - This item is not available for reordering. To reorder, you can - call your VA healthcare team{' '} - or{' '} - - send them a message - - . -

    - )} -
    -
    - )); +
    + {supply.productName}
    + Device: {supply.deviceName}
    + Quantity: {supply.quantity}
    + Last ordered on {formatDate(supply.lastOrderDate)} +
    + {supply.availableForReorder && ( +

    + You can’t order this supply online until{' '} + {formatDate(supply.nextAvailabilityDate)}. If you need this supply + now call us at . +

    + )} + {!supply.availableForReorder && ( +

    + This item is not available for reordering. To reorder, you can call{' '} + your VA healthcare team or{' '} + + send them a message + + . +

    + )} + +
    + )); return ( <> - {supplies?.length > 0 && ( + {supplies.length > 0 && (

    Unavailable for reorder

    diff --git a/src/applications/mhv-supply-reordering/config/form.js b/src/applications/mhv-supply-reordering/config/form.js index 8cffd4e274e8..e94e15419839 100644 --- a/src/applications/mhv-supply-reordering/config/form.js +++ b/src/applications/mhv-supply-reordering/config/form.js @@ -89,13 +89,6 @@ const chapters = { }, }; -// const formOptions = { -// noTitle: true, -// noTopNav: true, -// fullWidth: true, -// noBottomNav: true, -// }; - /** @type {FormConfig} */ const formConfig = { rootUrl: manifest.rootUrl, @@ -119,7 +112,6 @@ const formConfig = { chapters, getHelp, footerContent, - // formOptions, useTopBackLink: true, }; diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/errors.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/errors.js new file mode 100644 index 000000000000..034ad3a2310e --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/errors.js @@ -0,0 +1,39 @@ +const unauthenticated = { + errors: [ + { + title: 'Not authorized', + detail: 'Not authorized', + code: '401', + status: '401', + }, + ], +}; + +const internalServerError = { + errors: [ + { + title: 'Internal server error', + detail: 'Internal server error', + code: '500', + status: '500', + }, + ], +}; + +const notFound = { + errors: [ + { + title: 'Veteran Not Found', + detail: 'The veteran could not be found', + code: 'MDOT_invalid', + source: 'MDOT::Client', + status: '404', + }, + ], +}; + +module.exports = { + unauthenticated, + internalServerError, + notFound, +}; diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js index 53bb015eb52e..dc1b2fd2038a 100644 --- a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js @@ -1,3 +1,6 @@ +// eslint-disable-next-line no-unused-vars +import { unauthenticated, internalServerError, notFound } from './errors'; + const supplies = [ { productName: 'ERHK HE11 680 MINI', @@ -79,43 +82,6 @@ const getOk = { }, }; -// eslint-disable-next-line no-unused-vars -const unauthenticated = { - errors: [ - { - title: 'Not authorized', - detail: 'Not authorized', - code: '401', - status: '401', - }, - ], -}; - -// eslint-disable-next-line no-unused-vars -const internalServerError = { - errors: [ - { - title: 'Internal server error', - detail: 'Internal server error', - code: '500', - status: '500', - }, - ], -}; - -// eslint-disable-next-line no-unused-vars -const notFound = { - errors: [ - { - title: 'Veteran Not Found', - detail: 'The veteran could not be found', - code: 'MDOT_invalid', - source: 'MDOT::Client', - status: '404', - }, - ], -}; - const putOk = { data: { id: '12345', diff --git a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx index 2c9a59703d49..dbd7f60f82a9 100644 --- a/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx +++ b/src/applications/mhv-supply-reordering/pages/chooseSupplies.jsx @@ -3,17 +3,12 @@ import React from 'react'; import { checkboxGroupSchema } from '@department-of-veterans-affairs/platform-forms-system/web-component-patterns'; import { + numberOfSuppliesPhrase, suppliesReplaceSchema, suppliesUpdateUiSchema, suppliesUi, } from '../utils/helpers'; -const numberOfSuppliesPhrase = count => { - if (count > 1) return `${count} supplies`; - if (count === 1) return `${count} supply`; - return 'no supplies'; -}; - const Description = ({ formData }) => { const count = formData?.supplies?.length || 0; diff --git a/src/applications/mhv-supply-reordering/selectors/index.js b/src/applications/mhv-supply-reordering/selectors/index.js index 1d49a5f2e313..2e0f5a4b99fe 100644 --- a/src/applications/mhv-supply-reordering/selectors/index.js +++ b/src/applications/mhv-supply-reordering/selectors/index.js @@ -28,7 +28,7 @@ const showAlertNoRecordForUser = state => const showAlertNoSuppliesForReorder = state => state?.mdotInProgressForm?.formData?.supplies?.every( - supply => supply?.availableForReorder === undefined, + supply => !supply?.availableForReorder, ) || false; const showAlertReorderAccessExpired = state => diff --git a/src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js b/src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js new file mode 100644 index 000000000000..f44288d95e62 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js @@ -0,0 +1,58 @@ +import { expect } from 'chai'; + +import { + GET_MDOT_IN_PROGRESS_FORM_STARTED, + GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED, + GET_MDOT_IN_PROGRESS_FORM_FAILED, +} from '../../actions/mdotInProgressForm'; +import { mdotInProgressFormReducer as reducer } from '../../reducers/mdotInProgressFormReducer'; +import { internalServerError } from '../../mocks/in-progress-forms/mdot/errors'; +import mockEndpoints from '../../mocks'; + +const inProgressFormBody = mockEndpoints['GET /v0/in_progress_forms/MDOT']; + +describe('mdotInProgressFormReducer', () => { + let state; + let nextState; + let action; + + beforeEach(() => { + state = undefined; + }); + + describe('GET_MDOT_IN_PROGRESS_FORM_STARTED', () => { + it('sets loading', () => { + action = { + type: GET_MDOT_IN_PROGRESS_FORM_STARTED, + }; + nextState = reducer(state, action); + expect(nextState.loading).to.be.true; + }); + }); + + describe('GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED', () => { + it('sets formData', () => { + action = { + type: GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED, + payload: inProgressFormBody, + }; + nextState = reducer(state, action); + expect(nextState.formData).to.deep.equal(inProgressFormBody.formData); + expect(nextState.loading).to.be.false; + expect(nextState.error).to.be.false; + }); + }); + + describe('GET_MDOT_IN_PROGRESS_FORM_FAILED', () => { + it('sets error', () => { + action = { + type: GET_MDOT_IN_PROGRESS_FORM_FAILED, + payload: internalServerError, + }; + nextState = reducer(state, action); + expect(nextState.error).to.deep.equal(internalServerError.errors.at(0)); + expect(nextState.loading).to.be.false; + expect(nextState.formData).to.deep.equal({}); + }); + }); +}); diff --git a/src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js b/src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js new file mode 100644 index 000000000000..c85f9502c645 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js @@ -0,0 +1,98 @@ +import { expect } from 'chai'; +import { MDOT_ERROR_CODES } from '../../constants'; +import { + showAlertDeceased, + showAlertNoRecordForUser, + showAlertNoSuppliesForReorder, + showAlertReorderAccessExpired, + showAlertSomethingWentWrong, +} from '../../selectors'; + +const stateFn = ({ formData = {}, error = false, loading = false } = {}) => ({ + mdotInProgressForm: { + formData, + error, + loading, + }, +}); + +let result; +let state; + +describe('showAlertDeceased', () => { + it('returns true when error.code is MDOT_ERROR_CODES.DECEASED', () => { + state = stateFn({ error: { code: MDOT_ERROR_CODES.DECEASED } }); + result = showAlertDeceased(state); + expect(result).to.eq(true); + }); + + it('returns false, otherwise', () => { + result = showAlertDeceased(stateFn()); + expect(result).to.eq(false); + }); +}); + +describe('showAlertNoRecordForUser', () => { + it('returns true when error.code is MDOT_ERROR_CODES.INVALID', () => { + state = stateFn({ error: { code: MDOT_ERROR_CODES.INVALID } }); + result = showAlertNoRecordForUser(state); + expect(result).to.eq(true); + }); + + it('returns false, otherwise', () => { + result = showAlertNoRecordForUser(stateFn()); + expect(result).to.eq(false); + }); +}); + +describe('showAlertNoSuppliesForReorder', () => { + it('returns true when supply.availableForReorder prop is falsy for all supplies', () => { + const supplies = [ + { productId: '123', availableForReorder: null }, + { productId: '456', availableForReorder: false }, + { productId: '789', availableForReorder: undefined }, + { productId: '101' }, + ]; + state = stateFn({ formData: { supplies } }); + result = showAlertNoSuppliesForReorder(state); + expect(result).to.eq(true); + }); + + it('returns false, otherwise', () => { + const supplies = [{ productId: '123', availableForReorder: true }]; + result = showAlertNoSuppliesForReorder(stateFn({ formData: { supplies } })); + expect(result).to.eq(false); + }); +}); + +describe('showAlertReorderAccessExpired', () => { + it('returns true when error.code is MDOT_ERROR_CODES.SUPPLIES_NOT_FOUND', () => { + state = stateFn({ error: { code: MDOT_ERROR_CODES.SUPPLIES_NOT_FOUND } }); + result = showAlertReorderAccessExpired(state); + expect(result).to.eq(true); + }); + + it('returns false, otherwise', () => { + result = showAlertReorderAccessExpired(stateFn()); + expect(result).to.eq(false); + }); +}); + +describe('showAlertSomethingWentWrong', () => { + it('returns true when error.status is 500', () => { + state = stateFn({ error: { status: 500 } }); + result = showAlertSomethingWentWrong(state); + expect(result).to.eq(true); + }); + + it('returns true when error.status is 503', () => { + state = stateFn({ error: { status: 503 } }); + result = showAlertSomethingWentWrong(state); + expect(result).to.eq(true); + }); + + it('returns false, otherwise', () => { + result = showAlertSomethingWentWrong(stateFn()); + expect(result).to.eq(false); + }); +}); diff --git a/src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js b/src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js index 8cd8e584c7e8..7ec9798412b1 100644 --- a/src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js +++ b/src/applications/mhv-supply-reordering/tests/utils/helpers.unit.spec.js @@ -68,11 +68,8 @@ describe('suppliesUpdateUiSchema', () => { { productId: 123, productName: 'Product 123' }, { productId: 456, productName: 'Product 456' }, ]; - const expected = { - '123': 'Product 123', - '456': 'Product 456', - }; + const expectedKeys = ['123', '456']; const result = suppliesUpdateUiSchema({ supplies }); - expect(result).to.deep.equal(expected); + expect(Object.keys(result)).to.deep.equal(expectedKeys); }); }); diff --git a/src/applications/mhv-supply-reordering/utils/helpers.js b/src/applications/mhv-supply-reordering/utils/helpers.js index 7deb5088b19b..0a2ac65188e6 100644 --- a/src/applications/mhv-supply-reordering/utils/helpers.js +++ b/src/applications/mhv-supply-reordering/utils/helpers.js @@ -4,7 +4,17 @@ import { checkboxGroupUI, } from '@department-of-veterans-affairs/platform-forms-system/web-component-patterns'; -const formatDate = dateString => format(new Date(dateString), 'MMMM d, yyyy'); +const formatDate = dateString => + dateString ? format(new Date(dateString), 'MMMM d, yyyy') : undefined; + +const numberOfSuppliesPhrase = count => { + if (count > 1) return `${count} supplies`; + if (count === 1) return `${count} supply`; + return 'no supplies'; +}; + +const sortSupplies = supplies => + supplies?.sort((a, b) => a.productName.localCompare(b.productName)); const suppliesReplaceSchema = formData => checkboxGroupSchema((formData?.supplies || []).map(s => s.productId)); @@ -42,6 +52,8 @@ const suppliesUi = ({ export { formatDate, + numberOfSuppliesPhrase, + sortSupplies, suppliesReplaceSchema, suppliesUpdateUiSchema, suppliesUi, From 03e8cc3f927a63cfcc25a0391d1df060254404ab Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Nov 2024 15:25:30 -0500 Subject: [PATCH 25/28] Adds selector specs --- .../mhv-supply-reordering/README.md | 6 ++- .../mocks/in-progress-forms/mdot/index.js | 40 +------------- .../mocks/in-progress-forms/mdot/supplies.js | 39 ++++++++++++++ .../mhv-supply-reordering/selectors/index.js | 2 +- .../mdotInProgressFormsReducer.unit.spec.js | 6 +-- .../tests/selectors/canReorderOn.unit.spec.js | 30 +++++++++++ .../selectors/selectSupplies.unit.spec.js | 52 +++++++++++++++++++ 7 files changed, 130 insertions(+), 45 deletions(-) create mode 100644 src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/supplies.js create mode 100644 src/applications/mhv-supply-reordering/tests/selectors/canReorderOn.unit.spec.js create mode 100644 src/applications/mhv-supply-reordering/tests/selectors/selectSupplies.unit.spec.js diff --git a/src/applications/mhv-supply-reordering/README.md b/src/applications/mhv-supply-reordering/README.md index b720b0670065..737c8fe4fe2a 100644 --- a/src/applications/mhv-supply-reordering/README.md +++ b/src/applications/mhv-supply-reordering/README.md @@ -91,8 +91,10 @@ When requesting `GET /v0/in_progress_forms/MDOT`, the MDOT client in vets-api wi see `src/applications/disability-benefits/all-claims/pages/toxicExposure/toxicExposureConditions.js` for an example. -## Device Types +## Device Types, Device Names How do we access other device types? (e.g. - assistive devices, nebulizers). Are these included in the request for supplies? -The `productGroup` property of a supply can be one of the following values: `['accessories', 'batteries', 'apnea']`. +The `productGroup` property of a supply can be one of the following values: `['accessories', 'batteries', 'apnea']`. `'assistive devices'` will be added to this list in the near future. + +The `deviceName` property of a supply indicates the associated device for the supply. diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js index dc1b2fd2038a..b06cfe0a0d8f 100644 --- a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js @@ -1,44 +1,6 @@ // eslint-disable-next-line no-unused-vars import { unauthenticated, internalServerError, notFound } from './errors'; - -const supplies = [ - { - productName: 'ERHK HE11 680 MINI', - productGroup: 'Accessory', - productId: 6584, - availableForReorder: true, - lastOrderDate: '2022-05-16', - nextAvailabilityDate: '2022-10-16', - quantity: 5, - }, - { - productName: 'AIRFIT F10 M', - productGroup: 'Apnea', - productId: 6641, - availableForReorder: true, - lastOrderDate: '2022-07-05', - nextAvailabilityDate: '2022-12-05', - quantity: 1, - }, - { - productName: 'AIRFIT P10', - productGroup: 'Apnea', - productId: 6650, - availableForReorder: true, - lastOrderDate: '2022-07-05', - nextAvailabilityDate: '2022-12-05', - quantity: 1, - }, - { - productName: 'AIRCURVE10-ASV-CLIMATELINE', - productGroup: 'Apnea', - productId: 8467, - lastOrderDate: '2022-07-06', - nextAvailabilityDate: '2022-12-06', - quantity: 1, - }, -]; - +import { supplies } from './supplies'; // const supplies = []; const getOk = { diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/supplies.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/supplies.js new file mode 100644 index 000000000000..7c1bbe86a856 --- /dev/null +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/supplies.js @@ -0,0 +1,39 @@ +const supplies = [ + { + productName: 'ERHK HE11 680 MINI', + productGroup: 'Accessory', + productId: 6584, + availableForReorder: true, + lastOrderDate: '2022-05-16', + nextAvailabilityDate: '2022-10-16', + quantity: 5, + }, + { + productName: 'AIRFIT F10 M', + productGroup: 'Apnea', + productId: 6641, + availableForReorder: true, + lastOrderDate: '2022-07-05', + nextAvailabilityDate: '2022-12-05', + quantity: 1, + }, + { + productName: 'AIRFIT P10', + productGroup: 'Apnea', + productId: 6650, + availableForReorder: true, + lastOrderDate: '2022-07-05', + nextAvailabilityDate: '2022-12-05', + quantity: 1, + }, + { + productName: 'AIRCURVE10-ASV-CLIMATELINE', + productGroup: 'Apnea', + productId: 8467, + lastOrderDate: '2022-07-06', + nextAvailabilityDate: '2022-12-06', + quantity: 1, + }, +]; + +module.exports = { supplies }; diff --git a/src/applications/mhv-supply-reordering/selectors/index.js b/src/applications/mhv-supply-reordering/selectors/index.js index 2e0f5a4b99fe..31397fb925cd 100644 --- a/src/applications/mhv-supply-reordering/selectors/index.js +++ b/src/applications/mhv-supply-reordering/selectors/index.js @@ -15,7 +15,7 @@ const selectUnavailableSupplies = state => ) || []; const canReorderOn = state => - state?.mdotInProgressForm?.formData?.supplies + selectUnavailableSupplies(state) ?.map(s => s?.nextAvailabilityDate) ?.sort() ?.at(0); diff --git a/src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js b/src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js index f44288d95e62..753b5b78b70e 100644 --- a/src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js +++ b/src/applications/mhv-supply-reordering/tests/reducers/mdotInProgressFormsReducer.unit.spec.js @@ -20,7 +20,7 @@ describe('mdotInProgressFormReducer', () => { state = undefined; }); - describe('GET_MDOT_IN_PROGRESS_FORM_STARTED', () => { + describe('action.type: GET_MDOT_IN_PROGRESS_FORM_STARTED', () => { it('sets loading', () => { action = { type: GET_MDOT_IN_PROGRESS_FORM_STARTED, @@ -30,7 +30,7 @@ describe('mdotInProgressFormReducer', () => { }); }); - describe('GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED', () => { + describe('action.type: GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED', () => { it('sets formData', () => { action = { type: GET_MDOT_IN_PROGRESS_FORM_SUCCEEDED, @@ -43,7 +43,7 @@ describe('mdotInProgressFormReducer', () => { }); }); - describe('GET_MDOT_IN_PROGRESS_FORM_FAILED', () => { + describe('action.type: GET_MDOT_IN_PROGRESS_FORM_FAILED', () => { it('sets error', () => { action = { type: GET_MDOT_IN_PROGRESS_FORM_FAILED, diff --git a/src/applications/mhv-supply-reordering/tests/selectors/canReorderOn.unit.spec.js b/src/applications/mhv-supply-reordering/tests/selectors/canReorderOn.unit.spec.js new file mode 100644 index 000000000000..5f64d23bce63 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/selectors/canReorderOn.unit.spec.js @@ -0,0 +1,30 @@ +import { expect } from 'chai'; +import { canReorderOn } from '../../selectors'; +import { supplies as suppliesData } from '../../mocks/in-progress-forms/mdot/supplies'; + +const stateFn = ({ supplies = [] } = {}) => ({ + mdotInProgressForm: { + formData: { + supplies, + }, + error: false, + loading: false, + }, +}); + +let state; + +describe('canReorderOn', () => { + it('returns undefined when state is not set', () => { + expect(canReorderOn({})).to.equal(undefined); + }); + + it('returns the closest date that supplies are able to be reordered', () => { + const unavailableSupplies = suppliesData.map(s => ({ + productName: s.productName, + nextAvailabilityDate: s.nextAvailabilityDate, + })); + state = stateFn({ supplies: unavailableSupplies }); + expect(canReorderOn(state)).to.equal('2022-10-16'); + }); +}); diff --git a/src/applications/mhv-supply-reordering/tests/selectors/selectSupplies.unit.spec.js b/src/applications/mhv-supply-reordering/tests/selectors/selectSupplies.unit.spec.js new file mode 100644 index 000000000000..ae7d3bf422f7 --- /dev/null +++ b/src/applications/mhv-supply-reordering/tests/selectors/selectSupplies.unit.spec.js @@ -0,0 +1,52 @@ +import { expect } from 'chai'; +import { selectSupplies, selectUnavailableSupplies } from '../../selectors'; +import { supplies as suppliesData } from '../../mocks/in-progress-forms/mdot/supplies'; + +const stateFn = ({ supplies = [] } = {}) => ({ + mdotInProgressForm: { + formData: { + supplies, + }, + error: false, + loading: false, + }, +}); + +let result; +let state; + +describe('selectSupplies', () => { + it('returns [] when state is not set', () => { + expect(selectSupplies({})).to.deep.equal([]); + }); + + it('returns supplies that are availableForReorder', () => { + state = stateFn({ supplies: suppliesData }); + result = selectSupplies(state); + expect(suppliesData.length).to.eq(4); + expect(result.length).to.eq(3); + const productNames = result.map(({ productName }) => productName); + const expectedProductNames = [ + 'ERHK HE11 680 MINI', + 'AIRFIT F10 M', + 'AIRFIT P10', + ]; + expect(productNames).to.deep.equal(expectedProductNames); + }); +}); + +describe('selectUnavailableSupplies', () => { + it('returns [] when state is not set', () => { + expect(selectUnavailableSupplies({})).to.deep.equal([]); + }); + + it('returns supplies that are not availableForReorder', () => { + state = stateFn({ supplies: suppliesData }); + result = selectUnavailableSupplies(state); + expect(suppliesData.length).to.eq(4); + expect(result.length).to.eq(1); + const productNames = result.map(({ productName }) => productName); + const expectedProductNames = ['AIRCURVE10-ASV-CLIMATELINE']; + expect(productNames).to.deep.equal(expectedProductNames); + }); +}); From cb3b95191a0717821eafce9c45ce09df6874cae2 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Nov 2024 16:04:41 -0500 Subject: [PATCH 26/28] Updates --- .../mhv-supply-reordering/components/Help.jsx | 34 +++++++-------- .../tests/selectors/showAlert.unit.spec.js | 43 +++++++++++-------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/applications/mhv-supply-reordering/components/Help.jsx b/src/applications/mhv-supply-reordering/components/Help.jsx index cb2e1dad81f7..e7368be8d44e 100644 --- a/src/applications/mhv-supply-reordering/components/Help.jsx +++ b/src/applications/mhv-supply-reordering/components/Help.jsx @@ -2,23 +2,21 @@ import React from 'react'; import DlcTelephoneLink from './DlcTelephoneLink'; import { HEALTH_FACILITIES_URL } from '../constants'; -const Help = () => { - return ( - <> -

    - If you have trouble using your supplies,{' '} - - find the phone number for your local VA health facility - - . -

    -

    - If you have questions about your supplies, call our VA - Denver Logistics Center at . We’re here Monday - through Friday, 8:15 a.m. to 5:00 p.m. ET. -

    - - ); -}; +const Help = () => ( + <> +

    + If you have trouble using your supplies,{' '} + + find the phone number for your local VA health facility + + . +

    +

    + If you have questions about your supplies, call our VA + Denver Logistics Center at . We’re here Monday through + Friday, 8:15 a.m. to 5:00 p.m. ET. +

    + +); export default Help; diff --git a/src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js b/src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js index c85f9502c645..20a2dabb5a03 100644 --- a/src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js +++ b/src/applications/mhv-supply-reordering/tests/selectors/showAlert.unit.spec.js @@ -27,8 +27,8 @@ describe('showAlertDeceased', () => { }); it('returns false, otherwise', () => { - result = showAlertDeceased(stateFn()); - expect(result).to.eq(false); + expect(showAlertDeceased(stateFn())).to.eq(false); + expect(showAlertDeceased({})).to.eq(false); }); }); @@ -40,8 +40,8 @@ describe('showAlertNoRecordForUser', () => { }); it('returns false, otherwise', () => { - result = showAlertNoRecordForUser(stateFn()); - expect(result).to.eq(false); + expect(showAlertNoRecordForUser(stateFn())).to.eq(false); + expect(showAlertNoRecordForUser({})).to.eq(false); }); }); @@ -58,11 +58,16 @@ describe('showAlertNoSuppliesForReorder', () => { expect(result).to.eq(true); }); - it('returns false, otherwise', () => { + it('returns false when supplies are availableForReorder', () => { const supplies = [{ productId: '123', availableForReorder: true }]; result = showAlertNoSuppliesForReorder(stateFn({ formData: { supplies } })); expect(result).to.eq(false); }); + + it('returns false, otherwise', () => { + expect(showAlertNoSuppliesForReorder(stateFn())).to.eq(false); + expect(showAlertNoSuppliesForReorder({})).to.eq(false); + }); }); describe('showAlertReorderAccessExpired', () => { @@ -73,26 +78,30 @@ describe('showAlertReorderAccessExpired', () => { }); it('returns false, otherwise', () => { - result = showAlertReorderAccessExpired(stateFn()); - expect(result).to.eq(false); + expect(showAlertReorderAccessExpired(stateFn())).to.eq(false); + expect(showAlertReorderAccessExpired({})).to.eq(false); }); }); describe('showAlertSomethingWentWrong', () => { - it('returns true when error.status is 500', () => { - state = stateFn({ error: { status: 500 } }); - result = showAlertSomethingWentWrong(state); - expect(result).to.eq(true); + [500, 502, 503].forEach(status => { + it(`returns true when error.status is ${status} (server error)`, () => { + state = stateFn({ error: { status } }); + result = showAlertSomethingWentWrong(state); + expect(result).to.eq(true); + }); }); - it('returns true when error.status is 503', () => { - state = stateFn({ error: { status: 503 } }); - result = showAlertSomethingWentWrong(state); - expect(result).to.eq(true); + [401, 403, 404].forEach(status => { + it(`returns false when error.status is ${status} (client error)`, () => { + state = stateFn({ error: { status } }); + result = showAlertSomethingWentWrong(state); + expect(result).to.eq(false); + }); }); it('returns false, otherwise', () => { - result = showAlertSomethingWentWrong(stateFn()); - expect(result).to.eq(false); + expect(showAlertSomethingWentWrong(stateFn())).to.eq(false); + expect(showAlertSomethingWentWrong({})).to.eq(false); }); }); From 4a0831598a642749862352909cd111672455adc7 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Tue, 26 Nov 2024 16:34:31 -0500 Subject: [PATCH 27/28] Use require instead of import for mocks --- .../mocks/in-progress-forms/mdot/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js index b06cfe0a0d8f..e6a511d58f77 100644 --- a/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js +++ b/src/applications/mhv-supply-reordering/mocks/in-progress-forms/mdot/index.js @@ -1,6 +1,6 @@ // eslint-disable-next-line no-unused-vars -import { unauthenticated, internalServerError, notFound } from './errors'; -import { supplies } from './supplies'; +const { unauthenticated, internalServerError, notFound } = require('./errors'); +const { supplies } = require('./supplies'); // const supplies = []; const getOk = { From 2073076d39ffc09ff34a950f4d51917f0c6ebb67 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Wed, 27 Nov 2024 09:06:36 -0500 Subject: [PATCH 28/28] Updates readme --- src/applications/mhv-supply-reordering/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/mhv-supply-reordering/README.md b/src/applications/mhv-supply-reordering/README.md index 737c8fe4fe2a..28bc97915b51 100644 --- a/src/applications/mhv-supply-reordering/README.md +++ b/src/applications/mhv-supply-reordering/README.md @@ -7,7 +7,7 @@ Slack Channel: [#va-cto-supply-reordering](https://dsva.slack.com/archives/C05DF ## App -Form app generated with `yarn app:new`. Changes to the following files were reverted, since `VA_FORM_IDS.FORM_VA_2346A` already exists. +Form app generated with `yarn new:app`. Changes to the following files were reverted, since `VA_FORM_IDS.FORM_VA_2346A` already exists. - `src/platform/forms/constants.js` - `src/platform/forms/tests/forms.unit.spec.js`