From e3d92398d9ed4921b853f1efe3e1bdfe620aacb6 Mon Sep 17 00:00:00 2001 From: Kyle Cardwell <79024398+KyleCardwell@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:37:57 -0600 Subject: [PATCH] Mhv 61207 contact list navigate away (#31756) * added guards for browser navigation with unsaved changes * added unit tests * MHV-61207 recycling beforeunload in sm nav guard * add navigation away tests --------- Co-authored-by: Alex Morgun Co-authored-by: fazilqa --- .../shared/SmRouteNavigationGuard.jsx | 49 ++++++++- .../containers/EditContactList.unit.spec.jsx | 42 ++++++++ ...ing-contact-list-interface.cypress.spec.js | 16 +-- ...list-multi-facility-errors.cypress.spec.js | 16 +-- ...ility-navigate-away-errors.cypress.spec.js | 102 ++++++++++++++++++ ...ist-single-facility-errors.cypress.spec.js | 8 +- ...ility-navigate-away-errors.cypress.spec.js | 93 ++++++++++++++++ 7 files changed, 304 insertions(+), 22 deletions(-) rename src/applications/mhv-secure-messaging/tests/e2e/{ => contact-list-tests}/secure-messaging-contact-list-interface.cypress.spec.js (64%) rename src/applications/mhv-secure-messaging/tests/e2e/{ => contact-list-tests}/secure-messaging-contact-list-multi-facility-errors.cypress.spec.js (79%) create mode 100644 src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-multi-facility-navigate-away-errors.cypress.spec.js rename src/applications/mhv-secure-messaging/tests/e2e/{ => contact-list-tests}/secure-messaging-contact-list-single-facility-errors.cypress.spec.js (90%) create mode 100644 src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-single-facility-navigate-away-errors.cypress.spec.js diff --git a/src/applications/mhv-secure-messaging/components/shared/SmRouteNavigationGuard.jsx b/src/applications/mhv-secure-messaging/components/shared/SmRouteNavigationGuard.jsx index 5d34d3c9c461..4c5b98cdf5ba 100644 --- a/src/applications/mhv-secure-messaging/components/shared/SmRouteNavigationGuard.jsx +++ b/src/applications/mhv-secure-messaging/components/shared/SmRouteNavigationGuard.jsx @@ -1,7 +1,8 @@ -import React, { useEffect, useState, useCallback } from 'react'; +import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { Prompt } from 'react-router-dom'; import PropTypes from 'prop-types'; import { VaModal } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; +import { resetUserSession } from '../../util/helpers'; const SmRouteNavigationGuard = ({ when, @@ -43,12 +44,56 @@ const SmRouteNavigationGuard = ({ [isBlocking, setIsBlocking, updateModalVisible], ); + const localStorageValues = useMemo(() => { + return { + atExpires: localStorage.atExpires, + hasSession: localStorage.hasSession, + sessionExpiration: localStorage.sessionExpiration, + userFirstName: localStorage.userFirstName, + }; + }, []); + + const { signOutMessage, timeoutId } = resetUserSession(localStorageValues); + + const noTimeout = useCallback( + () => { + clearTimeout(timeoutId); + }, + [timeoutId], + ); + + const beforeUnloadHandler = useCallback( + e => { + if (when) { + e.preventDefault(); + window.onbeforeunload = () => signOutMessage; + e.returnValue = signOutMessage; + } + }, + [when, signOutMessage], + ); + // Update blocking state based on the `when` prop useEffect( () => { setIsBlocking(when); + + // beforeunload prevents the user from navigating away from the page + // without saving their work. This is a browser feature and cannot be + // disabled. The message displayed to the user is also a browser feature + if (when) { + window.addEventListener('beforeunload', beforeUnloadHandler); + } else { + window.removeEventListener('beforeunload', beforeUnloadHandler); + noTimeout(); + } + return () => { + window.removeEventListener('beforeunload', beforeUnloadHandler); + window.onbeforeunload = null; + noTimeout(); + }; }, - [when], + [when, beforeUnloadHandler, noTimeout], ); return ( diff --git a/src/applications/mhv-secure-messaging/tests/containers/EditContactList.unit.spec.jsx b/src/applications/mhv-secure-messaging/tests/containers/EditContactList.unit.spec.jsx index d8d3294b658b..785c54fc32ef 100644 --- a/src/applications/mhv-secure-messaging/tests/containers/EditContactList.unit.spec.jsx +++ b/src/applications/mhv-secure-messaging/tests/containers/EditContactList.unit.spec.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { renderWithStoreAndRouter } from '@department-of-veterans-affairs/platform-testing/react-testing-library-helpers'; import { expect } from 'chai'; import { cleanup, fireEvent, waitFor } from '@testing-library/react'; +import sinon from 'sinon'; import noBlockedRecipients from '../fixtures/json-triage-mocks/triage-teams-mock.json'; import oneBlockedRecipient from '../fixtures/json-triage-mocks/triage-teams-one-blocked-mock.json'; import oneBlockedFacility from '../fixtures/json-triage-mocks/triage-teams-facility-blocked-mock.json'; @@ -263,4 +264,45 @@ describe('Edit Contact List container', async () => { ErrorMessages.ContactList.MINIMUM_SELECTION, ); }); + + it('adds eventListener if path is /contact-list', async () => { + const screen = setup(); + + const addEventListenerSpy = sinon.spy(window, 'addEventListener'); + expect(addEventListenerSpy.calledWith('beforeunload')).to.be.false; + + const checkbox = await screen.findByTestId( + 'contact-list-select-team-1013155', + ); + + checkVaCheckbox(checkbox, false); + + await waitFor(() => { + expect(addEventListenerSpy.calledWith('beforeunload')).to.be.true; + }); + }); + + it('removes eventListener if contact list changes are reverted', async () => { + const screen = setup(); + + const addEventListenerSpy = sinon.spy(window, 'addEventListener'); + const removeEventListenerSpy = sinon.spy(window, 'removeEventListener'); + expect(addEventListenerSpy.calledWith('beforeunload')).to.be.false; + + const checkbox = await screen.findByTestId( + 'contact-list-select-team-1013155', + ); + + checkVaCheckbox(checkbox, false); + + await waitFor(() => { + expect(addEventListenerSpy.calledWith('beforeunload')).to.be.true; + }); + + checkVaCheckbox(checkbox, true); + + await waitFor(() => { + expect(removeEventListenerSpy.calledWith('beforeunload')).to.be.true; + }); + }); }); diff --git a/src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-interface.cypress.spec.js b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-interface.cypress.spec.js similarity index 64% rename from src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-interface.cypress.spec.js rename to src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-interface.cypress.spec.js index cb8810fe3af6..a8e18157b839 100644 --- a/src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-interface.cypress.spec.js +++ b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-interface.cypress.spec.js @@ -1,11 +1,11 @@ -import SecureMessagingSite from './sm_site/SecureMessagingSite'; -import PatientInboxPage from './pages/PatientInboxPage'; -import ContactListPage from './pages/ContactListPage'; -import { AXE_CONTEXT } from './utils/constants'; -import mockMixedCernerFacilitiesUser from './fixtures/userResponse/user-cerner-mixed.json'; -import mockFacilities from './fixtures/facilityResponse/cerner-facility-mock-data.json'; -import mockEhrData from './fixtures/userResponse/vamc-ehr-cerner-mixed.json'; -import mockMixRecipients from './fixtures/multi-facilities-recipients-response.json'; +import SecureMessagingSite from '../sm_site/SecureMessagingSite'; +import PatientInboxPage from '../pages/PatientInboxPage'; +import ContactListPage from '../pages/ContactListPage'; +import { AXE_CONTEXT } from '../utils/constants'; +import mockMixedCernerFacilitiesUser from '../fixtures/userResponse/user-cerner-mixed.json'; +import mockFacilities from '../fixtures/facilityResponse/cerner-facility-mock-data.json'; +import mockEhrData from '../fixtures/userResponse/vamc-ehr-cerner-mixed.json'; +import mockMixRecipients from '../fixtures/multi-facilities-recipients-response.json'; describe('SM Contact list', () => { it('verify base web-elements - single facility', () => { diff --git a/src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-multi-facility-errors.cypress.spec.js b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-multi-facility-errors.cypress.spec.js similarity index 79% rename from src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-multi-facility-errors.cypress.spec.js rename to src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-multi-facility-errors.cypress.spec.js index 24778a7f0b08..82ef2c9b2d10 100644 --- a/src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-multi-facility-errors.cypress.spec.js +++ b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-multi-facility-errors.cypress.spec.js @@ -1,11 +1,11 @@ -import SecureMessagingSite from './sm_site/SecureMessagingSite'; -import PatientInboxPage from './pages/PatientInboxPage'; -import ContactListPage from './pages/ContactListPage'; -import { AXE_CONTEXT } from './utils/constants'; -import mockEhrData from './fixtures/userResponse/vamc-ehr-cerner-mixed.json'; -import mockMixedCernerFacilitiesUser from './fixtures/userResponse/user-cerner-mixed.json'; -import mockFacilities from './fixtures/facilityResponse/cerner-facility-mock-data.json'; -import mockMixRecipients from './fixtures/multi-facilities-recipients-response.json'; +import SecureMessagingSite from '../sm_site/SecureMessagingSite'; +import PatientInboxPage from '../pages/PatientInboxPage'; +import ContactListPage from '../pages/ContactListPage'; +import { AXE_CONTEXT } from '../utils/constants'; +import mockEhrData from '../fixtures/userResponse/vamc-ehr-cerner-mixed.json'; +import mockMixedCernerFacilitiesUser from '../fixtures/userResponse/user-cerner-mixed.json'; +import mockFacilities from '../fixtures/facilityResponse/cerner-facility-mock-data.json'; +import mockMixRecipients from '../fixtures/multi-facilities-recipients-response.json'; describe('SM Single Facility Contact list', () => { beforeEach(() => { diff --git a/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-multi-facility-navigate-away-errors.cypress.spec.js b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-multi-facility-navigate-away-errors.cypress.spec.js new file mode 100644 index 000000000000..ae27710f4f72 --- /dev/null +++ b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-multi-facility-navigate-away-errors.cypress.spec.js @@ -0,0 +1,102 @@ +import SecureMessagingSite from '../sm_site/SecureMessagingSite'; +import PatientInboxPage from '../pages/PatientInboxPage'; +import ContactListPage from '../pages/ContactListPage'; +import { AXE_CONTEXT, Paths } from '../utils/constants'; +import mockEhrData from '../fixtures/userResponse/vamc-ehr-cerner-mixed.json'; +import mockMixedCernerFacilitiesUser from '../fixtures/userResponse/user-cerner-mixed.json'; +import mockFacilities from '../fixtures/facilityResponse/cerner-facility-mock-data.json'; +import mockMixRecipients from '../fixtures/multi-facilities-recipients-response.json'; + +describe('SM Single Facility Contact list', () => { + beforeEach(() => { + SecureMessagingSite.login( + mockEhrData, + true, + mockMixedCernerFacilitiesUser, + mockFacilities, + ); + PatientInboxPage.loadInboxMessages(); + ContactListPage.loadContactList(mockMixRecipients); + ContactListPage.selectAllCheckBox(); + }); + + it('navigate away using secondary navigation', () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.visit(`${Paths.UI_MAIN}/medications/about`); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/medications/about`, + ); + }); + }); + }); + + it(`navigate away using navbar`, () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.visit(`${Paths.UI_MAIN}/find-locations`); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/find-locations`, + ); + }); + }); + }); + + it(`navigate away using browser back button`, () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.go(`back`); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/inbox/`, + ); + }); + }); + }); + + it(`navigate away using browser reload`, () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.reload(); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/contact-list`, + ); + }); + }); + }); +}); diff --git a/src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-single-facility-errors.cypress.spec.js b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-single-facility-errors.cypress.spec.js similarity index 90% rename from src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-single-facility-errors.cypress.spec.js rename to src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-single-facility-errors.cypress.spec.js index 0a372ac17352..52e39d0ac111 100644 --- a/src/applications/mhv-secure-messaging/tests/e2e/secure-messaging-contact-list-single-facility-errors.cypress.spec.js +++ b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-single-facility-errors.cypress.spec.js @@ -1,7 +1,7 @@ -import SecureMessagingSite from './sm_site/SecureMessagingSite'; -import PatientInboxPage from './pages/PatientInboxPage'; -import ContactListPage from './pages/ContactListPage'; -import { AXE_CONTEXT } from './utils/constants'; +import SecureMessagingSite from '../sm_site/SecureMessagingSite'; +import PatientInboxPage from '../pages/PatientInboxPage'; +import ContactListPage from '../pages/ContactListPage'; +import { AXE_CONTEXT } from '../utils/constants'; describe('SM Single Facility Contact list', () => { beforeEach(() => { diff --git a/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-single-facility-navigate-away-errors.cypress.spec.js b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-single-facility-navigate-away-errors.cypress.spec.js new file mode 100644 index 000000000000..520b770cfb67 --- /dev/null +++ b/src/applications/mhv-secure-messaging/tests/e2e/contact-list-tests/secure-messaging-contact-list-single-facility-navigate-away-errors.cypress.spec.js @@ -0,0 +1,93 @@ +import SecureMessagingSite from '../sm_site/SecureMessagingSite'; +import PatientInboxPage from '../pages/PatientInboxPage'; +import ContactListPage from '../pages/ContactListPage'; +import { AXE_CONTEXT, Paths } from '../utils/constants'; + +describe('SM Single Facility Contact list', () => { + beforeEach(() => { + SecureMessagingSite.login(); + PatientInboxPage.loadInboxMessages(); + ContactListPage.loadContactList(); + ContactListPage.selectAllCheckBox(); + }); + + it('navigate away using secondary navigation', () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.visit(`${Paths.UI_MAIN}/medications/about`); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/medications/about`, + ); + }); + }); + }); + + it(`navigate away using navbar`, () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.visit(`${Paths.UI_MAIN}/find-locations`); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/find-locations`, + ); + }); + }); + }); + + it(`navigate away using browser back button`, () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.go(`back`); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/inbox/`, + ); + }); + }); + }); + + it(`navigate away using browser reload`, () => { + cy.injectAxe(); + cy.axeCheck(AXE_CONTEXT); + + cy.window().then(win => { + const beforeUnloadStub = cy.stub(); + + win.addEventListener('beforeunload', beforeUnloadStub); + + cy.reload(); + + cy.then(() => { + expect(beforeUnloadStub).to.have.been.called; + expect(win.location.href).to.eq( + `http://localhost:3001${Paths.UI_MAIN}/contact-list`, + ); + }); + }); + }); +});