Skip to content

Commit

Permalink
VACMS-16432 banner updates (#294)
Browse files Browse the repository at this point in the history
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: Eric Oliver <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ryan Koch <[email protected]>
  • Loading branch information
4 people authored Dec 29, 2023
1 parent d60d3c1 commit c670c93
Show file tree
Hide file tree
Showing 15 changed files with 100 additions and 110 deletions.
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ const nextConfig = {
trailingSlash: true,
staticPageGenerationTimeout: 180, //arbitrary; 60 is default but it's too small
}

module.exports = nextConfig
15 changes: 10 additions & 5 deletions playwright/tests/a11y.spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
/* eslint-disable no-console */
import AxeBuilder from '@axe-core/playwright'
import { test, expect } from '@playwright/test'
const { test } = require('../utils/next-test')
const getSitemapLocations = require('../utils/getSitemapLocations')

async function runA11yTestsForPages(pages, testName, page) {
async function runA11yTestsForPages(pages, testName, page, makeAxeBuilder) {
let a11yFailures = []

for (const pageUrl of pages) {
await page.goto(pageUrl)
const accessibilityScanResults = await new AxeBuilder({ page }).analyze()
const accessibilityScanResults = await makeAxeBuilder().analyze()

if (accessibilityScanResults.violations.length > 0) {
accessibilityScanResults.violations.forEach((violation) => {
Expand Down Expand Up @@ -56,8 +55,14 @@ test.describe('Accessibility Tests', () => {
for (let i = 0; i < 5; i++) {
test(`the site should be accessible - Segment ${i + 1}`, async ({
page,
makeAxeBuilder,
}) => {
await runA11yTestsForPages(pageSegments[i], `Segment ${i + 1}`, page)
await runA11yTestsForPages(
pageSegments[i],
`Segment ${i + 1}`,
page,
makeAxeBuilder
)
})
}
})
46 changes: 28 additions & 18 deletions src/data/queries/banners.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import { QueryFormatter } from 'next-drupal-query'
import { NodeBanner } from '@/types/drupal/node'
import { Banner, FacilityBanner, PromoBanner } from '@/types/formatted/banners'
import { QueryData, QueryFormatter } from 'next-drupal-query'
import { NodeBanner, NodeBannerType } from '@/types/drupal/node'
import { BannersData } from '@/types/formatted/banners'
import { drupalClient } from '@/lib/drupal/drupalClient'

export const BannerDisplayType = {
PROMO_BANNER: 'promoBanner',
FACILITY_BANNER: 'facilityBanner',
BANNER: 'banner',
export type BannerDataOpts = {
itemPath?: string
}

export const BannerTypeMapping = {
[BannerDisplayType.PROMO_BANNER]: 'node--promo_banner',
[BannerDisplayType.FACILITY_BANNER]: 'node--full_width_banner_alert',
[BannerDisplayType.BANNER]: 'node--banner',
// The banner data endpoint is a custom endpoint provided by Drupal due to how banners are associated with a page.
// A given page does not reference a banner node via entity reference, banner node types have a field that lists what
// paths they are supposed to be visible on. This endpoint queries banners based on their path lists.
// See docroot/modules/custom/va_gov_api/src/Resources/BannerAlerts.php in va.gov-cms for more info.
export const data: QueryData<BannerDataOpts, NodeBanner[]> = async (opts) => {
if (opts.itemPath) {
const bannerUrl = `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}/jsonapi/banner-alerts?item-path=${opts.itemPath}`

const response = await drupalClient.fetch(bannerUrl)
const data: [] | unknown = drupalClient.deserialize(await response.json())

return data as NodeBanner[]
}

return []
}

export const formatter: QueryFormatter<
NodeBanner[],
Array<PromoBanner | Banner | FacilityBanner | NodeBanner>
> = (entities: NodeBanner[]) => {
export const formatter: QueryFormatter<NodeBanner[], BannersData> = (
entities: NodeBanner[]
) => {
return entities?.map((banner) => {
switch (banner?.type as string) {
case BannerTypeMapping[BannerDisplayType.BANNER]:
case NodeBannerType.BANNER:
// this field returns 'perm' or 'dismiss' string instead of bool
const dismiss = banner.field_dismissible_option === 'dismiss'

Expand All @@ -32,15 +41,15 @@ export const formatter: QueryFormatter<
dismiss,
type: banner.type,
}
case BannerTypeMapping[BannerDisplayType.PROMO_BANNER]:
case NodeBannerType.PROMO_BANNER:
return {
id: banner.id,
title: banner.title,
href: banner.field_link?.uri,
alertType: banner.field_promo_type,
type: banner.type,
}
case BannerTypeMapping[BannerDisplayType.FACILITY_BANNER]:
case NodeBannerType.FACILITY_BANNER:
return {
id: banner.id,
title: banner.title,
Expand All @@ -50,6 +59,7 @@ export const formatter: QueryFormatter<
operatingStatus: banner.field_alert_operating_status_cta,
inheritanceSubpages: banner.field_alert_inheritance_subpages,
path: banner.path?.alias,
findFacilities: banner.field_alert_find_facilities_cta,
bannerAlertVamcs: banner.field_banner_alert_vamcs,
type: banner.type,
}
Expand Down
4 changes: 2 additions & 2 deletions src/data/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as QuestionAnswer from './questionAnswer'
import * as ExpandableText from './expandableText'
import * as LinkTeaser from './linkTeaser'
import * as MediaImage from './mediaImage'
import * as Banner from './banners'
import * as Banners from './banners'
import * as PersonProfile from './personProfile'
import * as Button from './button'
import * as AudienceTopics from './audienceTopics'
Expand Down Expand Up @@ -41,7 +41,7 @@ export const QUERIES_MAP = {
'block_content--promo': PromoBlock,

// Custom queries
'banner--alerts_lookup': Banner,
'banner-data': Banners,
'header-footer-data': HeaderFooter,

// Static Path Generation
Expand Down
1 change: 1 addition & 0 deletions src/data/queries/tests/__snapshots__/banners.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ exports[`Banners return formatted data outputs formatted data 1`] = `
",
},
"dismiss": true,
"findFacilities": false,
"id": "ccd9d30f-78f9-4358-80d7-191f99b18d43",
"inheritanceSubpages": false,
"operatingStatus": true,
Expand Down
4 changes: 1 addition & 3 deletions src/data/queries/tests/banners.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ describe('Banners return formatted data', () => {
test('outputs formatted data', () => {
windowSpy.mockImplementation(() => undefined)
const formattedData = nodeBannerMock
expect(
queries.formatData('banner--alerts_lookup', formattedData)
).toMatchSnapshot()
expect(queries.formatData('banner-data', formattedData)).toMatchSnapshot()
})
})
44 changes: 15 additions & 29 deletions src/lib/drupal/getGlobalElements.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,31 @@
import { formatter } from '@/data/queries/banners'
import { drupalClient } from '@/lib/drupal/drupalClient'
import { LayoutProps } from '@/templates/globals/wrapper'
import { NodeBanner } from '@/types/drupal/node'
import { queries } from '@/data/queries'

const nonSlugRoute = `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}/jsonapi/banner-alerts?item-path=/`

// Helper function to fetch global elements for layout.
// It is called once for every page on build.
// TODO: add cache layer to drupalClient query results

// Helper function to fetch global elements for layout. This is called once for every page during a build,
// because banners are associated with individual pages via slug lookup (itemPath).
export async function getGlobalElements(
jsonApiEntryPoint?: string,
itemPath?: string,
headerOnly: boolean = false
): Promise<LayoutProps> {
let banners = []
// This query is cached so header and footer menu data is only directly requested once per build.
const headerFooterData = await queries.getData('header-footer-data')

// If we are not in headerOnly mode and the necessary parameters are provided, fetch banners
// move all of this into @/data/queries/banners.ts
if (!headerOnly && jsonApiEntryPoint && itemPath) {
let bannerPath = `${jsonApiEntryPoint}/banner-alerts?item-path=${itemPath}`
// Banners can be fetched as long as we have context and a path (slug).
if (!headerOnly && itemPath) {
// Gather data for banners currently visible on this page.
const bannerData = await queries.getData('banner-data', {
itemPath,
})

if (itemPath.includes('home')) {
bannerPath = nonSlugRoute
return {
bannerData,
headerFooterData,
}

const requestBanner = await drupalClient.fetch(`${bannerPath}`)
const bannerData: [] | unknown = drupalClient.deserialize(
await requestBanner.json()
)
banners = formatter(bannerData as NodeBanner[])

// gather data for banners currently visible on this page
// const bannerData = await queries.getData('banner--alerts_lookup')
}

const headerFooterData = await queries.getData('header-footer-data')

// Otherwise return header data without banners.
return {
bannerData: banners,
bannerData: [],
headerFooterData,
}
}
1 change: 0 additions & 1 deletion src/lib/drupal/staticProps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { GetStaticPropsContext } from 'next'
import { DrupalTranslatedPath } from 'next-drupal'
import { QueryOpts } from 'next-drupal-query'
import { drupalClient } from '@/lib/drupal/drupalClient'
import { queries } from '@/data/queries'
import {
Expand Down
5 changes: 2 additions & 3 deletions src/pages/404.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ const Error404Page = ({ headerFooterData }) => {
export async function getStaticProps() {
try {
const { headerFooterData } = await getGlobalElements(
undefined,
undefined,
true
undefined, // no banners on 404
true // header only
)
return {
props: {
Expand Down
4 changes: 1 addition & 3 deletions src/pages/[[...slug]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,9 @@ export async function getStaticProps(context: GetStaticPropsContext) {
notFound: true,
}
}

// If resource is good, gather additional data for global elements.
// This will be cached in the future so the header isn't re-requested a million times.
// The headerFooter data is cached, banner content is requested per page
const { bannerData, headerFooterData } = await getGlobalElements(
pathInfo.jsonapi?.entryPoint,
expandedContext.drupalPath
)

Expand Down
11 changes: 5 additions & 6 deletions src/templates/globals/banners/facilityBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useRef, useEffect, useState } from 'react'
import { recordEvent } from '@/lib/analytics/recordEvent'
import { regionBaseURL } from '@/lib/utils/helpers'
// import { regionBaseURL } from '@/lib/utils/helpers'
import { VaBanner } from '@department-of-veterans-affairs/component-library/dist/react-bindings'
import { FacilityBanner as FormattedFacilityBanner } from '@/types/formatted/banners'

Expand Down Expand Up @@ -30,17 +30,16 @@ export const FacilityBanner = ({
return () => window.removeEventListener('click', handler)
}, [])

const findPath = path
const hideOnSubpages = inheritanceSubpages
// const findPath = path
// const hideOnSubpages = inheritanceSubpages
const alertType = fieldAlertType === 'information' ? 'info' : fieldAlertType

const region = '/' + regionBaseURL(findPath)
const lastArg = findPath?.substring(findPath?.lastIndexOf('/'))
// const region = '/' + regionBaseURL(findPath)
// const lastArg = findPath?.substring(findPath?.lastIndexOf('/'))

let content = body
const statusUrl = ''

// TODO: Banner AlertVAMCS data is a special case. we need to call a relationship which our current banner endpoint does not support. node--vamc_operating_status_and_alerts
// if (bannerAlertVamcs) {
// bannerAlertVamcs?.map((vamc) => {
// if (region == vamc?.office?.path) {
Expand Down
23 changes: 13 additions & 10 deletions src/templates/globals/wrapper/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ const banners = [
type: 'node--banner',
},
]
const props = { bannerData: [], headerFooterData: {} }

describe('<Wrapper> renders', () => {
test('body', () => {
render(
<>
<Wrapper>
<Wrapper {...props}>
<div>This is the layout</div>
</Wrapper>
</>
Expand All @@ -29,9 +30,11 @@ describe('<Wrapper> renders', () => {
})

test('<Banner> when bannerData exists', () => {
const props = { bannerData: banners }

render(<Wrapper bannerData={props.bannerData}>{children}</Wrapper>)
render(
<Wrapper {...props} bannerData={banners}>
{children}
</Wrapper>
)
expect(screen.getByRole('region')).toHaveAttribute(
'headline',
'COVID-19 vaccines at VA'
Expand All @@ -40,18 +43,18 @@ describe('<Wrapper> renders', () => {
})

test('<Banner> when bannerData does not exist', () => {
const props = { bannerData: null }

render(<Wrapper bannerData={props.bannerData}>{children}</Wrapper>)
render(<Wrapper {...props}>{children}</Wrapper>)
expect(
screen.queryByText(/This is the banner body/)
).not.toBeInTheDocument()
})

test('Header and Footer are present', () => {
const props = { bannerData: banners }

render(<Wrapper bannerData={props.bannerData}>{children}</Wrapper>)
render(
<Wrapper {...props} bannerData={banners}>
{children}
</Wrapper>
)

expect(screen.getByRole('banner')).toHaveClass('header')

Expand Down
Loading

0 comments on commit c670c93

Please sign in to comment.