diff --git a/pkg/handlers/internalapi/moves.go b/pkg/handlers/internalapi/moves.go index 5deb85a9d97..6188b30153f 100644 --- a/pkg/handlers/internalapi/moves.go +++ b/pkg/handlers/internalapi/moves.go @@ -368,6 +368,18 @@ func (h GetAllMovesHandler) Handle(params moveop.GetAllMovesParams) middleware.R } /** End of Feature Flag Block **/ + /** Feature Flag - Mobile Home Shipment **/ + featureFlagNameMH := "mobileHome" + isMobileHomeFeatureOn := false + flagMH, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(params.HTTPRequest.Context(), appCtx, featureFlagNameMH, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", featureFlagName), zap.Error(err)) + isMobileHomeFeatureOn = false + } else { + isMobileHomeFeatureOn = flagMH.Match + } + /** End of Feature Flag Block **/ + for _, move := range movesList { /** Feature Flag - Boat Shipment **/ @@ -448,6 +460,23 @@ func (h GetAllMovesHandler) Handle(params moveop.GetAllMovesParams) middleware.R } /** End of Feature Flag Block **/ + /** Feature Flag - Mobile Home Shipment **/ + if !isMobileHomeFeatureOn { + var filteredShipments models.MTOShipments + if move.MTOShipments != nil { + filteredShipments = models.MTOShipments{} + } + for i, shipment := range move.MTOShipments { + if shipment.ShipmentType == models.MTOShipmentTypeMobileHome { + continue + } + + filteredShipments = append(filteredShipments, move.MTOShipments[i]) + } + move.MTOShipments = filteredShipments + } + /** End of Feature Flag Block **/ + previousMovesList = append(previousMovesList, move) } } diff --git a/pkg/services/orchestrators/shipment/shipment_updater.go b/pkg/services/orchestrators/shipment/shipment_updater.go index b8f3d9d30d2..574bad183f0 100644 --- a/pkg/services/orchestrators/shipment/shipment_updater.go +++ b/pkg/services/orchestrators/shipment/shipment_updater.go @@ -80,7 +80,7 @@ func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment mtoShipment.BoatShipment = boatShipment return nil - } else if shipment.ShipmentType == models.MTOShipmentTypeMobileHome { + } else if shipment.ShipmentType == models.MTOShipmentTypeMobileHome && shipment.MobileHome != nil { shipment.MobileHome.ShipmentID = mtoShipment.ID shipment.MobileHome.Shipment = *mtoShipment diff --git a/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.jsx b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.jsx new file mode 100644 index 00000000000..ed539caa480 --- /dev/null +++ b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.jsx @@ -0,0 +1,330 @@ +import React from 'react'; +import { func } from 'prop-types'; +import * as Yup from 'yup'; +import { Formik, Field } from 'formik'; +import { Button, Form, Label, Textarea } from '@trussworks/react-uswds'; +import classnames from 'classnames'; + +import styles from './MobileHomeShipmentForm.module.scss'; + +import SectionWrapper from 'components/Customer/SectionWrapper'; +import Hint from 'components/Hint'; +import Fieldset from 'shared/Fieldset'; +import formStyles from 'styles/form.module.scss'; +import { ShipmentShape } from 'types/shipment'; +import TextField from 'components/form/fields/TextField/TextField'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import Callout from 'components/Callout'; +import { ErrorMessage } from 'components/form/index'; +import { convertInchesToFeetAndInches } from 'utils/formatMtoShipment'; + +const currentYear = new Date().getFullYear(); +const maxYear = currentYear + 2; + +const validationShape = { + year: Yup.number().required('Required').min(1700, 'Invalid year').max(maxYear, 'Invalid year'), + make: Yup.string().required('Required'), + model: Yup.string().required('Required'), + lengthFeet: Yup.number() + .min(0) + .nullable() + .when('lengthInches', { + is: (lengthInches) => !lengthInches, + then: (schema) => schema.required('Required'), + otherwise: (schema) => schema.notRequired(), + }), + lengthInches: Yup.number(), + widthFeet: Yup.number() + .min(0) + .nullable() + .when('widthInches', { + is: (widthInches) => !widthInches, + then: (schema) => schema.required('Required'), + otherwise: (schema) => schema.notRequired(), + }), + widthInches: Yup.number().min(0), + heightFeet: Yup.number() + .min(0) + .nullable() + .when('heightInches', { + is: (heightInches) => !heightInches, + then: (schema) => schema.required('Required'), + otherwise: (schema) => schema.notRequired(), + }), + heightInches: Yup.number().min(0), + customerRemarks: Yup.string(), +}; + +const MobileHomeShipmentForm = ({ mtoShipment, onBack, onSubmit }) => { + const { year, make, model, lengthInInches, widthInInches, heightInInches } = mtoShipment?.mobileHomeShipment || {}; + + const length = convertInchesToFeetAndInches(lengthInInches); + const width = convertInchesToFeetAndInches(widthInInches); + const height = convertInchesToFeetAndInches(heightInInches); + + const initialValues = { + year: year?.toString() || null, + make: make || '', + model: model || '', + lengthFeet: length.feet, + lengthInches: length.inches, + widthFeet: width.feet, + widthInches: width.inches, + heightFeet: height.feet, + heightInches: height.inches, + customerRemarks: mtoShipment?.customerRemarks, + }; + + return ( + + {({ isValid, handleSubmit, values, errors, touched, setFieldTouched, setFieldError, validateForm }) => { + const lengthHasError = !!( + (touched.lengthFeet && errors.lengthFeet) || + (touched.lengthInches && errors.lengthFeet) + ); + const widthHasError = !!((touched.widthFeet && errors.widthFeet) || (touched.widthInches && errors.widthFeet)); + const heightHasError = !!( + (touched.heightFeet && errors.heightFeet) || + (touched.heightInches && errors.heightFeet) + ); + if (touched.lengthInches && !touched.lengthFeet) { + setFieldTouched('lengthFeet', true); + } + if (touched.widthInches && !touched.widthFeet) { + setFieldTouched('widthFeet', true); + } + if (touched.heightInches && !touched.heightFeet) { + setFieldTouched('heightFeet', true); + } + // manually turn off 'required' error when page loads if field is empty. + if (values.year === null && !touched.year && errors.year === 'Required') { + setFieldError('year', null); + } + return ( +
+
+ +

Mobile home Information

+
+
+ { + setFieldError('year', null); + }} + onBlur={() => { + setFieldTouched('year', true); + setFieldError('year', null); + validateForm(); + }} + required + /> +
+
+
+ + +
+
+ +

Mobile Home Dimensions

+

Enter all of the dimensions of the mobile home.

+
+
+
+ Length + Required +
+
+
+ +
+
+ +
+
+
+
+
+ Width + Required +
+
+
+ +
+
+ +
+
+
+
+
+ Height + Required +
+
+
+ +
+
+ +
+
+
+
+
+ +
+ Remarks Optional +
+ } + > + + + + Examples + + + + + +

250 characters

+
+ + +
+ + +
+ + + ); + }} +
+ ); +}; + +MobileHomeShipmentForm.propTypes = { + mtoShipment: ShipmentShape, + onBack: func.isRequired, + onSubmit: func.isRequired, +}; + +MobileHomeShipmentForm.defaultProps = { + mtoShipment: undefined, +}; + +export default MobileHomeShipmentForm; diff --git a/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.module.scss b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.module.scss new file mode 100644 index 00000000000..09d5efe98f8 --- /dev/null +++ b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.module.scss @@ -0,0 +1,121 @@ +@import 'shared/styles/_basics'; +@import 'shared/styles/_variables'; +@import 'shared/styles/colors'; + +.formContainer { + :global(.usa-legend) { + max-width: none; + } + .formTextFieldWrapper { + :global(.usa-form-group) { + margin-top: 0; + } + .hide { + display: none; + } + } + .formFieldContainer { + margin-top: 1.6rem; + margin-bottom: 0; + padding-bottom: 0; + border: none; + } + .form { + max-width: none; + + :global(.usa-input) { + @include u-display('inline-block'); + width: unset; + } + + :global(.usa-form-group--error), + :global(.usa-form-group.warning) { + margin-top: 1.6rem; + } + + :global(.usa-form-group:first-of-type .usa-label) { + margin-top: 0; + } + + // last section wrapper on mobile shouldn't render divider + @include at-media-max('tablet') { + .sectionWrapper:nth-last-child(2) { + border-bottom: none; + @include u-padding-bottom(0); + @include u-margin-bottom(3); + } + } + } + + h2 { + @include u-margin-bottom(2.5); + } + + // fixes collapsing margins cross-browser for Storage section + h2 + fieldset legend { + @include u-padding-top(1.5); + } + + .sectionWrapper { + border-bottom: 1px solid $base-lighter; + + @include at-media-max('tablet') { + @include u-padding-bottom(4); + @include u-margin-top(4); + } + } + + .sectionWrapper:last-of-type { + border-bottom: none; + } + + fieldset { + @include u-margin-top(2); + @include u-padding-top(0); + + legend:global(.usa-label) { + @include u-margin-top(0); + } + } + + :global(.usa-label), + :global(.usa-checkbox__label) { + @include u-margin-top(2); + } + + :global(.usa-label.usa-label--error) { + @include u-margin-top(0); + } + + :global(.usa-legend) { + @include u-margin-top(0); + } + + .innerHint { + @include u-margin-top(1); + } + + .hint { + @include u-margin-top(2); + } +} + +.buttonContainer { + @include u-display(flex); + flex-wrap: wrap; + + button:global(.usa-button) { + @include u-margin-top(2); + @include u-margin-bottom(0); + } + + @include at-media-max(mobile-lg) { + .backButton { + order: 2; + } + + .saveButton { + order: 1; + } + } +} diff --git a/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.stories.jsx b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.stories.jsx new file mode 100644 index 00000000000..9cbee05fd9a --- /dev/null +++ b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.stories.jsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { expect } from '@storybook/jest'; +import { action } from '@storybook/addon-actions'; +import { Grid, GridContainer } from '@trussworks/react-uswds'; +import { within, userEvent } from '@storybook/testing-library'; + +import MobileHomeShipmentForm from './MobileHomeShipmentForm'; + +export default { + title: 'Customer Components / Mobile Home Shipment / Mobile Home Shipment Form', + component: MobileHomeShipmentForm, + decorators: [ + (Story) => ( + + + + + + + + ), + ], +}; + +const Template = (args) => ; + +export const BlankMobileHomeShipmentForm = Template.bind({}); +BlankMobileHomeShipmentForm.args = { + onSubmit: action('submit button clicked'), + onBack: action('back button clicked'), + mtoShipment: { + mobileHomeShipment: { + year: '', + make: '', + model: '', + lengthInInches: null, + widthInInches: null, + heightInInches: null, + }, + }, +}; + +export const FilledMobileHomeShipmentForm = Template.bind({}); +FilledMobileHomeShipmentForm.args = { + onSubmit: action('submit button clicked'), + onBack: action('back button clicked'), + mtoShipment: { + mobileHomeShipment: { + year: 2022, + make: 'Yamaha', + model: '242X', + lengthInInches: 288, // 24 feet + widthInInches: 102, // 8 feet 6 inches + heightInInches: 84, // 7 feet + }, + }, +}; + +export const ErrorMobileHomeShipmentForm = Template.bind({}); +ErrorMobileHomeShipmentForm.args = { + onSubmit: action('submit button clicked'), + onBack: action('back button clicked'), + mtoShipment: { + mobileHomeShipment: { + year: '', + make: '', + model: '', + lengthInInches: null, + widthInInches: null, + heightInInches: null, + }, + }, +}; +ErrorMobileHomeShipmentForm.play = async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await expect(canvas.getByRole('button', { name: 'Continue' })).toBeEnabled(); + + await userEvent.click(canvas.getByRole('button', { name: 'Continue' })); +}; +ErrorMobileHomeShipmentForm.parameters = { + happo: false, +}; diff --git a/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.test.jsx b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.test.jsx new file mode 100644 index 00000000000..eb65ff30eb4 --- /dev/null +++ b/src/components/Customer/MobileHomeShipment/MobileHomeShipmentForm/MobileHomeShipmentForm.test.jsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { render, screen, act, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import MobileHomeShipmentForm from './MobileHomeShipmentForm'; + +const mtoShipment = { + mobileHomeShipment: { + year: '2022', + make: 'Skyline Homes', + model: 'Crown', + lengthInInches: 288, // 24 feet + widthInInches: 102, // 8 feet 6 inches + heightInInches: 84, // 7 feet + }, +}; +const emptyMobileHomeInfo = { + mobileHomeShipment: { + year: '', + make: '', + model: '', + lengthInInches: 0, // 24 feet + widthInInches: 0, // 8 feet 6 inches + heightInInches: 0, // 7 feet + }, +}; + +const defaultProps = { + onSubmit: jest.fn(), + onBack: jest.fn(), + mtoShipment, +}; + +const emptyInfoProps = { + onSubmit: jest.fn(), + onBack: jest.fn(), + emptyMobileHomeInfo, +}; + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('MobileHomeShipmentForm component', () => { + describe('displays form', () => { + it('renders filled form on load', async () => { + render(); + expect(screen.getByTestId('year')).toHaveValue(mtoShipment.mobileHomeShipment.year); + expect(screen.getByTestId('make')).toHaveValue(mtoShipment.mobileHomeShipment.make); + expect(screen.getByTestId('model')).toHaveValue(mtoShipment.mobileHomeShipment.model); + expect(screen.getByTestId('lengthFeet')).toHaveValue('24'); + expect(screen.getByTestId('lengthInches')).toHaveValue('0'); + expect(screen.getByTestId('widthFeet')).toHaveValue('8'); + expect(screen.getByTestId('widthInches')).toHaveValue('6'); + expect(screen.getByTestId('heightFeet')).toHaveValue('7'); + expect(screen.getByTestId('heightInches')).toHaveValue('0'); + expect( + screen.getByLabelText( + 'Are there things about this mobile home shipment that your counselor or movers should know or discuss with you?', + ), + ).toBeVisible(); + }); + }); + + describe('validates form fields and displays error messages', () => { + it('marks required inputs when left empty', async () => { + render(); + + const requiredFields = [ + 'year', + 'make', + 'model', + 'lengthFeet', + 'lengthInches', + 'widthFeet', + 'widthInches', + 'heightFeet', + 'heightInches', + ]; + + await act(async () => { + requiredFields.forEach(async (field) => { + const input = screen.getByTestId(field); + await userEvent.clear(input); + // await userEvent.click(input); + fireEvent.blur(input); + }); + }); + + expect(screen.getAllByTestId('errorMessage').length).toBe(requiredFields.length); + }); + }); + + describe('form submission', () => { + it('submits the form with valid data', async () => { + render(); + + await act(async () => { + await userEvent.click(screen.getByRole('button', { name: 'Continue' })); + }); + + expect(defaultProps.onSubmit).toHaveBeenCalled(); + }); + + it('does not submit the form with invalid data', async () => { + render(); + + await act(async () => { + await userEvent.clear(screen.getByTestId('year')); + await userEvent.click(screen.getByRole('button', { name: 'Continue' })); + }); + + expect(defaultProps.onSubmit).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx index 3cf57673224..354407ad2a6 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx @@ -189,7 +189,9 @@ class MtoShipmentForm extends Component { const isNTS = shipmentType === SHIPMENT_OPTIONS.NTS; const isNTSR = shipmentType === SHIPMENT_OPTIONS.NTSR; const isBoat = shipmentType === SHIPMENT_TYPES.BOAT_HAUL_AWAY || shipmentType === SHIPMENT_TYPES.BOAT_TOW_AWAY; - const shipmentNumber = shipmentType === SHIPMENT_OPTIONS.HHG || isBoat ? this.getShipmentNumber() : null; + const isMobileHome = shipmentType === SHIPMENT_TYPES.MOBILE_HOME; + const shipmentNumber = + shipmentType === SHIPMENT_OPTIONS.HHG || isBoat || isMobileHome ? this.getShipmentNumber() : null; const isRetireeSeparatee = orders.orders_type === ORDERS_TYPE.RETIREMENT || orders.orders_type === ORDERS_TYPE.SEPARATION; @@ -662,7 +664,7 @@ class MtoShipmentForm extends Component {

You can change details about your move by talking with your counselor or your movers

- {isBoat ? ( + {isBoat || isMobileHome ? (
+ | + +
+ )} + + +
+ + +
+
Mobile Home year
+
{year}
+
+
+
Mobile Home make
+
{make}
+
+
+
Mobile Home model
+
{model}
+
+
+
Dimensions
+
{formattedDimensions}
+
+ {remarks && ( +
+
Remarks
+
{remarks}
+
+ )} +
+ + + ); +}; + +MobileHomeShipmentCard.propTypes = { + shipment: ShipmentShape.isRequired, + shipmentNumber: number, + showEditAndDeleteBtn: bool.isRequired, + onEditClick: func, + onDeleteClick: func, + onIncompleteClick: func, +}; + +MobileHomeShipmentCard.defaultProps = { + shipmentNumber: undefined, + onEditClick: undefined, + onDeleteClick: undefined, + onIncompleteClick: undefined, +}; + +export default MobileHomeShipmentCard; diff --git a/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx new file mode 100644 index 00000000000..ada4a0772dd --- /dev/null +++ b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import MobileHomeShipmentCard from './MobileHomeShipmentCard'; + +import { SHIPMENT_TYPES } from 'shared/constants'; + +const defaultProps = { + showEditAndDeleteBtn: true, + onEditClick: jest.fn(), + onDeleteClick: jest.fn(), + shipmentNumber: 1, + requestedPickupDate: new Date('01/01/2020').toISOString(), + requestedDeliveryDate: new Date('03/01/2020').toISOString(), + pickupLocation: { + streetAddress1: '17 8th St', + city: 'New York', + state: 'NY', + postalCode: '11111', + }, + destinationLocation: { + streetAddress1: '17 8th St', + city: 'New York', + state: 'NY', + postalCode: '73523', + }, + releasingAgent: { + firstName: 'Super', + lastName: 'Mario', + phone: '(555) 555-5555', + email: 'superMario@gmail.com', + }, + destinationZIP: '73523', + receivingAgent: { + firstName: 'Princess', + lastName: 'Peach', + phone: '(999) 999-9999', + email: 'princessPeach@gmail.com', + }, + remarks: + 'This is 500 characters of customer remarks right here. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + shipment: { + moveTaskOrderID: 'testMove123', + id: '20fdbf58-879e-4692-b3a6-8a71f6dcfeaa', + shipmentLocator: 'testMove123-01', + shipmentType: SHIPMENT_TYPES.MOBILE_HOME, + mobileHomeShipment: { + year: 2020, + make: 'Test Make', + model: 'Test Model', + lengthInInches: 240, + widthInInches: 120, + heightInInches: 72, + }, + }, +}; + +describe('MobileHomeShipmentCard component', () => { + it('renders component with all fields', () => { + render(); + + expect(screen.getAllByTestId('ShipmentCardNumber').length).toBe(1); + expect(screen.getByText(/^#testMove123-01$/, { selector: 'p' })).toBeInTheDocument(); + + expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument(); + + const descriptionDefinitions = screen.getAllByRole('definition'); + + const expectedRows = [ + ['Requested pickup date', '01 Jan 2020'], + ['Pickup location', '17 8th St New York, NY 11111'], + ['Releasing agent', 'Super Mario (555) 555-5555 superMario@gmail.com'], + ['Requested delivery date', '01 Mar 2020'], + ['Destination', '17 8th St New York, NY 73523'], + ['Receiving agent', 'Princess Peach (999) 999-9999 princessPeach@gmail.com'], + ['Mobile Home year', '2020'], + ['Mobile Home make', 'Test Make'], + ['Mobile Home model', 'Test Model'], + ['Dimensions', `20' L x 10' W x 6' H`], + [ + 'Remarks', + 'This is 500 characters of customer remarks right here. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + ], + ]; + + expect(descriptionDefinitions.length).toBe(expectedRows.length); + + expectedRows.forEach((expectedRow, index) => { + // dt (definition terms) are not accessible elements that can be found with getByRole although + // testing library claims this is fixed we need to find the node package that is out of date + expect(descriptionDefinitions[index].previousElementSibling).toHaveTextContent(expectedRow[0]); + expect(descriptionDefinitions[index]).toHaveTextContent(expectedRow[1]); + }); + }); + + it('omits the edit button when showEditAndDeleteBtn prop is false', () => { + render(); + + expect(screen.queryByRole('button', { name: 'Edit' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Delete' })).not.toBeInTheDocument(); + }); + + it('calls onEditClick when edit button is pressed', async () => { + render(); + const editBtn = screen.getByRole('button', { name: 'Edit' }); + await userEvent.click(editBtn); + expect(defaultProps.onEditClick).toHaveBeenCalledTimes(1); + }); + + it('calls onDeleteClick when delete button is pressed', async () => { + render(); + const deleteBtn = screen.getByRole('button', { name: 'Delete' }); + await userEvent.click(deleteBtn); + expect(defaultProps.onDeleteClick).toHaveBeenCalledTimes(1); + }); + + it('renders incomplete shipment label and tooltip when shipment is incomplete', async () => { + const incompleteShipmentProps = { + ...defaultProps, + shipment: { + ...defaultProps.shipment, + requestedPickupDate: '', + mobileHomeShipment: defaultProps.shipment.mobileHomeShipment, + }, + onIncompleteClick: jest.fn(), + }; + + render(); + + expect(screen.getByText('Incomplete')).toBeInTheDocument(); + expect(screen.getByTitle('Help about incomplete shipment')).toBeInTheDocument(); + + await userEvent.click(screen.getByTitle('Help about incomplete shipment')); + expect(screen.getAllByTestId('ShipmentCardNumber').length).toBe(1); + }); +}); diff --git a/src/components/Customer/Review/Summary/Summary.jsx b/src/components/Customer/Review/Summary/Summary.jsx index 3f89f04caac..2bafeb88b1a 100644 --- a/src/components/Customer/Review/Summary/Summary.jsx +++ b/src/components/Customer/Review/Summary/Summary.jsx @@ -21,6 +21,7 @@ import NTSRShipmentCard from 'components/Customer/Review/ShipmentCard/NTSRShipme import NTSShipmentCard from 'components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard'; import PPMShipmentCard from 'components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard'; import BoatShipmentCard from 'components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard'; +import MobileHomeShipmentCard from 'components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard'; import SectionWrapper from 'components/Customer/SectionWrapper'; import { ORDERS_BRANCH_OPTIONS, ORDERS_PAY_GRADE_OPTIONS } from 'constants/orders'; import { customerRoutes } from 'constants/routes'; @@ -56,6 +57,7 @@ export class Summary extends Component { enableNTS: true, enableNTSR: true, enableBoat: true, + enableMobileHome: true, }; } @@ -90,6 +92,11 @@ export class Summary extends Component { enableBoat: enabled, }); }); + isBooleanFlagEnabled(FEATURE_FLAG_KEYS.MOBILE_HOME).then((enabled) => { + this.setState({ + enableMobileHome: enabled, + }); + }); } handleEditClick = (path) => { @@ -164,6 +171,7 @@ export class Summary extends Component { let hhgShipmentNumber = 0; let ppmShipmentNumber = 0; let boatShipmentNumber = 0; + let mobileHomeShipmentNumber = 0; return sortedShipments.map((shipment) => { let receivingAgent; let releasingAgent; @@ -265,6 +273,35 @@ export class Summary extends Component { /> ); } + if (shipment.shipmentType === SHIPMENT_TYPES.MOBILE_HOME) { + mobileHomeShipmentNumber += 1; + return ( + + ); + } hhgShipmentNumber += 1; return ( ); diff --git a/src/components/Customer/modals/BoatInfoModal/BoatInfoModal.jsx b/src/components/Customer/modals/BoatAndMobileInfoModal/BoatAndMobileInfoModal.jsx similarity index 73% rename from src/components/Customer/modals/BoatInfoModal/BoatInfoModal.jsx rename to src/components/Customer/modals/BoatAndMobileInfoModal/BoatAndMobileInfoModal.jsx index 03b4159f142..3f924d14370 100644 --- a/src/components/Customer/modals/BoatInfoModal/BoatInfoModal.jsx +++ b/src/components/Customer/modals/BoatAndMobileInfoModal/BoatAndMobileInfoModal.jsx @@ -4,11 +4,11 @@ import { Button } from '@trussworks/react-uswds'; import Modal, { ModalTitle, ModalClose, ModalActions, connectModal } from 'components/Modal/Modal'; -export const BoatInfoModal = ({ closeModal }) => ( +export const BoatAndMobileInfoModal = ({ closeModal }) => ( -

Boat & mobile homes info

+

Boat & Mobile homes info

Boat shipment @@ -20,6 +20,10 @@ export const BoatInfoModal = ({ closeModal }) => ( above dimensions shall be shipped with household goods and not be considered a separate shipment. If your boat is under those dimensions, choose the "HHG" option above.

+

+ Mobile Home shipment +

+

This option is for privately owned mobile homes.