diff --git a/dotcom-rendering/src/components/ArticleMeta.web.test.tsx b/dotcom-rendering/src/components/ArticleMeta.web.test.tsx index da5f8da1bfd..bf8f28e997d 100644 --- a/dotcom-rendering/src/components/ArticleMeta.web.test.tsx +++ b/dotcom-rendering/src/components/ArticleMeta.web.test.tsx @@ -102,4 +102,69 @@ describe('ArticleMeta', () => { container.querySelector(`.${interactiveLegacyClasses.shareIcons}`), ).toBeNull(); }); + + describe('dateline', () => { + const webPublicationDate = '2026-01-02T03:04:05.000Z'; + + const renderWithTags = ( + tags: Array<{ id: string; type: string; title: string }>, + ) => + render( + + + , + ).container; + + it('renders a time element with the raw publication date and no secondary dateline for a Filter series tag', () => { + const container = renderWithTags([ + { + id: 'thefilter/series/the-filter', + type: 'Series', + title: 'The Filter', + }, + ]); + + const time = container.querySelector('time'); + expect(time?.getAttribute('datetime')).toBe(webPublicationDate); + expect(time?.textContent).toBe('primary date line'); + expect(container.textContent).not.toContain('secondary date line'); + }); + + it('renders the standard Dateline for a non-Filter tag', () => { + const container = renderWithTags([ + { + id: 'lifeandstyle/series/observer-design', + type: 'Series', + title: 'Observer Design', + }, + ]); + + expect(container.querySelector('time')).toBeNull(); + expect(container.textContent).toContain('primary date line'); + expect(container.textContent).toContain('secondary date line'); + }); + }); }); diff --git a/dotcom-rendering/src/components/ArticleMeta.web.tsx b/dotcom-rendering/src/components/ArticleMeta.web.tsx index 12f6f93e422..8c74080a51a 100644 --- a/dotcom-rendering/src/components/ArticleMeta.web.tsx +++ b/dotcom-rendering/src/components/ArticleMeta.web.tsx @@ -23,6 +23,7 @@ import { import { getAudioData } from '../lib/audio-data'; import { getSoleContributor } from '../lib/byline'; import { decideMainMediaCaption } from '../lib/decide-caption'; +import { hasFilterTag } from '../lib/theFilter'; import { palette as themePalette } from '../palette'; import { hasPreferredSourceButton } from '../preferredSource'; import type { Branding as BrandingType } from '../types/branding'; @@ -339,11 +340,7 @@ export const ArticleMeta = ({ const podcast = getPodcast(tags); const rssFeedUrl = getRssFeedUrl(tags); - const isFilterArticle = tags.some( - (tag) => - tag.id === 'tracking/commissioningdesk/the-filter' || - tag.id === 'tracking/commissioningdesk/filter-us', - ); + const isFilterArticle = hasFilterTag(tags); const mediaType: | 'YouTubeVideo' diff --git a/dotcom-rendering/src/layouts/ImmersiveLayout.tsx b/dotcom-rendering/src/layouts/ImmersiveLayout.tsx index e06d5d7477a..4ebd76f3397 100644 --- a/dotcom-rendering/src/layouts/ImmersiveLayout.tsx +++ b/dotcom-rendering/src/layouts/ImmersiveLayout.tsx @@ -622,6 +622,9 @@ export const ImmersiveLayout = (props: WebProps | AppProps) => { secondaryDateline={ article.webPublicationSecondaryDateDisplay } + webPublicationDate={ + article.webPublicationDate + } isCommentable={ article.isCommentable } @@ -650,6 +653,9 @@ export const ImmersiveLayout = (props: WebProps | AppProps) => { secondaryDateline={ article.webPublicationSecondaryDateDisplay } + webPublicationDate={ + article.webPublicationDate + } isCommentable={ article.isCommentable } diff --git a/dotcom-rendering/src/layouts/StandardLayout.tsx b/dotcom-rendering/src/layouts/StandardLayout.tsx index aa366232619..72038af6309 100644 --- a/dotcom-rendering/src/layouts/StandardLayout.tsx +++ b/dotcom-rendering/src/layouts/StandardLayout.tsx @@ -28,6 +28,7 @@ import { import { canRenderAds } from '../lib/canRenderAds'; import { getContributionsServiceUrl } from '../lib/contributions'; import { decideStoryPackageTrails } from '../lib/decideTrail'; +import { hasFilterTag } from '../lib/theFilter'; import { worldCupTagId } from '../lib/worldCup2026'; import type { NavType } from '../model/extract-nav'; import { palette as themePalette } from '../palette'; @@ -109,6 +110,8 @@ export const StandardLayout = (props: WebProps | AppProps) => { const isWorldCup2026 = article.tags.some((tag) => tag.id === worldCupTagId); + const isFilterArticle = hasFilterTag(article.tags); + const renderAds = canRenderAds(article); return ( @@ -149,7 +152,8 @@ export const StandardLayout = (props: WebProps | AppProps) => { } } showSlimNav={ - format.display === ArticleDisplay.Immersive || + (format.display === ArticleDisplay.Immersive && + !isFilterArticle) || isInteractive } hasPageSkinContentSelfConstrain={true} diff --git a/dotcom-rendering/src/lib/theFilter.test.ts b/dotcom-rendering/src/lib/theFilter.test.ts index bc2723ddaf4..da8a1b24496 100644 --- a/dotcom-rendering/src/lib/theFilter.test.ts +++ b/dotcom-rendering/src/lib/theFilter.test.ts @@ -1,4 +1,4 @@ -import { isFilterPageId } from './theFilter'; +import { hasFilterTag, isFilterPageId } from './theFilter'; describe('isFilterPageId', () => { it('returns true for a UK Filter article pageId', () => { @@ -29,3 +29,45 @@ describe('isFilterPageId', () => { ).toBe(false); }); }); + +describe('hasFilterTag', () => { + it('returns true for the UK Filter series tag', () => { + expect( + hasFilterTag([ + { id: 'lifeandstyle/lifeandstyle' }, + { id: 'thefilter/series/the-filter' }, + ]), + ).toBe(true); + }); + + it('returns true for the US Filter series tag', () => { + expect(hasFilterTag([{ id: 'thefilter-us/series/thefilter-us' }])).toBe( + true, + ); + }); + + it('returns true for the UK Filter commissioning desk', () => { + expect( + hasFilterTag([{ id: 'tracking/commissioningdesk/the-filter' }]), + ).toBe(true); + }); + + it('returns true for the US Filter commissioning desk', () => { + expect( + hasFilterTag([{ id: 'tracking/commissioningdesk/filter-us' }]), + ).toBe(true); + }); + + it('returns false when there is no Filter series or desk tag', () => { + expect( + hasFilterTag([ + { id: 'technology/technology' }, + { id: 'tracking/commissioningdesk/newsletters' }, + ]), + ).toBe(false); + }); + + it('returns false for no tags', () => { + expect(hasFilterTag([])).toBe(false); + }); +}); diff --git a/dotcom-rendering/src/lib/theFilter.ts b/dotcom-rendering/src/lib/theFilter.ts index 202d2650cbe..86a6c780fd0 100644 --- a/dotcom-rendering/src/lib/theFilter.ts +++ b/dotcom-rendering/src/lib/theFilter.ts @@ -1,3 +1,5 @@ +import type { TagType } from '../types/tag'; + /** * The Filter is the Guardian's shopping/product-review vertical. Its * article pageIds always start with one of these section prefixes, @@ -7,3 +9,19 @@ const FILTER_PAGE_ID_PREFIXES = ['thefilter/', 'thefilter-us/']; export const isFilterPageId = (pageId: string): boolean => FILTER_PAGE_ID_PREFIXES.some((prefix) => pageId.startsWith(prefix)); + +/** + * Tags that identify Filter content: the Filter series tags, which Composer + * uses to present an article as The Filter, and the Filter commissioning desk + * tracking tags. Matching either one counts as Filter content, so prefer this + * over the pageId when deciding whether something is The Filter. + */ +const FILTER_TAG_IDS = [ + 'thefilter/series/the-filter', + 'thefilter-us/series/thefilter-us', + 'tracking/commissioningdesk/the-filter', + 'tracking/commissioningdesk/filter-us', +]; + +export const hasFilterTag = (tags: Array>): boolean => + tags.some((tag) => FILTER_TAG_IDS.includes(tag.id));