diff --git a/src/components/Customer/Home/Step/Step.stories.jsx b/src/components/Customer/Home/Step/Step.stories.jsx index 0e807fa0527..c323c98ff9d 100644 --- a/src/components/Customer/Home/Step/Step.stories.jsx +++ b/src/components/Customer/Home/Step/Step.stories.jsx @@ -119,6 +119,7 @@ Shipments.args = { weightTickets: [], }, }, + { id: '0004', shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE }, ]} onShipmentClick={action('shipment edit icon clicked')} moveSubmitted={false} diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx index efb155918d2..a8ef7796371 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx @@ -190,8 +190,9 @@ class MtoShipmentForm extends Component { const isNTSR = shipmentType === SHIPMENT_OPTIONS.NTSR; const isBoat = shipmentType === SHIPMENT_TYPES.BOAT_HAUL_AWAY || shipmentType === SHIPMENT_TYPES.BOAT_TOW_AWAY; const isMobileHome = shipmentType === SHIPMENT_TYPES.MOBILE_HOME; + const isUB = shipmentType === SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE; const shipmentNumber = - shipmentType === SHIPMENT_OPTIONS.HHG || isBoat || isMobileHome ? this.getShipmentNumber() : null; + shipmentType === SHIPMENT_OPTIONS.HHG || isBoat || isMobileHome || isUB ? this.getShipmentNumber() : null; const isRetireeSeparatee = orders.orders_type === ORDERS_TYPE.RETIREMENT || orders.orders_type === ORDERS_TYPE.SEPARATION; @@ -311,14 +312,14 @@ class MtoShipmentForm extends Component {
-

{shipmentForm.header[`${shipmentType}`]}

- - Remember: You can move {formatWeight(orders.authorizedWeight)} total. You’ll be billed for any - excess weight you move. + Remember: You can move + {isUB + ? ` up to your UB allowance for this move` + : ` ${formatWeight(orders.authorizedWeight)} total`} + . You’ll be billed for any excess weight you move. -
{showPickupFields && ( diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.stories.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.stories.jsx index a3f5994a02f..173604f1ceb 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.stories.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.stories.jsx @@ -95,6 +95,7 @@ export const HHGShipmentRetiree = () => renderStory({ shipmentType: SHIPMENT_OPTIONS.HHG, orders: { orders_type: 'RETIREMENT', authorizedWeight: 5000 } }); export const NTSReleaseShipment = () => renderStory({ shipmentType: SHIPMENT_OPTIONS.NTSR }); export const NTSShipment = () => renderStory({ shipmentType: SHIPMENT_OPTIONS.NTS }); +export const UBShipment = () => renderStory({ shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE }); // edit shipment stories (form should prefill) export const EditHHGShipment = () => @@ -115,6 +116,12 @@ export const EditNTSShipment = () => isCreatePage: false, mtoShipment: mockMtoShipment, }); +export const EditUBShipment = () => + renderStory({ + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + isCreatePage: false, + mtoShipment: mockMtoShipment, + }); export const EditShipmentAsSeparatee = () => renderStory({ diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx index 1cf62db6834..3d6391e5c0c 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx @@ -68,6 +68,33 @@ const defaultProps = { shipmentType: SHIPMENT_OPTIONS.HHG, }; +const ubProps = { + isCreatePage: true, + pageList: ['page1', 'anotherPage/:foo/:bar'], + pageKey: 'page1', + showLoggedInUser: jest.fn(), + createMTOShipment: jest.fn(), + updateMTOShipment: jest.fn(), + dateSelectionIsWeekendHoliday: jest.fn().mockImplementation(() => Promise.resolve()), + newDutyLocationAddress: { + city: 'Fort Benning', + state: 'GA', + postalCode: '31905', + }, + currentResidence: { + city: 'Fort Benning', + state: 'GA', + postalCode: '31905', + streetAddress1: '123 Main', + streetAddress2: '', + }, + orders: { + orders_type: 'PERMANENT_CHANGE_OF_STATION', + has_dependents: false, + }, + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, +}; + const reviewPath = generatePath(customerRoutes.MOVE_REVIEW_PATH, { moveId }); beforeEach(() => { @@ -85,6 +112,13 @@ const renderMtoShipmentForm = (props) => { }); }; +const renderUBShipmentForm = (props) => { + return renderWithRouter(, { + path: customerRoutes.SHIPMENT_CREATE_PATH, + params: { moveId }, + }); +}; + describe('MtoShipmentForm component', () => { describe('when creating a new HHG shipment', () => { it('renders the HHG shipment form', async () => { @@ -1012,6 +1046,925 @@ describe('MtoShipmentForm component', () => { }); }); + describe('when creating a new UB shipment', () => { + it('renders the UB shipment form', async () => { + renderUBShipmentForm(); + + expect(await screen.findByText('UB')).toHaveClass('usa-tag'); + + expect(screen.getAllByText('Date')[0]).toBeInstanceOf(HTMLLegendElement); + expect(screen.getByLabelText(/Preferred pickup date/)).toBeInstanceOf(HTMLInputElement); + expect(screen.getByRole('heading', { level: 2, name: 'Pickup info' })).toBeInTheDocument(); + expect(screen.getByTestId('pickupDateHint')).toHaveTextContent( + 'This is the day movers would put this shipment on their truck. Packing starts earlier. Dates will be finalized when you talk to your Customer Care Representative. Your requested pickup/load date should be your latest preferred pickup/load date, or the date you need to be out of your origin residence.', + ); + expect(screen.getByText('Pickup location')).toBeInstanceOf(HTMLLegendElement); + expect(screen.getByLabelText('Use my current address')).toBeInstanceOf(HTMLInputElement); + expect(screen.getByLabelText(/Address 1/)).toBeInstanceOf(HTMLInputElement); + expect(screen.getByLabelText(/Address 2/)).toBeInstanceOf(HTMLInputElement); + expect(screen.getByLabelText(/City/)).toBeInstanceOf(HTMLInputElement); + expect(screen.getByLabelText(/State/)).toBeInstanceOf(HTMLSelectElement); + expect(screen.getByLabelText(/ZIP/)).toBeInstanceOf(HTMLInputElement); + + expect(screen.getByRole('heading', { level: 4, name: 'Second pickup location' })).toBeInTheDocument(); + expect(screen.getByTitle('Yes, I have a second pickup location')).toBeInstanceOf(HTMLInputElement); + expect(screen.getByTitle('No, I do not have a second pickup location')).toBeInstanceOf(HTMLInputElement); + + expect(screen.getByText(/Releasing agent/).parentElement).toBeInstanceOf(HTMLLegendElement); + expect(screen.getAllByLabelText(/First name/)[0]).toHaveAttribute('name', 'pickup.agent.firstName'); + expect(screen.getAllByLabelText(/Last name/)[0]).toHaveAttribute('name', 'pickup.agent.lastName'); + expect(screen.getAllByLabelText(/Phone/)[0]).toHaveAttribute('name', 'pickup.agent.phone'); + expect(screen.getAllByLabelText(/Email/)[0]).toHaveAttribute('name', 'pickup.agent.email'); + + expect(screen.getAllByText('Date')[1]).toBeInstanceOf(HTMLLegendElement); + expect(screen.getByLabelText(/Preferred delivery date/)).toBeInstanceOf(HTMLInputElement); + + expect(screen.getByText(/Delivery location/)).toBeInstanceOf(HTMLLegendElement); + expect(screen.getByTitle('Yes, I know my delivery address')).toBeInstanceOf(HTMLInputElement); + expect(screen.getByTitle('No, I do not know my delivery address')).toBeInstanceOf(HTMLInputElement); + + expect(screen.queryByRole('heading', { level: 4, name: 'Second Destination Location' })).not.toBeInTheDocument(); + expect(screen.queryByTitle('Yes, I have a second destination location')).not.toBeInTheDocument(); + expect(screen.queryByTitle('No, I do not have a second destination location')).not.toBeInTheDocument(); + + expect(screen.getByText(/Receiving agent/).parentElement).toBeInstanceOf(HTMLLegendElement); + expect(screen.getAllByLabelText(/First name/)[1]).toHaveAttribute('name', 'delivery.agent.firstName'); + expect(screen.getAllByLabelText(/Last name/)[1]).toHaveAttribute('name', 'delivery.agent.lastName'); + expect(screen.getAllByLabelText(/Phone/)[1]).toHaveAttribute('name', 'delivery.agent.phone'); + expect(screen.getAllByLabelText(/Email/)[1]).toHaveAttribute('name', 'delivery.agent.email'); + + expect( + screen.queryByText( + 'Details about the facility where your things are now, including the name or address (if you know them)', + ), + ).not.toBeInTheDocument(); + + expect( + screen.getByLabelText( + 'Are there things about this shipment that your counselor or movers should discuss with you?', + ), + ).toBeInstanceOf(HTMLTextAreaElement); + }); + + it('renders the correct helper text for Delivery Location when orders type is RETIREMENT', async () => { + renderUBShipmentForm({ orders: { orders_type: ORDERS_TYPE.RETIREMENT } }); + await waitFor(() => + expect( + screen.getByText('We can use the zip of the HOR, PLEAD or HOS you entered with your orders.') + .toBeInTheDocument, + ), + ); + }); + + it('renders the correct helper text for Delivery Location when orders type is SEPARATION', async () => { + renderUBShipmentForm({ orders: { orders_type: ORDERS_TYPE.SEPARATION } }); + await waitFor(() => + expect( + screen.getByText('We can use the zip of the HOR, PLEAD or HOS you entered with your orders.') + .toBeInTheDocument, + ), + ); + }); + + it('renders the correct helper text for Delivery Location when orders type is PERMANENT_CHANGE_OF_STATION', async () => { + renderUBShipmentForm({ orders: { orders_type: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION } }); + await waitFor(() => expect(screen.getByText(/We can use the zip of your new duty location./).toBeInTheDocument)); + }); + + it('renders the correct helper text for Delivery Location when orders type is LOCAL_MOVE', async () => { + renderUBShipmentForm({ orders: { orders_type: ORDERS_TYPE.LOCAL_MOVE } }); + await waitFor(() => expect(screen.getByText(/We can use the zip of your new duty location./).toBeInTheDocument)); + }); + + it('does not render special NTS What to expect section', async () => { + const { queryByTestId } = renderUBShipmentForm(); + + await waitFor(() => { + expect(queryByTestId('nts-what-to-expect')).not.toBeInTheDocument(); + }); + }); + + it('uses the current residence address for pickup address when checked', async () => { + const { queryByLabelText, queryAllByLabelText } = renderUBShipmentForm(); + + await userEvent.click(queryByLabelText('Use my current address')); + + await waitFor(() => { + expect(queryAllByLabelText(/Address 1/)[0]).toHaveValue(defaultProps.currentResidence.streetAddress1); + expect(queryAllByLabelText(/Address 2/)[0]).toHaveValue(''); + expect(queryAllByLabelText(/City/)[0]).toHaveValue(defaultProps.currentResidence.city); + expect(queryAllByLabelText(/State/)[0]).toHaveValue(defaultProps.currentResidence.state); + expect(queryAllByLabelText(/ZIP/)[0]).toHaveValue(defaultProps.currentResidence.postalCode); + }); + }); + + it('renders a second address fieldset when the user has a second pickup address', async () => { + renderUBShipmentForm(); + + await userEvent.click(screen.getByTitle('Yes, I have a second pickup location')); + + const streetAddress1 = await screen.findAllByLabelText(/Address 1/); + expect(streetAddress1[1]).toHaveAttribute('name', 'secondaryPickup.address.streetAddress1'); + + const streetAddress2 = await screen.findAllByLabelText(/Address 2/); + expect(streetAddress2[1]).toHaveAttribute('name', 'secondaryPickup.address.streetAddress2'); + + const city = await screen.findAllByLabelText(/City/); + expect(city[1]).toHaveAttribute('name', 'secondaryPickup.address.city'); + + const state = await screen.findAllByLabelText(/State/); + expect(state[1]).toHaveAttribute('name', 'secondaryPickup.address.state'); + + const zip = await screen.findAllByLabelText(/ZIP/); + expect(zip[1]).toHaveAttribute('name', 'secondaryPickup.address.postalCode'); + }); + + it('renders a second address fieldset when the user has a delivery address', async () => { + renderUBShipmentForm(); + + await userEvent.click(screen.getByTitle('Yes, I know my delivery address')); + + const streetAddress1 = await screen.findAllByLabelText(/Address 1/); + expect(streetAddress1[0]).toHaveAttribute('name', 'pickup.address.streetAddress1'); + expect(streetAddress1[1]).toHaveAttribute('name', 'delivery.address.streetAddress1'); + + const streetAddress2 = await screen.findAllByLabelText(/Address 2/); + expect(streetAddress2[0]).toHaveAttribute('name', 'pickup.address.streetAddress2'); + expect(streetAddress2[1]).toHaveAttribute('name', 'delivery.address.streetAddress2'); + + const city = await screen.findAllByLabelText(/City/); + expect(city[0]).toHaveAttribute('name', 'pickup.address.city'); + expect(city[1]).toHaveAttribute('name', 'delivery.address.city'); + + const state = await screen.findAllByLabelText(/State/); + expect(state[0]).toHaveAttribute('name', 'pickup.address.state'); + expect(state[1]).toHaveAttribute('name', 'delivery.address.state'); + + const zip = await screen.findAllByLabelText(/ZIP/); + expect(zip[0]).toHaveAttribute('name', 'pickup.address.postalCode'); + expect(zip[1]).toHaveAttribute('name', 'delivery.address.postalCode'); + }); + + it('renders the secondary destination address question once a user says they have a primary destination address', async () => { + renderUBShipmentForm(); + + expect(screen.queryByRole('heading', { level: 4, name: 'Second Destination Location' })).not.toBeInTheDocument(); + expect(screen.queryByTitle('Yes, I have a second destination location')).not.toBeInTheDocument(); + expect(screen.queryByTitle('No, I do not have a second destination location')).not.toBeInTheDocument(); + + await userEvent.click(screen.getByTitle('Yes, I know my delivery address')); + + expect(await screen.findByRole('heading', { level: 4, name: 'Second delivery location' })).toBeInTheDocument(); + expect(screen.getByTitle('Yes, I have a second destination location')).toBeInstanceOf(HTMLInputElement); + expect(screen.getByTitle('No, I do not have a second destination location')).toBeInstanceOf(HTMLInputElement); + }); + + it('renders another address fieldset when the user has a second destination address', async () => { + renderUBShipmentForm(); + + await userEvent.click(screen.getByTitle('Yes, I know my delivery address')); + await userEvent.click(screen.getByTitle('Yes, I have a second destination location')); + + const streetAddress1 = await screen.findAllByLabelText(/Address 1/); + expect(streetAddress1.length).toBe(3); + expect(streetAddress1[2]).toHaveAttribute('name', 'secondaryDelivery.address.streetAddress1'); + + const streetAddress2 = await screen.findAllByLabelText(/Address 2/); + expect(streetAddress2.length).toBe(3); + expect(streetAddress2[2]).toHaveAttribute('name', 'secondaryDelivery.address.streetAddress2'); + + const city = await screen.findAllByLabelText(/City/); + expect(city.length).toBe(3); + expect(city[2]).toHaveAttribute('name', 'secondaryDelivery.address.city'); + + const state = await screen.findAllByLabelText(/State/); + expect(state.length).toBe(3); + expect(state[2]).toHaveAttribute('name', 'secondaryDelivery.address.state'); + + const zip = await screen.findAllByLabelText(/ZIP/); + expect(zip.length).toBe(3); + expect(zip[2]).toHaveAttribute('name', 'secondaryDelivery.address.postalCode'); + }); + + it('goes back when the back button is clicked', async () => { + renderUBShipmentForm(); + + const backButton = await screen.findByRole('button', { name: 'Back' }); + await userEvent.click(backButton); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(-1); + }); + }); + + it('can submit a new UB shipment successfully', async () => { + const shipmentInfo = { + requestedPickupDate: '07 Jun 2021', + pickupAddress: { + streetAddress1: '812 S 129th St', + streetAddress2: '#123', + city: 'San Antonio', + state: 'TX', + postalCode: '78234', + }, + requestedDeliveryDate: '14 Jun 2021', + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + }; + + const expectedPayload = { + agents: [ + { agentType: 'RELEASING_AGENT', email: '', firstName: '', lastName: '', phone: '' }, + { agentType: 'RECEIVING_AGENT', email: '', firstName: '', lastName: '', phone: '' }, + ], + moveTaskOrderID: moveId, + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + customerRemarks: '', + requestedPickupDate: '2021-06-07', + pickupAddress: { ...shipmentInfo.pickupAddress }, + requestedDeliveryDate: '2021-06-14', + hasSecondaryPickupAddress: false, + hasSecondaryDeliveryAddress: false, + hasTertiaryPickupAddress: false, + hasTertiaryDeliveryAddress: false, + }; + + const updatedAt = '2021-06-11T18:12:11.918Z'; + const expectedCreateResponse = { + createdAt: '2021-06-11T18:12:11.918Z', + customerRemarks: '', + eTag: window.btoa(updatedAt), + id: uuidv4(), + moveTaskOrderID: moveId, + pickupAddress: { ...shipmentInfo.pickupAddress, id: uuidv4() }, + requestedDeliveryDate: expectedPayload.requestedDeliveryDate, + requestedPickupDate: expectedPayload.requestedPickupDate, + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + status: 'SUBMITTED', + updatedAt, + }; + + createMTOShipment.mockImplementation(() => Promise.resolve(expectedCreateResponse)); + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm(); + + const pickupDateInput = await screen.findByLabelText(/Preferred pickup date/); + await userEvent.type(pickupDateInput, shipmentInfo.requestedPickupDate); + + const pickupAddress1Input = screen.getByLabelText(/Address 1/); + await userEvent.type(pickupAddress1Input, shipmentInfo.pickupAddress.streetAddress1); + + const pickupAddress2Input = screen.getByLabelText(/Address 2/); + await userEvent.type(pickupAddress2Input, shipmentInfo.pickupAddress.streetAddress2); + + const pickupCityInput = screen.getByLabelText(/City/); + await userEvent.type(pickupCityInput, shipmentInfo.pickupAddress.city); + + const pickupStateInput = screen.getByLabelText(/State/); + await userEvent.selectOptions(pickupStateInput, shipmentInfo.pickupAddress.state); + + const pickupPostalCodeInput = screen.getByLabelText(/ZIP/); + await userEvent.type(pickupPostalCodeInput, shipmentInfo.pickupAddress.postalCode); + + const deliveryDateInput = await screen.findByLabelText(/Preferred delivery date/); + await userEvent.type(deliveryDateInput, shipmentInfo.requestedDeliveryDate); + + const nextButton = await screen.findByRole('button', { name: 'Next' }); + expect(nextButton).not.toBeDisabled(); + await userEvent.click(nextButton); + + await waitFor(() => { + expect(createMTOShipment).toHaveBeenCalledWith(expectedPayload); + }); + + expect(ubProps.updateMTOShipment).toHaveBeenCalledWith(expectedCreateResponse); + + expect(mockNavigate).toHaveBeenCalledWith(reviewPath); + }); + + it('shows an error when there is an error with the submission', async () => { + const shipmentInfo = { + requestedPickupDate: '07 Jun 2021', + pickupAddress: { + streetAddress1: '812 S 129th St', + streetAddress2: '#123', + city: 'San Antonio', + state: 'TX', + postalCode: '78234', + }, + requestedDeliveryDate: '14 Jun 2021', + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + }; + + const errorMessage = 'Something broke!'; + const errorResponse = { response: { errorMessage } }; + createMTOShipment.mockImplementation(() => Promise.reject(errorResponse)); + getResponseError.mockImplementation(() => errorMessage); + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm(); + + const pickupDateInput = await screen.findByLabelText(/Preferred pickup date/); + await userEvent.type(pickupDateInput, shipmentInfo.requestedPickupDate); + + const pickupAddress1Input = screen.getByLabelText(/Address 1/); + await userEvent.type(pickupAddress1Input, shipmentInfo.pickupAddress.streetAddress1); + + const pickupAddress2Input = screen.getByLabelText(/Address 2/); + await userEvent.type(pickupAddress2Input, shipmentInfo.pickupAddress.streetAddress2); + + const pickupCityInput = screen.getByLabelText(/City/); + await userEvent.type(pickupCityInput, shipmentInfo.pickupAddress.city); + + const pickupStateInput = screen.getByLabelText(/State/); + await userEvent.selectOptions(pickupStateInput, shipmentInfo.pickupAddress.state); + + const pickupPostalCodeInput = screen.getByLabelText(/ZIP/); + await userEvent.type(pickupPostalCodeInput, shipmentInfo.pickupAddress.postalCode); + + const deliveryDateInput = await screen.findByLabelText(/Preferred delivery date/); + await userEvent.type(deliveryDateInput, shipmentInfo.requestedDeliveryDate); + + const nextButton = await screen.findByRole('button', { name: 'Next' }); + expect(nextButton).not.toBeDisabled(); + await userEvent.click(nextButton); + + await waitFor(() => { + expect(createMTOShipment).toHaveBeenCalled(); + }); + + expect(getResponseError).toHaveBeenCalledWith( + errorResponse.response, + 'failed to create MTO shipment due to server error', + ); + + expect(await screen.findByText(errorMessage)).toBeInTheDocument(); + }); + }); + + describe('editing an already existing UB shipment', () => { + const updatedAt = '2021-06-11T18:12:11.918Z'; + + const mockMtoShipment = { + id: uuidv4(), + eTag: window.btoa(updatedAt), + createdAt: '2021-06-11T18:12:11.918Z', + updatedAt, + moveTaskOrderId: moveId, + customerRemarks: 'mock remarks', + requestedPickupDate: '2021-08-01', + requestedDeliveryDate: '2021-08-11', + pickupAddress: { + id: uuidv4(), + streetAddress1: '812 S 129th St', + city: 'San Antonio', + state: 'TX', + postalCode: '78234', + }, + destinationAddress: { + id: uuidv4(), + streetAddress1: '441 SW Rio de la Plata Drive', + city: 'Tacoma', + state: 'WA', + postalCode: '98421', + }, + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + }; + + it('renders the UB shipment form with pre-filled values', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + + expect(await screen.findByLabelText(/Preferred pickup date/)).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText('Use my current address')).not.toBeChecked(); + expect(screen.getAllByLabelText(/Address 1/)[0]).toHaveValue('812 S 129th St'); + expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); + expect(screen.getAllByLabelText(/City/)[0]).toHaveValue('San Antonio'); + expect(screen.getAllByLabelText(/State/)[0]).toHaveValue('TX'); + expect(screen.getAllByLabelText(/ZIP/)[0]).toHaveValue('78234'); + expect(screen.getByLabelText(/Preferred delivery date/)).toHaveValue('11 Aug 2021'); + expect(screen.getByTitle('Yes, I know my delivery address')).toBeChecked(); + expect(screen.getAllByLabelText(/Address 1/)[1]).toHaveValue('441 SW Rio de la Plata Drive'); + expect(screen.getAllByLabelText(/Address 2/)[1]).toHaveValue(''); + expect(screen.getAllByLabelText(/City/)[1]).toHaveValue('Tacoma'); + expect(screen.getAllByLabelText(/State/)[1]).toHaveValue('WA'); + expect(screen.getAllByLabelText(/ZIP/)[1]).toHaveValue('98421'); + expect( + screen.getByLabelText( + 'Are there things about this shipment that your counselor or movers should discuss with you?', + ), + ).toHaveValue('mock remarks'); + + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getAllByText( + 'Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(1); + }); + + it('renders the UB shipment form with pre-filled secondary addresses', async () => { + const shipment = { + ...mockMtoShipment, + secondaryPickupAddress: { + streetAddress1: '142 E Barrel Hoop Circle', + streetAddress2: '#4A', + city: 'Corpus Christi', + state: 'TX', + postalCode: '78412', + }, + secondaryDeliveryAddress: { + streetAddress1: '3373 NW Martin Luther King Jr Blvd', + streetAddress2: '', + city: mockMtoShipment.destinationAddress.city, + state: mockMtoShipment.destinationAddress.state, + postalCode: mockMtoShipment.destinationAddress.postalCode, + }, + }; + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: shipment }); + + expect(await screen.findByTitle('Yes, I have a second pickup location')).toBeChecked(); + expect(await screen.findByTitle('Yes, I have a second destination location')).toBeChecked(); + + const streetAddress1 = await screen.findAllByLabelText(/Address 1/); + expect(streetAddress1.length).toBe(4); + + const streetAddress2 = await screen.findAllByLabelText(/Address 2/); + expect(streetAddress2.length).toBe(4); + + const city = await screen.findAllByLabelText(/City/); + expect(city.length).toBe(4); + + const state = await screen.findAllByLabelText(/State/); + expect(state.length).toBe(4); + + const zip = await screen.findAllByLabelText(/ZIP/); + expect(zip.length).toBe(4); + + // Secondary pickup address should be the 2nd address + expect(streetAddress1[1]).toHaveValue('142 E Barrel Hoop Circle'); + expect(streetAddress2[1]).toHaveValue('#4A'); + expect(city[1]).toHaveValue('Corpus Christi'); + expect(state[1]).toHaveValue('TX'); + expect(zip[1]).toHaveValue('78412'); + + // Secondary delivery address should be the 4th address + expect(streetAddress1[3]).toHaveValue('3373 NW Martin Luther King Jr Blvd'); + expect(streetAddress2[3]).toHaveValue(''); + expect(city[3]).toHaveValue(mockMtoShipment.destinationAddress.city); + expect(state[3]).toHaveValue(mockMtoShipment.destinationAddress.state); + expect(zip[3]).toHaveValue(mockMtoShipment.destinationAddress.postalCode); + }); + + it.each([ + [/Address 1/, 'Some Address'], + [/Address 2/, '123'], + [/City/, 'Some City'], + [/ZIP/, '92131'], + ])( + 'does not allow the user to save the form if the %s field on a secondary addreess is the only one filled out', + async (fieldName, text) => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + + // Verify that the form is good to submit by checking that the save button is not disabled. + const saveButton = await screen.findByRole('button', { name: 'Save' }); + expect(saveButton).not.toBeDisabled(); + + await userEvent.click(screen.getByTitle('Yes, I have a second pickup location')); + await userEvent.click(screen.getByTitle('Yes, I have a second destination location')); + + const address = await screen.findAllByLabelText(fieldName); + // The second instance of a field is the secondary pickup + await userEvent.type(address[1], text); + await waitFor(() => { + expect(saveButton).toBeDisabled(); + }); + + // Clear the field so that the secondary delivery address can be checked + await userEvent.clear(address[1]); + await waitFor(() => { + expect(saveButton).not.toBeDisabled(); + }); + + // The fourth instance found is the secondary delivery + await userEvent.type(address[3], text); + await waitFor(() => { + expect(saveButton).toBeDisabled(); + }); + + await userEvent.clear(address[3]); + await waitFor(() => { + expect(saveButton).not.toBeDisabled(); + }); + }, + ); + + // Similar test as above, but with the state input. + // Extracted out since the state field is not a text input. + it('does not allow the user to save the form if the state field on a secondary addreess is the only one filled out', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + + // Verify that the form is good to submit by checking that the save button is not disabled. + const saveButton = await screen.findByRole('button', { name: 'Save' }); + expect(saveButton).not.toBeDisabled(); + + await userEvent.click(screen.getByTitle('Yes, I have a second pickup location')); + await userEvent.click(screen.getByTitle('Yes, I have a second destination location')); + + const state = await screen.findAllByLabelText(/State/); + // The second instance of a field is the secondary pickup + await userEvent.selectOptions(state[1], 'CA'); + await waitFor(() => { + expect(saveButton).toBeDisabled(); + }); + + // Change the selection to blank so that the secondary delivery address can be checked + await userEvent.selectOptions(state[1], ''); + await waitFor(() => { + expect(saveButton).not.toBeDisabled(); + }); + + // The fourth instance found is the secondary delivery + await userEvent.selectOptions(state[3], 'CA'); + await waitFor(() => { + expect(saveButton).toBeDisabled(); + }); + + await userEvent.selectOptions(state[3], ''); + await waitFor(() => { + expect(saveButton).not.toBeDisabled(); + }); + }); + + it('goes back when the cancel button is clicked', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + + const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); + await userEvent.click(cancelButton); + + await waitFor(() => { + expect(mockNavigate).toHaveBeenCalledWith(-1); + }); + }); + + it('can submit edits to a UB shipment successfully', async () => { + const shipmentInfo = { + pickupAddress: { + streetAddress1: '6622 Airport Way S', + streetAddress2: '#1430', + city: 'San Marcos', + state: 'TX', + postalCode: '78666', + }, + }; + + const expectedPayload = { + moveTaskOrderID: moveId, + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + pickupAddress: { ...shipmentInfo.pickupAddress }, + customerRemarks: mockMtoShipment.customerRemarks, + requestedPickupDate: mockMtoShipment.requestedPickupDate, + requestedDeliveryDate: mockMtoShipment.requestedDeliveryDate, + destinationAddress: { ...mockMtoShipment.destinationAddress, streetAddress2: '' }, + secondaryDeliveryAddress: undefined, + hasSecondaryDeliveryAddress: false, + secondaryPickupAddress: undefined, + hasSecondaryPickupAddress: false, + tertiaryDeliveryAddress: undefined, + hasTertiaryDeliveryAddress: false, + tertiaryPickupAddress: undefined, + hasTertiaryPickupAddress: false, + agents: [ + { agentType: 'RELEASING_AGENT', email: '', firstName: '', lastName: '', phone: '' }, + { agentType: 'RECEIVING_AGENT', email: '', firstName: '', lastName: '', phone: '' }, + ], + counselorRemarks: undefined, + }; + delete expectedPayload.destinationAddress.id; + + const newUpdatedAt = '2021-06-11T21:20:22.150Z'; + const expectedUpdateResponse = { + ...mockMtoShipment, + pickupAddress: { ...shipmentInfo.pickupAddress }, + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + eTag: window.btoa(newUpdatedAt), + status: 'SUBMITTED', + }; + + patchMTOShipment.mockImplementation(() => Promise.resolve(expectedUpdateResponse)); + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + + const pickupAddress1Input = screen.getAllByLabelText(/Address 1/)[0]; + await userEvent.clear(pickupAddress1Input); + await userEvent.type(pickupAddress1Input, shipmentInfo.pickupAddress.streetAddress1); + + const pickupAddress2Input = screen.getAllByLabelText(/Address 2/)[0]; + await userEvent.clear(pickupAddress2Input); + await userEvent.type(pickupAddress2Input, shipmentInfo.pickupAddress.streetAddress2); + + const pickupCityInput = screen.getAllByLabelText(/City/)[0]; + await userEvent.clear(pickupCityInput); + await userEvent.type(pickupCityInput, shipmentInfo.pickupAddress.city); + + const pickupStateInput = screen.getAllByLabelText(/State/)[0]; + await userEvent.selectOptions(pickupStateInput, shipmentInfo.pickupAddress.state); + + const pickupPostalCodeInput = screen.getAllByLabelText(/ZIP/)[0]; + await userEvent.clear(pickupPostalCodeInput); + await userEvent.type(pickupPostalCodeInput, shipmentInfo.pickupAddress.postalCode); + + const saveButton = await screen.findByRole('button', { name: 'Save' }); + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + + await waitFor(() => { + expect(patchMTOShipment).toHaveBeenCalledWith(mockMtoShipment.id, expectedPayload, mockMtoShipment.eTag); + }); + + expect(ubProps.updateMTOShipment).toHaveBeenCalledWith(expectedUpdateResponse); + + expect(mockNavigate).toHaveBeenCalledWith(reviewPath); + }); + + it('shows an error when there is an error with the submission', async () => { + const shipmentInfo = { + pickupAddress: { + streetAddress1: '6622 Airport Way S', + streetAddress2: '#1430', + city: 'San Marcos', + state: 'TX', + postalCode: '78666', + }, + }; + + const errorMessage = 'Something broke!'; + const errorResponse = { response: { errorMessage } }; + patchMTOShipment.mockImplementation(() => Promise.reject(errorResponse)); + getResponseError.mockImplementation(() => errorMessage); + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + + const pickupAddress1Input = screen.getAllByLabelText(/Address 1/)[0]; + await userEvent.clear(pickupAddress1Input); + await userEvent.type(pickupAddress1Input, shipmentInfo.pickupAddress.streetAddress1); + + const pickupAddress2Input = screen.getAllByLabelText(/Address 2/)[0]; + await userEvent.clear(pickupAddress2Input); + await userEvent.type(pickupAddress2Input, shipmentInfo.pickupAddress.streetAddress2); + + const pickupCityInput = screen.getAllByLabelText(/City/)[0]; + await userEvent.clear(pickupCityInput); + await userEvent.type(pickupCityInput, shipmentInfo.pickupAddress.city); + + const pickupStateInput = screen.getAllByLabelText(/State/)[0]; + await userEvent.selectOptions(pickupStateInput, shipmentInfo.pickupAddress.state); + + const pickupPostalCodeInput = screen.getAllByLabelText(/ZIP/)[0]; + await userEvent.clear(pickupPostalCodeInput); + await userEvent.type(pickupPostalCodeInput, shipmentInfo.pickupAddress.postalCode); + + const saveButton = await screen.findByRole('button', { name: 'Save' }); + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + + await waitFor(() => { + expect(patchMTOShipment).toHaveBeenCalled(); + }); + + expect(getResponseError).toHaveBeenCalledWith( + errorResponse.response, + 'failed to update MTO shipment due to server error', + ); + + expect(await screen.findByText(errorMessage)).toBeInTheDocument(); + }); + + it('renders the UB shipment form with pre-filled values', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText(/Preferred pickup date/)).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText('Use my current address')).not.toBeChecked(); + expect(screen.getAllByLabelText(/Address 1/)[0]).toHaveValue('812 S 129th St'); + expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); + expect(screen.getAllByLabelText(/City/)[0]).toHaveValue('San Antonio'); + expect(screen.getAllByLabelText(/State/)[0]).toHaveValue('TX'); + expect(screen.getAllByLabelText(/ZIP/)[0]).toHaveValue('78234'); + expect(screen.getByLabelText(/Preferred delivery date/)).toHaveValue('11 Aug 2021'); + expect(screen.getByTitle('Yes, I know my delivery address')).toBeChecked(); + expect(screen.getAllByLabelText(/Address 1/)[1]).toHaveValue('441 SW Rio de la Plata Drive'); + expect(screen.getAllByLabelText(/Address 2/)[1]).toHaveValue(''); + expect(screen.getAllByLabelText(/City/)[1]).toHaveValue('Tacoma'); + expect(screen.getAllByLabelText(/State/)[1]).toHaveValue('WA'); + expect(screen.getAllByLabelText(/ZIP/)[1]).toHaveValue('98421'); + expect( + screen.getByLabelText( + 'Are there things about this shipment that your counselor or movers should discuss with you?', + ), + ).toHaveValue('mock remarks'); + }); + + it('renders the UB shipment with date validaton alerts for weekend and holiday', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United of States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText(/Preferred pickup date/)).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText(/Preferred delivery date/)).toHaveValue('11 Aug 2021'); + await waitFor(() => { + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United of States. This date may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getByText( + /Preferred delivery date 11 Aug 2021 is on a holiday and weekend in the United of States. This date may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + }); + }); + + it('renders the UB shipment with date validaton alerts for weekend', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText(/Preferred pickup date/)).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText(/Preferred delivery date/)).toHaveValue('11 Aug 2021'); + await waitFor(() => { + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a weekend in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getByText( + /Preferred delivery date 11 Aug 2021 is on a weekend in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + }); + }); + + it('renders the UB shipment with date validaton alerts for holiday', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText(/Preferred pickup date/)).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText(/Preferred delivery date/)).toHaveValue('11 Aug 2021'); + await waitFor(() => { + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a holiday in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getByText( + /Preferred delivery date 11 Aug 2021 is on a holiday in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + }); + }); + + it('renders the UB shipment with no date validaton alerts for pickup/delivery', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderUBShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText(/Preferred pickup date/)).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText(/Preferred delivery date/)).toHaveValue('11 Aug 2021'); + expect( + screen.getByLabelText( + 'Are there things about this shipment that your counselor or movers should discuss with you?', + ), + ).toHaveValue('mock remarks'); + + await waitFor(() => { + expect( + screen.queryAllByText( + 'Preferred pickup date 01 Aug 2021 is on a holiday in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(0); + expect( + screen.queryAllByText( + 'Preferred delivery date 11 Aug 2021 is on a holiday in the United States. This date may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(0); + }); + }); + }); + describe('creating a new NTS shipment', () => { it('renders the NTS shipment form', async () => { renderMtoShipmentForm({ shipmentType: SHIPMENT_OPTIONS.NTS }); diff --git a/src/components/Customer/MtoShipmentForm/getShipmentOptions.js b/src/components/Customer/MtoShipmentForm/getShipmentOptions.js index 86c6ddb48f0..17569ce6f28 100644 --- a/src/components/Customer/MtoShipmentForm/getShipmentOptions.js +++ b/src/components/Customer/MtoShipmentForm/getShipmentOptions.js @@ -163,6 +163,13 @@ function getShipmentOptions(shipmentType, userRole) { } } + case SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE: + return { + schema: hhgShipmentSchema, + showPickupFields: true, + showDeliveryFields: true, + }; + default: throw new Error('unrecognized shipment type'); } diff --git a/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx index dfdbe85c95c..bbcbdb9546c 100644 --- a/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx @@ -7,6 +7,7 @@ import userEvent from '@testing-library/user-event'; import HHGShipmentCard from 'components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard'; import { formatCustomerDate } from 'utils/formatters'; import { shipmentStatuses } from 'constants/shipments'; +import { SHIPMENT_OPTIONS } from 'shared/constants'; const defaultProps = { moveId: 'testMove123', @@ -205,3 +206,187 @@ describe('HHGShipmentCard component', () => { expect(mockedOnIncompleteClickFunction).toHaveBeenCalledWith('HHG 1', 'ABC123K-01', 'HHG'); }); }); + +const ubProps = { + moveId: 'testMove123', + editPath: '', + onEditClick: jest.fn(), + onDeleteClick: jest.fn(), + shipmentNumber: 1, + shipmentId: '#ABC123K', + shipmentLocator: '#ABC123K-01', + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + showEditAndDeleteBtn: false, + requestedPickupDate: new Date('01/01/2020').toISOString(), + pickupLocation: { + streetAddress1: '17 8th St', + city: 'New York', + state: 'NY', + postalCode: '11111', + }, + releasingAgent: { + firstName: 'Jo', + lastName: 'Xi', + phone: '(555) 555-5555', + email: 'jo.xi@email.com', + }, + requestedDeliveryDate: new Date('03/01/2020').toISOString(), + destinationZIP: '73523', + receivingAgent: { + firstName: 'Dorothy', + lastName: 'Lagomarsino', + phone: '(999) 999-9999', + email: 'dorothy.lagomarsino@email.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.', +}; + +const incompleteUBProps = { + moveId: 'testMove123', + editPath: '', + onEditClick: jest.fn(), + onDeleteClick: jest.fn(), + onIncompleteClick: mockedOnIncompleteClickFunction, + shipmentNumber: 1, + shipmentId: 'ABC123K', + shipmentLocator: 'ABC123K-01', + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + showEditAndDeleteBtn: false, + requestedPickupDate: new Date('01/01/2020').toISOString(), + status: shipmentStatuses.DRAFT, +}; + +const completeUBProps = { + moveId: 'testMove123', + editPath: '', + onEditClick: jest.fn(), + onDeleteClick: jest.fn(), + shipmentNumber: 1, + shipmentId: 'ABC123K', + shipmentLocator: 'ABC123K-01', + shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, + showEditAndDeleteBtn: false, + requestedPickupDate: new Date('01/01/2020').toISOString(), + status: shipmentStatuses.SUBMITTED, +}; + +function mountHHGShipmentCardForUBShipment(props) { + return mount(); +} + +describe('HHGShipmentCard component can be reused for UB shipment card', () => { + it('renders component with all fields', () => { + const wrapper = mountHHGShipmentCardForUBShipment(); + const tableHeaders = [ + 'Requested pickup date', + 'Pickup location', + 'Releasing agent', + 'Requested delivery date', + 'Destination', + 'Receiving agent', + 'Remarks', + ]; + const { streetAddress1, city, state, postalCode } = ubProps.pickupLocation; + const { + firstName: releasingFirstName, + lastName: releasingLastName, + phone: releasingTelephone, + email: releasingEmail, + } = ubProps.releasingAgent; + const { + firstName: receivingFirstName, + lastName: receivingLastName, + phone: receivingTelephone, + email: receivingEmail, + } = ubProps.receivingAgent; + const tableData = [ + formatCustomerDate(ubProps.requestedPickupDate), + `${streetAddress1} ${city}, ${state} ${postalCode}`, + `${releasingFirstName} ${releasingLastName} ${releasingTelephone} ${releasingEmail}`, + formatCustomerDate(ubProps.requestedDeliveryDate), + ubProps.destinationZIP, + `${receivingFirstName} ${receivingLastName} ${receivingTelephone} ${receivingEmail}`, + ]; + + tableHeaders.forEach((label, index) => expect(wrapper.find('dt').at(index).text()).toBe(label)); + tableData.forEach((label, index) => expect(wrapper.find('dd').at(index).text()).toBe(label)); + expect(wrapper.find('.remarksCell').text()).toBe(ubProps.remarks); + }); + + it('should render UB shipment card without releasing/receiving agents and remarks', () => { + const wrapper = mountHHGShipmentCardForUBShipment({ + ...ubProps, + releasingAgent: null, + receivingAgent: null, + remarks: '', + }); + const tableHeaders = ['Requested pickup date', 'Pickup location', 'Requested delivery date', 'Destination']; + const { streetAddress1, city, state, postalCode } = ubProps.pickupLocation; + const tableData = [ + formatCustomerDate(ubProps.requestedPickupDate), + `${streetAddress1} ${city}, ${state} ${postalCode}`, + formatCustomerDate(ubProps.requestedDeliveryDate), + ubProps.destinationZIP, + ]; + tableHeaders.forEach((label, index) => expect(wrapper.find('dt').at(index).text()).toBe(label)); + tableData.forEach((label, index) => expect(wrapper.find('dd').at(index).text()).toBe(label)); + expect(wrapper.find('.remarksCell').length).toBe(0); + }); + + it('should not render a secondary pickup location on UB shipment card if not provided one', async () => { + render(); + + const secondPickupLocation = await screen.queryByText('Second pickup location'); + expect(secondPickupLocation).not.toBeInTheDocument(); + }); + + it('should not render a secondary destination location on UB shipment card if not provided one', async () => { + render(); + + const secondDestination = await screen.queryByText('Second Destination'); + expect(secondDestination).not.toBeInTheDocument(); + }); + + it('should render a UB shipment card secondary pickup location if provided one', async () => { + render(); + + const secondPickupLocation = await screen.getByText('Second pickup location'); + expect(secondPickupLocation).toBeInTheDocument(); + const secondPickupLocationInformation = await screen.getByText(/Some Other Street Name/); + expect(secondPickupLocationInformation).toBeInTheDocument(); + }); + + it('should render a UB shipment card secondary destination location if provided one', async () => { + render(); + + const secondDestination = await screen.getByText('Second Destination'); + expect(secondDestination).toBeInTheDocument(); + const secondDesintationInformation = await screen.getByText(/Some Street Name/); + expect(secondDesintationInformation).toBeInTheDocument(); + }); + + it('does not render UB shipment card incomplete label and tooltip icon for completed UB shipment with SUBMITTED status', async () => { + render(); + + expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('UB 1'); + expect(screen.getByText(/^#ABC123K-01$/, { selector: 'p' })).toBeInTheDocument(); + + expect(screen.queryByText('Incomplete')).toBeNull(); + }); + + it('renders incomplete label and tooltip icon for incomplete UB shipment with DRAFT status', async () => { + render(); + + expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('UB 1'); + expect(screen.getByText(/^#ABC123K-01$/, { selector: 'p' })).toBeInTheDocument(); + + expect(screen.getByText(/^Incomplete$/, { selector: 'span' })).toBeInTheDocument(); + + expect(screen.getByTitle('Help about incomplete shipment')).toBeInTheDocument(); + await userEvent.click(screen.getByTitle('Help about incomplete shipment')); + + // verify onclick is getting json string as parameter + expect(mockedOnIncompleteClickFunction).toHaveBeenCalledWith('UB 1', 'ABC123K-01', 'UNACCOMPANIED_BAGGAGE'); + }); +}); diff --git a/src/components/Customer/Review/Summary/Summary.jsx b/src/components/Customer/Review/Summary/Summary.jsx index 2bafeb88b1a..2690920c8f9 100644 --- a/src/components/Customer/Review/Summary/Summary.jsx +++ b/src/components/Customer/Review/Summary/Summary.jsx @@ -58,6 +58,7 @@ export class Summary extends Component { enableNTSR: true, enableBoat: true, enableMobileHome: true, + enableUB: true, }; } @@ -97,6 +98,11 @@ export class Summary extends Component { enableMobileHome: enabled, }); }); + isBooleanFlagEnabled(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE).then((enabled) => { + this.setState({ + enableUB: enabled, + }); + }); } handleEditClick = (path) => { @@ -172,6 +178,7 @@ export class Summary extends Component { let ppmShipmentNumber = 0; let boatShipmentNumber = 0; let mobileHomeShipmentNumber = 0; + let ubShipmentNumber = 0; return sortedShipments.map((shipment) => { let receivingAgent; let releasingAgent; @@ -302,6 +309,36 @@ export class Summary extends Component { /> ); } + if (shipment.shipmentType === SHIPMENT_TYPES.UNACCOMPANIED_BAGGAGE) { + ubShipmentNumber += 1; + return ( + + ); + } hhgShipmentNumber += 1; return ( { expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTSR); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.BOAT); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.MOBILE_HOME); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); }); it('add shipment modal displays still in dev mode', async () => { @@ -699,6 +700,7 @@ describe('Summary page', () => { expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.NTSR); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.BOAT); expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.MOBILE_HOME); + expect(isBooleanFlagEnabled).toBeCalledWith(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); }); }); afterEach(jest.clearAllMocks); diff --git a/src/components/Office/ShipmentContainer/ShipmentContainer.jsx b/src/components/Office/ShipmentContainer/ShipmentContainer.jsx index f9ac3d583e9..6a676db32bc 100644 --- a/src/components/Office/ShipmentContainer/ShipmentContainer.jsx +++ b/src/components/Office/ShipmentContainer/ShipmentContainer.jsx @@ -22,6 +22,7 @@ const ShipmentContainer = ({ id, className, children, shipmentType }) => { 'container--accent--ppm': shipmentType === SHIPMENT_OPTIONS.PPM, 'container--accent--boat': isBoat, 'container--accent--mobilehome': shipmentType === SHIPMENT_OPTIONS.MOBILE_HOME, + 'container--accent--ub': shipmentType === SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, }, className, ); diff --git a/src/components/Office/ShipmentContainer/ShipmentContainer.test.jsx b/src/components/Office/ShipmentContainer/ShipmentContainer.test.jsx index b7fc2fa74ef..2b8f6d0729d 100644 --- a/src/components/Office/ShipmentContainer/ShipmentContainer.test.jsx +++ b/src/components/Office/ShipmentContainer/ShipmentContainer.test.jsx @@ -59,6 +59,7 @@ describe('Shipment Container', () => { [SHIPMENT_OPTIONS.HHG, 'container--accent--hhg'], [SHIPMENT_OPTIONS.NTS, 'container--accent--nts'], [SHIPMENT_OPTIONS.NTSR, 'container--accent--ntsr'], + [SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, 'container--accent--ub'], ])('renders a container for a shipment (%s) with className %s ', async (shipmentType, expectedClass) => { const newHeadingInfo = { ...headingInfo, diff --git a/src/components/ShipmentList/ShipmentList.jsx b/src/components/ShipmentList/ShipmentList.jsx index d5692e8e406..f7c3530d2b5 100644 --- a/src/components/ShipmentList/ShipmentList.jsx +++ b/src/components/ShipmentList/ShipmentList.jsx @@ -40,6 +40,7 @@ export const ShipmentListItem = ({ [styles[`shipment-list-item-PPM`]]: isPPM, [styles[`shipment-list-item-Boat`]]: isBoat, [styles[`shipment-list-item-MobileHome`]]: isMobileHome, + [styles[`shipment-list-item-UB`]]: shipment.shipmentType === SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, }); const estimated = 'Estimated'; const actual = 'Actual'; diff --git a/src/components/ShipmentList/ShipmentList.module.scss b/src/components/ShipmentList/ShipmentList.module.scss index 2e2e1cd6cc3..34a8d484f1a 100644 --- a/src/components/ShipmentList/ShipmentList.module.scss +++ b/src/components/ShipmentList/ShipmentList.module.scss @@ -103,6 +103,10 @@ border-left: 5px solid $accent-mobile-home; } +.shipment-list-item-UB { + border-left: 5px solid $accent-ub; +} + .spaceBetween { display: flex; justify-content: center; diff --git a/src/components/ShipmentList/ShipmentList.test.jsx b/src/components/ShipmentList/ShipmentList.test.jsx index 5bb673bc46f..e432853d1e3 100644 --- a/src/components/ShipmentList/ShipmentList.test.jsx +++ b/src/components/ShipmentList/ShipmentList.test.jsx @@ -24,6 +24,7 @@ describe('ShipmentList component', () => { { id: 'ID-2', shipmentType: SHIPMENT_OPTIONS.HHG }, { id: 'ID-3', shipmentType: SHIPMENT_OPTIONS.NTS }, { id: 'ID-4', shipmentType: SHIPMENT_OPTIONS.NTSR }, + { id: 'ID-5', shipmentType: SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE }, ]; const onShipmentClick = jest.fn(); const onDeleteClick = jest.fn(); @@ -36,11 +37,12 @@ describe('ShipmentList component', () => { it('renders ShipmentList with shipments', async () => { render(); - expect(screen.getAllByTestId('shipment-list-item-container').length).toBe(4); + expect(screen.getAllByTestId('shipment-list-item-container').length).toBe(5); expect(screen.getAllByTestId('shipment-list-item-container')[0]).toHaveTextContent(/^ppm/i); expect(screen.getAllByTestId('shipment-list-item-container')[1]).toHaveTextContent(/^hhg/i); expect(screen.getAllByTestId('shipment-list-item-container')[2]).toHaveTextContent(/^nts/i); expect(screen.getAllByTestId('shipment-list-item-container')[3]).toHaveTextContent(/^nts-release/i); + expect(screen.getAllByTestId('shipment-list-item-container')[4]).toHaveTextContent(/^UB/i); }); it.each([ @@ -72,6 +74,9 @@ describe('ShipmentList component', () => { editBtn = queryByRole(screen.getAllByTestId('shipment-list-item-container')[3], 'button', { name: 'Edit' }); await checkShipmentClick('ID-4', 1, SHIPMENT_OPTIONS.NTSR); + + editBtn = queryByRole(screen.getAllByTestId('shipment-list-item-container')[4], 'button', { name: 'Edit' }); + await checkShipmentClick('ID-5', 1, SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE); }); it.each([ @@ -79,6 +84,7 @@ describe('ShipmentList component', () => { [SHIPMENT_OPTIONS.HHG, 2], [SHIPMENT_OPTIONS.NTS, 3], [SHIPMENT_OPTIONS.NTSR, 4], + [SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, 5], ])('calls onDeleteClick for shipment type %s when delete is clicked', async (_, id) => { render(); const deleteBtn = getByRole(screen.getAllByTestId('shipment-list-item-container')[id - 1], 'button', { diff --git a/src/constants/shipments.js b/src/constants/shipments.js index 114ed709f8c..e0487cc8520 100644 --- a/src/constants/shipments.js +++ b/src/constants/shipments.js @@ -10,6 +10,7 @@ export const shipmentTypes = { [SHIPMENT_OPTIONS.BOAT]: 'Boat', [SHIPMENT_TYPES.BOAT_HAUL_AWAY]: 'Boat', [SHIPMENT_TYPES.BOAT_TOW_AWAY]: 'Boat', + [SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE]: 'UB', }; export const shipmentModificationTypes = { @@ -24,6 +25,7 @@ export const mtoShipmentTypes = { [SHIPMENT_OPTIONS.NTSR]: 'Non-temp storage release', [SHIPMENT_TYPES.BOAT_HAUL_AWAY]: 'Boat haul-away', [SHIPMENT_TYPES.BOAT_TOW_AWAY]: 'Boat tow-away', + [SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE]: 'Unaccompanied baggage', }; export const shipmentStatuses = { diff --git a/src/content/shipments.js b/src/content/shipments.js index 828401103fd..2cabf052de4 100644 --- a/src/content/shipments.js +++ b/src/content/shipments.js @@ -10,6 +10,7 @@ export const shipmentTypeLabels = { [SHIPMENT_TYPES.BOAT_HAUL_AWAY]: 'Boat', [SHIPMENT_TYPES.BOAT_TOW_AWAY]: 'Boat', [SHIPMENT_TYPES.MOBILE_HOME]: 'Mobile Home', + [SHIPMENT_TYPES.UNACCOMPANIED_BAGGAGE]: 'UB', }; export const shipmentForm = { diff --git a/src/shared/constants.js b/src/shared/constants.js index 0571f76d0ad..94903bd65c4 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -83,6 +83,7 @@ export const SHIPMENT_OPTIONS = { BOAT_HAUL_AWAY: 'BOAT', BOAT_TOW_AWAY: 'BOAT', MOBILE_HOME: 'MOBILE_HOME', + UNACCOMPANIED_BAGGAGE: 'UNACCOMPANIED_BAGGAGE', }; export const SHIPMENT_TYPES = { @@ -93,6 +94,7 @@ export const SHIPMENT_TYPES = { BOAT_HAUL_AWAY: 'BOAT_HAUL_AWAY', BOAT_TOW_AWAY: 'BOAT_TOW_AWAY', MOBILE_HOME: 'MOBILE_HOME', + UNACCOMPANIED_BAGGAGE: 'UNACCOMPANIED_BAGGAGE', }; // These constants are used for forming URLs that have the shipment type in @@ -104,6 +106,7 @@ export const SHIPMENT_OPTIONS_URL = { NTSrelease: 'NTSrelease', BOAT: 'Boat', MOBILE_HOME: 'Mobilehome', + UNACCOMPANIED_BAGGAGE: 'UB', }; export const LOA_TYPE = { @@ -125,6 +128,7 @@ export const shipmentOptionLabels = [ { key: SHIPMENT_OPTIONS.MOBILE_HOME, label: 'Mobile Home' }, { key: SHIPMENT_TYPES.BOAT_HAUL_AWAY, label: 'Boat' }, { key: SHIPMENT_TYPES.BOAT_TOW_AWAY, label: 'Boat' }, + { key: SHIPMENT_TYPES.UNACCOMPANIED_BAGGAGE, label: 'UB' }, ]; export const SERVICE_ITEM_STATUS = { @@ -198,6 +202,7 @@ export const FEATURE_FLAG_KEYS = { NTSR: 'ntsr', BOAT: 'boat', MOBILE_HOME: 'mobile_home', + UNACCOMPANIED_BAGGAGE: 'unaccompanied_baggage', }; export const MOVE_DOCUMENT_TYPE = {