diff --git a/.yarn/cache/@babel-runtime-npm-7.26.0-9afa3c4ef6-c8e2c0504a.zip b/.yarn/cache/@babel-runtime-npm-7.26.0-9afa3c4ef6-c8e2c0504a.zip new file mode 100644 index 000000000..4b9e5e548 Binary files /dev/null and b/.yarn/cache/@babel-runtime-npm-7.26.0-9afa3c4ef6-c8e2c0504a.zip differ diff --git a/.yarn/cache/@department-of-veterans-affairs-component-library-npm-48.0.1-61178d9ee8-05df1e119d.zip b/.yarn/cache/@department-of-veterans-affairs-component-library-npm-48.0.1-61178d9ee8-05df1e119d.zip new file mode 100644 index 000000000..97c13135c Binary files /dev/null and b/.yarn/cache/@department-of-veterans-affairs-component-library-npm-48.0.1-61178d9ee8-05df1e119d.zip differ diff --git a/.yarn/cache/@department-of-veterans-affairs-web-components-npm-16.0.1-1da5e33457-63cb0a4a66.zip b/.yarn/cache/@department-of-veterans-affairs-web-components-npm-16.0.1-1da5e33457-63cb0a4a66.zip new file mode 100644 index 000000000..f1337fd7a Binary files /dev/null and b/.yarn/cache/@department-of-veterans-affairs-web-components-npm-16.0.1-1da5e33457-63cb0a4a66.zip differ diff --git a/.yarn/cache/chromatic-npm-11.12.5-837f739e77-92b8de184e.zip b/.yarn/cache/chromatic-npm-11.12.5-837f739e77-92b8de184e.zip deleted file mode 100644 index fb0b6e0e0..000000000 Binary files a/.yarn/cache/chromatic-npm-11.12.5-837f739e77-92b8de184e.zip and /dev/null differ diff --git a/.yarn/cache/chromatic-npm-11.18.1-582a700d2c-c55e349155.zip b/.yarn/cache/chromatic-npm-11.18.1-582a700d2c-c55e349155.zip new file mode 100644 index 000000000..afec50be2 Binary files /dev/null and b/.yarn/cache/chromatic-npm-11.18.1-582a700d2c-c55e349155.zip differ diff --git a/.yarn/cache/tslib-npm-2.8.1-66590b21b8-e4aba30e63.zip b/.yarn/cache/tslib-npm-2.8.1-66590b21b8-e4aba30e63.zip new file mode 100644 index 000000000..501b0302c Binary files /dev/null and b/.yarn/cache/tslib-npm-2.8.1-66590b21b8-e4aba30e63.zip differ diff --git a/README.md b/README.md index ee5639e97..93e850d80 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,9 @@ You should set these up before attempting to install the repo. 7. In the `next-build` directory, run `yarn setup` to pull initial built assets from the `vets-website` repo. This will grab a bunch of files from a vets-website S3 bucket and place them into the appropriate `public/` folders. -8. Run `yarn dev`. +8. In your `envs/.env.local` file, uncomment `FEATURE_NEXT_BUILD_CONTENT_ALL=true`. + +9. Run `yarn dev`. You will now have a Next.js development server running at http://localhost:3999, which will refresh with changes to your local environment. (Note: your local port may differ if you changed the value for `PORT` in .env.local). diff --git a/READMEs/tugboat.md b/READMEs/tugboat.md index 2cbc8d592..2b3ae547a 100644 --- a/READMEs/tugboat.md +++ b/READMEs/tugboat.md @@ -17,7 +17,7 @@ See the [va.gov-cms tugboat docs](https://github.com/department-of-veterans-affa At VA, our lower environments are each built from a Tugboat Base Preview, in some fashion. Our Tugboat configuration is relevant to the discussion: 1. **Project**: [next-build](https://tugboat.vfs.va.gov/64d5537c2d3036648da7c7ff) - 1. **Repository**: [next-build Pull Request Environments](https://tugboat.vfs.va.gov/64d5537c2d3036648da7c7ff5fd3b8ee7b4657022b5722d6) — Is used for managing PR Previews, automatically triggered by Pull Requests in next-build repo. + 1. **Repository**: [next-build Pull Request Environments](https://tugboat.vfs.va.gov/64d5537c2d3036648da7c7ff) — Is used for managing PR Previews, automatically triggered by Pull Requests in next-build repo. 1. **Base Preview**: Built nightly at 11am UTC (6am EST, 5am EDT). It is built one hour later than the CMS Mirror Base Preview's nightly refresh because it is the default target endpoint for data. This data will then be used for all next-build PR Preview envs until the next time this Base Preview is refreshed. ## CLI Setup diff --git a/additional.d.ts b/additional.d.ts index dc6917c89..aeea9d9e0 100644 --- a/additional.d.ts +++ b/additional.d.ts @@ -22,14 +22,16 @@ declare module 'debug' declare namespace JSX { interface IntrinsicElements { - 'va-alert' - 'va-link' - 'va-icon' - 'va-button' - 'va-breadcrumbs' 'va-accordion' 'va-accordion-item' - 'va-on-this-page' + 'va-alert' 'va-back-to-top' + 'va-breadcrumbs' + 'va-button' + 'va-icon' + 'va-link' + 'va-link-action' + 'va-on-this-page' + 'va-telephone' } } diff --git a/generator-templates/component/test.hbs b/generator-templates/component/test.hbs index 94c606157..b4592a3dd 100644 --- a/generator-templates/component/test.hbs +++ b/generator-templates/component/test.hbs @@ -1,3 +1,4 @@ +import React from 'react' import { render, screen } from '@testing-library/react' import { {{pascalCase name}} } from './index' diff --git a/generator-templates/query/test.hbs b/generator-templates/query/test.hbs index 9a25d567d..139a3cd05 100644 --- a/generator-templates/query/test.hbs +++ b/generator-templates/query/test.hbs @@ -1,25 +1,28 @@ +/** +* @jest-environment node +*/ + import { {{pascalCase name}} } from '@/types/drupal/node' import { queries } from '@/data/queries' import mockData from '@/mocks/{{camelCase name}}.mock.json' const {{pascalCase name}}Mock: {{pascalCase name}} = mockData -describe('{{pascalCase name}} formatData', () => { - let windowSpy - - beforeEach(() => { - windowSpy = jest.spyOn(window, 'window', 'get') - }) - - afterEach(() => { - windowSpy.mockRestore() +// remove if this component does not have a data fetch +describe('DrupalJsonApiParams configuration', () => { + test('params function sets the correct include fields', () => { + // TODO }) +}) +describe('{{pascalCase name}} formatData', () => { test('outputs formatted data', () => { - windowSpy.mockImplementation(() => undefined) - expect( queries.formatData('node--{{snakeCase name}}', {{pascalCase name}}Mock) ).toMatchSnapshot() }) + + test('handles no answers correctly', () => { + // TODO + }) }) diff --git a/src/data/queries/index.ts b/src/data/queries/index.ts index 823f32f2b..f1d17f142 100644 --- a/src/data/queries/index.ts +++ b/src/data/queries/index.ts @@ -1,51 +1,51 @@ import { createQueries } from 'next-drupal-query' -import * as PressRelease from './pressRelease' -import * as PressReleaseTeaser from './pressReleaseTeaser' -import * as PressReleaseListing from './pressReleaseListing' -import * as NewsStory from './newsStory' -import * as NewsStoryTeaser from './newsStoryTeaser' -import * as StoryListing from './storyListing' -import * as QuestionAnswer from './questionAnswer' -import * as ExpandableText from './expandableText' -import * as LinkTeaser from './linkTeaser' -import * as MediaImage from './mediaImage' -import * as MediaDocument from './mediaDocument' -import * as MediaVideo from './mediaVideo' -import * as Banners from './banners' -import * as PersonProfile from './personProfile' -import * as Button from './button' -import * as AudienceTopics from './audienceTopics' +import * as Accordion from './accordion' import * as Alert from './alert' import * as AlertBlock from './alertBlock' -import * as AlertSingle from './alertSingle' import * as AlertNonReusable from './alertNonReusable' -import * as EmailContact from './emailContact' -import * as PhoneNumber from './phoneNumber' -import * as ContactInfo from './contactInfo' +import * as AlertSingle from './alertSingle' +import * as AudienceTopics from './audienceTopics' +import * as Banners from './banners' import * as BenefitsHub from './benefitsHubLinks' -import * as Wysiwyg from './wysiwyg' -import * as StaticPathResources from './staticPathResources' -import * as HeaderFooter from './headerFooter' -import * as ProcessList from './processList' -import * as PromoBlock from './promoBlock' +import * as Button from './button' +import * as CollapsiblePanel from './collapsiblePanel' +import * as CollapsiblePanelItem from './collapsiblePanelItem' +import * as ContactInfo from './contactInfo' +import * as EmailContact from './emailContact' import * as Event from './event' -import * as EventTeaser from './eventTeaser' import * as EventListing from './eventListing' -import * as VamcEhr from './vamcEhr' +import * as EventTeaser from './eventTeaser' +import * as ExpandableText from './expandableText' import * as FeaturedContent from './featuredContent' -import * as Accordion from './accordion' -import * as SupportServices from './supportServices' -import * as ResourcesSupport from './resourcesSupport' -import * as CollapsiblePanel from './collapsiblePanel' -import * as CollapsiblePanelItem from './collapsiblePanelItem' +import * as HeaderFooter from './headerFooter' +import * as HealthServices from './healthServices' +import * as LinkTeaser from './linkTeaser' +import * as MediaDocument from './mediaDocument' +import * as MediaImage from './mediaImage' +import * as MediaVideo from './mediaVideo' +import * as NewsStory from './newsStory' +import * as NewsStoryTeaser from './newsStoryTeaser' import * as NumberCallout from './numberCallout' -import * as Table from './table' -import * as ReactWidget from './reactWidget' +import * as PersonProfile from './personProfile' +import * as PhoneNumber from './phoneNumber' +import * as PressRelease from './pressRelease' +import * as PressReleaseListing from './pressReleaseListing' +import * as PressReleaseTeaser from './pressReleaseTeaser' +import * as ProcessList from './processList' +import * as PromoBlock from './promoBlock' +import * as QaGroup from './qaGroup' import * as QaParagraph from './qaParagraph' import * as QaSection from './qaSection' -import * as QaGroup from './qaGroup' +import * as QuestionAnswer from './questionAnswer' +import * as ReactWidget from './reactWidget' +import * as ResourcesSupport from './resourcesSupport' +import * as StaticPathResources from './staticPathResources' +import * as StoryListing from './storyListing' +import * as SupportServices from './supportServices' +import * as Table from './table' +import * as VamcEhr from './vamcEhr' import * as VetCenter from './vetCenter' -import * as HealthServices from './healthServices' +import * as Wysiwyg from './wysiwyg' import { ResourceType, ParagraphResourceType, @@ -57,22 +57,22 @@ import { export const QUERIES_MAP = { // Standard Drupal entity data queries // Nodes + [RESOURCE_TYPES.BENEFITS_HUB]: BenefitsHub, // "Benefits Hub Landing Page" + [RESOURCE_TYPES.EVENT]: Event, + [`${RESOURCE_TYPES.EVENT}--teaser` as const]: EventTeaser, + [RESOURCE_TYPES.EVENT_LISTING]: EventListing, + [RESOURCE_TYPES.HEALTH_SERVICES]: HealthServices, [RESOURCE_TYPES.STORY]: NewsStory, [`${RESOURCE_TYPES.STORY}--teaser` as const]: NewsStoryTeaser, [RESOURCE_TYPES.STORY_LISTING]: StoryListing, [RESOURCE_TYPES.QA]: QuestionAnswer, - [RESOURCE_TYPES.EVENT]: Event, - [`${RESOURCE_TYPES.EVENT}--teaser` as const]: EventTeaser, - [RESOURCE_TYPES.EVENT_LISTING]: EventListing, [RESOURCE_TYPES.PERSON_PROFILE]: PersonProfile, [RESOURCE_TYPES.PRESS_RELEASE]: PressRelease, [`${RESOURCE_TYPES.PRESS_RELEASE}--teaser` as const]: PressReleaseTeaser, [RESOURCE_TYPES.PRESS_RELEASE_LISTING]: PressReleaseListing, - [RESOURCE_TYPES.BENEFITS_HUB]: BenefitsHub, // "Benefits Hub Landing Page" - [RESOURCE_TYPES.SUPPORT_SERVICES]: SupportServices, [RESOURCE_TYPES.RESOURCES_SUPPORT]: ResourcesSupport, + [RESOURCE_TYPES.SUPPORT_SERVICES]: SupportServices, [RESOURCE_TYPES.VET_CENTER]: VetCenter, - [RESOURCE_TYPES.HEALTH_SERVICES]: HealthServices, // Paragraphs [PARAGRAPH_RESOURCE_TYPES.ACCORDION_ITEM]: Accordion, diff --git a/src/data/queries/supportServices.ts b/src/data/queries/supportServices.ts index a7603fffd..36048836e 100644 --- a/src/data/queries/supportServices.ts +++ b/src/data/queries/supportServices.ts @@ -15,8 +15,8 @@ export const formatter: QueryFormatter = ( if (!entity) return null return { - title: entity.title, - value: entity.field_phone_number, + label: entity.title, + number: entity.field_phone_number, href: entity.field_link.uri, } } diff --git a/src/data/queries/tests/__snapshots__/contactInfo.test.tsx.snap b/src/data/queries/tests/__snapshots__/contactInfo.test.tsx.snap index 7051de88f..503163b1d 100644 --- a/src/data/queries/tests/__snapshots__/contactInfo.test.tsx.snap +++ b/src/data/queries/tests/__snapshots__/contactInfo.test.tsx.snap @@ -13,8 +13,8 @@ exports[`ContactInfo formatData outputs formatted data 1`] = ` "contactType": "DC", "defaultContact": { "href": "tel:1-800-698-2411", - "title": "MyVA411 main information line:", - "value": "800-698-2411", + "label": "MyVA411 main information line:", + "number": "800-698-2411", }, "entityId": 13094, "id": "ebfce5d0-1bc5-45b9-93e9-669a1c1da645", diff --git a/src/data/queries/tests/__snapshots__/newsStory.test.tsx.snap b/src/data/queries/tests/__snapshots__/newsStory.test.tsx.snap index 5f04decb8..211221862 100644 --- a/src/data/queries/tests/__snapshots__/newsStory.test.tsx.snap +++ b/src/data/queries/tests/__snapshots__/newsStory.test.tsx.snap @@ -362,7 +362,7 @@ exports[`node--news_story formatData outputs formatted data 1`] = ` "width": 456, }, "introText": "When a hospital has a host of great doctors, honoring just two every year is challenging. ", - "lastUpdated": "2019-05-14T15:35:12+00:00", + "lastUpdated": "2020-03-24T20:10:28+00:00", "listing": "/pittsburgh-health-care/stories", "metatags": [ { diff --git a/src/data/queries/tests/__snapshots__/personProfile.test.tsx.snap b/src/data/queries/tests/__snapshots__/personProfile.test.tsx.snap index e9abde7a0..4af0c4eed 100644 --- a/src/data/queries/tests/__snapshots__/personProfile.test.tsx.snap +++ b/src/data/queries/tests/__snapshots__/personProfile.test.tsx.snap @@ -14,7 +14,7 @@ exports[`Person profile returns formatted data outputs formatted data 1`] = ` "id": "fe42bc63-0933-40bb-978a-ef951fa75684", "introText": null, "lastName": "Doyle", - "lastUpdated": "2019-08-26T22:03:30+00:00", + "lastUpdated": "2019-08-27T17:49:03+00:00", "media": { "alt": "Patrick J. Doyle", "height": 129, diff --git a/src/data/queries/tests/__snapshots__/pressRelease.test.tsx.snap b/src/data/queries/tests/__snapshots__/pressRelease.test.tsx.snap index 9136e49f6..54ae6aa44 100644 --- a/src/data/queries/tests/__snapshots__/pressRelease.test.tsx.snap +++ b/src/data/queries/tests/__snapshots__/pressRelease.test.tsx.snap @@ -41,7 +41,7 @@ exports[`node--press_release formatData output formatted data 1`] = ` "fullText": "

We invite you to come and read our 2019 Annual Report.

", "id": "6153ed5b-85c2-4ead-9893-3d656ad5d758", "introText": "We invite you to come and read our 2019 Annual Report. ", - "lastUpdated": "2021-04-12T14:25:27+00:00", + "lastUpdated": "2021-04-12T14:27:39+00:00", "listing": "/wilmington-health-care/news-releases", "metatags": [ { diff --git a/src/data/queries/tests/__snapshots__/resourcesSupport.test.tsx.snap b/src/data/queries/tests/__snapshots__/resourcesSupport.test.tsx.snap index cebed773b..cc6540084 100644 --- a/src/data/queries/tests/__snapshots__/resourcesSupport.test.tsx.snap +++ b/src/data/queries/tests/__snapshots__/resourcesSupport.test.tsx.snap @@ -51,8 +51,8 @@ exports[`Resources Support formatData outputs formatted data 1`] = ` "contactType": "DC", "defaultContact": { "href": "tel:1-800-698-2411", - "title": "MyVA411 main information line:", - "value": "800-698-2411", + "label": "MyVA411 main information line:", + "number": "800-698-2411", }, "entityId": 23309, "id": "ab6ce4bb-c9c4-426a-a157-7db1b9c4e70e", @@ -63,7 +63,7 @@ exports[`Resources Support formatData outputs formatted data 1`] = ` "id": "5b4943dc-764e-454c-a698-dd3f0bc0df32", "intro": "

We offer some life insurance programs to service members and Veterans that are now closed to new enrollees. Learn more about these programs and what to do if you want to convert your policy.

", - "lastUpdated": "2020-12-18T18:21:27+00:00", + "lastUpdated": "2020-12-22T17:31:57+00:00", "mainContent": [ { "html": "

United States Government Life Insurance (USGLI)

diff --git a/src/data/queries/tests/supportServices.test.tsx b/src/data/queries/tests/supportServices.test.tsx index 373f60d54..fafbbc463 100644 --- a/src/data/queries/tests/supportServices.test.tsx +++ b/src/data/queries/tests/supportServices.test.tsx @@ -11,8 +11,8 @@ describe('SupportServices formatter function', () => { activeServiceMock as unknown as NodeSupportService ) expect(formatted).toEqual({ - title: activeServiceMock.title, - value: activeServiceMock.field_phone_number, + label: activeServiceMock.title, + number: activeServiceMock.field_phone_number, href: activeServiceMock.field_link.uri, }) }) diff --git a/src/lib/drupal/query.ts b/src/lib/drupal/query.ts index b0cf29f99..92c87bd13 100644 --- a/src/lib/drupal/query.ts +++ b/src/lib/drupal/query.ts @@ -135,6 +135,7 @@ export const entityBaseFields = (entity: NodeTypes): PublishedEntity => { title: entity.title, metatags: entity.metatag, breadcrumbs: entity.breadcrumbs, - lastUpdated: entity.field_last_saved_by_an_editor || entity.created, + lastUpdated: + entity.field_last_saved_by_an_editor || entity.changed || entity.created, } } diff --git a/src/pages/[[...slug]].tsx b/src/pages/[[...slug]].tsx index 860f14e00..db6431759 100644 --- a/src/pages/[[...slug]].tsx +++ b/src/pages/[[...slug]].tsx @@ -13,15 +13,7 @@ import dynamic from 'next/dynamic' import Script from 'next/script' import { drupalClient } from '@/lib/drupal/drupalClient' import { getGlobalElements } from '@/lib/drupal/getGlobalElements' -import { Wrapper } from '@/templates/layouts/wrapper' -import { NewsStory } from '@/templates/layouts/newsStory' -import { PressRelease } from '@/templates/layouts/pressRelease' -import { PressReleaseListing } from '@/templates/layouts/pressReleaseListing' -import { StoryListing } from '@/templates/layouts/storyListing' -import HTMLComment from '@/templates/common/util/HTMLComment' import { shouldHideHomeBreadcrumb } from '@/lib/utils/breadcrumbs' -import { Event } from '@/templates/layouts/event' -import { EventListing } from '@/templates/layouts/eventListing' import { getStaticPathsByResourceType } from '@/lib/drupal/staticPaths' import { RESOURCE_TYPES, @@ -33,19 +25,31 @@ import { } from '@/lib/drupal/staticProps' import { StaticPropsResource } from '@/lib/drupal/staticProps' import { FormattedPageResource } from '@/data/queries' -import { LayoutProps } from '@/templates/layouts/wrapper' + +// Types +import { Event as FormattedEvent } from '@/types/formatted/event' +import { EventListing as FormattedEventListing } from '@/types/formatted/eventListing' import { NewsStory as FormattedNewsStory } from '@/types/formatted/newsStory' import { PressRelease as FormattedPressRelease } from '@/types/formatted/pressRelease' import { PressReleaseListing as FormattedPressReleaseListing } from '@/types/formatted/pressReleaseListing' +import { ResourcesSupport as FormattedResourcesSupport } from '@/types/formatted/resourcesSupport' import { StoryListing as FormattedStoryListing } from '@/types/formatted/storyListing' -import { EventListing as FormattedEventListing } from '@/types/formatted/eventListing' -import { Event as FormattedEvent } from '@/types/formatted/event' +import { VetCenter as FormattedVetCenter } from '@/types/formatted/vetCenter' + +// Templates +import HTMLComment from '@/templates/common/util/HTMLComment' +import { Event } from '@/templates/layouts/event' +import { EventListing } from '@/templates/layouts/eventListing' +import { LayoutProps } from '@/templates/layouts/wrapper' import { Meta } from '@/templates/common/meta' +import { NewsStory } from '@/templates/layouts/newsStory' +import { PressRelease } from '@/templates/layouts/pressRelease' +import { PressReleaseListing } from '@/templates/layouts/pressReleaseListing' import { PreviewCrumb } from '@/templates/common/preview' -import { ResourcesSupport as FormattedResourcesSupport } from '@/types/formatted/resourcesSupport' import { ResourcesSupport } from '@/templates/layouts/resourcesSupport' -import { VetCenter as FormattedVetCenter } from '@/types/formatted/vetCenter' +import { StoryListing } from '@/templates/layouts/storyListing' import { VetCenter } from '@/templates/layouts/vetCenter' +import { Wrapper } from '@/templates/layouts/wrapper' // IMPORTANT: in order for a content type to build in Next Build, it must have an appropriate // environment variable set in one of two places: @@ -119,32 +123,32 @@ export default function ResourcePage({
- {resource.type === RESOURCE_TYPES.STORY_LISTING && ( - + {resource.type === RESOURCE_TYPES.EVENT && ( + + )} + {resource.type === RESOURCE_TYPES.EVENT_LISTING && ( + )} {resource.type === RESOURCE_TYPES.STORY && ( )} + {resource.type === RESOURCE_TYPES.PRESS_RELEASE && ( + + )} {resource.type === RESOURCE_TYPES.PRESS_RELEASE_LISTING && ( )} - {resource.type === RESOURCE_TYPES.PRESS_RELEASE && ( - - )} {/* {resource.type === RESOURCE_TYPES.QA && ( )} */} - {resource.type === RESOURCE_TYPES.EVENT_LISTING && ( - - )} - {resource.type === RESOURCE_TYPES.EVENT && ( - - )} {resource.type === RESOURCE_TYPES.RESOURCES_SUPPORT && ( )} + {resource.type === RESOURCE_TYPES.STORY_LISTING && ( + + )} {resource.type === RESOURCE_TYPES.VET_CENTER && ( )} diff --git a/src/templates/common/breadcrumbs/index.tsx b/src/templates/common/breadcrumbs/index.tsx index d26e5bdd5..fc5312e9f 100644 --- a/src/templates/common/breadcrumbs/index.tsx +++ b/src/templates/common/breadcrumbs/index.tsx @@ -68,14 +68,7 @@ const Breadcrumbs = ({ return (
- +
) } diff --git a/src/templates/common/link/index.test.tsx b/src/templates/common/link/index.test.tsx deleted file mode 100644 index 598add379..000000000 --- a/src/templates/common/link/index.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { render } from '@testing-library/react' -import Link from './' - -// Mock useRouter hook for link component -jest.mock('next/router', () => ({ - useRouter: () => ({ - basePath: '', - pathname: '/', - query: {}, - asPath: '', - push: jest.fn(), - replace: jest.fn(), - reload: jest.fn(), - back: jest.fn(), - prefetch: async () => undefined, - beforePopState: () => undefined, - events: { - on: () => undefined, - off: () => undefined, - emit: () => undefined, - }, - isFallback: false, - }), -})) - -describe('Link Component', () => { - test('renders link with text content correctly', () => { - const { getByText } = render( - - Link Text - - ) - - const linkElement = getByText('Link Text') - expect(linkElement).toBeInTheDocument() - expect(linkElement.tagName).toBe('SPAN') - }) -}) diff --git a/src/templates/common/link/index.tsx b/src/templates/common/link/index.tsx deleted file mode 100644 index 21c428c87..000000000 --- a/src/templates/common/link/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import NextLink from 'next/link' -import type { LinkProps as NextLinkProps } from 'next/link' -import { useRouter } from 'next/router' - -import { isRelative } from '@/lib/utils/helpers' - -interface LinkProps extends NextLinkProps { - href: string - children?: React.ReactElement -} - -export default function Link({ - href, - passHref, - as, - children, - ...props -}: LinkProps) { - const router = useRouter() - - if (!href) { - return null - } - - // Use Next Link for internal links, and for others. - if (isRelative(href)) { - // Disable prefetching in preview mode. - // We do this here inside of inline `prefetch={!router.isPreview}` - // because `prefetch={true}` is not allowed. - // See https://nextjs.org/docs/messages/prefetch-true-deprecated - const linkProps = router.isPreview ? { prefetch: false, ...props } : props - - return ( - - {children} - - ) - } - - return React.cloneElement(children, { - href, - }) -} diff --git a/src/templates/common/relatedLinks/index.test.tsx b/src/templates/common/relatedLinks/index.test.tsx new file mode 100644 index 000000000..47ab1e375 --- /dev/null +++ b/src/templates/common/relatedLinks/index.test.tsx @@ -0,0 +1,64 @@ +import { render } from '@testing-library/react' +import { RelatedLinks } from '.' +import { FormattedRelatedLinks } from '@/types/formatted/relatedLinks' + +describe('RelatedLinks Component', () => { + test('renders the correct number of links when there are multiple', () => { + const relatedLinks: FormattedRelatedLinks = { + links: [ + { + uri: 'https://va.gov/burials-memorials/eligibility', + title: 'Eligibility for burial in a VA national cemetery', + summary: + 'Here is a summary for this URL so you can see how it displays underneath.', + }, + { + uri: 'https://va.gov/burials-memorials/schedule-a-burial', + title: 'Schedule a burial for a Veteran or family member', + summary: null, + }, + ], + sectionTitle: 'Related information', + } + + const { container } = render() + + expect(container.innerHTML).toContain( + 'Eligibility for burial in a VA national cemetery' + ) + expect(container.innerHTML).toContain( + 'href="https://va.gov/burials-memorials/eligibility"' + ) + expect(container.innerHTML).toContain( + 'Here is a summary for this URL so you can see how it displays underneath.' + ) + expect(container.innerHTML).toContain( + 'Schedule a burial for a Veteran or family member' + ) + expect(container.innerHTML).toContain( + 'href="https://va.gov/burials-memorials/schedule-a-burial"' + ) + }) + + test('renders link correctly when there is only one', () => { + const oneLink: FormattedRelatedLinks = { + links: [ + { + uri: 'https://va.gov/burials-memorials/schedule-a-burial', + title: 'Schedule a burial for a Veteran or family member', + summary: null, + }, + ], + sectionTitle: 'VA benefits', + } + + const { container } = render() + + expect(container.innerHTML).toContain( + 'Schedule a burial for a Veteran or family member' + ) + expect(container.innerHTML).toContain( + 'href="https://va.gov/burials-memorials/schedule-a-burial"' + ) + }) +}) diff --git a/src/templates/common/relatedLinks/index.tsx b/src/templates/common/relatedLinks/index.tsx new file mode 100644 index 000000000..ee8638df5 --- /dev/null +++ b/src/templates/common/relatedLinks/index.tsx @@ -0,0 +1,51 @@ +import { isEmpty } from 'lodash' +import { FormattedRelatedLinks } from '@/types/formatted/relatedLinks' + +// General component used for one or more links with a line of descriptive text underneath +// Does not map directly to any one Drupal type; it is simply a shared UI component +export const RelatedLinks = ({ + links, + sectionTitle, +}: FormattedRelatedLinks): JSX.Element => { + if (isEmpty(links)) { + return null + } + + let link + const renderLink = (uri, title, summary) => ( + <> +

+ + + +

+ {summary &&

{summary}

} + + ) + + if (links.length === 1) { + link = links[0] + } + + return ( +
+ {sectionTitle && ( +

+ {sectionTitle} +

+ )} + + {links.length > 1 && ( +
    + {links.map((link, index) => ( +
  • + {renderLink(link.uri, link.title, link?.summary)} +
  • + ))} +
+ )} + + {links.length === 1 && renderLink(link.uri, link.title, link?.summary)} +
+ ) +} diff --git a/src/templates/common/relatedLinks/relatedLinks.stories.ts b/src/templates/common/relatedLinks/relatedLinks.stories.ts new file mode 100644 index 000000000..fae1f25d3 --- /dev/null +++ b/src/templates/common/relatedLinks/relatedLinks.stories.ts @@ -0,0 +1,30 @@ +import { Meta, StoryObj } from '@storybook/react' + +import { RelatedLinks } from '.' + +const meta: Meta = { + title: 'Common/Related Links', + component: RelatedLinks, +} +export default meta + +type Story = StoryObj + +export const Example: Story = { + args: { + sectionTitle: 'VA benefits', + links: [ + { + uri: 'https://va.gov/burials-memorials/eligibility', + title: 'Eligibility for burial in a VA national cemetery', + summary: + 'Here is a summary for this URL so you can see how it displays underneath.', + }, + { + uri: 'https://va.gov/burials-memorials/schedule-a-burial', + title: 'Schedule a burial for a Veteran or family member', + summary: null, + }, + ], + }, +} diff --git a/src/templates/common/secondaryButtonGroup/index.test.tsx b/src/templates/common/secondaryButtonGroup/index.test.tsx new file mode 100644 index 000000000..b1889270e --- /dev/null +++ b/src/templates/common/secondaryButtonGroup/index.test.tsx @@ -0,0 +1,51 @@ +import { render } from '@testing-library/react' +import { SecondaryButtonGroup } from './' +import { Button as FormattedButton } from '@/types/formatted/button' +import { ParagraphComponent } from '@/types/formatted/paragraph' + +describe('SecondaryButtonGroup Component', () => { + test('renders action links correctly when there are multiple', () => { + const multipleButtons: ParagraphComponent[] = [ + { + id: '1', + label: 'Button one', + url: 'https://www.va.gov/button-one', + }, + { + id: '2', + label: 'Button two', + url: 'https://www.va.gov/button-two', + }, + ] + + const { container } = render( + + ) + + expect(container.innerHTML).toContain('Button one') + expect(container.innerHTML).toContain( + 'href="https://www.va.gov/button-one"' + ) + expect(container.innerHTML).toContain('Button two') + expect(container.innerHTML).toContain( + 'href="https://www.va.gov/button-two"' + ) + }) + + test('renders action link correctly when there is only one', () => { + const oneButton: ParagraphComponent[] = [ + { + id: '1', + label: 'Single button', + url: 'https://www.va.gov/single-button', + }, + ] + + const { container } = render() + + expect(container.innerHTML).toContain('Single button') + expect(container.innerHTML).toContain( + 'href="https://www.va.gov/single-button"' + ) + }) +}) diff --git a/src/templates/common/secondaryButtonGroup/index.tsx b/src/templates/common/secondaryButtonGroup/index.tsx new file mode 100644 index 000000000..d62eac734 --- /dev/null +++ b/src/templates/common/secondaryButtonGroup/index.tsx @@ -0,0 +1,35 @@ +import { Button as FormattedButton } from '@/types/formatted/button' +import { ParagraphComponent } from '@/types/formatted/paragraph' + +// Used for R&S pages; either a single blue CTA link or multiple +export const SecondaryButtonGroup = ({ + buttons, +}: { + buttons: ParagraphComponent[] +}): JSX.Element => { + if (buttons.length > 1) { + return ( +
    + {buttons?.map((button, index) => ( +
  • + +
  • + ))} +
+ ) + } + + const button = buttons[0] + + if (button) { + return ( + + ) + } + + return null +} diff --git a/src/templates/common/secondaryButtonGroup/secondaryButtonGroup.stories.ts b/src/templates/common/secondaryButtonGroup/secondaryButtonGroup.stories.ts new file mode 100644 index 000000000..d9a8a35d8 --- /dev/null +++ b/src/templates/common/secondaryButtonGroup/secondaryButtonGroup.stories.ts @@ -0,0 +1,30 @@ +import { Meta, StoryObj } from '@storybook/react' + +import { SecondaryButtonGroup } from '.' + +const meta: Meta = { + title: 'Common/Secondary Button Group', + component: SecondaryButtonGroup, +} +export default meta + +type Story = StoryObj + +export const Example: Story = { + args: { + buttons: [ + { + id: '1', + type: 'paragraph--button', + url: 'https://www.va.gov/careers-employment/vocational-rehabilitation/eligibility', + label: 'See eligibility for VR&E benefits', + }, + { + id: '2', + type: 'paragraph--button', + url: 'https://www.va.gov/careers-employment/vocational-rehabilitation/how-to-apply', + label: 'Find out how to apply for VR&E benefits', + }, + ], + }, +} diff --git a/src/templates/components/contactInfo/contactInfo.stories.ts b/src/templates/components/contactInfo/contactInfo.stories.ts index 039fb4939..31b290be9 100644 --- a/src/templates/components/contactInfo/contactInfo.stories.ts +++ b/src/templates/components/contactInfo/contactInfo.stories.ts @@ -14,8 +14,8 @@ export const Default: Story = { args: { contactType: 'DC', defaultContact: { - title: 'Phone Number', - value: '(855) 867-5309', + label: 'Phone Number', + number: '(855) 867-5309', href: 'tel:8558675309', }, }, @@ -25,8 +25,8 @@ export const AdditionalContactPhone: Story = { args: { contactType: 'DC', defaultContact: { - title: 'Phone Number', - value: '(855) 867-5309', + label: 'Phone Number', + number: '(855) 867-5309', href: 'tel:8558675309', }, additionalContact: { @@ -43,8 +43,8 @@ export const AdditionalContactEmail: Story = { args: { contactType: 'DC', defaultContact: { - title: 'Phone Number', - value: '(855) 867-5309', + label: 'Phone Number', + number: '(855) 867-5309', href: 'tel:8558675309', }, additionalContact: { @@ -61,28 +61,28 @@ export const BenefitsHubContact: Story = { contactType: 'BHC', benefitHubContacts: [ { - title: 'Health benefits hotline: ', - value: '877-222-VETS (8387)', + label: 'Health benefits hotline: ', + number: '877-222-VETS (8387)', href: 'tel:8772228387', }, { - title: 'My HealtheVet help desk: ', - value: '877-327-0022', + label: 'My HealtheVet help desk: ', + number: '877-327-0022', href: 'tel:8773270022', }, { - title: 'eBenefits technical support:', - value: '800-983-0937', + label: 'eBenefits technical support:', + number: '800-983-0937', href: 'tel:8009830937', }, { - title: 'MyVA411 main information line:', - value: '800-698-2411', + label: 'MyVA411 main information line:', + number: '800-698-2411', href: 'tel:8006982411', }, { - title: 'Telecommunications Relay Services (using TTY)', - value: 'TTY: 711', + label: 'Telecommunications Relay Services (using TTY)', + number: 'TTY: 711', href: 'tel:1+711', }, ], diff --git a/src/templates/components/contactInfo/index.test.tsx b/src/templates/components/contactInfo/index.test.tsx index c77a619d8..b24b436c3 100644 --- a/src/templates/components/contactInfo/index.test.tsx +++ b/src/templates/components/contactInfo/index.test.tsx @@ -1,7 +1,5 @@ import { render, screen } from '@testing-library/react' -import { fireEvent, getByRole } from '@testing-library/dom' jest.mock('@/lib/analytics/recordEvent') -import * as recordEvent from '@/lib/analytics/recordEvent' import { ContactInfo } from './index' import { ParagraphComponent } from '@/types/formatted/paragraph' import { ContactInfo as FormattedContactInfo } from '@/types/formatted/contactInfo' @@ -11,8 +9,8 @@ describe('ContactInfo with valid data', () => { id: '1', contactType: 'DC', defaultContact: { - title: 'Phone Number', - value: '(855) 867-5309', + label: 'Phone Number', + number: '(855) 867-5309', href: 'tel:8558675309', }, } @@ -45,50 +43,39 @@ describe('ContactInfo with valid data', () => { contactType: 'BHC', benefitHubContacts: [ { - title: 'Health benefits hotline: ', - value: '877-222-VETS (8387)', + label: 'Health benefits hotline: ', + number: '877-222-VETS (8387)', href: 'tel:8772228387', }, { - title: 'My HealtheVet help desk: ', - value: '877-327-0022', + label: 'My HealtheVet help desk: ', + number: '877-327-0022', href: 'tel:8773270022', }, { - title: 'eBenefits technical support:', - value: '800-983-0937', + label: 'eBenefits technical support:', + number: '800-983-0937', href: 'tel:8009830937', }, { - title: 'MyVA411 main information line:', - value: '800-698-2411', + label: 'MyVA411 main information line:', + number: '800-698-2411', href: 'tel:8006982411', }, { - title: 'Telecommunications Relay Services (using TTY)', - value: 'TTY: 711', + label: 'Telecommunications Relay Services (using TTY)', + number: 'TTY: 711', href: 'tel:1+711', }, ], } - render() + const { container } = render() expect(screen.queryByText(/Phone Number/)).not.toBeInTheDocument() expect(screen.queryByText(/My HealtheVet help desk/)).toBeInTheDocument() - }) - - test('click event sends correct params to recordEvent', () => { - data.defaultContact.value = 't$st.vet=ran@va.gov' - const { container } = render() - const link = getByRole(container, 'link') - - fireEvent.click(link) - expect(recordEvent.recordEvent).toHaveBeenCalledWith({ - event: 'nav-linkslist', - 'links-list-header': 't%24st.vet%3Dran%40va.gov', - 'links-list-section-header': 'Need more help?', - }) - jest.restoreAllMocks() + expect(container.innerHTML).toContain( + '' + ) }) }) diff --git a/src/templates/components/contactInfo/index.tsx b/src/templates/components/contactInfo/index.tsx index d6677a0fd..3f898d4ac 100644 --- a/src/templates/components/contactInfo/index.tsx +++ b/src/templates/components/contactInfo/index.tsx @@ -1,76 +1,58 @@ -import { recordEvent } from '@/lib/analytics/recordEvent' -import Link from 'next/link' import { Contact, - AdditionalContact as FormattedAdditionalContact, - BenefitHubContact, ContactInfo as FormattedContactInfo, EmailContact as FormattedEmailContact, PhoneContact as FormattedPhoneContact, - PressContact, } from '@/types/formatted/contactInfo' import { PARAGRAPH_RESOURCE_TYPES } from '@/lib/constants/resourceTypes' import { ParagraphComponent } from '@/types/formatted/paragraph' -const analytic = (header) => { - return { - event: 'nav-linkslist', - 'links-list-header': `${encodeURIComponent(header)}`, - 'links-list-section-header': 'Need more help?', +const canUseWebComponent = (telephone) => { + if (!telephone || /[a-zA-Z+]/.test(telephone)) { + return false } -} -// simple contact info base component -export const DefaultContact = ({ title, value, href }: Contact) => { - return ( - <> - {title} - recordEvent(analytic(value))} - href={href} - rel="noreferrer noopener" - passHref - > - {value} - - - ) + return true } export const EmailContact = ( email: ParagraphComponent ) => { - return ( -
  • - -
  • - ) + const { address, label } = email + + if (label && address) { + return ( +
  • + {label}  + +
  • + ) + } + + return null } export const PhoneContact = ( phone: ParagraphComponent ) => { - const phoneNumber = phone.extension - ? `${phone.number}p${phone.extension}` - : phone.number + const { extension, label, number } = phone - return ( -
  • - -
  • - ) + if (label && number) { + return ( +
  • + {label}  + +
  • + ) + } + + return null } // nested paragraphs -const AdditionalContact = (contact: FormattedAdditionalContact) => { +const AdditionalContact = ( + contact: FormattedEmailContact | FormattedPhoneContact +) => { switch (contact.type) { case PARAGRAPH_RESOURCE_TYPES.EMAIL_CONTACT: return @@ -81,12 +63,27 @@ const AdditionalContact = (contact: FormattedAdditionalContact) => { } // node--support-service nodes that get included -const BenefitHubContacts = ({ services }: BenefitHubContact) => { - return services.map((s) => ( -
  • - -
  • - )) +const BenefitHubContacts = ({ contacts }) => { + return contacts.map((contact) => { + const { href, label, number } = contact + + if (number && canUseWebComponent(number)) { + const phone = { + extension: null, + label, + number, + } + + return + } + + return ( +
  • + {label}  + +
  • + ) + }) } // wrapper around all types of contact info @@ -113,18 +110,21 @@ export function ContactInfo({ Need more help? {useDefaultContact ? ( - + <> + {defaultContact.label}  + + ) : ( -
      +
        {additionalContact && ( )} {contactType === 'BHC' && benefitHubContacts && ( - + )}
      )} diff --git a/src/templates/layouts/resourcesSupport/index.test.tsx b/src/templates/layouts/resourcesSupport/index.test.tsx index 20fb75e35..907284d25 100644 --- a/src/templates/layouts/resourcesSupport/index.test.tsx +++ b/src/templates/layouts/resourcesSupport/index.test.tsx @@ -58,12 +58,9 @@ describe(' Component', () => { type: 'paragraph--contact_information' as ContactInfo['type'], contactType: 'DC' as ContactInfo['contactType'], defaultContact: { - name: 'Test Name', - phone: '123-456-7890', - email: 'test@example.com', - title: 'Test contact title', - value: 'Test Value', - href: '/test-contact-href/', + href: 'tel:1-800-698-2411', + label: 'MyVA411 main information line:', + number: '800-698-2411', }, }, benefitsHubLinks: [ @@ -102,6 +99,6 @@ describe(' Component', () => { expect(screen.getByText('If you need support...')).toBeInTheDocument() expect(screen.getByText('Test Audience')).toBeInTheDocument() expect(screen.getByText('Test Topic')).toBeInTheDocument() - expect(screen.getByText('Test contact title')).toBeInTheDocument() + expect(screen.getByText('Need more help?')).toBeInTheDocument() }) }) diff --git a/src/types/formatted/contactInfo.ts b/src/types/formatted/contactInfo.ts index 464bd6cf9..f3f2e6ae7 100644 --- a/src/types/formatted/contactInfo.ts +++ b/src/types/formatted/contactInfo.ts @@ -9,8 +9,8 @@ export type ContactInfo = PublishedParagraph & { } export type Contact = { - title: string - value: string + label: string + number: string href: string } @@ -29,11 +29,6 @@ export type EmailContact = PublishedParagraph & { export type AdditionalContact = PhoneContact | EmailContact -// TODO: Is this being used? benefitHubContacts is typed as Contact[] not BenefitHubcontact[] -export type BenefitHubContact = { - services: Contact[] -} - export type PressContact = { id: string name: string diff --git a/src/types/formatted/relatedLinks.ts b/src/types/formatted/relatedLinks.ts new file mode 100644 index 000000000..724584d5d --- /dev/null +++ b/src/types/formatted/relatedLinks.ts @@ -0,0 +1,10 @@ +export type RelatedLink = { + title: string + uri: string + summary?: string +} + +export type FormattedRelatedLinks = { + sectionTitle: string + links: RelatedLink[] +}