diff --git a/playwright/tests/eventListing.spec.js b/playwright/tests/eventListing.spec.js new file mode 100644 index 000000000..b6c58b015 --- /dev/null +++ b/playwright/tests/eventListing.spec.js @@ -0,0 +1,22 @@ +const { test, expect } = require('../utils/next-test') + +test.describe('eventListing', () => { + test('Event Listing page renders with events that can be navigated to', async ({ + page, + }) => { + await page.goto('/butler-health-care/events') + await page.locator('.events a').first().click() + await expect(page).toHaveURL(/\/butler-health-care\/events\//) + }) + + test('Should render without a11y errors', async ({ + page, + makeAxeBuilder, + }) => { + await page.goto('/butler-health-care/events') + + const accessibilityScanResults = await makeAxeBuilder().analyze() + + expect(accessibilityScanResults.violations).toEqual([]) + }) +}) diff --git a/plopfile.js b/plopfile.js index 1fb6d479a..51e8990c5 100644 --- a/plopfile.js +++ b/plopfile.js @@ -66,7 +66,7 @@ module.exports = function (plop) { 'You will need to do a few steps manually:', '- Import & add your query to src/data/queries/index.ts', '- Add your resource type to src/lib/constants/resourceTypes.ts', - '- Update the mock.json with correct data', + '- Update the mock.json with correct data. See src/pages/_playgroud/api-explorer.tsx', '- Run `yarn test:u` to update test snapshots for your new query!', ], }) @@ -107,7 +107,7 @@ module.exports = function (plop) { 'You will need to do a few steps manually:', '- Import & add your query to src/data/queries/index.ts', '- Add your resource type to src/lib/constants/resourceTypes.ts', - '- Update the mock.json with correct data', + '- Update the mock.json with correct data. See src/pages/_playgroud/api-explorer.tsx', '- Run `yarn test:u` to update test snapshots for your new query!', // Create react component + test files for new Page type. { diff --git a/src/data/queries/eventListing.ts b/src/data/queries/eventListing.ts new file mode 100644 index 000000000..e38eff621 --- /dev/null +++ b/src/data/queries/eventListing.ts @@ -0,0 +1,105 @@ +import { QueryData, QueryFormatter, QueryParams } from 'next-drupal-query' +import { queries } from '.' +import { NodeEvent, NodeEventListing } from '@/types/drupal/node' +import { Menu } from '@/types/drupal/menu' +import { EventListing } from '@/types/formatted/eventListing' +import { RESOURCE_TYPES } from '@/lib/constants/resourceTypes' +import { PAGE_SIZES } from '@/lib/constants/pageSizes' +import { ExpandedStaticPropsContext } from '@/lib/drupal/staticProps' +import { + entityBaseFields, + fetchAndConcatAllResourceCollectionPages, + fetchSingleEntityOrPreview, + getMenu, +} from '@/lib/drupal/query' +import { buildSideNavDataFromMenu } from '@/lib/drupal/facilitySideNav' + +const PAGE_SIZE = PAGE_SIZES[RESOURCE_TYPES.EVENT_LISTING] + +// Define the query params for fetching node--event_listing. +export const params: QueryParams = () => { + return queries.getParams().addInclude(['field_office']) +} + +// Define the option types for the data loader. +export type EventListingDataOpts = { + id: string + context?: ExpandedStaticPropsContext +} + +type EventListingData = { + entity: NodeEventListing + events: NodeEvent[] + menu?: Menu + totalItems: number + totalPages: number +} + +const listingParams: QueryParams = (listingEntityId: string) => { + return queries + .getParams('node--event--teaser') + .addFilter('field_listing.id', listingEntityId) + .addSort('-created') +} + +// Implement the data loader. +export const data: QueryData = async ( + opts +) => { + const entity = (await fetchSingleEntityOrPreview( + opts, + RESOURCE_TYPES.EVENT_LISTING, + params + )) as NodeEventListing + + // Fetch list of events related to this listing + const { data: events } = + await fetchAndConcatAllResourceCollectionPages( + RESOURCE_TYPES.EVENT, + listingParams(entity.id), + PAGE_SIZE + ) + + // Fetch the menu name dynamically off of the field_office reference if available. + // The `/outreach-and-events/events` event listing page has no menu attached to it. + let menu = null + if (entity.field_office.field_system_menu) { + menu = await getMenu( + entity.field_office.field_system_menu.resourceIdObjMeta + .drupal_internal__target_id + ) + } + + return { + entity, + events, + menu, + totalItems: events.length, + totalPages: 1, // We don't want to paginate event listing pages. The widget handles pagination. + } +} + +export const formatter: QueryFormatter = ({ + entity, + events, + menu, + totalItems, + totalPages, +}) => { + const formattedEvents = events.map((event) => { + return queries.formatData('node--event--teaser', event) + }) + + let formattedMenu = null + if (menu !== null) + formattedMenu = buildSideNavDataFromMenu(entity.path.alias, menu) + + return { + ...entityBaseFields(entity), + introText: entity.field_intro_text, + events: formattedEvents, + menu: formattedMenu, + totalItems, + totalPages, + } +} diff --git a/src/data/queries/eventTeaser.ts b/src/data/queries/eventTeaser.ts new file mode 100644 index 000000000..ecb1a9dd6 --- /dev/null +++ b/src/data/queries/eventTeaser.ts @@ -0,0 +1,97 @@ +import { QueryFormatter, QueryParams } from 'next-drupal-query' +import { queries } from '.' +import { NodeEvent } from '@/types/drupal/node' +import { EventWidgetTeaser } from '@/types/formatted/event' +import { formatDateObject } from '@/lib/utils/date' + +// Define the query params for fetching node--event--teaser. +export const params: QueryParams = () => { + return queries + .getParams() + .addInclude([ + 'field_listing', + 'field_administration', + 'field_facility_location', + ]) +} + +// This formats Event nodes in the manner that the events widget from vets-website expects. +export const formatter: QueryFormatter = ( + entity: NodeEvent +) => { + const time = formatDateObject(entity.field_datetime_range_timezone) + + return { + changed: entity.changed, + entityBundle: entity.type, + entityId: entity.id, + entityPublished: entity.status, + entityUrl: { + path: entity.path.alias, + }, + fieldAdditionalInformationAbo: entity.field_additional_information_abo, + fieldAdditionalListings: null, + fieldAddress: { + addressLine1: entity.field_address?.address_line1 || null, + addressLine2: entity.field_address?.address_line2 || null, + administrativeArea: entity.field_address?.administrative_area || null, + countryCode: entity.field_address?.country_code || null, + locality: entity.field_address?.locality || null, + postalCode: entity.field_address?.postal_code || null, + }, + fieldAdministration: { + entity: { + entityId: entity.field_administration.id, + }, + }, + fieldBody: entity.field_body, + fieldCtaEmail: entity.field_cta_email, + fieldDatetimeRangeTimezone: time, + fieldDescription: entity.field_description, + fieldEventCost: entity.field_event_cost, + fieldEventCta: entity.field_event_cta, + fieldEventRegistrationrequired: entity.field_event_registrationrequired, + fieldFacilityLocation: entity.field_facility_location + ? { + entity: { + entityUrl: { + path: entity.field_facility_location.path?.alias || null, + }, + fieldAddress: { + addressLine1: + entity.field_facility_location.field_address?.address_line1 || + null, + addressLine2: + entity.field_facility_location.field_address?.address_line2 || + null, + administrativeArea: + entity.field_facility_location.field_address + ?.administrative_area || null, + countryCode: + entity.field_facility_location.field_address?.country_code || + null, + locality: + entity.field_facility_location.field_address?.locality || null, + postalCode: + entity.field_facility_location.field_address?.postal_code || + null, + }, + title: entity.field_facility_location?.title || null, + }, + } + : null, + fieldFeatured: entity.field_featured, + fieldHowToSignUp: entity.field_how_to_sign_up, + fieldLink: entity.field_link, + fieldListing: { + entity: { + entityId: entity.field_listing.id, + }, + }, + fieldLocationHumanreadable: entity.field_location_humanreadable, + fieldLocationType: entity.field_location_type, + fieldOrder: entity.field_order, + fieldUrlOfAnOnlineEvent: entity.field_url_of_an_online_event, + title: entity.title, + } +} diff --git a/src/data/queries/index.ts b/src/data/queries/index.ts index b1ccb22e5..4df0447a6 100644 --- a/src/data/queries/index.ts +++ b/src/data/queries/index.ts @@ -18,6 +18,8 @@ import * as StaticPathResources from './staticPathResources' import * as HeaderFooter from './headerFooter' import * as PromoBlock from './promoBlock' import * as Event from './event' +import * as EventTeaser from './eventTeaser' +import * as EventListing from './eventListing' import * as VamcEhr from './vamcEhr' import { ResourceType } from '@/lib/constants/resourceTypes' @@ -28,6 +30,8 @@ export const QUERIES_MAP = { 'node--story_listing': StoryListing, 'node--q_a': QuestionAnswer, 'node--event': Event, + 'node--event--teaser': EventTeaser, + 'node--event_listing': EventListing, 'node--person_profile': PersonProfile, 'node--landing_page': BenefitsHub, // "Benefits Hub Landing Page" 'paragraph--audience_topics': AudienceTopics, diff --git a/src/data/queries/storyListing.ts b/src/data/queries/storyListing.ts index ec2e94d8b..f2792ab9d 100644 --- a/src/data/queries/storyListing.ts +++ b/src/data/queries/storyListing.ts @@ -17,7 +17,7 @@ import { const PAGE_SIZE = PAGE_SIZES[RESOURCE_TYPES.STORY_LISTING] -// Define the query params for fetching node--news_story. +// Define the query params for fetching node--story_listing. export const params: QueryParams = () => { return queries.getParams().addInclude(['field_office']) } diff --git a/src/data/queries/tests/__snapshots__/eventListing.test.tsx.snap b/src/data/queries/tests/__snapshots__/eventListing.test.tsx.snap new file mode 100644 index 000000000..96a6eb519 --- /dev/null +++ b/src/data/queries/tests/__snapshots__/eventListing.test.tsx.snap @@ -0,0 +1,226 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EventListing formatData outputs formatted data 1`] = ` +{ + "breadcrumbs": [ + { + "options": [], + "title": "Home", + "uri": "https://va-gov-cms.ddev.site/", + }, + { + "options": [], + "title": "VA Wilkes-Barre health care", + "uri": "https://va-gov-cms.ddev.site/wilkes-barre-health-care", + }, + { + "options": [], + "title": "News and events", + "uri": "internal:#", + }, + { + "options": [], + "title": "Events", + "uri": "internal:#", + }, + ], + "entityId": 3036, + "entityPath": "/wilkes-barre-health-care/events", + "events": [ + { + "changed": "2023-08-16T15:20:30+00:00", + "entityBundle": "node--event", + "entityId": "16349f16-be65-46e3-9660-1d3d598a4a0b", + "entityPublished": true, + "entityUrl": { + "path": "/central-iowa-health-care/events/52265", + }, + "fieldAdditionalInformationAbo": null, + "fieldAdditionalListings": null, + "fieldAddress": { + "addressLine1": "9000 Douglas Ave", + "addressLine2": null, + "administrativeArea": "IA", + "countryCode": "US", + "locality": "Urbandale", + "postalCode": null, + }, + "fieldAdministration": { + "entity": { + "entityId": "3a530a55-e922-48de-b18c-9c9f92a9ae98", + }, + }, + "fieldBody": { + "format": "rich_text", + "processed": "

Pickleball Club

+ +

Meets Thursdays from 9 to 11 a.m. 

+ +

Contact Kay Queck (515) 214-4578

", + "value": "

Pickleball Club

+ +

Meets Thursdays from 9 to 11 a.m. 

+ +

Contact Kay Queck (515) 214-4578

+", + }, + "fieldCtaEmail": "test@va.gov", + "fieldDatetimeRangeTimezone": [ + { + "duration": 120, + "endTime": "2023-09-07T16:00:00+00:00", + "endValue": 1694102400, + "end_value": "2023-09-07T16:00:00+00:00", + "rrule": 180, + "rrule_index": 1, + "startTime": "2023-09-07T14:00:00+00:00", + "timezone": "America/Chicago", + "value": 1694095200, + }, + { + "duration": 120, + "endTime": "2023-09-14T16:00:00+00:00", + "endValue": 1694707200, + "end_value": "2023-09-14T16:00:00+00:00", + "rrule": 180, + "rrule_index": 2, + "startTime": "2023-09-14T14:00:00+00:00", + "timezone": "America/Chicago", + "value": 1694700000, + }, + ], + "fieldDescription": "Pickleball ", + "fieldEventCost": "Free", + "fieldEventCta": null, + "fieldEventRegistrationrequired": false, + "fieldFacilityLocation": null, + "fieldFeatured": false, + "fieldHowToSignUp": "email", + "fieldLink": null, + "fieldListing": { + "entity": { + "entityId": "55cb823c-df6d-4f77-814d-6024e08cd4f6", + }, + }, + "fieldLocationHumanreadable": "Walker Johnston Park", + "fieldLocationType": "non_facility", + "fieldOrder": null, + "fieldUrlOfAnOnlineEvent": null, + "title": "Pickleball Club", + }, + ], + "id": "4c24a406-37dc-4709-9ba7-24f430973cfb", + "introText": "Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.", + "lastUpdated": "2021-02-11T16:15:00+00:00", + "menu": { + "data": null, + "rootPath": "/wilkes-barre-health-care/events/", + }, + "metatags": [ + { + "attributes": { + "content": "Events | VA Wilkes-Barre health care | Veterans Affairs", + "name": "title", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.", + "name": "description", + }, + "tag": "meta", + }, + { + "attributes": { + "href": "https://www.va.gov/img/design/logo/va-og-image.png", + "rel": "image_src", + }, + "tag": "link", + }, + { + "attributes": { + "content": "Veterans Affairs", + "property": "og:site_name", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "Events | Veterans Affairs", + "property": "og:title", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.", + "property": "og:description", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "https://www.va.gov/img/design/logo/va-og-image.png", + "property": "og:image", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "U.S. Department of Veterans Affairs", + "property": "og:image:alt", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "summary_large_image", + "name": "twitter:card", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.", + "name": "twitter:description", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "Events | VA Wilkes-Barre health care | Veterans Affairs", + "name": "twitter:title", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "@DeptVetAffairs", + "name": "twitter:site", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "https://www.va.gov/img/design/logo/va-og-image.png", + "name": "twitter:image", + }, + "tag": "meta", + }, + { + "attributes": { + "content": "U.S. Department of Veterans Affairs", + "name": "twitter:image:alt", + }, + "tag": "meta", + }, + ], + "moderationState": "published", + "published": true, + "title": "Events", + "totalItems": 1, + "totalPages": 1, + "type": "node--event_listing", +} +`; diff --git a/src/data/queries/tests/eventListing.test.tsx b/src/data/queries/tests/eventListing.test.tsx new file mode 100644 index 000000000..445eae97c --- /dev/null +++ b/src/data/queries/tests/eventListing.test.tsx @@ -0,0 +1,33 @@ +import { NodeEvent, NodeEventListing } from '@/types/drupal/node' +import { queries } from '@/data/queries' +import { mockResponse } from '@/mocks/eventListing.mock.js' +import mockEventData from '@/mocks/event.mock.json' + +const EventListingMock: NodeEventListing = mockResponse +const EventMock: NodeEvent[] = [mockEventData] + +describe('EventListing formatData', () => { + let windowSpy + + beforeEach(() => { + windowSpy = jest.spyOn(window, 'window', 'get') + }) + + afterEach(() => { + windowSpy.mockRestore() + }) + + test('outputs formatted data', () => { + windowSpy.mockImplementation(() => undefined) + + expect( + queries.formatData('node--event_listing', { + entity: EventListingMock, + events: EventMock, + menu: { items: [], tree: [] }, + totalItems: EventMock.length, + totalPages: 1, + }) + ).toMatchSnapshot() + }) +}) diff --git a/src/lib/constants/pageSizes.ts b/src/lib/constants/pageSizes.ts index 9345abea8..811b4cb38 100644 --- a/src/lib/constants/pageSizes.ts +++ b/src/lib/constants/pageSizes.ts @@ -2,5 +2,6 @@ import { RESOURCE_TYPES } from '@/lib/constants/resourceTypes' export const PAGE_SIZES = { [RESOURCE_TYPES.STORY_LISTING]: 10, + [RESOURCE_TYPES.EVENT_LISTING]: 50, MAX: 50, //50 is JSON:API limit. Use this for fetching as many as possible at a time. } as const diff --git a/src/lib/constants/resourceTypes.ts b/src/lib/constants/resourceTypes.ts index 2eecf8198..536902cf5 100644 --- a/src/lib/constants/resourceTypes.ts +++ b/src/lib/constants/resourceTypes.ts @@ -2,6 +2,7 @@ export const RESOURCE_TYPES = { STORY_LISTING: 'node--story_listing', STORY: 'node--news_story', EVENT: 'node--event', + EVENT_LISTING: 'node--event_listing', VAMC_FACILITY: 'node--health_care_local_facility', VAMC_SYSTEM: 'node--health_care_region_page', // QA: 'node--q_a', diff --git a/src/lib/drupal/listingPages.ts b/src/lib/drupal/listingPages.ts index bd8663947..1808d740a 100644 --- a/src/lib/drupal/listingPages.ts +++ b/src/lib/drupal/listingPages.ts @@ -10,7 +10,10 @@ import { LOVELL } from '@/lib/drupal/lovell/constants' import { PAGE_SIZES } from '@/lib/constants/pageSizes' import { ExpandedStaticPropsContext } from './staticProps' -const LISTING_RESOURCE_TYPES = [RESOURCE_TYPES.STORY_LISTING] as const +const LISTING_RESOURCE_TYPES = [ + RESOURCE_TYPES.STORY_LISTING, + RESOURCE_TYPES.EVENT_LISTING, +] as const export type ListingResourceType = (typeof LISTING_RESOURCE_TYPES)[number] @@ -54,6 +57,7 @@ export const LISTING_RESOURCE_TYPE_URL_SEGMENTS: Readonly<{ [key: string]: string }> = { [RESOURCE_TYPES.STORY_LISTING]: 'stories', + [RESOURCE_TYPES.EVENT_LISTING]: 'events', } export function isListingResourceType(resourceType: ResourceType): boolean { @@ -166,6 +170,7 @@ export async function getAllPagedListingStaticPathResources( listingPageStaticPathResources, listingResourceType ) + // Paging step 2: Each listing resource will become multiple resources, one for each of its pages const allListingResources = addStaticPathResourcesFromPagingData( resourcesWithPagingData @@ -200,6 +205,9 @@ export function getListingPageStaticPropsContext( const isSlugSubsequentListingPage: string[] | false = isSlugPossibleListingPage && slug.length === 3 && + // EVENT_LISTING pages should only generate one page. + slug[1] !== + LISTING_RESOURCE_TYPE_URL_SEGMENTS[RESOURCE_TYPES.EVENT_LISTING] && slug[2].match(/^page-(\d)+$/) const page = isSlugSubsequentListingPage ? parseInt(isSlugSubsequentListingPage[1]) diff --git a/src/lib/drupal/lovell/constants.ts b/src/lib/drupal/lovell/constants.ts index b9c76c55a..f918d5016 100644 --- a/src/lib/drupal/lovell/constants.ts +++ b/src/lib/drupal/lovell/constants.ts @@ -33,6 +33,11 @@ export const LOVELL = { export const LOVELL_RESOURCE_TYPES = [ RESOURCE_TYPES.STORY, RESOURCE_TYPES.STORY_LISTING, + RESOURCE_TYPES.EVENT, + RESOURCE_TYPES.EVENT_LISTING, ] -export const LOVELL_BIFURCATED_RESOURCE_TYPES = [RESOURCE_TYPES.STORY] +export const LOVELL_BIFURCATED_RESOURCE_TYPES = [ + RESOURCE_TYPES.STORY, + RESOURCE_TYPES.EVENT, +] diff --git a/src/lib/drupal/lovell/staticProps.ts b/src/lib/drupal/lovell/staticProps.ts index af0c1946f..8d876150c 100644 --- a/src/lib/drupal/lovell/staticProps.ts +++ b/src/lib/drupal/lovell/staticProps.ts @@ -11,6 +11,7 @@ import { LISTING_RESOURCE_TYPE_URL_SEGMENTS, } from '@/lib/drupal/listingPages' import { PAGE_SIZES } from '@/lib/constants/pageSizes' +import { RESOURCE_TYPES } from '@/lib/constants/resourceTypes' import { LovellStaticPropsContextProps, LovellChildVariant, @@ -129,10 +130,27 @@ async function getLovellListingPageStaticPropsResource( const itemProp = LISTING_RESOURCE_TYPE_URL_SEGMENTS[resourceType] const allMergedItems = [ ...childVariantPage[itemProp], - ...federalPage[itemProp].map((item) => ({ - ...item, - link: getLovellVariantOfUrl(item.link, context.lovell.variant), - })), + ...federalPage[itemProp].map((item) => { + // Event Listing pages have the list items constructed a little differently. + // See queries/eventTeaser.ts + if ( + itemProp === + LISTING_RESOURCE_TYPE_URL_SEGMENTS[RESOURCE_TYPES.EVENT_LISTING] + ) { + return { + ...item, + link: getLovellVariantOfUrl( + item.entityUrl.path, + context.lovell.variant + ), + } + } + // See queries/newsStoryTeaser.ts + return { + ...item, + link: getLovellVariantOfUrl(item.link, context.lovell.variant), + } + }), ] const pageSize = PAGE_SIZES[resourceType] const sliceStart = (context.listing.page - 1) * pageSize diff --git a/src/lib/drupal/staticPaths.ts b/src/lib/drupal/staticPaths.ts index ad64e5eb7..0cd04e988 100644 --- a/src/lib/drupal/staticPaths.ts +++ b/src/lib/drupal/staticPaths.ts @@ -4,13 +4,14 @@ import { getAllPagedListingStaticPathResources, isListingResourceType, } from '@/lib/drupal/listingPages' -import { RESOURCE_TYPES, ResourceType } from '@/lib/constants/resourceTypes' +import { ResourceType } from '@/lib/constants/resourceTypes' import { queries } from '@/data/queries' import { StaticPathResource } from '@/types/formatted/staticPathResource' import { bifurcateLovellFederalPathResources, removeLovellFederalPathResources, } from '@/lib/drupal/lovell/staticPaths' +import { isLovellBifurcatedResourceType } from '@/lib/drupal/lovell/utils' import { pathToSlug } from '@/lib/utils/slug' /** @@ -26,7 +27,7 @@ async function modifyStaticPathResourcesByResourceType( resourceType: ResourceType, resources: StaticPathResource[] ): Promise { - if (resourceType === RESOURCE_TYPES.STORY) { + if (isLovellBifurcatedResourceType(resourceType)) { return bifurcateLovellFederalPathResources(resources) } diff --git a/src/lib/drupal/staticProps.ts b/src/lib/drupal/staticProps.ts index 95df4fd91..03718d26e 100644 --- a/src/lib/drupal/staticProps.ts +++ b/src/lib/drupal/staticProps.ts @@ -5,6 +5,7 @@ import { queries } from '@/data/queries' import { ListingPageStaticPropsContextProps, getListingPageStaticPropsContext, + isListingResourceType, } from '@/lib/drupal/listingPages' import { getLovellStaticPropsResource, @@ -88,7 +89,7 @@ export function getStaticPropsQueryOpts( } // Listing Page types need to know what page # to query for - if (resourceType === RESOURCE_TYPES.STORY_LISTING) { + if (isListingResourceType(resourceType)) { return { ...defaultQueryOpts, page: context.listing.page, diff --git a/src/lib/utils/breadcrumbs.ts b/src/lib/utils/breadcrumbs.ts index b226b1e99..92598392e 100644 --- a/src/lib/utils/breadcrumbs.ts +++ b/src/lib/utils/breadcrumbs.ts @@ -72,7 +72,10 @@ export function filterInvalidCrumbs( } export const shouldHideHomeBreadcrumb = (resourceType) => { - const typesToShowHomeBreadcrumb = [RESOURCE_TYPES.EVENT] + const typesToShowHomeBreadcrumb = [ + RESOURCE_TYPES.EVENT, + RESOURCE_TYPES.EVENT_LISTING, + ] return !typesToShowHomeBreadcrumb.includes(resourceType) } diff --git a/src/mocks/event.mock.json b/src/mocks/event.mock.json index 47e662268..16fab75fb 100644 --- a/src/mocks/event.mock.json +++ b/src/mocks/event.mock.json @@ -100,7 +100,10 @@ } }, "field_additional_listings": [], - "field_administration": null, + "field_administration": { + "type": "taxonomy_term--administration", + "id": "3a530a55-e922-48de-b18c-9c9f92a9ae98" + }, "field_facility_location": null, "field_listing": { "type": "node--event_listing", diff --git a/src/mocks/eventListing.mock.js b/src/mocks/eventListing.mock.js new file mode 100644 index 000000000..1d5fb55e7 --- /dev/null +++ b/src/mocks/eventListing.mock.js @@ -0,0 +1,929 @@ +export const mockResponse = { + type: 'node--event_listing', + id: '4c24a406-37dc-4709-9ba7-24f430973cfb', + drupal_internal__nid: 3036, + drupal_internal__vid: 17354, + langcode: 'en', + status: true, + sticky: false, + default_langcode: true, + title: 'Events', + created: '2020-03-02T21:38:24+00:00', + changed: '2021-02-11T16:15:01+00:00', + breadcrumbs: [ + { + uri: 'https://va-gov-cms.ddev.site/', + title: 'Home', + options: [], + }, + { + uri: 'https://va-gov-cms.ddev.site/wilkes-barre-health-care', + title: 'VA Wilkes-Barre health care', + options: [], + }, + { + uri: 'internal:#', + title: 'News and events', + options: [], + }, + { + uri: 'internal:#', + title: 'Events', + options: [], + }, + ], + moderation_state: 'published', + metatag: [ + { + tag: 'meta', + attributes: { + name: 'title', + content: 'Events | VA Wilkes-Barre health care | Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + name: 'description', + content: + 'Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.', + }, + }, + { + tag: 'link', + attributes: { + rel: 'image_src', + href: 'https://www.va.gov/img/design/logo/va-og-image.png', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:site_name', + content: 'Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:title', + content: 'Events | Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:description', + content: + 'Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:image', + content: 'https://www.va.gov/img/design/logo/va-og-image.png', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:image:alt', + content: 'U.S. Department of Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:card', + content: 'summary_large_image', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:description', + content: + 'Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:title', + content: 'Events | VA Wilkes-Barre health care | Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:site', + content: '@DeptVetAffairs', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:image', + content: 'https://www.va.gov/img/design/logo/va-og-image.png', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:image:alt', + content: 'U.S. Department of Veterans Affairs', + }, + }, + ], + path: { + alias: '/wilkes-barre-health-care/events', + pid: 3880, + langcode: 'en', + }, + field_description: + 'Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.', + field_enforce_unique_combo: null, + field_intro_text: + 'Learn more about events in our VA Wilkes-Barre health care community, including classes on health and wellness.', + field_last_saved_by_an_editor: '2021-02-11T16:15:00+00:00', + field_meta_tags: null, + links: { + self: { + href: 'https://va-gov-cms.ddev.site/jsonapi/node/event_listing/4c24a406-37dc-4709-9ba7-24f430973cfb?resourceVersion=id%3A364663', + }, + }, + node_type: { + type: 'node_type--node_type', + id: '61ebe186-0b8b-4696-934f-e0f9e720032f', + resourceIdObjMeta: { + drupal_internal__target_id: 'event_listing', + }, + }, + field_administration: { + type: 'taxonomy_term--administration', + id: '58ec709b-be3a-44f7-98f3-7e5e848c5be3', + drupal_internal__tid: 186, + drupal_internal__revision_id: 1573, + langcode: 'en', + revision_created: '2023-04-03T19:41:40+00:00', + revision_log_message: null, + status: true, + name: 'VA Wilkes-Barre health care', + description: null, + weight: 0, + changed: '2023-04-03T19:41:40+00:00', + default_langcode: true, + revision_translation_affected: true, + moderation_state: null, + metatag: [ + { + tag: 'meta', + attributes: { + name: 'title', + content: 'VA Wilkes-Barre health care | VA.gov CMS', + }, + }, + { + tag: 'link', + attributes: { + rel: 'canonical', + href: 'https://va-gov-cms.ddev.site/section/vha/vamc-facilities/va-wilkes-barre-health-care', + }, + }, + ], + path: { + alias: '/section/vha/vamc-facilities/va-wilkes-barre-health-care', + pid: 1770, + langcode: 'en', + }, + content_translation_source: 'und', + content_translation_outdated: false, + content_translation_created: null, + field_description: null, + links: { + self: { + href: 'https://va-gov-cms.ddev.site/jsonapi/taxonomy_term/administration/58ec709b-be3a-44f7-98f3-7e5e848c5be3?resourceVersion=id%3A1573', + }, + }, + resourceIdObjMeta: { + drupal_internal__target_id: 186, + }, + vid: { + type: 'taxonomy_vocabulary--taxonomy_vocabulary', + id: '645055c5-e567-4683-b6db-459ce04522ce', + resourceIdObjMeta: { + drupal_internal__target_id: 'administration', + }, + }, + revision_user: { + type: 'user--user', + id: '20b2bc97-2210-4def-9a0f-46c22d250eb7', + resourceIdObjMeta: { + drupal_internal__target_id: 0, + }, + }, + parent: [ + { + type: 'taxonomy_term--administration', + id: '62ba5bc4-5bf3-4888-a2bb-0489ead0c76f', + resourceIdObjMeta: { + drupal_internal__target_id: 157, + }, + }, + ], + content_translation_uid: null, + field_product: { + type: 'taxonomy_term--products', + id: '2fe1cb98-c03e-49fe-bb09-d73b66b99c8d', + resourceIdObjMeta: { + drupal_internal__target_id: 284, + }, + }, + relationshipNames: [ + 'vid', + 'revision_user', + 'parent', + 'content_translation_uid', + 'field_product', + ], + }, + field_office: { + type: 'node--health_care_region_page', + id: 'e032db41-0160-493e-8d02-8c2790d031c6', + drupal_internal__nid: 2361, + drupal_internal__vid: 768751, + langcode: 'en', + revision_timestamp: '2022-11-08T19:59:55+00:00', + revision_log: 'Revised Medical Records Office link - CMS Helpdesk', + status: true, + title: 'VA Wilkes-Barre health care', + created: '2019-12-06T14:32:50+00:00', + changed: '2023-05-03T14:31:41+00:00', + promote: false, + sticky: false, + default_langcode: true, + revision_translation_affected: true, + breadcrumbs: [ + { + uri: 'https://va-gov-cms.ddev.site/', + title: 'Home', + options: [], + }, + { + uri: 'internal:#', + title: 'VA Wilkes-Barre health care', + options: [], + }, + ], + moderation_state: 'published', + metatag: [ + { + tag: 'meta', + attributes: { + name: 'title', + content: 'VA Wilkes-Barre health care | Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + name: 'description', + content: + 'At the VA Wilkes-Barre Healthcare System, our expert health care teams focus on your needs and the needs of other Veterans, your families, and caregivers. Find a health care facility near you, and manage your health online.', + }, + }, + { + tag: 'link', + attributes: { + rel: 'image_src', + href: 'https://www.va.gov/img/design/logo/va-og-image.png', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:site_name', + content: 'Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:title', + content: 'VA Wilkes-Barre health care | Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:description', + content: + 'At the VA Wilkes-Barre Healthcare System, our expert health care teams focus on your needs and the needs of other Veterans, your families, and caregivers. Find a health care facility near you, and manage your health online.', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:image', + content: + 'http://va-gov-cms.ddev.site/sites/default/files/styles/3_2_medium_thumbnail/public/2021-07/Wilkes-BarreVA_InternetPagePhoto_1.jpg', + }, + }, + { + tag: 'meta', + attributes: { + property: 'og:image:alt', + content: 'Photo out front of Wilkes-Barre VA Medical Center', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:card', + content: 'summary_large_image', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:description', + content: + 'At the VA Wilkes-Barre Healthcare System, our expert health care teams focus on your needs and the needs of other Veterans, your families, and caregivers. Find a health care facility near you, and manage your health online.', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:title', + content: 'VA Wilkes-Barre health care | Veterans Affairs', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:site', + content: '@DeptVetAffairs', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:image', + content: + 'http://va-gov-cms.ddev.site/sites/default/files/styles/3_2_medium_thumbnail/public/2021-07/Wilkes-BarreVA_InternetPagePhoto_1.jpg', + }, + }, + { + tag: 'meta', + attributes: { + name: 'twitter:image:alt', + content: 'Photo out front of Wilkes-Barre VA Medical Center', + }, + }, + ], + path: { + alias: '/wilkes-barre-health-care', + pid: 3088, + langcode: 'en', + }, + content_translation_source: 'und', + content_translation_outdated: false, + field_appointments_online: false, + field_description: + 'At the VA Wilkes-Barre Healthcare System, our expert health care teams focus on your needs and the needs of other Veterans, your families, and caregivers. Find a health care facility near you, and manage your health online.', + field_facebook: { + uri: 'https://www.facebook.com/VAWilkesBarre', + title: 'Wilkes-Barre Facebook page.', + options: [], + }, + field_flickr: null, + field_govdelivery_id_emerg: 'USVHA_397', + field_govdelivery_id_news: 'USVHA_193', + field_instagram: null, + field_intro_text: + 'At the VA Wilkes-Barre Healthcare System, our expert health care teams focus on your needs and the needs of other Veterans, your families, and caregivers. Find a health care facility near you, and manage your health online. Sign up for community events and updates, and learn what’s new at your local VA medical center and clinics.', + field_last_saved_by_an_editor: '2022-11-08T19:59:54+00:00', + field_meta_tags: null, + field_operating_status: { + uri: 'entity:node/2369', + title: 'Operating status - VA Wilkes-Barre health care', + options: [], + }, + field_other_va_locations: ['vba_310e', 'vba_310', 'vc_0229V'], + field_twitter: null, + field_vamc_ehr_system: 'vista', + field_vamc_system_official_name: 'Wilkes-Barre Healthcare System', + field_va_health_connect_phone: null, + field_youtube: null, + links: { + self: { + href: 'https://va-gov-cms.ddev.site/jsonapi/node/health_care_region_page/e032db41-0160-493e-8d02-8c2790d031c6?resourceVersion=id%3A768751', + }, + }, + resourceIdObjMeta: { + drupal_internal__target_id: 2361, + }, + node_type: { + type: 'node_type--node_type', + id: 'd611ade3-cc27-4359-8af1-6fba3384200f', + resourceIdObjMeta: { + drupal_internal__target_id: 'health_care_region_page', + }, + }, + revision_uid: { + type: 'user--user', + id: 'f687120a-7fbd-429e-a9c0-4b287ce0df37', + resourceIdObjMeta: { + drupal_internal__target_id: 4201, + }, + }, + uid: { + type: 'user--user', + id: '8bea8773-6a06-4afd-99b5-1dfa7a2192ea', + resourceIdObjMeta: { + drupal_internal__target_id: 197, + }, + }, + field_administration: { + type: 'taxonomy_term--administration', + id: '58ec709b-be3a-44f7-98f3-7e5e848c5be3', + drupal_internal__tid: 186, + drupal_internal__revision_id: 1573, + langcode: 'en', + revision_created: '2023-04-03T19:41:40+00:00', + revision_log_message: null, + status: true, + name: 'VA Wilkes-Barre health care', + description: null, + weight: 0, + changed: '2023-04-03T19:41:40+00:00', + default_langcode: true, + revision_translation_affected: true, + moderation_state: null, + metatag: [ + { + tag: 'meta', + attributes: { + name: 'title', + content: 'VA Wilkes-Barre health care | VA.gov CMS', + }, + }, + { + tag: 'link', + attributes: { + rel: 'canonical', + href: 'https://va-gov-cms.ddev.site/section/vha/vamc-facilities/va-wilkes-barre-health-care', + }, + }, + ], + path: { + alias: '/section/vha/vamc-facilities/va-wilkes-barre-health-care', + pid: 1770, + langcode: 'en', + }, + content_translation_source: 'und', + content_translation_outdated: false, + content_translation_created: null, + field_description: null, + links: { + self: { + href: 'https://va-gov-cms.ddev.site/jsonapi/taxonomy_term/administration/58ec709b-be3a-44f7-98f3-7e5e848c5be3?resourceVersion=id%3A1573', + }, + }, + resourceIdObjMeta: { + drupal_internal__target_id: 186, + }, + vid: { + type: 'taxonomy_vocabulary--taxonomy_vocabulary', + id: '645055c5-e567-4683-b6db-459ce04522ce', + resourceIdObjMeta: { + drupal_internal__target_id: 'administration', + }, + }, + revision_user: { + type: 'user--user', + id: '20b2bc97-2210-4def-9a0f-46c22d250eb7', + resourceIdObjMeta: { + drupal_internal__target_id: 0, + }, + }, + parent: [ + { + type: 'taxonomy_term--administration', + id: '62ba5bc4-5bf3-4888-a2bb-0489ead0c76f', + resourceIdObjMeta: { + drupal_internal__target_id: 157, + }, + }, + ], + content_translation_uid: null, + field_product: { + type: 'taxonomy_term--products', + id: '2fe1cb98-c03e-49fe-bb09-d73b66b99c8d', + resourceIdObjMeta: { + drupal_internal__target_id: 284, + }, + }, + relationshipNames: [ + 'vid', + 'revision_user', + 'parent', + 'content_translation_uid', + 'field_product', + ], + }, + field_clinical_health_services: [ + { + type: 'node--regional_health_care_service_des', + id: 'dd781b8c-9d4c-4acb-a1fa-50bc0d068341', + resourceIdObjMeta: { + drupal_internal__target_id: 6406, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '96eeb8c7-cd32-45bd-9107-53795bcf3bd9', + resourceIdObjMeta: { + drupal_internal__target_id: 6407, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '5e686325-edd6-41cf-9816-a0b63006589b', + resourceIdObjMeta: { + drupal_internal__target_id: 6408, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '3eff3a1a-dcb2-46d5-a24d-2780e900790c', + resourceIdObjMeta: { + drupal_internal__target_id: 6409, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '95f00976-7995-4161-977d-9a00725e762c', + resourceIdObjMeta: { + drupal_internal__target_id: 6410, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '140a213a-22d9-402c-984b-ea1774acfd3e', + resourceIdObjMeta: { + drupal_internal__target_id: 6411, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '28f67392-f386-4f4f-8bd6-b3061d333ec8', + resourceIdObjMeta: { + drupal_internal__target_id: 6412, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '18ef5f22-bb90-4b97-8c35-106a233a00a0', + resourceIdObjMeta: { + drupal_internal__target_id: 6413, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '140f4c01-7f4f-46db-a53c-3e28f01cf3e7', + resourceIdObjMeta: { + drupal_internal__target_id: 6414, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '73825661-aabe-4088-be86-cc774161a14e', + resourceIdObjMeta: { + drupal_internal__target_id: 6415, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '4cd168eb-a70d-4985-8e88-cdd5995ba832', + resourceIdObjMeta: { + drupal_internal__target_id: 6416, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'fe7b31e6-aed0-4b32-8aa7-8527e7f8f975', + resourceIdObjMeta: { + drupal_internal__target_id: 6417, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'f5129eaa-42a6-4bf8-bfc6-3dd57817bee0', + resourceIdObjMeta: { + drupal_internal__target_id: 6418, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '064c1a25-e1e9-46a8-8660-a6cb3aa4c525', + resourceIdObjMeta: { + drupal_internal__target_id: 6419, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '4c012daa-6231-4256-949d-bf4fca40258c', + resourceIdObjMeta: { + drupal_internal__target_id: 6420, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'c8570f58-66db-4154-aab5-c1f3229d777d', + resourceIdObjMeta: { + drupal_internal__target_id: 6421, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'fae255d8-19fb-4a73-8660-787f9c4413d4', + resourceIdObjMeta: { + drupal_internal__target_id: 6422, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '92bb5c35-29aa-48a0-ac49-be7208dc3ec8', + resourceIdObjMeta: { + drupal_internal__target_id: 6423, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '11f09c72-5797-4102-96d0-fa795aff2332', + resourceIdObjMeta: { + drupal_internal__target_id: 6424, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '332bb4a9-0091-4a05-99cf-2ae4142c9e50', + resourceIdObjMeta: { + drupal_internal__target_id: 6425, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '1240a02b-1645-43e4-8349-fbca39d53c85', + resourceIdObjMeta: { + drupal_internal__target_id: 6426, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '087a4fe5-542c-466d-8875-07bc60dac8ec', + resourceIdObjMeta: { + drupal_internal__target_id: 6427, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '8dd25629-81e3-4188-9ee6-ed1e84d7602e', + resourceIdObjMeta: { + drupal_internal__target_id: 6428, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'e4c00b5c-de98-4b07-95f5-c13bb13fbc4c', + resourceIdObjMeta: { + drupal_internal__target_id: 6429, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'dd9560c2-d7c0-4921-b42b-da4e29b5bebf', + resourceIdObjMeta: { + drupal_internal__target_id: 6430, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '660aa168-d2a6-4081-8c4b-55e875c444d0', + resourceIdObjMeta: { + drupal_internal__target_id: 6431, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '2c48f0eb-b60d-4a8f-9f29-82f6113a742a', + resourceIdObjMeta: { + drupal_internal__target_id: 6432, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '3b9af66b-b3cb-4cec-af57-7dac97c05645', + resourceIdObjMeta: { + drupal_internal__target_id: 6433, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '0234deef-f35c-4d2f-be7a-906d0a9579c2', + resourceIdObjMeta: { + drupal_internal__target_id: 6434, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '9bd5868e-f829-4990-9076-bddd2e562302', + resourceIdObjMeta: { + drupal_internal__target_id: 6435, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '4cb7f0dc-a49e-4a24-9081-be376a343001', + resourceIdObjMeta: { + drupal_internal__target_id: 6436, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '4e0b5d4a-1985-4b54-97ac-6184e667d475', + resourceIdObjMeta: { + drupal_internal__target_id: 6437, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '1ea6ffb1-dc23-4b86-be8b-22fad02cccfa', + resourceIdObjMeta: { + drupal_internal__target_id: 6438, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '1a4cd2ff-08bd-4b8d-8dd8-1057f6509d36', + resourceIdObjMeta: { + drupal_internal__target_id: 6439, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '3eea2506-a2e8-440e-aa12-9035bb983a97', + resourceIdObjMeta: { + drupal_internal__target_id: 6440, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '28b9ab5f-f434-4094-8eed-64ffeb17bb26', + resourceIdObjMeta: { + drupal_internal__target_id: 6441, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'd85f665f-c3e8-4919-9dd2-9c4f9052916f', + resourceIdObjMeta: { + drupal_internal__target_id: 6442, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'b1ce7d16-0d3c-483c-8b41-db348fbc5d08', + resourceIdObjMeta: { + drupal_internal__target_id: 6443, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '896e3caf-747f-4930-b61e-c6539a14eab1', + resourceIdObjMeta: { + drupal_internal__target_id: 6444, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'f8cd2255-9a28-42fa-925a-d29ea37ced3a', + resourceIdObjMeta: { + drupal_internal__target_id: 6445, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'aad1edcc-e16e-4e39-853b-b06c4fb08c89', + resourceIdObjMeta: { + drupal_internal__target_id: 6446, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '20b3b494-3078-4bad-aae7-ed3e05f325ee', + resourceIdObjMeta: { + drupal_internal__target_id: 6447, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '279d2805-d7f4-4788-ab04-c5d82e34c25c', + resourceIdObjMeta: { + drupal_internal__target_id: 6448, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '0f29745f-6654-42d6-8d66-f56c52470ec7', + resourceIdObjMeta: { + drupal_internal__target_id: 6449, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '31562840-047d-4de1-bc96-6e435dd26e7f', + resourceIdObjMeta: { + drupal_internal__target_id: 6450, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'ff875407-1aa7-4a9e-af8f-c223aa29bb3d', + resourceIdObjMeta: { + drupal_internal__target_id: 6451, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'fd8adfd3-d854-46ab-8973-19126daccd1f', + resourceIdObjMeta: { + drupal_internal__target_id: 6452, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: '976e9114-fc16-4862-b61f-1b11899ce723', + resourceIdObjMeta: { + drupal_internal__target_id: 15314, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'ba43d312-cc5b-4095-8294-248b8d7abd78', + resourceIdObjMeta: { + drupal_internal__target_id: 49005, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'ac3a7951-3cdd-4d75-932d-c5382de36fb2', + resourceIdObjMeta: { + drupal_internal__target_id: 55618, + }, + }, + { + type: 'node--regional_health_care_service_des', + id: 'ea1fd80f-26ec-4bfb-8895-af82e35d1c3e', + resourceIdObjMeta: { + drupal_internal__target_id: 55932, + }, + }, + ], + field_media: { + type: 'media--image', + id: '519a0e11-bc1e-4978-aeab-45446abcd2bb', + resourceIdObjMeta: { + drupal_internal__target_id: 4666, + }, + }, + field_related_links: { + type: 'paragraph--list_of_link_teasers', + id: 'e243669d-68d8-445e-beef-b4616860602e', + resourceIdObjMeta: { + target_revision_id: 792073, + drupal_internal__target_id: 7807, + }, + }, + field_system_menu: { + type: 'menu--menu', + id: '67c39a0f-687f-4d6d-9143-919dbe76f6e2', + resourceIdObjMeta: { + drupal_internal__target_id: 'va-wilkes-barre-health-care', + }, + }, + relationshipNames: [ + 'node_type', + 'revision_uid', + 'uid', + 'field_administration', + 'field_clinical_health_services', + 'field_media', + 'field_related_links', + 'field_system_menu', + ], + }, + relationshipNames: ['node_type', 'field_administration', 'field_office'], +} diff --git a/src/templates/layouts/storyListing/mockFormattedList.js b/src/mocks/formattedNewsStories.mock.js similarity index 100% rename from src/templates/layouts/storyListing/mockFormattedList.js rename to src/mocks/formattedNewsStories.mock.js diff --git a/src/mocks/storyListing.mock.json b/src/mocks/storyListing.mock.json new file mode 100644 index 000000000..a9c1dca93 --- /dev/null +++ b/src/mocks/storyListing.mock.json @@ -0,0 +1,20 @@ +{ + "type": "node--story_listing", + "id": "a75c369c-a7f5-402b-aeb9-74ad8d24fd36", + "drupal_internal__nid": 2806, + "drupal_internal__vid": 16489, + "langcode": "en", + "status": true, + "title": "Stories", + "created": "2020-03-02T21:36:30+00:00", + "changed": "2020-03-11T20:37:01+00:00", + "path": { + "alias": "/pittsburgh-health-care/stories", + "pid": 3592, + "langcode": "en" + }, + "field_description:": "Read about our VA Pittsburgh health care community.", + "field_intro_text": "Read about our VA Pittsburgh health care community.", + "published": true, + "lastUpdated": "2020-03-11T20:37:01+00:00" +} diff --git a/src/pages/[[...slug]].tsx b/src/pages/[[...slug]].tsx index e032f7e55..52539b744 100644 --- a/src/pages/[[...slug]].tsx +++ b/src/pages/[[...slug]].tsx @@ -14,6 +14,7 @@ import { StoryListing } from '@/templates/layouts/storyListing' import HTMLComment from '@/templates/globals/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 } from '@/lib/constants/resourceTypes' import { @@ -25,6 +26,7 @@ import { FormattedResource } from '@/data/queries' import { LayoutProps } from '@/templates/globals/wrapper' import { NewsStory as FormattedNewsStory } from '@/types/formatted/newsStory' 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 { Meta } from '@/templates/globals/meta' import { PreviewCrumb } from '@/templates/common/preview' @@ -33,6 +35,7 @@ const RESOURCE_TYPES_TO_BUILD = [ RESOURCE_TYPES.STORY_LISTING, RESOURCE_TYPES.STORY, RESOURCE_TYPES.EVENT, + RESOURCE_TYPES.EVENT_LISTING, ] as const export type BuiltResourceType = (typeof RESOURCE_TYPES_TO_BUILD)[number] @@ -92,6 +95,9 @@ export default function ResourcePage({ {/* {resource.type === RESOURCE_TYPES.QA && ( )} */} + {resource.type === RESOURCE_TYPES.EVENT_LISTING && ( + + )} {resource.type === RESOURCE_TYPES.EVENT && ( )} diff --git a/src/templates/common/contentFooter/index.tsx b/src/templates/common/contentFooter/index.tsx index 099719d65..a4f9bfe30 100644 --- a/src/templates/common/contentFooter/index.tsx +++ b/src/templates/common/contentFooter/index.tsx @@ -68,7 +68,7 @@ export function ContentFooter({ lastUpdated }: ContentFooterProps) { return ( <> -
+
diff --git a/src/templates/layouts/eventListing/eventListing.stories.ts b/src/templates/layouts/eventListing/eventListing.stories.ts new file mode 100644 index 000000000..bbf012cd5 --- /dev/null +++ b/src/templates/layouts/eventListing/eventListing.stories.ts @@ -0,0 +1,19 @@ +import { Meta, StoryObj } from '@storybook/react' +import { EventListing } from './index' + +const meta: Meta = { + title: 'Layouts/Event Listing', + component: EventListing, +} +export default meta + +type Story = StoryObj + +export const Example: Story = { + args: { + id: '1', + title: 'Events', + introText: + 'Learn more about upcoming events at the VA Butler Healthcare System, including our weekly and monthly fitness classes, support groups, and more.', + }, +} diff --git a/src/templates/layouts/eventListing/index.test.tsx b/src/templates/layouts/eventListing/index.test.tsx new file mode 100644 index 000000000..c7da45d68 --- /dev/null +++ b/src/templates/layouts/eventListing/index.test.tsx @@ -0,0 +1,19 @@ +import { render, screen } from '@testing-library/react' +import { EventListing } from './index' +import { mockResponse } from '@/mocks/eventListing.mock.js' +import { formatter } from '@/data/queries/eventListing' + +describe('EventListing with valid data', () => { + const resource = formatter({ + entity: mockResponse, + events: [], + menu: { items: [], tree: [] }, + totalItems: 0, + totalPages: 1, + }) + test('renders EventListing component', () => { + render() + + expect(screen.queryByText(/Events/)).toBeInTheDocument() + }) +}) diff --git a/src/templates/layouts/eventListing/index.tsx b/src/templates/layouts/eventListing/index.tsx new file mode 100644 index 000000000..a64676da7 --- /dev/null +++ b/src/templates/layouts/eventListing/index.tsx @@ -0,0 +1,82 @@ +/** + * ### Overview + * Event Listing represents the collection pages for Events within a facility. + * + * Event Listing expects data of type {@link FormattedEventListing}. + * + * ### Examples + * @see https://va.gov/butler-health-care/events/ + * + */ +import { useEffect } from 'react' +import { EventListing as FormattedEventListing } from '@/types/formatted/eventListing' +import { EventWidgetTeaser } from '@/types/formatted/event' +import { SideNavMenu } from '@/types/formatted/sideNav' +import { ContentFooter } from '@/templates/common/contentFooter' +import { LovellStaticPropsResource } from '@/lib/drupal/lovell/types' +import { LovellSwitcher } from '@/templates/components/lovellSwitcher' + +// Allows additions to window object without overwriting global type +interface customWindow extends Window { + sideNav?: SideNavMenu + pastEvents?: { + entities: EventWidgetTeaser[] + } +} +declare const window: customWindow + +export function EventListing({ + title, + id, + introText, + events, + menu, + lovellVariant, + lovellSwitchPath, +}: LovellStaticPropsResource) { + // Add data to the window object for the sidebar & event widgets + useEffect(() => { + window.sideNav = menu + + // This populates the whole events widget, even upcoming events. IDK! + window.pastEvents = { entities: events } + }, [menu, events]) + + return ( + <> +
+ {/* Widget coming from vets-website */} + {menu && ( + + )} + +
+ + +

{title}

+
+ {introText && ( +

+ {introText} +

+ )} +
+
+ + {/* Events widget coming from vets-website */} +
+
+ +
+ +
+ + ) +} diff --git a/src/templates/layouts/staffProfile/staffProfile.test.tsx b/src/templates/layouts/staffProfile/staffProfile.test.tsx index 027fd3d72..162b92374 100644 --- a/src/templates/layouts/staffProfile/staffProfile.test.tsx +++ b/src/templates/layouts/staffProfile/staffProfile.test.tsx @@ -9,7 +9,7 @@ const thumbnail = { height: 136, styles: {}, links: { - '2_1_large': { + '1_1_square_medium_thumbnail': { href: 'https://content-build-medc0xjkxm4jmpzxl3tfbcs7qcddsivh.ci.cms.va.gov/sites/default/files/styles/1_1_square_medium_thumbnail/public/2019-08/William_W_Smathers.jpg', meta: { linkParams: {}, diff --git a/src/templates/layouts/storyListing/index.tsx b/src/templates/layouts/storyListing/index.tsx index 378ea18ea..43ad2d05f 100644 --- a/src/templates/layouts/storyListing/index.tsx +++ b/src/templates/layouts/storyListing/index.tsx @@ -1,8 +1,8 @@ /** * ### Overview - * Story Listing represents an individual story within a Facility. These are used for human-interest articles. + * Story Listing represents the collection pages for News Stories in a facility. * - * Story Listing expects data of type {@link StoryListingType}. + * Story Listing expects data of type {@link FormattedStoryListing}. * * ### Examples * @see https://va.gov/pittsburgh-health-care/stories/ diff --git a/src/templates/layouts/storyListing/nodeStoryListing.json b/src/templates/layouts/storyListing/nodeStoryListing.json deleted file mode 100644 index 5877e04b0..000000000 --- a/src/templates/layouts/storyListing/nodeStoryListing.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "type": "node--story_listing", - "id": "a75c369c-a7f5-402b-aeb9-74ad8d24fd36", - "drupal_internal__nid": 2806, - "drupal_internal__vid": 16489, - "langcode": "en", - "status": true, - "title": "Stories", - "created": "2020-03-02T21:36:30+00:00", - "changed": "2020-03-11T20:37:01+00:00", - "path": { - "alias": "/pittsburgh-health-care/stories", - "pid": 3592, - "langcode": "en" - }, - "field_description:": "Read about our VA Pittsburgh health care community.", - "field_intro_text": "Read about our VA Pittsburgh health care community.", - "field_meta_tags": null - } -] diff --git a/src/templates/layouts/storyListing/storyListing.stories.ts b/src/templates/layouts/storyListing/storyListing.stories.ts index 9d0499458..f1a104bc5 100644 --- a/src/templates/layouts/storyListing/storyListing.stories.ts +++ b/src/templates/layouts/storyListing/storyListing.stories.ts @@ -1,8 +1,8 @@ import { Meta, StoryObj } from '@storybook/react' import { StoryListing } from './index' -import data from './nodeStoryListing.json' -import { formattedStories } from './mockFormattedList' +import data from '@/mocks/storyListing.mock.json' +import { formattedStories } from '@/mocks/formattedNewsStories.mock' const meta: Meta = { title: 'Layouts/Story Listing', @@ -14,13 +14,13 @@ type Story = StoryObj export const NoStories: Story = { args: { - ...data[0], + ...data, }, } export const List: Story = { args: { - ...data[0], + ...data, stories: formattedStories, }, } diff --git a/src/types/drupal/field_type.d.ts b/src/types/drupal/field_type.d.ts index ccd098b05..8b57ba5ca 100644 --- a/src/types/drupal/field_type.d.ts +++ b/src/types/drupal/field_type.d.ts @@ -70,6 +70,21 @@ export interface FieldAdministration { name: string } +export interface FieldNestedLink { + url: { + path: string + } +} + +export interface FieldDateTimeRange { + value: string + end_value: string + duration: number + rrule: number + rrule_index: number + timezone: string +} + /** * Types for breadcrumb data */ diff --git a/src/types/drupal/node.ts b/src/types/drupal/node.ts index 927faa646..1940a60e5 100644 --- a/src/types/drupal/node.ts +++ b/src/types/drupal/node.ts @@ -10,7 +10,8 @@ import { FieldSocialMediaLinks, FieldTable, FieldAdministration, - BreadcrumbItem, + FieldDateTimeRange, + FieldNestedLink, } from './field_type' import { DrupalMediaImage } from './media' import { @@ -55,6 +56,7 @@ export type NodeTypes = | NodeSupportResourcesDetailPage | NodeSupportService | NodeEvent + | NodeEventListing /** Node resource types. */ export const enum NodeResourceType { @@ -67,6 +69,7 @@ export const enum NodeResourceType { PromoBanner = 'node--promo_banner', QuestionAnswer = 'node--q_a', StoryListing = 'node--story_listing', + EventListing = 'node--event_lising', SupportResourcesDetailPage = 'node--support_resources_detail_page', } @@ -290,7 +293,7 @@ export interface NodeEvent extends DrupalNode { field_cta_email: string field_how_to_sign_up: string field_event_cost: string - field_datetime_range_timezone: DateTimeRange[] + field_datetime_range_timezone: FieldDateTimeRange[] field_facility_location: NodeHealthCareLocalFacility field_featured: boolean @@ -302,58 +305,16 @@ export interface NodeEvent extends DrupalNode { field_event_registrationrequired: boolean field_description: string - field_link: fieldLink - field_url_of_an_online_event: urlOfOnlineEvent + field_link: FieldNestedLink + field_url_of_an_online_event: FieldLink field_listing: NodeEventListing field_last_saved_by_an_editor?: string | null } -interface fieldLink { - url: { - path: string - } -} - -export interface DateTimeRange { - value: string - end_value: string - duration: number - rrule: number - rrule_index: number - timezone: string -} - -export interface urlOfOnlineEvent { - uri: string - title: string - options: unknown -} - export interface NodeEventListing extends DrupalNode { - status: boolean - breadcrumbs: BreadcrumbItem[] - moderation_state: string - path: { - alias: string - pid: number - langcode: string - } field_description: string - field_enforce_unique_combo: boolean field_intro_text: string - links: { - self: { - href: string - } - } - resourceIdObjMeta: { - drupal_internal__target_id: number - } - node_type: { - type: string - id: string - resourceIdObjMeta: unknown - } + field_enforce_unique_combo: boolean } export interface NodePromoBanner extends DrupalNode { diff --git a/src/types/formatted/event.ts b/src/types/formatted/event.ts index 818749608..349f6f3c8 100644 --- a/src/types/formatted/event.ts +++ b/src/types/formatted/event.ts @@ -1,11 +1,11 @@ import { NodeHealthCareLocalFacility } from '../drupal/node' -import { urlOfOnlineEvent } from '../drupal/node' import { MediaImage } from './media' import { PublishedEntity } from './publishedEntity' import { FieldAddress, FieldFormattedText, SocialLinksProps, + FieldLink, } from '../drupal/field_type' interface DateTimeRangeItem { @@ -41,5 +41,79 @@ export type Event = PublishedEntity & { locationType: string description: string link: Link | null - urlOfOnlineEvent: urlOfOnlineEvent + urlOfOnlineEvent: FieldLink +} + +// export type EventTeaser = PublishedEntity & { +// This doesn't look like our other formatted types, but it is the shape that the vets-website event widget expects. +export type EventWidgetTeaser = { + changed: string + entityBundle: string + entityId: string + entityPublished: boolean + entityUrl: { + path: string + } + fieldAdditionalInformationAbo: FieldFormattedText + fieldAdditionalListings: [] + fieldAddress: { + addressLine1?: string + addressLine2?: string + administrativeArea?: string + countryCode?: string + locality?: string + postalCode?: string + } + fieldAdministration: { + entity: { + entityId: number + } + } + fieldBody: { + format: string + processed: string + value: string + } + fieldCtaEmail: string + fieldDatetimeRangeTimezone: { + duration: number + endTime: string + endValue: number + startTime: string + timezone: string + value: number + } + fieldDescription: string + fieldEventCost: string + fieldEventCta: string + fieldEventRegistrationrequired: boolean + fieldFacilityLocation?: { + entity: { + entityUrl: { + path: string + } + fieldAddress: { + addressLine1: string + addressLine2: string + administrativeArea: string + countryCode: string + locality: string + postalCode: string + } + title: string + } + } + fieldFeatured: boolean + fieldHowToSignUp: string + fieldLink: Link + fieldListing: { + entity: { + entityId: string + } + } + fieldLocationHumanreadable: string + fieldLocationType: string + fieldOrder: string + fieldUrlOfAnOnlineEvent: { uri: string; title: string } + title: string } diff --git a/src/types/formatted/eventListing.ts b/src/types/formatted/eventListing.ts new file mode 100644 index 000000000..6e9864968 --- /dev/null +++ b/src/types/formatted/eventListing.ts @@ -0,0 +1,12 @@ +import { EventWidgetTeaser } from './event' +import { PublishedEntity } from './publishedEntity' +import { SideNavMenu } from './sideNav' + +export type EventListing = PublishedEntity & { + title: string + introText: string + menu?: SideNavMenu + events: EventWidgetTeaser[] + totalItems: number + totalPages: number +}