From 1f68fa18b4eeb52c6652d6fc61573cd3150805b5 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 24 Jul 2026 15:27:54 +0100 Subject: [PATCH 01/40] create onward journey component key type --- src/app/pages/ArticlePage/searchReferrerComponentOrder.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/app/pages/ArticlePage/searchReferrerComponentOrder.ts diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts new file mode 100644 index 00000000000..5285b68803f --- /dev/null +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -0,0 +1,8 @@ +export type OJComponentKey = + | 'mostRead' + | 'topicDiscovery' + | 'pvCarousel' + | 'relatedContent' + | 'topStories' + | 'featuredArticles' + | 'locationBasedOJ'; From 8c348f7b258b8389521f0f09032cf88eda781501 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 24 Jul 2026 15:41:40 +0100 Subject: [PATCH 02/40] set up variants with decided oj component ordering --- .../searchReferrerComponentOrder.ts | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts index 5285b68803f..74636045ee7 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -6,3 +6,66 @@ export type OJComponentKey = | 'topStories' | 'featuredArticles' | 'locationBasedOJ'; + +export type SearchVariant = + | 'variant1related' + | 'variant2recommended' + | 'variant3hybrid' + | 'variant4relatedAndMid' + | 'variant5recommendedAndMid' + | 'variant6hybridAndMid'; + +export const SEARCH_COMPONENT_ORDER: Record = { + variant1related: [ + 'topicDiscovery', + 'relatedContent', + 'locationBasedOJ', + 'topStories', + 'featuredArticles', + 'pvCarousel', + 'mostRead', + ], + variant2recommended: [ + 'topicDiscovery', + 'mostRead', + 'relatedContent', + 'featuredArticles', + 'pvCarousel', + 'topStories', + 'locationBasedOJ', + ], + variant3hybrid: [ + 'topicDiscovery', + 'locationBasedOJ', + 'relatedContent', + 'mostRead', + 'pvCarousel', + 'topStories', + 'featuredArticles', + ], + variant4relatedAndMid: [ + 'topicDiscovery', + 'locationBasedOJ', + 'topStories', + 'featuredArticles', + 'pvCarousel', + 'mostRead', + ], + variant5recommendedAndMid: [ + 'topicDiscovery', + 'mostRead', + 'relatedContent', + 'featuredArticles', + 'pvCarousel', + 'topStories', + 'locationBasedOJ', + ], + variant6hybridAndMid: [ + 'topicDiscovery', + 'relatedContent', + 'mostRead', + 'pvCarousel', + 'topStories', + 'featuredArticles', + ], +}; From bef8dae26c330a85e958a54cf10a108bb6783915 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 24 Jul 2026 15:44:42 +0100 Subject: [PATCH 03/40] comments --- src/app/pages/ArticlePage/searchReferrerComponentOrder.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts index 74636045ee7..9f4c93f67c6 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -7,6 +7,7 @@ export type OJComponentKey = | 'featuredArticles' | 'locationBasedOJ'; +// temp variant names for now - these will change based on optimizely set-up export type SearchVariant = | 'variant1related' | 'variant2recommended' @@ -15,6 +16,7 @@ export type SearchVariant = | 'variant5recommendedAndMid' | 'variant6hybridAndMid'; +// temp variant names for now - these will change based on optimizely set-up export const SEARCH_COMPONENT_ORDER: Record = { variant1related: [ 'topicDiscovery', From 8ca74b987eff3d0dd2cf756ab8dd6aeb0806b97b Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 24 Jul 2026 15:59:28 +0100 Subject: [PATCH 04/40] make variants params to help with testing --- .../ArticlePage/useMobileOJComponentOrder.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/app/pages/ArticlePage/useMobileOJComponentOrder.ts diff --git a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts new file mode 100644 index 00000000000..a965d985fd7 --- /dev/null +++ b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts @@ -0,0 +1,17 @@ +import { SearchVariant } from './searchReferrerComponentOrder'; + +const getDebugVariant = (): SearchVariant | null => { + const params = new URLSearchParams(window.location.search); + const debugParam = params.get('debugVariant'); + if ( + debugParam === 'variant1related' || + debugParam === 'variant2recommended' || + debugParam === 'variant3hybrid' || + debugParam === 'variant4relatedAndMid' || + debugParam === 'variant5recommendedAndMid' || + debugParam === 'variant6hybridAndMid' + ) { + return debugParam; + } + return null; +}; From addd2688db39993c3249d4b719e5cee15f31d3a6 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 24 Jul 2026 16:14:34 +0100 Subject: [PATCH 05/40] return variant component order if user is on mobile --- .../ArticlePage/useMobileOJComponentOrder.ts | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts index a965d985fd7..7311e7640ef 100644 --- a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts +++ b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts @@ -1,4 +1,11 @@ -import { SearchVariant } from './searchReferrerComponentOrder'; +import { useEffect, useState } from 'react'; +import onClient from '#app/lib/utilities/onClient'; +import { GROUP_3_MAX_WIDTH_BP } from '#app/components/ThemeProvider/mediaQueries'; +import { + OJComponentKey, + SEARCH_COMPONENT_ORDER, + SearchVariant, +} from './searchReferrerComponentOrder'; const getDebugVariant = (): SearchVariant | null => { const params = new URLSearchParams(window.location.search); @@ -15,3 +22,39 @@ const getDebugVariant = (): SearchVariant | null => { } return null; }; + +const useMobileOJComponentOrder = (): OJComponentKey[] | null => { + const [order, setOrder] = useState(null); + + useEffect(() => { + if (!onClient()) return undefined; + + const mediaQuery = window.matchMedia( + `max-width: ${GROUP_3_MAX_WIDTH_BP}rem`, + ); + const debugVariant = getDebugVariant(); + const mobileOrder = debugVariant + ? SEARCH_COMPONENT_ORDER[debugVariant] + : null; + + const updateOrder = (matches: boolean) => { + setOrder(matches ? mobileOrder : null); + }; + + // Initial check + updateOrder(mediaQuery.matches); + + // Listen for changes and use the event's matches value + const handleChange = (event: MediaQueryListEvent) => { + updateOrder(event.matches); + }; + + mediaQuery.addEventListener('change', handleChange); + + return () => mediaQuery.removeEventListener('change', handleChange); + }, []); + + return order; +}; + +export default useMobileOJComponentOrder; From c08235b59b3285a8f3255cf8b57120be195cff1e Mon Sep 17 00:00:00 2001 From: holchris Date: Tue, 28 Jul 2026 12:08:40 +0100 Subject: [PATCH 06/40] adds oj reordering logic --- src/app/pages/ArticlePage/ArticlePage.tsx | 117 ++++++++++++++++++++-- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index e66dce0d0e5..88c7bdceeea 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -1,4 +1,4 @@ -import { use, useState, useCallback } from 'react'; +import { Fragment, ReactNode, useState, useCallback, use } from 'react'; import { useTheme } from '@emotion/react'; import useToggle from '#hooks/useToggle'; import useMediaQuery from '#hooks/useMediaQuery'; @@ -59,6 +59,7 @@ import ContinueReadingButton, { ContinueReadingButtonProps, } from '#app/components/ContinueReadingButton'; import SaveArticleButton from '#app/components/SaveArticleButton'; +import FeaturesAnalysis from '#containers/CpsFeaturesAnalysis'; import AccountPromotionalBannerExperiment from '#app/components/Account/AccountPromotionalBannerExperiment'; import ElectionBanner from './ElectionBanner'; import ArticleMessageBanner from './ArticleMessageBanner'; @@ -84,6 +85,7 @@ import RelatedContentSection from '../../components/RelatedContentSection'; import TopicDiscovery from '../../components/TopicDiscovery'; import Disclaimer from '../../components/Disclaimer'; import SecondaryColumn from './SecondaryColumn'; +import useMobileOJComponentOrder from './useMobileOJComponentOrder'; import styles from './ArticlePage.styles'; import { ComponentToRenderProps, TimeStampProps } from './types'; import ArticleHeadline from './ArticleHeadline'; @@ -92,6 +94,8 @@ import { isPortraitVideoUnderHeadline, } from '../../components/MediaLoader/utils/isPortraitVideo'; import LocationBasedTopicOJ from '../../components/LocationBasedTopicOJ'; +import { OJComponentKey } from './searchReferrerComponentOrder'; +import TopStoriesSection from './PagePromoSections/TopStoriesSection'; const getImageComponent = (preloadLeadImageToggle: boolean) => (props: ComponentToRenderProps) => ( @@ -425,6 +429,88 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { pageData?.countryCuration?.summaries?.length, ); + const mobileOJOrder = useMobileOJComponentOrder(); + + const topStoriesContent = pageData?.secondaryColumn?.topStories; + const featuresContent = pageData?.secondaryColumn?.features; + + const getTopicDiscoverySlot = () => { + if (showTopicDiscovery) { + return ( + + ); + } + if (showRelatedTopicsComponent) { + return ( + + ); + } + return null; + }; + + const topicDiscoverySlot = getTopicDiscoverySlot(); + + const mobileOJComponents: Record = { + mostRead: + !isApp && !isPGL ? ( + + ) : null, + topicDiscovery: topicDiscoverySlot, + relatedContent: , + pvCarousel: showPortraitVideoCarousel ? ( + + ) : null, + topStories: + !isApp && !isPGL && topStoriesContent ? ( +
+ +
+ ) : null, + featuredArticles: + !isApp && !isPGL && featuresContent ? ( +
+ +
+ ) : null, + locationBasedOJ: showCountryCuration ? ( + + ) : null, + }; + const shouldApplyCollapsedArticleSpacing = showContinueReadingButton && !showAllContent; @@ -500,7 +586,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { - {showTopicDiscovery && ( + {!mobileOJOrder && showTopicDiscovery && ( { topics={topics} /> )} - {showRelatedTopicsComponent && ( + {!mobileOJOrder && showRelatedTopicsComponent && ( { mobileDivider={false} /> )} - {showCountryCuration && } - {showPortraitVideoCarousel && ( + {!mobileOJOrder && showCountryCuration && ( + + )} + {!mobileOJOrder && showPortraitVideoCarousel && ( )} - + {!mobileOJOrder && } {showMediaCuration && (
@@ -550,10 +638,23 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { )}
- {!isApp && !isPGL && } + {!isApp && !isPGL && !mobileOJOrder && ( + + )}
- {!isApp && !isPGL && ( + {mobileOJOrder && ( +
+ {mobileOJOrder.map(key => ( + {mobileOJComponents[key]} + ))} +
+ )} + + {!isApp && !isPGL && !mobileOJOrder && ( Date: Tue, 28 Jul 2026 12:08:57 +0100 Subject: [PATCH 07/40] adds mobile oj container css --- src/app/pages/ArticlePage/ArticlePage.styles.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/pages/ArticlePage/ArticlePage.styles.ts b/src/app/pages/ArticlePage/ArticlePage.styles.ts index 784d53d8fb3..5f8b258fbac 100644 --- a/src/app/pages/ArticlePage/ArticlePage.styles.ts +++ b/src/app/pages/ArticlePage/ArticlePage.styles.ts @@ -243,4 +243,10 @@ export default { padding: 0, }, }), + mobileOJContainer: ({ mq }: Theme) => + css({ + [mq.GROUP_4_MIN_WIDTH]: { + display: 'none', + }, + }), }; From d676ea025b1b0e2f28038b6f7ebbd3ad8417f188 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Tue, 28 Jul 2026 16:20:28 +0100 Subject: [PATCH 08/40] add switch case for showing mid article ojs for each variant --- src/app/pages/ArticlePage/ArticlePage.tsx | 47 ++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 88c7bdceeea..6d3105a39c0 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -94,7 +94,7 @@ import { isPortraitVideoUnderHeadline, } from '../../components/MediaLoader/utils/isPortraitVideo'; import LocationBasedTopicOJ from '../../components/LocationBasedTopicOJ'; -import { OJComponentKey } from './searchReferrerComponentOrder'; +import { OJComponentKey, SearchVariant } from './searchReferrerComponentOrder'; import TopStoriesSection from './PagePromoSections/TopStoriesSection'; const getImageComponent = @@ -165,6 +165,7 @@ const getWsojComponent = ({ }) => ( ); + const DisclaimerWithPaddingOverride = (props: ComponentToRenderProps) => ( ); @@ -296,6 +297,39 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { ?.split(':') ?.includes('topcat'); + const getSearchMidArticleOJ = ({ + data, + experimentProps, + searchVariant, + }: { + data: Recommendation[]; + experimentProps?: ComponentExperimentProps | null; + searchVariant: SearchVariant | null; + }) => { + switch (searchVariant) { + case 'variant1related': + return ; + + case 'variant2recommended': + return ; + + case 'variant3hybrid': + return ; + + case 'variant4relatedAndMid': + return ; + + case 'variant5recommendedAndMid': + return ; + + case 'variant6hybridAndMid': + return ; + + default: + return getWsojComponent({ data, experimentProps }); + } + }; + const showPortraitVideoCarousel = Boolean( pageData?.portraitVideoItems?.portraitVideo?.blocks?.length && articlePortraitVideoEnabled, @@ -380,10 +414,13 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { group: gist, links: ArticleLinksBlock, mpu: getMpuComponent(allowAdvertising), - wsoj: ({ data }: { data: Recommendation[] }) => - getWsojComponent({ - data, - }), + wsoj: ({ + data, + searchVariant, + }: { + data: Recommendation[]; + searchVariant: SearchVariant | null; + }) => getSearchMidArticleOJ({ data, searchVariant }), disclaimer: DisclaimerWithPaddingOverride, podcastPromo: getPodcastPromoComponent(podcastPromoEnabled), ...(showContinueReadingButton && { From cd9dfef4d33335c83b266dc3c2ad2dbd062af48f Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Tue, 28 Jul 2026 17:38:11 +0100 Subject: [PATCH 09/40] add missing brackets to enable reordering to work --- src/app/pages/ArticlePage/useMobileOJComponentOrder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts index 7311e7640ef..6ec905cd7ba 100644 --- a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts +++ b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts @@ -30,7 +30,7 @@ const useMobileOJComponentOrder = (): OJComponentKey[] | null => { if (!onClient()) return undefined; const mediaQuery = window.matchMedia( - `max-width: ${GROUP_3_MAX_WIDTH_BP}rem`, + `(max-width: ${GROUP_3_MAX_WIDTH_BP}rem)`, ); const debugVariant = getDebugVariant(); const mobileOrder = debugVariant From 9ebb62596f758f6a0cdb970a721279c712f5a105 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Tue, 28 Jul 2026 17:46:36 +0100 Subject: [PATCH 10/40] amend switch for variants 1 to 3 as they all return the same component --- src/app/pages/ArticlePage/ArticlePage.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 6d3105a39c0..ffed3ad627f 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -306,13 +306,11 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { experimentProps?: ComponentExperimentProps | null; searchVariant: SearchVariant | null; }) => { + // eslint-disable-next-line no-console + console.log('mid-article searchVariant:', searchVariant); switch (searchVariant) { case 'variant1related': - return ; - case 'variant2recommended': - return ; - case 'variant3hybrid': return ; @@ -443,7 +441,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const authors = bylineLinkedData?.map(data => data?.authorName).join(','); - const showTopicDiscovery = topicDiscoveryEnabled && !isAmp && !isLite; + const showTopicDiscovery = true; const showRelatedTopicsComponent = Boolean( showRelatedTopics && topics.length > 0 && !showTopicDiscovery, From 29ba7151cfd534ea5487244bc890354b67033eed Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Tue, 28 Jul 2026 17:49:43 +0100 Subject: [PATCH 11/40] remove console log --- src/app/pages/ArticlePage/ArticlePage.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index ffed3ad627f..99d8a8398ff 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -306,8 +306,6 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { experimentProps?: ComponentExperimentProps | null; searchVariant: SearchVariant | null; }) => { - // eslint-disable-next-line no-console - console.log('mid-article searchVariant:', searchVariant); switch (searchVariant) { case 'variant1related': case 'variant2recommended': @@ -441,7 +439,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const authors = bylineLinkedData?.map(data => data?.authorName).join(','); - const showTopicDiscovery = true; + const showTopicDiscovery = topicDiscoveryEnabled && !isAmp && !isLite; const showRelatedTopicsComponent = Boolean( showRelatedTopics && topics.length > 0 && !showTopicDiscovery, From ea7022f335aaf6513522d8bf928c64b5f0fb222a Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Tue, 28 Jul 2026 18:04:18 +0100 Subject: [PATCH 12/40] update mobile OJ order to include searchVariant --- .../ArticlePage/useMobileOJComponentOrder.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts index 6ec905cd7ba..6d09f8803b6 100644 --- a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts +++ b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts @@ -23,7 +23,21 @@ const getDebugVariant = (): SearchVariant | null => { return null; }; -const useMobileOJComponentOrder = (): OJComponentKey[] | null => { +export const useDebugVariant = (): SearchVariant | null => { + const [debugVariant, setDebugVariant] = useState(null); + + useEffect(() => { + if (!onClient()) return; + + setDebugVariant(getDebugVariant()); + }, []); + + return debugVariant; +}; + +const useMobileOJComponentOrder = ( + searchVariant: SearchVariant | null, +): OJComponentKey[] | null => { const [order, setOrder] = useState(null); useEffect(() => { @@ -32,9 +46,9 @@ const useMobileOJComponentOrder = (): OJComponentKey[] | null => { const mediaQuery = window.matchMedia( `(max-width: ${GROUP_3_MAX_WIDTH_BP}rem)`, ); - const debugVariant = getDebugVariant(); - const mobileOrder = debugVariant - ? SEARCH_COMPONENT_ORDER[debugVariant] + + const mobileOrder = searchVariant + ? SEARCH_COMPONENT_ORDER[searchVariant] : null; const updateOrder = (matches: boolean) => { @@ -52,7 +66,7 @@ const useMobileOJComponentOrder = (): OJComponentKey[] | null => { mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); - }, []); + }, [searchVariant]); return order; }; From 99429fef05ab382ed890b272ade5eb7b45da1257 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Tue, 28 Jul 2026 18:05:32 +0100 Subject: [PATCH 13/40] small change to get switch case working for midarticle OJs --- src/app/pages/ArticlePage/ArticlePage.tsx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 99d8a8398ff..527779f0c73 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -85,7 +85,9 @@ import RelatedContentSection from '../../components/RelatedContentSection'; import TopicDiscovery from '../../components/TopicDiscovery'; import Disclaimer from '../../components/Disclaimer'; import SecondaryColumn from './SecondaryColumn'; -import useMobileOJComponentOrder from './useMobileOJComponentOrder'; +import useMobileOJComponentOrder, { + useDebugVariant, +} from './useMobileOJComponentOrder'; import styles from './ArticlePage.styles'; import { ComponentToRenderProps, TimeStampProps } from './types'; import ArticleHeadline from './ArticleHeadline'; @@ -384,6 +386,9 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { promoImageRawBlock?.model as { locator?: string } | undefined )?.locator; + const searchVariant = useDebugVariant(); + const mobileOJOrder = useMobileOJComponentOrder(searchVariant); + const componentsToRender = { visuallyHiddenHeadline, headline: getHeadlineComponent, @@ -410,13 +415,8 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { group: gist, links: ArticleLinksBlock, mpu: getMpuComponent(allowAdvertising), - wsoj: ({ - data, - searchVariant, - }: { - data: Recommendation[]; - searchVariant: SearchVariant | null; - }) => getSearchMidArticleOJ({ data, searchVariant }), + wsoj: ({ data }: { data: Recommendation[] }) => + getSearchMidArticleOJ({ data, searchVariant }), disclaimer: DisclaimerWithPaddingOverride, podcastPromo: getPodcastPromoComponent(podcastPromoEnabled), ...(showContinueReadingButton && { @@ -439,7 +439,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const authors = bylineLinkedData?.map(data => data?.authorName).join(','); - const showTopicDiscovery = topicDiscoveryEnabled && !isAmp && !isLite; + const showTopicDiscovery = true; const showRelatedTopicsComponent = Boolean( showRelatedTopics && topics.length > 0 && !showTopicDiscovery, @@ -462,8 +462,6 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { pageData?.countryCuration?.summaries?.length, ); - const mobileOJOrder = useMobileOJComponentOrder(); - const topStoriesContent = pageData?.secondaryColumn?.topStories; const featuresContent = pageData?.secondaryColumn?.features; From f5874fcf30ec1169b417db062ea6fade967c624b Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 10:02:42 +0100 Subject: [PATCH 14/40] fixes midarticle OJ changing at desktop --- src/app/pages/ArticlePage/ArticlePage.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 527779f0c73..edc184ccf12 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -415,8 +415,13 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { group: gist, links: ArticleLinksBlock, mpu: getMpuComponent(allowAdvertising), - wsoj: ({ data }: { data: Recommendation[] }) => - getSearchMidArticleOJ({ data, searchVariant }), + // renders wsoj if user is on desktop, otherwise renders a chosen OJ based on search referrer experiment on mobile + wsoj: ({ data }: { data: Recommendation[] }) => { + if (!isDesktopViewport) { + return getSearchMidArticleOJ({ data, searchVariant }); + } + return getWsojComponent({ data }); + }, disclaimer: DisclaimerWithPaddingOverride, podcastPromo: getPodcastPromoComponent(podcastPromoEnabled), ...(showContinueReadingButton && { @@ -439,7 +444,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const authors = bylineLinkedData?.map(data => data?.authorName).join(','); - const showTopicDiscovery = true; + const showTopicDiscovery = topicDiscoveryEnabled && !isAmp && !isLite; const showRelatedTopicsComponent = Boolean( showRelatedTopics && topics.length > 0 && !showTopicDiscovery, From 324577d83c8a555f3e223372ea2db4ae68dc9684 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 12:17:04 +0100 Subject: [PATCH 15/40] hide related topics when topic discovery is in midarticle position --- src/app/pages/ArticlePage/ArticlePage.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index edc184ccf12..6a22577d352 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -446,8 +446,16 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const showTopicDiscovery = topicDiscoveryEnabled && !isAmp && !isLite; + // Topic Discovery shows in the midarticle position for one variant + // We want to hide RelatedTopics when this happens + const topicDiscoveryInMidArticlePosition = + !isDesktopViewport && searchVariant === 'variant5recommendedAndMid'; + const showRelatedTopicsComponent = Boolean( - showRelatedTopics && topics.length > 0 && !showTopicDiscovery, + showRelatedTopics && + topics.length > 0 && + !showTopicDiscovery && + !topicDiscoveryInMidArticlePosition, ); const showMediaCuration = Boolean( From 159eba1f4c6524319d484a87c79ad1b9fedac5d9 Mon Sep 17 00:00:00 2001 From: holchris Date: Wed, 29 Jul 2026 15:24:51 +0100 Subject: [PATCH 16/40] update variant names --- .../searchReferrerComponentOrder.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts index 9f4c93f67c6..2876d5efe35 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -9,16 +9,16 @@ export type OJComponentKey = // temp variant names for now - these will change based on optimizely set-up export type SearchVariant = - | 'variant1related' - | 'variant2recommended' - | 'variant3hybrid' - | 'variant4relatedAndMid' - | 'variant5recommendedAndMid' - | 'variant6hybridAndMid'; + | 'variant_1_related' + | 'variant_2_recommended' + | 'variant_3_hybrid' + | 'variant_4_related_mid' + | 'variant_5_recommended_mid' + | 'variant_6_hybrid_mid'; // temp variant names for now - these will change based on optimizely set-up export const SEARCH_COMPONENT_ORDER: Record = { - variant1related: [ + variant_1_related: [ 'topicDiscovery', 'relatedContent', 'locationBasedOJ', @@ -27,7 +27,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'pvCarousel', 'mostRead', ], - variant2recommended: [ + variant_2_recommended: [ 'topicDiscovery', 'mostRead', 'relatedContent', @@ -36,7 +36,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'topStories', 'locationBasedOJ', ], - variant3hybrid: [ + variant_3_hybrid: [ 'topicDiscovery', 'locationBasedOJ', 'relatedContent', @@ -45,7 +45,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'topStories', 'featuredArticles', ], - variant4relatedAndMid: [ + variant_4_related_mid: [ 'topicDiscovery', 'locationBasedOJ', 'topStories', @@ -53,7 +53,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'pvCarousel', 'mostRead', ], - variant5recommendedAndMid: [ + variant_5_recommended_mid: [ 'topicDiscovery', 'mostRead', 'relatedContent', @@ -62,7 +62,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'topStories', 'locationBasedOJ', ], - variant6hybridAndMid: [ + variant_6_hybrid_mid: [ 'topicDiscovery', 'relatedContent', 'mostRead', From 18c506f72cfc4d48098ed8d7e2ddc9211abdbbd3 Mon Sep 17 00:00:00 2001 From: holchris Date: Wed, 29 Jul 2026 15:25:08 +0100 Subject: [PATCH 17/40] update variant names in article page --- src/app/pages/ArticlePage/ArticlePage.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 6a22577d352..2d93cc7b80f 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -309,18 +309,18 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { searchVariant: SearchVariant | null; }) => { switch (searchVariant) { - case 'variant1related': - case 'variant2recommended': - case 'variant3hybrid': + case 'variant_1_related': + case 'variant_2_recommended': + case 'variant_3_hybrid': return ; - case 'variant4relatedAndMid': + case 'variant_4_related_mid': return ; - case 'variant5recommendedAndMid': + case 'variant_5_recommended_mid': return ; - case 'variant6hybridAndMid': + case 'variant_6_hybrid_mid': return ; default: @@ -449,7 +449,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { // Topic Discovery shows in the midarticle position for one variant // We want to hide RelatedTopics when this happens const topicDiscoveryInMidArticlePosition = - !isDesktopViewport && searchVariant === 'variant5recommendedAndMid'; + !isDesktopViewport && searchVariant === 'variant_5_recommended_mid'; const showRelatedTopicsComponent = Boolean( showRelatedTopics && From e8bfe8e333d97882eb8a2fc8f3cd42e4292c1bfe Mon Sep 17 00:00:00 2001 From: holchris Date: Wed, 29 Jul 2026 15:25:27 +0100 Subject: [PATCH 18/40] update search variant params --- .../pages/ArticlePage/useMobileOJComponentOrder.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts index 6d09f8803b6..7f23c60cab4 100644 --- a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts +++ b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts @@ -11,12 +11,12 @@ const getDebugVariant = (): SearchVariant | null => { const params = new URLSearchParams(window.location.search); const debugParam = params.get('debugVariant'); if ( - debugParam === 'variant1related' || - debugParam === 'variant2recommended' || - debugParam === 'variant3hybrid' || - debugParam === 'variant4relatedAndMid' || - debugParam === 'variant5recommendedAndMid' || - debugParam === 'variant6hybridAndMid' + debugParam === 'variant_1_related' || + debugParam === 'variant_2_recommended' || + debugParam === 'variant_3_hybrid' || + debugParam === 'variant_4_related_mid' || + debugParam === 'variant_5_recommended_mid' || + debugParam === 'variant_6_hybrid_mid' ) { return debugParam; } From 1c19189db81e981652864154348c14327b953c53 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 17:26:00 +0100 Subject: [PATCH 19/40] adds marginBottom to topicDiscovery when in the midarticle position --- src/app/pages/ArticlePage/ArticlePage.styles.ts | 4 ++++ src/app/pages/ArticlePage/ArticlePage.tsx | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.styles.ts b/src/app/pages/ArticlePage/ArticlePage.styles.ts index 5f8b258fbac..5ad337a0aa2 100644 --- a/src/app/pages/ArticlePage/ArticlePage.styles.ts +++ b/src/app/pages/ArticlePage/ArticlePage.styles.ts @@ -249,4 +249,8 @@ export default { display: 'none', }, }), + midArticleTopicDiscovery: () => + css({ + marginBottom: `${pixelsToRem(40)}rem`, + }), }; diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 2d93cc7b80f..791097a1d48 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -318,7 +318,12 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { return ; case 'variant_5_recommended_mid': - return ; + return ( + + ); case 'variant_6_hybrid_mid': return ; From d663bdde32cec930b31a01671f532d6c213059bd Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 17:34:09 +0100 Subject: [PATCH 20/40] adds 40px marginBottom for location oj also --- src/app/pages/ArticlePage/ArticlePage.styles.ts | 2 +- src/app/pages/ArticlePage/ArticlePage.tsx | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.styles.ts b/src/app/pages/ArticlePage/ArticlePage.styles.ts index 5ad337a0aa2..8783aad40c6 100644 --- a/src/app/pages/ArticlePage/ArticlePage.styles.ts +++ b/src/app/pages/ArticlePage/ArticlePage.styles.ts @@ -249,7 +249,7 @@ export default { display: 'none', }, }), - midArticleTopicDiscovery: () => + midArticleOJ: () => css({ marginBottom: `${pixelsToRem(40)}rem`, }), diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 791097a1d48..ccfc2dbb7bb 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -318,15 +318,14 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { return ; case 'variant_5_recommended_mid': - return ( - - ); + return ; case 'variant_6_hybrid_mid': - return ; + return ( +
+ +
+ ); default: return getWsojComponent({ data, experimentProps }); From 57e444116ff9f58aa711615b89d490fedbedefec Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 17:38:32 +0100 Subject: [PATCH 21/40] remove topicDiscovery from variant 5 --- src/app/pages/ArticlePage/searchReferrerComponentOrder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts index 2876d5efe35..d3351ce2535 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -54,7 +54,6 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'mostRead', ], variant_5_recommended_mid: [ - 'topicDiscovery', 'mostRead', 'relatedContent', 'featuredArticles', From 29d0668643174f543a986b334bcd63981ca44b0f Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 17:45:34 +0100 Subject: [PATCH 22/40] remove comment --- src/app/pages/ArticlePage/searchReferrerComponentOrder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts index d3351ce2535..4c23a157f6f 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -7,7 +7,6 @@ export type OJComponentKey = | 'featuredArticles' | 'locationBasedOJ'; -// temp variant names for now - these will change based on optimizely set-up export type SearchVariant = | 'variant_1_related' | 'variant_2_recommended' From 173fb7eb729a1aab98fa07d49c04790a6d15afad Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 17:49:08 +0100 Subject: [PATCH 23/40] reuse variant config for debug variant validation --- src/app/pages/ArticlePage/useMobileOJComponentOrder.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts index 7f23c60cab4..bbc7a678d4e 100644 --- a/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts +++ b/src/app/pages/ArticlePage/useMobileOJComponentOrder.ts @@ -11,14 +11,10 @@ const getDebugVariant = (): SearchVariant | null => { const params = new URLSearchParams(window.location.search); const debugParam = params.get('debugVariant'); if ( - debugParam === 'variant_1_related' || - debugParam === 'variant_2_recommended' || - debugParam === 'variant_3_hybrid' || - debugParam === 'variant_4_related_mid' || - debugParam === 'variant_5_recommended_mid' || - debugParam === 'variant_6_hybrid_mid' + debugParam && + Object.prototype.hasOwnProperty.call(SEARCH_COMPONENT_ORDER, debugParam) ) { - return debugParam; + return debugParam as SearchVariant; } return null; }; From c2a10121728618a311a9983a02a10da2dfefd56b Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 17:56:26 +0100 Subject: [PATCH 24/40] remove redundant styling --- src/app/pages/ArticlePage/ArticlePage.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index ccfc2dbb7bb..d14054ba91f 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -448,7 +448,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const authors = bylineLinkedData?.map(data => data?.authorName).join(','); - const showTopicDiscovery = topicDiscoveryEnabled && !isAmp && !isLite; + const showTopicDiscovery = true; // Topic Discovery shows in the midarticle position for one variant // We want to hide RelatedTopics when this happens @@ -692,10 +692,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { {mobileOJOrder && ( -
+
{mobileOJOrder.map(key => ( {mobileOJComponents[key]} ))} From 3a7b4b1ad1dc8d800e0d85f8caf454c21de3f7ab Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Wed, 29 Jul 2026 17:58:46 +0100 Subject: [PATCH 25/40] revert accidental change --- src/app/pages/ArticlePage/ArticlePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index d14054ba91f..80c5f94578f 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -448,7 +448,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const authors = bylineLinkedData?.map(data => data?.authorName).join(','); - const showTopicDiscovery = true; + const showTopicDiscovery = topicDiscoveryEnabled && !isAmp && !isLite; // Topic Discovery shows in the midarticle position for one variant // We want to hide RelatedTopics when this happens From e576f0c306cc9c0001199b4f7bcd0c8fdcdbf378 Mon Sep 17 00:00:00 2001 From: holchris Date: Thu, 30 Jul 2026 11:18:32 +0100 Subject: [PATCH 26/40] adds tests for search referrer --- .../searchReferrerComponentOrder.test.ts | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts new file mode 100644 index 00000000000..52b0fc58849 --- /dev/null +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -0,0 +1,197 @@ +import { renderHook, waitFor, act } from '@testing-library/react'; +import onClient from '#app/lib/utilities/onClient'; +import useMobileOJComponentOrder from './useMobileOJComponentOrder'; +import { SEARCH_COMPONENT_ORDER } from './searchReferrerComponentOrder'; + +jest.mock( + '#app/legacy/containers/PageHandlers/withOptimizelyProvider/userAttributes', +); +jest.mock('#app/lib/utilities/onClient'); + +const mockOnClient = onClient as jest.MockedFunction; + +describe('useMobileOJComponentOrder', () => { + let matchMediaMock: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + mockOnClient.mockReturnValue(true); + + // Mock window.matchMedia + matchMediaMock = jest.fn().mockReturnValue({ + matches: false, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: matchMediaMock, + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('Desktop behavior', () => { + it('should return null regardless of variant when on desktop', () => { + matchMediaMock.mockReturnValue({ + matches: false, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }); + + const { result: variant2Result } = renderHook(() => + useMobileOJComponentOrder('variant_2_recommended'), + ); + expect(variant2Result.current).toBeNull(); + + const { result: variant3Result } = renderHook(() => + useMobileOJComponentOrder('variant_3_hybrid'), + ); + expect(variant3Result.current).toBeNull(); + }); + }); + + describe('Mobile behavior with different variants', () => { + beforeEach(() => { + matchMediaMock.mockReturnValue({ + matches: true, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }); + }); + it.each([ + 'variant_1_related', + 'variant_2_recommended', + 'variant_3_hybrid', + 'variant_4_related_mid', + 'variant_5_recommended_mid', + 'variant_6_hybrid_mid', + ] as const)('returns the correct order for %s', searchVariant => { + const { result } = renderHook(() => + useMobileOJComponentOrder(searchVariant), + ); + + expect(result.current).toEqual(SEARCH_COMPONENT_ORDER[searchVariant]); + }); + it('returns null when no variant is provided', () => { + const { result } = renderHook(() => useMobileOJComponentOrder(null)); + expect(result.current).toBeNull(); + }); + }); + + describe('Media query listener', () => { + it('should add a change listener to media query on client', () => { + const addEventListenerMock = jest.fn(); + matchMediaMock.mockReturnValue({ + matches: false, + addEventListener: addEventListenerMock, + removeEventListener: jest.fn(), + }); + + renderHook(() => useMobileOJComponentOrder(null)); + + expect(addEventListenerMock).toHaveBeenCalledWith( + 'change', + expect.any(Function), + ); + }); + + it('should remove listener on unmount', () => { + const removeEventListenerMock = jest.fn(); + matchMediaMock.mockReturnValue({ + matches: false, + addEventListener: jest.fn(), + removeEventListener: removeEventListenerMock, + }); + + const { unmount } = renderHook(() => useMobileOJComponentOrder(null)); + + unmount(); + + expect(removeEventListenerMock).toHaveBeenCalledWith( + 'change', + expect.any(Function), + ); + }); + + it('should update order when viewport changes from desktop to mobile', async () => { + let listenerCallback: ((e: MediaQueryListEvent) => void) | null = null; + matchMediaMock.mockImplementation(() => ({ + matches: false, + addEventListener: jest.fn((event, callback) => { + if (event === 'change') { + listenerCallback = callback; + } + }), + removeEventListener: jest.fn(), + })); + + const { result } = renderHook(() => + useMobileOJComponentOrder('variant_1_related'), + ); + + expect(result.current).toBeNull(); + + if (listenerCallback) { + await act(async () => { + listenerCallback?.({ + matches: true, + } as MediaQueryListEvent); + }); + } + + await waitFor(() => { + expect(result.current).not.toBeNull(); + }); + + expect(result.current?.[0]).toBe('topicDiscovery'); + }); + + it('should update order when viewport changes from mobile to desktop', async () => { + let listenerCallback: ((e: MediaQueryListEvent) => void) | null = null; + matchMediaMock.mockImplementation(() => ({ + matches: true, + addEventListener: jest.fn((event, callback) => { + if (event === 'change') { + listenerCallback = callback; + } + }), + removeEventListener: jest.fn(), + })); + + const { result } = renderHook(() => + useMobileOJComponentOrder('variant_4_related_mid'), + ); + + expect(result.current).not.toBeNull(); + + if (listenerCallback) { + await act(async () => { + listenerCallback?.({ + matches: false, + } as MediaQueryListEvent); + }); + } + + await waitFor(() => { + expect(result.current).toBeNull(); + }); + }); + }); + + describe('Correct media query breakpoint', () => { + it('should use GROUP_3_MAX_WIDTH_BP for the breakpoint', () => { + mockOnClient.mockReturnValue(true); + + renderHook(() => useMobileOJComponentOrder(null)); + + const callArgs = matchMediaMock.mock.calls[0][0]; + // GROUP_3_MAX_WIDTH_BP = pixelsToRem(1007) = 62.9375rem + expect(callArgs).toContain('62.9375rem'); + expect(callArgs).toContain('max-width'); + }); + }); +}); From 501f64b6ce154e483c34f615c6b6848892b20599 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 11:28:29 +0100 Subject: [PATCH 27/40] remove commented code and use media query for the test instead of hardcoding --- .../pages/ArticlePage/searchReferrerComponentOrder.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index 52b0fc58849..1e727a6396d 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -1,5 +1,6 @@ import { renderHook, waitFor, act } from '@testing-library/react'; import onClient from '#app/lib/utilities/onClient'; +import { GROUP_3_MAX_WIDTH_BP } from '#app/components/ThemeProvider/mediaQueries'; import useMobileOJComponentOrder from './useMobileOJComponentOrder'; import { SEARCH_COMPONENT_ORDER } from './searchReferrerComponentOrder'; @@ -189,8 +190,8 @@ describe('useMobileOJComponentOrder', () => { renderHook(() => useMobileOJComponentOrder(null)); const callArgs = matchMediaMock.mock.calls[0][0]; - // GROUP_3_MAX_WIDTH_BP = pixelsToRem(1007) = 62.9375rem - expect(callArgs).toContain('62.9375rem'); + + expect(callArgs).toContain(`${GROUP_3_MAX_WIDTH_BP}rem`); expect(callArgs).toContain('max-width'); }); }); From 53eaa1700952c96ce4c32cca59fa12c48970fb7a Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 11:46:01 +0100 Subject: [PATCH 28/40] use searchVariant type --- .../pages/ArticlePage/searchReferrerComponentOrder.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index 1e727a6396d..fcf104eea7e 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -2,7 +2,10 @@ import { renderHook, waitFor, act } from '@testing-library/react'; import onClient from '#app/lib/utilities/onClient'; import { GROUP_3_MAX_WIDTH_BP } from '#app/components/ThemeProvider/mediaQueries'; import useMobileOJComponentOrder from './useMobileOJComponentOrder'; -import { SEARCH_COMPONENT_ORDER } from './searchReferrerComponentOrder'; +import { + SEARCH_COMPONENT_ORDER, + SearchVariant, +} from './searchReferrerComponentOrder'; jest.mock( '#app/legacy/containers/PageHandlers/withOptimizelyProvider/userAttributes', @@ -70,7 +73,7 @@ describe('useMobileOJComponentOrder', () => { 'variant_4_related_mid', 'variant_5_recommended_mid', 'variant_6_hybrid_mid', - ] as const)('returns the correct order for %s', searchVariant => { + ] as SearchVariant[])('returns the correct order for %s', searchVariant => { const { result } = renderHook(() => useMobileOJComponentOrder(searchVariant), ); From f7f3ad653443b55e629f50922ec039cfdc546038 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 13:32:48 +0100 Subject: [PATCH 29/40] refactor switch case for midarticleOJ --- src/app/pages/ArticlePage/ArticlePage.tsx | 27 ++++++++++++------- .../searchReferrerComponentOrder.ts | 17 +++++++++++- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 80c5f94578f..e1f5b513b0c 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -96,7 +96,11 @@ import { isPortraitVideoUnderHeadline, } from '../../components/MediaLoader/utils/isPortraitVideo'; import LocationBasedTopicOJ from '../../components/LocationBasedTopicOJ'; -import { OJComponentKey, SearchVariant } from './searchReferrerComponentOrder'; +import { + OJComponentKey, + SEARCH_MID_ARTICLE_COMPONENT, + SearchVariant, +} from './searchReferrerComponentOrder'; import TopStoriesSection from './PagePromoSections/TopStoriesSection'; const getImageComponent = @@ -308,19 +312,24 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { experimentProps?: ComponentExperimentProps | null; searchVariant: SearchVariant | null; }) => { - switch (searchVariant) { - case 'variant_1_related': - case 'variant_2_recommended': - case 'variant_3_hybrid': + const midarticleOJ = searchVariant + ? SEARCH_MID_ARTICLE_COMPONENT[searchVariant] + : null; + switch (midarticleOJ) { + case 'mostRead': return ; - case 'variant_4_related_mid': - return ; + case 'relatedContent': + return ( +
+ {' '} +
+ ); - case 'variant_5_recommended_mid': + case 'topicDiscovery': return ; - case 'variant_6_hybrid_mid': + case 'locationBasedOJ': return (
diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts index 4c23a157f6f..d7bfda5b584 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -15,7 +15,6 @@ export type SearchVariant = | 'variant_5_recommended_mid' | 'variant_6_hybrid_mid'; -// temp variant names for now - these will change based on optimizely set-up export const SEARCH_COMPONENT_ORDER: Record = { variant_1_related: [ 'topicDiscovery', @@ -69,3 +68,19 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'featuredArticles', ], }; + +export type MidArticleOJ = + | 'mostRead' + | 'topicDiscovery' + | 'relatedContent' + | 'locationBasedOJ'; + +export const SEARCH_MID_ARTICLE_COMPONENT: Record = + { + variant_1_related: 'mostRead', + variant_2_recommended: 'mostRead', + variant_3_hybrid: 'mostRead', + variant_4_related_mid: 'relatedContent', + variant_5_recommended_mid: 'topicDiscovery', + variant_6_hybrid_mid: 'locationBasedOJ', + }; From 5de937cd22f6b171229a1c82fd1a2097fa9a388f Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 13:37:38 +0100 Subject: [PATCH 30/40] test midarticle OJ to variant mapping --- .../searchReferrerComponentOrder.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index fcf104eea7e..e993215dd8a 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -4,6 +4,7 @@ import { GROUP_3_MAX_WIDTH_BP } from '#app/components/ThemeProvider/mediaQueries import useMobileOJComponentOrder from './useMobileOJComponentOrder'; import { SEARCH_COMPONENT_ORDER, + SEARCH_MID_ARTICLE_COMPONENT, SearchVariant, } from './searchReferrerComponentOrder'; @@ -86,6 +87,21 @@ describe('useMobileOJComponentOrder', () => { }); }); + describe('Mid-article component ordering', () => { + it.each([ + ['variant_1_related', 'mostRead'], + ['variant_2_recommended', 'mostRead'], + ['variant_3_hybrid', 'mostRead'], + ['variant_4_related_mid', 'relatedContent'], + ['variant_5_recommended_mid', 'topicDiscovery'], + ['variant_6_hybrid_mid', 'locationBasedOJ'], + ] as const)('%o returns %o', (searchVariant, expectedComponent) => { + expect(SEARCH_MID_ARTICLE_COMPONENT[searchVariant]).toBe( + expectedComponent, + ); + }); + }); + describe('Media query listener', () => { it('should add a change listener to media query on client', () => { const addEventListenerMock = jest.fn(); From c7814e96b1ebc917403965c377c2f03e12cd1055 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 15:05:24 +0100 Subject: [PATCH 31/40] refactor condition and remove accidental brackets --- src/app/pages/ArticlePage/ArticlePage.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index e1f5b513b0c..852f94b09da 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -322,7 +322,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { case 'relatedContent': return (
- {' '} +
); @@ -429,12 +429,10 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { links: ArticleLinksBlock, mpu: getMpuComponent(allowAdvertising), // renders wsoj if user is on desktop, otherwise renders a chosen OJ based on search referrer experiment on mobile - wsoj: ({ data }: { data: Recommendation[] }) => { - if (!isDesktopViewport) { - return getSearchMidArticleOJ({ data, searchVariant }); - } - return getWsojComponent({ data }); - }, + wsoj: ({ data }: { data: Recommendation[] }) => + !isDesktopViewport + ? getSearchMidArticleOJ({ data, searchVariant }) + : getWsojComponent({ data }), disclaimer: DisclaimerWithPaddingOverride, podcastPromo: getPodcastPromoComponent(podcastPromoEnabled), ...(showContinueReadingButton && { From ebcac792ee78db008e0ff15fb764e836fea9796c Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 15:49:20 +0100 Subject: [PATCH 32/40] fallback to most read midarticle oj when location based oj is not present --- src/app/pages/ArticlePage/ArticlePage.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index 852f94b09da..e0b994c3f6b 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -303,6 +303,14 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { ?.split(':') ?.includes('topcat'); + const showCountryCuration = Boolean( + !isAmp && + !isLite && + !isApp && + countryCurationEnabled && + pageData?.countryCuration?.summaries?.length, + ); + const getSearchMidArticleOJ = ({ data, experimentProps, @@ -330,10 +338,12 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { return ; case 'locationBasedOJ': - return ( + return showCountryCuration ? (
+ ) : ( + ); default: @@ -478,14 +488,6 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { articleVideoCurationEnabled, ); - const showCountryCuration = Boolean( - !isAmp && - !isLite && - !isApp && - countryCurationEnabled && - pageData?.countryCuration?.summaries?.length, - ); - const topStoriesContent = pageData?.secondaryColumn?.topStories; const featuresContent = pageData?.secondaryColumn?.features; From 62a816c2322ea04dba17e8ddb7c018922f69d449 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 16:51:41 +0100 Subject: [PATCH 33/40] update types to be videoOJ to cater for video curations also --- .../ArticlePage/searchReferrerComponentOrder.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts index d7bfda5b584..f7dae5184d5 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.ts @@ -1,7 +1,7 @@ export type OJComponentKey = | 'mostRead' | 'topicDiscovery' - | 'pvCarousel' + | 'videoOJ' | 'relatedContent' | 'topStories' | 'featuredArticles' @@ -22,7 +22,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'locationBasedOJ', 'topStories', 'featuredArticles', - 'pvCarousel', + 'videoOJ', 'mostRead', ], variant_2_recommended: [ @@ -30,7 +30,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'mostRead', 'relatedContent', 'featuredArticles', - 'pvCarousel', + 'videoOJ', 'topStories', 'locationBasedOJ', ], @@ -39,7 +39,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'locationBasedOJ', 'relatedContent', 'mostRead', - 'pvCarousel', + 'videoOJ', 'topStories', 'featuredArticles', ], @@ -48,14 +48,14 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'locationBasedOJ', 'topStories', 'featuredArticles', - 'pvCarousel', + 'videoOJ', 'mostRead', ], variant_5_recommended_mid: [ 'mostRead', 'relatedContent', 'featuredArticles', - 'pvCarousel', + 'videoOJ', 'topStories', 'locationBasedOJ', ], @@ -63,7 +63,7 @@ export const SEARCH_COMPONENT_ORDER: Record = { 'topicDiscovery', 'relatedContent', 'mostRead', - 'pvCarousel', + 'videoOJ', 'topStories', 'featuredArticles', ], From 42abb8403909da50c959f6bb64367a3385177edd Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 16:52:17 +0100 Subject: [PATCH 34/40] add video oj helper to cater for both pvCarousel and video curations --- src/app/pages/ArticlePage/ArticlePage.tsx | 41 +++++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index e0b994c3f6b..b825b7a4990 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -523,6 +523,38 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { const topicDiscoverySlot = getTopicDiscoverySlot(); + const getVideoOJComponent = (): ReactNode => { + if (showPortraitVideoCarousel) { + return ( + + ); + } + if (showMediaCuration) { + return ( +
+
+ +
+
+ ); + } + return null; + }; + const mobileOJComponents: Record = { mostRead: !isApp && !isPGL ? ( @@ -537,12 +569,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { ) : null, topicDiscovery: topicDiscoverySlot, relatedContent: , - pvCarousel: showPortraitVideoCarousel ? ( - - ) : null, + videoOJ: getVideoOJComponent(), topStories: !isApp && !isPGL && topStoriesContent ? (
{ /> )} {!mobileOJOrder && } - {showMediaCuration && ( + {!mobileOJOrder && showMediaCuration && (
Date: Thu, 30 Jul 2026 17:40:13 +0100 Subject: [PATCH 35/40] rendered mobile variant tests wip copilot --- src/app/pages/ArticlePage/ArticlePage.tsx | 2 +- .../searchReferrerComponentOrder.test.ts | 132 +++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index b825b7a4990..fcd6a46ed67 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -728,7 +728,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
{mobileOJOrder && ( -
+
{mobileOJOrder.map(key => ( {mobileOJComponents[key]} ))} diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index e993215dd8a..cb989c5ae40 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -1,17 +1,80 @@ +import { createElement } from 'react'; import { renderHook, waitFor, act } from '@testing-library/react'; import onClient from '#app/lib/utilities/onClient'; import { GROUP_3_MAX_WIDTH_BP } from '#app/components/ThemeProvider/mediaQueries'; +import { articleDataNews } from '#pages/ArticlePage/fixtureData'; +import { Article } from '#app/models/types/optimo'; +import { + render, + screen, +} from '#app/components/react-testing-library-with-providers'; +import ArticlePage from './ArticlePage'; import useMobileOJComponentOrder from './useMobileOJComponentOrder'; import { + OJComponentKey, SEARCH_COMPONENT_ORDER, SEARCH_MID_ARTICLE_COMPONENT, SearchVariant, } from './searchReferrerComponentOrder'; +jest.mock('#app/components/ThemeProvider'); +jest.mock('#app/components/ChartbeatAnalytics', () => { + const ChartbeatAnalytics = () => null; + return ChartbeatAnalytics; +}); +jest.mock('#app/components/ATIAnalytics', () => { + const ATIAnalytics = () => null; + return ATIAnalytics; +}); + +/* eslint-disable global-require, @typescript-eslint/no-var-requires */ +jest.mock( + '#app/components/MostRead', + () => () => + require('react').createElement('div', { 'data-testid': 'most-read' }), +); +jest.mock( + '#app/components/TopicDiscovery', + () => () => + require('react').createElement('div', { 'data-testid': 'topic-discovery' }), +); +jest.mock( + '#app/components/RelatedContentSection', + () => () => + require('react').createElement('div', { 'data-testid': 'related-content' }), +); +jest.mock( + '#app/components/PortraitVideoCarousel', + () => () => + require('react').createElement('div', { + 'data-testid': 'portrait-video-carousel', + }), +); +jest.mock( + '#app/components/LocationBasedTopicOJ', + () => () => + require('react').createElement('div', { + 'data-testid': 'location-based-topic-oj', + }), +); +/* eslint-enable global-require, @typescript-eslint/no-var-requires */ + +jest.mock('#app/components/OptimizelyPageMetrics'); +jest.mock('#app/hooks/useScrollDepthTracker', () => jest.fn(() => null)); +jest.mock('#hooks/useMediaQuery', () => jest.fn()); +jest.mock('#app/hooks/useOptimizelyVariation', () => ({ + __esModule: true, + ...jest.requireActual('#app/hooks/useOptimizelyVariation'), + default: jest.fn(), +})); jest.mock( '#app/legacy/containers/PageHandlers/withOptimizelyProvider/userAttributes', ); -jest.mock('#app/lib/utilities/onClient'); +jest.mock('#app/lib/utilities/onClient', () => ({ + __esModule: true, + default: jest.fn(), + onClient: jest.fn(() => true), +})); const mockOnClient = onClient as jest.MockedFunction; @@ -85,6 +148,73 @@ describe('useMobileOJComponentOrder', () => { const { result } = renderHook(() => useMobileOJComponentOrder(null)); expect(result.current).toBeNull(); }); + + describe('rendered mobile variant', () => { + const OJ_TEST_IDS: Record = { + mostRead: 'most-read', + topicDiscovery: 'topic-discovery', + pvCarousel: 'portrait-video-carousel', + relatedContent: 'related-content', + topStories: 'top-stories', + featuredArticles: 'features', + locationBasedOJ: 'location-based-topic-oj', + }; + + const renderVariant = (variant: SearchVariant) => { + window.history.replaceState(null, '', `?debugVariant=${variant}`); + + const pageData = { + ...articleDataNews, + countryCuration: { summaries: [{ title: 'Country' }] }, + portraitVideoItems: { portraitVideo: { blocks: [{}] } }, + secondaryColumn: { topStories: [], features: [] }, + } as unknown as Article; + + return render(createElement(ArticlePage, { pageData }), { + service: 'news', + toggles: { + topicDiscovery: { enabled: true }, + articlePortraitVideo: { enabled: true }, + locationTopicCuration: { enabled: true }, + }, + }); + }; + + afterEach(() => { + window.history.replaceState(null, '', '/'); + }); + + it.each([ + 'variant_1_related', + 'variant_2_recommended', + 'variant_3_hybrid', + 'variant_4_related_mid', + 'variant_5_recommended_mid', + 'variant_6_hybrid_mid', + ] as SearchVariant[])( + 'renders the OJ components in the configured order for %s', + async searchVariant => { + renderVariant(searchVariant); + + const orderedContainer = await screen.findByTestId( + 'mobile-oj-container', + ); + + const ojTestIds = Object.values(OJ_TEST_IDS); + const renderedOrder = Array.from( + orderedContainer.querySelectorAll('[data-testid]'), + ) + .map(element => element.getAttribute('data-testid')) + .filter(testId => ojTestIds.includes(testId as string)); + + const expectedOrder = SEARCH_COMPONENT_ORDER[searchVariant].map( + key => OJ_TEST_IDS[key], + ); + + expect(renderedOrder).toEqual(expectedOrder); + }, + ); + }); }); describe('Mid-article component ordering', () => { From 05de34cd701021854bfbccb9d7bab97222a8e3d7 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 17:46:49 +0100 Subject: [PATCH 36/40] fallback for related content --- src/app/pages/ArticlePage/ArticlePage.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx index b825b7a4990..8e6eeedf1db 100644 --- a/src/app/pages/ArticlePage/ArticlePage.tsx +++ b/src/app/pages/ArticlePage/ArticlePage.tsx @@ -311,6 +311,10 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { pageData?.countryCuration?.summaries?.length, ); + const hasRelatedContent = blocks.some( + block => block.type === 'relatedContent', + ); + const getSearchMidArticleOJ = ({ data, experimentProps, @@ -328,10 +332,12 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => { return ; case 'relatedContent': - return ( + return hasRelatedContent ? (
+ ) : ( + ); case 'topicDiscovery': From 44c9775e87beb237b9666f4fb6365c214971d946 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Thu, 30 Jul 2026 17:54:36 +0100 Subject: [PATCH 37/40] temporarily skip wip tests just added --- src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index cb989c5ae40..95922a392d5 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -149,7 +149,7 @@ describe('useMobileOJComponentOrder', () => { expect(result.current).toBeNull(); }); - describe('rendered mobile variant', () => { + describe.skip('rendered mobile variant', () => { const OJ_TEST_IDS: Record = { mostRead: 'most-read', topicDiscovery: 'topic-discovery', From ca4e4fc505e7f8dcaac1cf5c1b160419f27ab4c4 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 31 Jul 2026 10:04:25 +0100 Subject: [PATCH 38/40] update OJ name --- .../pages/ArticlePage/searchReferrerComponentOrder.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index 95922a392d5..13b794061dd 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -149,11 +149,11 @@ describe('useMobileOJComponentOrder', () => { expect(result.current).toBeNull(); }); - describe.skip('rendered mobile variant', () => { + describe('rendered mobile variant', () => { const OJ_TEST_IDS: Record = { mostRead: 'most-read', topicDiscovery: 'topic-discovery', - pvCarousel: 'portrait-video-carousel', + videoOJ: 'portrait-video-carousel', relatedContent: 'related-content', topStories: 'top-stories', featuredArticles: 'features', From 3ce49889c6da11dfa8bec66b40d9c298d511ca97 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 31 Jul 2026 10:12:41 +0100 Subject: [PATCH 39/40] test change --- .../searchReferrerComponentOrder.test.ts | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index 13b794061dd..bd5f8e291a0 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -150,6 +150,14 @@ describe('useMobileOJComponentOrder', () => { }); describe('rendered mobile variant', () => { + beforeEach(() => { + matchMediaMock.mockReturnValue({ + matches: true, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }); + }); + const OJ_TEST_IDS: Record = { mostRead: 'most-read', topicDiscovery: 'topic-discovery', @@ -184,6 +192,13 @@ describe('useMobileOJComponentOrder', () => { window.history.replaceState(null, '', '/'); }); + const getRenderedOJOrder = (container: HTMLElement) => { + const ojTestIds = Object.values(OJ_TEST_IDS); + return Array.from(container.querySelectorAll('[data-testid]')) + .map(element => element.getAttribute('data-testid')) + .filter(testId => ojTestIds.includes(testId as string)); + }; + it.each([ 'variant_1_related', 'variant_2_recommended', @@ -196,22 +211,13 @@ describe('useMobileOJComponentOrder', () => { async searchVariant => { renderVariant(searchVariant); - const orderedContainer = await screen.findByTestId( - 'mobile-oj-container', - ); - - const ojTestIds = Object.values(OJ_TEST_IDS); - const renderedOrder = Array.from( - orderedContainer.querySelectorAll('[data-testid]'), - ) - .map(element => element.getAttribute('data-testid')) - .filter(testId => ojTestIds.includes(testId as string)); + const container = await screen.findByTestId('mobile-oj-container'); const expectedOrder = SEARCH_COMPONENT_ORDER[searchVariant].map( key => OJ_TEST_IDS[key], ); - expect(renderedOrder).toEqual(expectedOrder); + expect(getRenderedOJOrder(container)).toEqual(expectedOrder); }, ); }); From 02c8712b3e65ce041a0b169afd85ed88e5cd7fb5 Mon Sep 17 00:00:00 2001 From: Louise Archibald Date: Fri, 31 Jul 2026 10:16:24 +0100 Subject: [PATCH 40/40] update describe --- src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts index bd5f8e291a0..dbe3bd854d4 100644 --- a/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts +++ b/src/app/pages/ArticlePage/searchReferrerComponentOrder.test.ts @@ -149,7 +149,7 @@ describe('useMobileOJComponentOrder', () => { expect(result.current).toBeNull(); }); - describe('rendered mobile variant', () => { + describe('Renderered mobile OJ components', () => { beforeEach(() => { matchMediaMock.mockReturnValue({ matches: true,