diff --git a/app/scripts/components/common/banner/index.tsx b/app/scripts/components/common/banner/index.tsx index d3b1ac55e..6272bb57f 100644 --- a/app/scripts/components/common/banner/index.tsx +++ b/app/scripts/components/common/banner/index.tsx @@ -1,6 +1,9 @@ -import React, { useState } from "react"; -import { Icon } from "@trussworks/react-uswds"; -import { USWDSBanner, USWDSBannerContent } from "$components/common/uswds/banner"; +import React, { useState } from 'react'; +import { Icon } from '@trussworks/react-uswds'; +import { + USWDSBanner, + USWDSBannerContent +} from '$components/common/uswds/banner'; const BANNER_KEY = 'dismissedBannerUrl'; @@ -12,53 +15,64 @@ function hasExpired(expiryDatetime) { enum BannerType { info = 'info', - warning ='warning' + warning = 'warning' } const infoTypeFlag = BannerType.info; interface BannerProps { - appTitle: string, - expires: Date, - url: string, - text: string, - type?: BannerType + appTitle: string; + expires: Date; + url: string; + text: string; + type?: BannerType; } -export default function Banner({appTitle, expires, url, text, type = infoTypeFlag }: BannerProps) { +export default function Banner({ + appTitle, + expires, + url, + text, + type = infoTypeFlag +}: BannerProps) { const showBanner = localStorage.getItem(BANNER_KEY) !== url; - const [isOpen, setIsOpen] = useState(showBanner && !(hasExpired(expires))); + const [isOpen, setIsOpen] = useState(showBanner && !hasExpired(expires)); - function onClose () { - localStorage.setItem( - BANNER_KEY, - url - ); + function onClose() { + localStorage.setItem(BANNER_KEY, url); setIsOpen(false); } return (
- {isOpen && - (
- + {isOpen && ( +
+ - -

+ +

+
- + > + +
-
)} +
+ )}
); } diff --git a/app/scripts/components/common/cookie-consent/cookieConsent.spec.js b/app/scripts/components/common/cookie-consent/cookieConsent.spec.js index ae1a92a07..7c12ad9f5 100644 --- a/app/scripts/components/common/cookie-consent/cookieConsent.spec.js +++ b/app/scripts/components/common/cookie-consent/cookieConsent.spec.js @@ -2,21 +2,49 @@ import React from 'react'; import '@testing-library/jest-dom'; import { render, screen, fireEvent } from '@testing-library/react'; -import { COOKIE_CONSENT_KEY } from './utils'; -import { CookieConsent } from './index'; +import { MemoryRouter } from 'react-router-dom'; // For testing +import { createMemoryHistory } from 'history'; + +import * as utils from './utils'; +import CookieConsent from './index'; +const lodash = require('lodash'); describe('Cookie consent form should render with correct content.', () => { + const setDisplayCookieConsent = jest.fn(); + const setGoogleTagManager = jest.fn(); const cookieData = { title: 'Cookie Consent', - copy: '

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our Privacy Policy

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our [Privacy Policy](https://www.nasa.gov/privacy/#cookies)' + copy: '

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our Privacy Policy

We use cookies to enhance your browsing experience and to help us understand how our website is used. These cookies allow us to collect data on site usage and improve our services based on your interactions. To learn more about it, see our [Privacy Policy](https://www.nasa.gov/privacy/#cookies)', + setDisplayCookieConsent, + setGoogleTagManager }; + const onFormInteraction = jest.fn(); - beforeEach(() => { - render( - - ); + + const history = createMemoryHistory({ initialEntries: ['/home'] }); + + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: () => ({ + pathname: 'localhost:3000/example/path' + }) + })); + + lodash.debounce = jest.fn((fn) => fn); + + afterEach(() => { + jest.clearAllMocks(); + + // Clear cookies after each test + document.cookie = `${utils.COOKIE_CONSENT_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; }); + it('Renders correct content', () => { + render( + + + + ); expect( screen.getByRole('link', { name: 'Privacy Policy' }) ).toHaveAttribute('href', 'https://www.nasa.gov/privacy/#cookies'); @@ -34,30 +62,32 @@ describe('Cookie consent form should render with correct content.', () => { ).toBeInTheDocument(); }); - it('Check correct cookie initialization', () => { - const resultCookie = document.cookie; - - expect(resultCookie).toBe( - `${COOKIE_CONSENT_KEY}={"responded":false,"answer":false}` - ); - }); - it('Check correct cookie content on Decline click', () => { + render( + + + + ); const button = screen.getByRole('button', { name: 'Decline Cookies' }); fireEvent.click(button); const resultCookie = document.cookie; expect(resultCookie).toBe( - `${COOKIE_CONSENT_KEY}={"responded":true,"answer":false}` + `${utils.COOKIE_CONSENT_KEY}={"responded":true,"answer":false}` ); }); it('Check correct cookie content on Accept click', () => { - const button = screen.getByRole('button', { name: 'Accept Cookies' }); - fireEvent.click(button); + render( + + + + ); + const acceptButton = screen.getByRole('button', { name: 'Accept Cookies' }); + fireEvent.click(acceptButton); const resultCookie = document.cookie; expect(resultCookie).toBe( - `${COOKIE_CONSENT_KEY}={"responded":true,"answer":true}` + `${utils.COOKIE_CONSENT_KEY}={"responded":true,"answer":true}` ); }); }); diff --git a/app/scripts/components/common/cookie-consent/index.tsx b/app/scripts/components/common/cookie-consent/index.tsx index 3190584dd..1b0606491 100644 --- a/app/scripts/components/common/cookie-consent/index.tsx +++ b/app/scripts/components/common/cookie-consent/index.tsx @@ -1,6 +1,8 @@ import React, { useState, useEffect } from 'react'; +import { debounce } from 'lodash'; import { Icon } from '@trussworks/react-uswds'; -import { COOKIE_CONSENT_KEY } from './utils'; + +import { setCookie, getCookie } from './utils'; import { USWDSAlert, USWDSButton, @@ -12,97 +14,142 @@ import './index.scss'; interface CookieConsentProps { title?: string | undefined; copy?: string | undefined; - onFormInteraction: () => void; + pathname: string; + setDisplayCookieConsentForm: (boolean) => void; + setGoogleTagManager: () => void; } -function addAttribute (copy) { +function addAttribute(copy) { return copy.replaceAll(' { - const [cookieConsentResponded, SetCookieConsentResponded] = + const [cookieConsentResponded, setCookieConsentResponded] = useState(false); - const [cookieConsentAnswer, SetCookieConsentAnswer] = + // Debounce the setDisplayCookieConsentForm function + const debouncedSetCookieConsentResponded = debounce( + setCookieConsentResponded, + 500 + ); + + const [cookieConsentAnswer, setCookieConsentAnswer] = useState(false); const [closeConsent, setCloseConsent] = useState(false); - //Setting expiration date for cookie to expire and re-ask user for consent. - const setCookieExpiration = () => { - const today = new Date(); - today.setMonth(today.getMonth() + 3); - return today.toUTCString(); - }; - const setCookie = (cookieValue, closeConsent) => { - document.cookie = `${COOKIE_CONSENT_KEY}=${JSON.stringify( - cookieValue - )}; path=/; expires=${closeConsent ? '0' : setCookieExpiration()}`; - }; + useEffect(() => { + const cookieContents = getCookie(); + if (cookieContents) { + if (!cookieContents.responded) { + setCloseConsent(false); + return; + } + cookieContents.answer && setGoogleTagManager(); + setCookieConsentResponded(cookieContents.responded); + setCookieConsentAnswer(cookieContents.answer); + setDisplayCookieConsentForm(false); + } else { + setCloseConsent(false); + } + // Only run on the first render + }, [setGoogleTagManager, setDisplayCookieConsentForm]); + + useEffect(() => { + if (!cookieConsentResponded) setCloseConsent(false); + // To render the component when user hasn't answered yet + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pathname]); useEffect(() => { + // When not responded, do nothing. + if (!cookieConsentResponded) return; + // When answer is accept cookie, + // 1. set up google manager + cookieConsentAnswer && setGoogleTagManager(); + // 2. update the cookie value const cookieValue = { responded: cookieConsentResponded, answer: cookieConsentAnswer }; setCookie(cookieValue, closeConsent); - onFormInteraction(); - // Ignoring setcookie for now sine it will make infinite rendering - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cookieConsentResponded, cookieConsentAnswer, closeConsent, onFormInteraction]); + // 3. Tell the layout that we don't have to render this consent form + // from the next render of layout. + setTimeout(() => { + setDisplayCookieConsentForm(false); + }, 500); + }, [ + cookieConsentResponded, + cookieConsentAnswer, + closeConsent, + setDisplayCookieConsentForm, + setGoogleTagManager + ]); return ( -