diff --git a/src/app/components/MostRead/index.tsx b/src/app/components/MostRead/index.tsx
index 0460859b1ce..637ef4944ad 100644
--- a/src/app/components/MostRead/index.tsx
+++ b/src/app/components/MostRead/index.tsx
@@ -33,6 +33,11 @@ interface MostReadProps {
headingBackgroundColour?: string;
className?: string;
eventTrackingData?: EventTrackingData;
+ experimentProps?: {
+ sendOptimizelyEvents: boolean;
+ experimentName: string;
+ experimentVariant: string;
+ };
}
// We render amp on ONLY STY, CSP and ARTICLE pages using amp-list.
@@ -104,6 +109,7 @@ const MostRead = ({
mobileDivider = false,
headingBackgroundColour = WHITE,
className = '',
+ experimentProps,
eventTrackingData,
}: MostReadProps) => {
const { isAmp, pageType, variant } = use(RequestContext);
@@ -129,6 +135,14 @@ const MostRead = ({
variant,
isBff,
});
+ // Use the eventTrackingData prop if provided, otherwise construct a minimal default
+ // this is because most read is used on both the article page and the home page
+ // on the home page, we have an object containing more information passed in
+ // on the article page we don't have all of these pieces of data (do we need it?), but we do need the optimizely props
+ const trackingData = eventTrackingData || {
+ componentName: 'most-read',
+ ...(experimentProps && experimentProps),
+ };
return isAmp ? (
);
};
diff --git a/src/app/components/Recommendations/RecommendationsItem/index.tsx b/src/app/components/Recommendations/RecommendationsItem/index.tsx
index edc68d3c725..7e4e0edc55c 100644
--- a/src/app/components/Recommendations/RecommendationsItem/index.tsx
+++ b/src/app/components/Recommendations/RecommendationsItem/index.tsx
@@ -3,17 +3,16 @@ import { use } from 'react';
import { RequestContext } from '#contexts/RequestContext';
import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler';
import { Recommendation } from '#app/models/types/onwardJourney';
+import { EventTrackingData } from '#app/lib/analyticsUtils/types';
import RecommendationsImage from '../RecommendationsPromoImage';
import styles from './index.styles';
-const eventTrackingData = {
- componentName: 'midarticle-mostread',
-};
-
const RecommendationsItem = ({
recommendation,
+ eventTrackingData,
}: {
recommendation: Recommendation | null;
+ eventTrackingData?: EventTrackingData;
}) => {
const { isLite } = use(RequestContext);
diff --git a/src/app/components/Recommendations/fixtures.ts b/src/app/components/Recommendations/fixtures.ts
index 8ee52da9b4b..9d9d61d5178 100644
--- a/src/app/components/Recommendations/fixtures.ts
+++ b/src/app/components/Recommendations/fixtures.ts
@@ -1,4 +1,4 @@
-export default [
+export const recommendationsFixtures = [
{
id: '123',
title: 'Recommendation One',
@@ -26,3 +26,729 @@ export default [
href: 'https://www.bbc.co.uk',
},
];
+
+export const topStoriesContentFixture = [
+ {
+ id: 'urn:bbc:ares::article:c3d05pkxee3o',
+ locators: {
+ canonicalUrl: 'https://www.bbc.com/mundo/articles/c3d05pkxee3o',
+ },
+ headlines: {
+ promoHeadline: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: '"Si mi hermana no estuviera allá, con gusto te hablaría": el miedo de los venezolanos en EE.UU. a opinar sobre la amenaza militar de Trump ',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: '"Si mi hermana no estuviera allá, con gusto te hablaría": el miedo de los venezolanos en EE.UU. a opinar sobre la amenaza militar de Trump ',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ images: {
+ defaultPromoImage: {
+ blocks: [
+ {
+ type: 'altText',
+ model: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: 'alt text',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: 'alt text',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ type: 'rawImage',
+ model: {
+ width: 880,
+ height: 495,
+ locator: 'bbf5/live/b4da0380-cc97-11f0-81ab-2d2febf9fb8f.jpg',
+ originCode: 'cpsprodpb',
+ copyrightHolder: 'Getty Images',
+ suitableForSyndication: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'urn:bbc:ares::article:cgkemkrz18po',
+ locators: {
+ canonicalUrl: 'https://www.bbc.com/mundo/articles/cgkemkrz18po',
+ },
+ headlines: {
+ promoHeadline: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: '"EE.UU. pretende apoderarse de las vastas reservas de petróleo de Venezuela": la carta que Maduro le envió a la OPEP denunciando la amenaza militar de Washington',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: '"EE.UU. pretende apoderarse de las vastas reservas de petróleo de Venezuela": la carta que Maduro le envió a la OPEP denunciando la amenaza militar de Washington',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ images: {
+ defaultPromoImage: {
+ blocks: [
+ {
+ type: 'altText',
+ model: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: '"EE.UU. pretende apoderarse de las vastas reservas de petróleo de Venezuela": la carta que Maduro le envió a la OPEP denunciando la amenaza militar de Washington',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: '"EE.UU. pretende apoderarse de las vastas reservas de petróleo de Venezuela": la carta que Maduro le envió a la OPEP denunciando la amenaza militar de Washington',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ type: 'rawImage',
+ model: {
+ width: 1008,
+ height: 567,
+ locator: '9a5d/live/4c52d9c0-cea1-11f0-a58d-8f327674649c.jpg',
+ originCode: 'cpsprodpb',
+ copyrightHolder: 'Getty Images',
+ suitableForSyndication: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+];
+
+export const featuresContentFixture = [
+ {
+ id: 'urn:bbc:ares::article:cy5glkkkgwko',
+ locators: {
+ canonicalUrl: 'https://www.bbc.com/mundo/articles/cy5glkkkgwko',
+ },
+ headlines: {
+ promoHeadline: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: 'Por qué Honduras sigue siendo el país más pobre de Centroamérica pese a una década de crecimiento',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: 'Por qué Honduras sigue siendo el país más pobre de Centroamérica pese a una década de crecimiento',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ images: {
+ defaultPromoImage: {
+ blocks: [
+ {
+ type: 'altText',
+ model: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: 'Mujer cocinando en Honduras. Maria de la Cruz Rodriguez, 80 en El Guanabano.',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: 'Mujer cocinando en Honduras. Maria de la Cruz Rodriguez, 80 en El Guanabano.',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ type: 'rawImage',
+ model: {
+ width: 792,
+ height: 446,
+ locator: '7506/live/1cdb8560-c984-11f0-9e01-e9414f337bc1.jpg',
+ originCode: 'cpsprodpb',
+ copyrightHolder: 'AFP via Getty Images',
+ suitableForSyndication: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+ {
+ id: 'urn:bbc:ares::article:cp3dk117nnpo',
+ locators: {
+ canonicalUrl: 'https://www.bbc.com/mundo/articles/cp3dk117nnpo',
+ },
+ headlines: {
+ promoHeadline: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: 'Por qué si todos viviéramos como Uruguay se frenaría la "estafa piramidal" que estamos haciendo con los recursos del planeta',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: 'Por qué si todos viviéramos como Uruguay se frenaría la "estafa piramidal" que estamos haciendo con los recursos del planeta',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ images: {
+ defaultPromoImage: {
+ blocks: [
+ {
+ type: 'altText',
+ model: {
+ blocks: [
+ {
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ type: 'paragraph',
+ model: {
+ text: 'Persona andando en bicicleta en Maldonado, Uruguay',
+ blocks: [
+ {
+ type: 'fragment',
+ model: {
+ text: 'Persona andando en bicicleta en Maldonado, Uruguay',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ type: 'rawImage',
+ model: {
+ width: 2121,
+ height: 1193,
+ locator: 'a191/live/b2dd7770-c93a-11f0-a414-099360380435.jpg',
+ originCode: 'cpsprodpb',
+ copyrightHolder: 'Getty Images',
+ suitableForSyndication: true,
+ },
+ },
+ ],
+ },
+ },
+ },
+];
+
+export const relatedContentBlocksFixture = [
+ {
+ id: 'ddec8c0b',
+ type: 'headline',
+ model: {
+ blocks: [
+ {
+ id: '2e85aec0',
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ id: '59852a1a',
+ type: 'paragraph',
+ model: {
+ text: 'Lo que el hallazgo de una escultura de 12.000 años de antigüedad revela sobre el ego de nuestros ancestros',
+ blocks: [
+ {
+ id: '01e6cbaf',
+ type: 'fragment',
+ model: {
+ text: 'Lo que el hallazgo de una escultura de 12.000 años de antigüedad revela sobre el ego de nuestros ancestros',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: '69ce9993',
+ type: 'image',
+ model: {
+ blocks: [
+ {
+ id: '7b3a255a',
+ type: 'caption',
+ model: {
+ blocks: [
+ {
+ id: '38afe3fe',
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ id: '52e2ffd7',
+ type: 'paragraph',
+ model: {
+ text: 'El relieve hallado en el yacimiento arqueológico de Karahantepe muestra afilados rasgos faciales, una nariz pequeña y ojos hundidos. ',
+ blocks: [
+ {
+ id: '87257636',
+ type: 'fragment',
+ model: {
+ text: 'El relieve hallado en el yacimiento arqueológico de Karahantepe muestra afilados rasgos faciales, una nariz pequeña y ojos hundidos. ',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'ae4f1f4c',
+ type: 'altText',
+ model: {
+ blocks: [
+ {
+ id: '561aa37b',
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ id: 'fb8fd0a3',
+ type: 'paragraph',
+ model: {
+ text: 'Un pilar con forma de T y un rostro tallado en la piedra. ',
+ blocks: [
+ {
+ id: 'de09569d',
+ type: 'fragment',
+ model: {
+ text: 'Un pilar con forma de T y un rostro tallado en la piedra. ',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'ceec0366',
+ type: 'rawImage',
+ model: {
+ width: 957,
+ height: 539,
+ locator: '68f7/live/44316c80-b974-11f0-a949-2fea9e2120e8.png',
+ originCode: 'cpsprodpb',
+ copyrightHolder: 'Ministerio de Cultura y Turismo de Turquía',
+ suitableForSyndication: true,
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: '9a2e430f',
+ type: 'timestamp',
+ model: {
+ firstPublished: 1762426601655,
+ lastPublished: 1762426601655,
+ },
+ },
+ {
+ id: 'db7f02c5',
+ type: 'relatedContent',
+ model: {
+ blocks: [
+ {
+ id: 'd3f9a500',
+ type: 'link',
+ model: {
+ locator: 'urn:bbc:optimo:asset:c629n28z490o',
+ blocks: [
+ {
+ id: '2fee3a9a',
+ type: 'image',
+ model: {
+ blocks: [
+ {
+ id: '70295b4c',
+ type: 'altText',
+ model: {
+ blocks: [
+ {
+ id: '1b5772c0',
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ id: 'b081b220',
+ type: 'paragraph',
+ model: {
+ text: "Visitors walk past Tutankhamun's gold-and-turquoise funerary mask on display at the Egyptian Museum in Cairo, on 2 December 2024.",
+ blocks: [
+ {
+ id: 'e5623606',
+ type: 'fragment',
+ model: {
+ text: "Visitors walk past Tutankhamun's gold-and-turquoise funerary mask on display at the Egyptian Museum in Cairo, on 2 December 2024.",
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'a560788e',
+ type: 'rawImage',
+ model: {
+ width: 1441,
+ height: 810,
+ locator:
+ 'ff8d/live/9c945880-b4ea-11f0-b2a1-6f537f66f9aa.jpg',
+ originCode: 'cpsprodpb',
+ copyrightHolder:
+ 'Photo by AMIR MAKAR/AFP via Getty Images',
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'fa05c79c',
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ id: '73131948',
+ type: 'paragraph',
+ model: {
+ text: 'Gran Museo Egipcio, la gigantesca obra que exhibe los secretos de Tutankamón y que se inaugura tras décadas de trabajos',
+ blocks: [
+ {
+ id: '1200bfda',
+ type: 'urlLink',
+ model: {
+ text: 'Gran Museo Egipcio, la gigantesca obra que exhibe los secretos de Tutankamón y que se inaugura tras décadas de trabajos',
+ locator:
+ 'https://www.bbc.com/mundo/articles/c629n28z490o',
+ blocks: [
+ {
+ id: '0d08fb26',
+ type: 'fragment',
+ model: {
+ text: 'Gran Museo Egipcio, la gigantesca obra que exhibe los secretos de Tutankamón y que se inaugura tras décadas de trabajos',
+ attributes: [],
+ },
+ },
+ ],
+ isExternal: false,
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'd3f9a501',
+ type: 'link',
+ model: {
+ locator: 'urn:bbc:optimo:asset:c629n28z491p',
+ blocks: [
+ {
+ id: '2fee3a9b',
+ type: 'image',
+ model: {
+ blocks: [
+ {
+ id: '70295b4d',
+ type: 'altText',
+ model: {
+ blocks: [
+ {
+ id: '1b5772c1',
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ id: 'b081b221',
+ type: 'paragraph',
+ model: {
+ text: 'Second related content alt text.',
+ blocks: [
+ {
+ id: 'e5623607',
+ type: 'fragment',
+ model: {
+ text: 'Second related content alt text.',
+ attributes: [],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'a560788f',
+ type: 'rawImage',
+ model: {
+ width: 1200,
+ height: 800,
+ locator: 'ff8d/live/second-related-content.jpg',
+ originCode: 'cpsprodpb',
+ copyrightHolder: 'Photo by SECOND/AFP via Getty Images',
+ },
+ },
+ ],
+ },
+ },
+ {
+ id: 'fa05c79d',
+ type: 'text',
+ model: {
+ blocks: [
+ {
+ id: '73131949',
+ type: 'paragraph',
+ model: {
+ text: 'Second related content title',
+ blocks: [
+ {
+ id: '1200bfdb',
+ type: 'urlLink',
+ model: {
+ text: 'Second related content title',
+ locator:
+ 'https://www.bbc.com/mundo/articles/c629n28z491p',
+ blocks: [
+ {
+ id: '0d08fb27',
+ type: 'fragment',
+ model: {
+ text: 'Second related content title',
+ attributes: [],
+ },
+ },
+ ],
+ isExternal: false,
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+];
+
+export const mostReadDataFixture = [
+ {
+ id: 'urn:bbc:optimo:asset:c5yjx8yw950o',
+ title:
+ 'El conteo de las elecciones presidenciales en Honduras muestra una reñida contienda entre el conservador Asfura, apoyado por Trump, y el centrista Nasralla',
+ href: 'https://www.bbc.com/mundo/articles/c5yjx8yw950o',
+ image: {
+ width: 1024,
+ height: 683,
+ altText:
+ 'Tarjetón electoral con las imágenes de Nasry Asfura y Salvador Nasralla.',
+ copyrightHolder: 'Getty Images',
+ originCode: 'cpsprodpb',
+ locator: 'd341/live/fbf192f0-ceb9-11f0-a892-01d657345866.jpg',
+ },
+ },
+ {
+ id: 'urn:bbc:optimo:asset:cx2ewd5yen7o',
+ title:
+ '"Estoy desolado, tenía tantos vecinos y amigos allí": los dramáticos testimonios de los residentes de las torres de apartamentos de Hong Kong donde un enorme incendio dejó decenas de muertos',
+ href: 'https://www.bbc.com/mundo/articles/cx2ewd5yen7o',
+ image: {
+ width: 976,
+ height: 549,
+ altText: 'Harry Cheung',
+ copyrightHolder: 'Reuters',
+ originCode: 'cpsprodpb',
+ locator: '4862/live/2ee88820-cb87-11f0-8c06-f5d460985095.jpg',
+ },
+ },
+ {
+ id: 'urn:bbc:optimo:asset:c709elze6ywo',
+ title:
+ 'Mueren más de 500 personas tras las devastadoras inundaciones registradas en Indonesia',
+ href: 'https://www.bbc.com/mundo/articles/c709elze6ywo',
+ image: {
+ width: 1017,
+ height: 572,
+ altText:
+ 'Rescatistas cargan a un hombre de una lancha inflable a una ribera enlodada ',
+ copyrightHolder: 'AFP via Getty Images',
+ originCode: 'cpsprodpb',
+ locator: '63c8/live/ec2e8510-ce07-11f0-8c06-f5d460985095.jpg',
+ },
+ },
+ {
+ id: 'urn:bbc:optimo:asset:cpd6l44qgwqo',
+ title:
+ 'Qué son las ingeniosas cajas de Ward y cómo transformaron la economía mundial',
+ href: 'https://www.bbc.com/mundo/articles/cpd6l44qgwqo',
+ image: {
+ width: 1024,
+ height: 576,
+ altText:
+ 'Una caja triangular de madera y vidrio, en este caso también con varas de metal, en medio de follaje',
+ copyrightHolder: 'Getty Images',
+ originCode: 'cpsprodpb',
+ locator: '7c68/live/3e246ba0-c97b-11f0-9a12-2dc0b71304e7.jpg',
+ },
+ },
+];
+
+export default recommendationsFixtures;
diff --git a/src/app/components/Recommendations/helpers/index.tsx b/src/app/components/Recommendations/helpers/index.tsx
new file mode 100644
index 00000000000..13c1e2b6378
--- /dev/null
+++ b/src/app/components/Recommendations/helpers/index.tsx
@@ -0,0 +1,290 @@
+import pathOr from 'ramda/src/pathOr';
+import pathEq from 'ramda/src/pathEq';
+import tail from 'ramda/src/tail';
+import slice from 'ramda/src/slice';
+import last from 'ramda/src/last';
+import filter from 'ramda/src/filter';
+import pipe from 'ramda/src/pipe';
+import { OptimoBlock } from '#app/models/types/optimo';
+import { Recommendation } from '#app/models/types/onwardJourney';
+import { TopStoryItem } from '#app/pages/ArticlePage/PagePromoSections/TopStoriesSection/types';
+
+type Features = {
+ id: string;
+ locators: {
+ canonicalUrl?: string;
+ };
+ headlines: {
+ promoHeadline?: {
+ blocks: Array<{
+ model: {
+ blocks: Array<{
+ model: {
+ text: string;
+ blocks?: Array<{
+ model: {
+ text: string;
+ attributes?: unknown[];
+ };
+ }>;
+ };
+ }>;
+ };
+ }>;
+ };
+ seoHeadline?: string;
+ };
+ images?: {
+ defaultPromoImage?: {
+ blocks?: any[];
+ };
+ };
+};
+
+// --- Shared utilities for extracting image data from defaultPromoImage ---
+
+const getAltTextFromDefaultPromoImage = (defaultPromoImage?: {
+ blocks?: any[];
+}) => {
+ const altTextBlock = defaultPromoImage?.blocks?.find(
+ block => block.type === 'altText',
+ );
+ return (
+ altTextBlock?.model?.blocks?.[0]?.model?.blocks?.[0]?.model?.blocks?.[0]
+ ?.model?.text || ''
+ );
+};
+
+const getRawImageBlock = (defaultPromoImage?: { blocks?: any[] }) =>
+ defaultPromoImage?.blocks?.find(block => block.type === 'rawImage')?.model ??
+ {};
+
+// --- Related Content (Optimo) ---
+
+export const getRelatedContentData = (blocks: OptimoBlock[]) => {
+ const BLOCKS_TO_IGNORE = ['wsoj', 'mpu', 'continueReading'];
+ const removeCustomBlocks = pipe(
+ filter((block: OptimoBlock) => !BLOCKS_TO_IGNORE.includes(block.type)),
+ last,
+ );
+ const relatedContentBlock = removeCustomBlocks(blocks);
+ if (
+ !relatedContentBlock ||
+ !pathEq('relatedContent', ['type'], relatedContentBlock)
+ ) {
+ return [];
+ }
+ const items = pathOr([], ['model', 'blocks'], relatedContentBlock);
+ const hasCustomTitle =
+ pathEq('title', [0, 'type'], items) &&
+ pathOr(
+ '',
+ [0, 'model', 'blocks', 0, 'model', 'blocks', 0, 'model', 'text'],
+ items,
+ );
+ const storyPromoItems = hasCustomTitle ? tail(items) : items;
+ return slice(0, 4, storyPromoItems);
+};
+
+export const getHeadlineFromOptimoBlock = (block: any) => {
+ const headlineFirst = pathOr(
+ '',
+ ['model', 'blocks', 0, 'model', 'blocks', 0, 'model', 'text'],
+ block,
+ );
+ const headlineSecond = pathOr(
+ '',
+ ['model', 'blocks', 1, 'model', 'blocks', 0, 'model', 'text'],
+ block,
+ );
+ return headlineFirst || headlineSecond;
+};
+
+export const getHrefFromOptimoBlock = (block: any) => {
+ const assetUriFirst = pathOr(
+ '',
+ [
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'locator',
+ ],
+ block,
+ );
+ const assetUriSecond = pathOr(
+ '',
+ [
+ 'model',
+ 'blocks',
+ 1,
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'locator',
+ ],
+ block,
+ );
+ return assetUriFirst || assetUriSecond;
+};
+
+export const getAltTextFromOptimoBlock = (block: any) =>
+ pathOr(
+ '',
+ [
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'blocks',
+ 0,
+ 'model',
+ 'text',
+ ],
+ block,
+ );
+
+export const getImageFromOptimoBlock = (block: any) => {
+ const imageBlock = block?.model?.blocks?.find((b: any) => b.type === 'image');
+ const rawImageBlock = imageBlock?.model?.blocks?.find(
+ (b: any) => b.type === 'rawImage',
+ );
+ return {
+ locator: rawImageBlock?.model?.locator ?? '',
+ altText: getAltTextFromOptimoBlock(block),
+ width: rawImageBlock?.model?.width ?? 0,
+ height: rawImageBlock?.model?.height ?? 0,
+ copyrightHolder: rawImageBlock?.model?.copyrightHolder ?? '',
+ originCode: rawImageBlock?.model?.originCode ?? '',
+ };
+};
+
+export const mapOptimoBlockToRecommendation = (block: any): Recommendation => ({
+ id: block.id,
+ title: getHeadlineFromOptimoBlock(block),
+ href: getHrefFromOptimoBlock(block),
+ image: getImageFromOptimoBlock(block),
+});
+
+// --- Features ---
+
+export const mapFeaturesToRecommendation = (featuresContent: Features) => {
+ const promoHeadlineText =
+ featuresContent.headlines?.promoHeadline?.blocks?.[0]?.model?.blocks?.[0]
+ ?.model?.text ??
+ featuresContent.headlines?.seoHeadline ??
+ '';
+
+ const defaultPromoImage = featuresContent.images?.defaultPromoImage;
+ const rawImage = getRawImageBlock(defaultPromoImage);
+
+ const image = {
+ locator: rawImage.locator ?? '',
+ altText:
+ getAltTextFromDefaultPromoImage(defaultPromoImage) || promoHeadlineText,
+ width: rawImage.width ?? 0,
+ height: rawImage.height ?? 0,
+ copyrightHolder: rawImage.copyrightHolder ?? '',
+ originCode: rawImage.originCode ?? '',
+ };
+
+ return {
+ id: featuresContent.id,
+ title: promoHeadlineText,
+ href: featuresContent.locators?.canonicalUrl ?? '',
+ image,
+ };
+};
+
+// --- Top Stories ---
+
+const getTopStoryHeadline = (item: TopStoryItem) => {
+ const overtypedHeadline = item?.headlines?.overtyped ?? '';
+ const mainHeadline = item?.headlines?.headline ?? '';
+ const promoHeadlineBlocks = Array.isArray(
+ item?.headlines?.promoHeadline?.blocks,
+ )
+ ? item.headlines.promoHeadline.blocks
+ : [];
+ const promoHeadlineText =
+ promoHeadlineBlocks?.[0]?.model?.blocks?.[0]?.model?.text ?? '';
+
+ const name = item?.name ?? '';
+ return overtypedHeadline || mainHeadline || promoHeadlineText || name;
+};
+
+export const mapTopStoryToRecommendation = (item: TopStoryItem) => {
+ if (item.isLive) {
+ const title = item.headline ?? '';
+ const href = item.destinationUrl ?? '';
+ const imageData = item.image ?? {};
+ const imagePath =
+ typeof imageData.path === 'string'
+ ? imageData.path.replace(/^\/?cpsprodpb\//, '')
+ : '';
+ const image = {
+ locator: imagePath,
+ altText: imageData.altText ?? title,
+ width: imageData.width ?? 0,
+ height: imageData.height ?? 0,
+ copyrightHolder: imageData.copyrightHolder ?? '',
+ originCode: imageData.originCode ?? '',
+ };
+
+ return {
+ id: item.id,
+ title,
+ href,
+ image,
+ };
+ }
+
+ const title = getTopStoryHeadline(item);
+
+ // Prefer defaultPromoImage if present, otherwise use indexImage
+ const defaultPromoImage = item.images?.defaultPromoImage;
+ const rawImage = getRawImageBlock(defaultPromoImage);
+
+ const { indexImage } = item as any;
+
+ const image = indexImage
+ ? {
+ locator: indexImage.href ?? '',
+ altText: indexImage.altText ?? title,
+ width: indexImage.width ?? 0,
+ height: indexImage.height ?? 0,
+ copyrightHolder: indexImage.copyrightHolder ?? '',
+ originCode: indexImage.originCode ?? '',
+ }
+ : {
+ locator: rawImage.locator ?? '',
+ altText: getAltTextFromDefaultPromoImage(defaultPromoImage) || title,
+ width: rawImage.width ?? 0,
+ height: rawImage.height ?? 0,
+ copyrightHolder: rawImage.copyrightHolder ?? '',
+ originCode: rawImage.originCode ?? '',
+ };
+
+ return {
+ id: item.id,
+ title,
+ href: item.locators?.canonicalUrl ?? item.uri ?? '',
+ image,
+ };
+};
diff --git a/src/app/components/Recommendations/index.test.tsx b/src/app/components/Recommendations/index.test.tsx
index 4a184ae753c..13d04e481a4 100644
--- a/src/app/components/Recommendations/index.test.tsx
+++ b/src/app/components/Recommendations/index.test.tsx
@@ -1,6 +1,13 @@
import { render } from '#app/components/react-testing-library-with-providers';
+import { OptimoBlock } from '#app/models/types/optimo';
import Recommendations from '.';
-import recommendationsFixtures from './fixtures';
+import {
+ mostReadDataFixture,
+ recommendationsFixtures,
+ topStoriesContentFixture,
+ featuresContentFixture,
+ relatedContentBlocksFixture,
+} from './fixtures';
describe('Recommendations', () => {
it('should render a single recommendation', () => {
@@ -51,4 +58,263 @@ describe('Recommendations', () => {
const listEl = document.querySelector('ul');
expect(listEl).not.toBeInTheDocument();
});
+ it('should render top stories with title, href, image, and alt text when referrerVariant is adaptive_direct', () => {
+ render(
+ ,
+ {
+ service: 'mundo',
+ toggles: { midArticleOnwardJourney: { enabled: true } },
+ },
+ );
+ const sectionTitle = document.querySelector(
+ '[id="recommendations-heading"]',
+ );
+ expect(sectionTitle).toBeInTheDocument();
+ expect(sectionTitle?.textContent).toBe('Principales noticias');
+ const listItems = document.querySelectorAll('li[role="listitem"]');
+ expect(listItems).toHaveLength(topStoriesContentFixture.length);
+
+ listItems.forEach((item, index) => {
+ const story = topStoriesContentFixture[index];
+ const title =
+ story.headlines.promoHeadline.blocks[0].model.blocks[0].model.text;
+ const href = story.locators.canonicalUrl;
+
+ // Extract alt text from fixture
+ const altText =
+ story.images?.defaultPromoImage?.blocks?.find(
+ block => block.type === 'altText',
+ )?.model?.blocks?.[0]?.model?.blocks?.[0]?.model?.text || '';
+
+ const link = item.querySelector('a');
+ expect(link).toBeInTheDocument();
+ expect(link?.getAttribute('href')).toBe(href);
+ expect(link?.textContent).toContain(title);
+
+ const image = item.querySelector('img');
+ expect(image).toBeInTheDocument();
+ expect(image?.getAttribute('src')).toBeTruthy();
+ expect(image?.getAttribute('alt')).toBe(altText);
+ });
+ });
+ it('should render features with title, href, image, and alt text when referrerVariant is adaptive_social', () => {
+ render(
+ ,
+ {
+ service: 'mundo',
+ toggles: { midArticleOnwardJourney: { enabled: true } },
+ },
+ );
+
+ const sectionTitle = document.querySelector(
+ '[id="recommendations-heading"]',
+ );
+ expect(sectionTitle).toBeInTheDocument();
+ expect(sectionTitle?.textContent).toBe('No te lo pierdas');
+ const listItems = document.querySelectorAll('li[role="listitem"]');
+ expect(listItems).toHaveLength(featuresContentFixture.length);
+
+ listItems.forEach((item, index) => {
+ const story = featuresContentFixture[index];
+ const title =
+ story.headlines.promoHeadline.blocks[0].model.blocks[0].model.text;
+ const href = story.locators.canonicalUrl;
+
+ const altText =
+ story.images?.defaultPromoImage?.blocks?.find(
+ block => block.type === 'altText',
+ )?.model?.blocks?.[0]?.model?.blocks?.[0]?.model?.text || '';
+
+ const link = item.querySelector('a');
+ expect(link).toBeInTheDocument();
+ expect(link?.getAttribute('href')).toBe(href);
+ expect(link?.textContent).toContain(title);
+
+ const image = item.querySelector('img');
+ expect(image).toBeInTheDocument();
+ expect(image?.getAttribute('src')).toBeTruthy();
+ expect(image?.getAttribute('alt')).toBe(altText);
+ });
+ });
+ it('should render related content with title, href, image, and alt text when referrerVariant is adaptive_search', () => {
+ render(
+ ,
+ {
+ service: 'mundo',
+ toggles: { midArticleOnwardJourney: { enabled: true } },
+ },
+ );
+
+ const sectionTitle = document.querySelector(
+ '[id="recommendations-heading"]',
+ );
+ expect(sectionTitle).toBeInTheDocument();
+ expect(sectionTitle?.textContent).toBe('Contenido relacionado');
+ const listItems = document.querySelectorAll('li[role="listitem"]');
+ expect(listItems.length).toBe(2);
+
+ const firstLink = listItems[0].querySelector('a');
+ expect(firstLink).toBeInTheDocument();
+ expect(firstLink?.getAttribute('href')).toBe(
+ 'https://www.bbc.com/mundo/articles/c629n28z490o',
+ );
+ expect(firstLink?.textContent).toContain(
+ 'Gran Museo Egipcio, la gigantesca obra que exhibe los secretos de Tutankamón y que se inaugura tras décadas de trabajos',
+ );
+
+ const firstImage = listItems[0].querySelector('img');
+ expect(firstImage).toBeInTheDocument();
+ expect(firstImage?.getAttribute('src')).toBeTruthy();
+ expect(firstImage?.getAttribute('alt')).toBe(
+ "Visitors walk past Tutankhamun's gold-and-turquoise funerary mask on display at the Egyptian Museum in Cairo, on 2 December 2024.",
+ );
+
+ const secondLink = listItems[1].querySelector('a');
+ expect(secondLink).toBeInTheDocument();
+ expect(secondLink?.getAttribute('href')).toBe(
+ 'https://www.bbc.com/mundo/articles/c629n28z491p',
+ );
+ expect(secondLink?.textContent).toContain('Second related content title');
+
+ const secondImage = listItems[1].querySelector('img');
+ expect(secondImage).toBeInTheDocument();
+ expect(secondImage?.getAttribute('src')).toBeTruthy();
+ expect(secondImage?.getAttribute('alt')).toBe(
+ 'Second related content alt text.',
+ );
+ });
+ it('should render a single related content item without a list when only one item is present', () => {
+ const singleRelatedContentBlocks = [
+ ...relatedContentBlocksFixture.slice(0, 3),
+ relatedContentBlocksFixture[3]
+ ? {
+ ...relatedContentBlocksFixture[3],
+ model: {
+ blocks: relatedContentBlocksFixture[3]?.model?.blocks
+ ? [relatedContentBlocksFixture[3].model.blocks[0]]
+ : [],
+ },
+ }
+ : undefined,
+ ].filter(Boolean) as OptimoBlock[];
+ const { getByText } = render(
+ ,
+ {
+ service: 'mundo',
+ toggles: { midArticleOnwardJourney: { enabled: true } },
+ },
+ );
+
+ // Should not render a list
+ const listItems = document.querySelectorAll('li[role="listitem"]');
+ expect(listItems.length).toBe(0);
+
+ // Should render the single promo directly
+ const link = getByText(
+ 'Gran Museo Egipcio, la gigantesca obra que exhibe los secretos de Tutankamón y que se inaugura tras décadas de trabajos',
+ ).closest('a');
+ expect(link).toBeInTheDocument();
+ expect(link?.getAttribute('href')).toBe(
+ 'https://www.bbc.com/mundo/articles/c629n28z490o',
+ );
+
+ const image = document.querySelector('img');
+ expect(image).toBeInTheDocument();
+ expect(image?.getAttribute('src')).toBeTruthy();
+ expect(image?.getAttribute('alt')).toBe(
+ "Visitors walk past Tutankhamun's gold-and-turquoise funerary mask on display at the Egyptian Museum in Cairo, on 2 December 2024.",
+ );
+ });
+ it.each([
+ [undefined, 'undefined'],
+ ['off', 'off'],
+ ['control', 'control'],
+ ['something_control', 'something_control'],
+ ['', 'empty string'],
+ ])(
+ 'should render most read data when referrerVariant is %s',
+ (referrerVariant, label) => {
+ const { getByText, rerender } = render(
+ ,
+ {
+ service: 'mundo',
+ toggles: { midArticleOnwardJourney: { enabled: true } },
+ },
+ );
+
+ mostReadDataFixture.forEach(item => {
+ const link = getByText(item.title).closest('a');
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', item.href);
+
+ // Traverse up to the promo wrapper, then find the image
+ const promoWrapper = link?.closest(
+ '[data-e2e="recommendations-wrapper"]',
+ );
+ const image = promoWrapper?.querySelector('img');
+ expect(image).toBeInTheDocument();
+ expect(image).toHaveAttribute('alt', item.image.altText);
+ expect(image).toHaveAttribute(
+ 'src',
+ expect.stringContaining(item.image.locator),
+ );
+ });
+
+ // For all but the first run, rerender with the next variant
+ if (label !== 'undefined') {
+ rerender(
+ ,
+ );
+
+ // Assert section title after rerender
+ const rerenderedSectionTitle = document.querySelector(
+ '[id="recommendations-heading"]',
+ );
+ expect(rerenderedSectionTitle).toBeInTheDocument();
+ expect(rerenderedSectionTitle?.textContent).toBe('Más leídas');
+ mostReadDataFixture.forEach(item => {
+ const link = getByText(item.title).closest('a');
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', item.href);
+
+ const promoWrapper = link?.closest(
+ '[data-e2e="recommendations-wrapper"]',
+ );
+ const image = promoWrapper?.querySelector('img');
+ expect(image).toBeInTheDocument();
+ expect(image).toHaveAttribute('alt', item.image.altText);
+ expect(image).toHaveAttribute(
+ 'src',
+ expect.stringContaining(item.image.locator),
+ );
+ });
+ }
+ },
+ );
});
diff --git a/src/app/components/Recommendations/index.tsx b/src/app/components/Recommendations/index.tsx
index 0a6f6e8757a..cda08e950c5 100644
--- a/src/app/components/Recommendations/index.tsx
+++ b/src/app/components/Recommendations/index.tsx
@@ -8,28 +8,101 @@ import SkipLinkWrapper from '#components/SkipLinkWrapper';
import { ServiceContext } from '#contexts/ServiceContext';
import useViewTracker from '#app/hooks/useViewTracker';
import { Recommendation } from '#app/models/types/onwardJourney';
-import RecommendationsItem from './RecommendationsItem';
+import { OptimoBlock } from '#app/models/types/optimo';
import styles from './index.styles';
-
-const eventTrackingData = {
- componentName: 'midarticle-mostread',
-};
-
-const Recommendations = ({ data }: { data: Recommendation[] }) => {
- const { recommendations, mostRead, script, service, dir } =
+import RecommendationsItem from './RecommendationsItem';
+import {
+ getRelatedContentData,
+ mapOptimoBlockToRecommendation,
+ mapFeaturesToRecommendation,
+ mapTopStoryToRecommendation,
+} from './helpers';
+
+interface RecommendationsProps {
+ data: Recommendation[];
+ blocks?: OptimoBlock[];
+ topStoriesContent?: unknown;
+ featuresContent?: unknown;
+ referrerVariant?: string;
+ experimentProps?: Record;
+ referrer?: string | null;
+}
+
+const Recommendations = ({
+ data, // control
+ blocks, // search
+ topStoriesContent, // direct
+ featuresContent, // social
+ referrerVariant, // experiment variant for referrer
+ experimentProps,
+ referrer,
+}: RecommendationsProps) => {
+ const { recommendations, script, service, dir, translations } =
use(ServiceContext);
- const viewTracker = useViewTracker(eventTrackingData);
-
const {
palette: { GREY_2 },
} = useTheme();
const { enabled } = useToggle('midArticleOnwardJourney');
- const { hasMostRead } = mostRead || {};
+ let displayData: Recommendation[] = [];
+ const { skipLink, header } = recommendations || {};
- if (!enabled || !hasMostRead || !data?.length) return null;
+ let title = header ?? 'Most read';
+ // EXPERIMENT: Referrer Experiment
+ // most read was there originally, so is there for control and when the user is not in an experiment
+ if (
+ !referrerVariant ||
+ referrerVariant === 'off' ||
+ referrerVariant.includes('control')
+ ) {
+ displayData = data ?? [];
+ } else if (referrerVariant === 'adaptive_variant') {
+ switch (referrer) {
+ case 'search':
+ displayData = getRelatedContentData(blocks ?? []).map(
+ mapOptimoBlockToRecommendation,
+ );
+ title = translations?.relatedContent ?? 'Related Content';
+ break;
+ case 'direct':
+ displayData = Array.isArray(topStoriesContent)
+ ? topStoriesContent.map(mapTopStoryToRecommendation)
+ : [];
+ title = translations?.topStoriesTitle ?? 'Top Stories';
+ break;
+ case 'social':
+ displayData = Array.isArray(featuresContent)
+ ? featuresContent.slice(0, 4).map(mapFeaturesToRecommendation)
+ : [];
+ title = translations?.featuresAnalysisTitle ?? 'Features & Analysis';
+ break;
+ default:
+ displayData = data ?? [];
+ break;
+ }
+ }
+ const componentName = 'midarticle-mostread';
+ const groupTracker = {
+ name: title,
+ type: componentName,
+ itemCount: 4,
+ };
+
+ const baseEventTrackingData = {
+ componentName,
+ groupTracker,
+ };
+
+ const eventTrackingData = {
+ ...baseEventTrackingData,
+ ...(experimentProps && experimentProps),
+ };
+
+ const viewTracker = useViewTracker(eventTrackingData);
+
+ if (!enabled || !displayData.length) return null;
const labelId = 'recommendations-heading';
@@ -38,18 +111,14 @@ const Recommendations = ({ data }: { data: Recommendation[] }) => {
'aria-labelledby': labelId,
};
- const { skipLink, header } = recommendations || {};
-
const { text, endTextVisuallyHidden } = skipLink || {
text: 'Skip %title% and continue reading',
endTextVisuallyHidden: 'End of %title%',
};
- const title = header ?? 'Most read';
-
const terms = { '%title%': title };
- const isSinglePromo = data?.length === 1;
+ const isSinglePromo = displayData.length === 1;
const endTextId = `end-of-recommendations`;
@@ -84,12 +153,22 @@ const Recommendations = ({ data }: { data: Recommendation[] }) => {
) : null}
{isSinglePromo ? (
-
+
) : (
- {data?.map(recommendation => (
+ {displayData?.map((recommendation, index) => (
-
-
+
))}
diff --git a/src/app/components/RelatedContentSection/index.tsx b/src/app/components/RelatedContentSection/index.tsx
index 2beb29f0caf..8a8da7cfe65 100644
--- a/src/app/components/RelatedContentSection/index.tsx
+++ b/src/app/components/RelatedContentSection/index.tsx
@@ -39,9 +39,8 @@ type Props = {
experimentProps?: ComponentExperimentProps;
};
-const RelatedContentSection = ({ content }: Props) => {
+const RelatedContentSection = ({ content, experimentProps }: Props) => {
const { translations, script, service } = use(ServiceContext);
-
const {
palette: { GREY_2 },
} = useTheme();
@@ -50,6 +49,7 @@ const RelatedContentSection = ({ content }: Props) => {
const eventTrackingData = {
block: {
componentName: 'related-content',
+ ...(experimentProps && experimentProps),
},
};
const viewTracker = useViewTracker(eventTrackingData.block);
diff --git a/src/app/components/TopBarOJs/Promo/index.tsx b/src/app/components/TopBarOJs/Promo/index.tsx
index 498fc9c3dcc..12125575b90 100644
--- a/src/app/components/TopBarOJs/Promo/index.tsx
+++ b/src/app/components/TopBarOJs/Promo/index.tsx
@@ -16,7 +16,6 @@ const Promo = ({ block, eventTrackingData }: PromoProps) => {
const overtypedHeadline = block?.headlines?.overtyped ?? '';
const mainHeadline = block?.headlines?.headline ?? '';
const headlineBlockText =
- // @ts-expect-error - Optimo nested block structure
block?.headlines?.promoHeadline?.blocks?.[0]?.model?.blocks?.[0]?.model
?.text ?? '';
const name = block?.name ?? '';
diff --git a/src/app/contexts/RequestContext/index.tsx b/src/app/contexts/RequestContext/index.tsx
index 8600dc740d2..b40d1908c49 100644
--- a/src/app/contexts/RequestContext/index.tsx
+++ b/src/app/contexts/RequestContext/index.tsx
@@ -113,6 +113,7 @@ export const RequestContextProvider = ({
};
const platform = getPlatform();
+
const statsDestination = getStatsDestination({
isUK: platform === 'amp' ? true : formattedIsUK, // getDestination requires that statsDestination is a PS variant on AMP
env,
diff --git a/src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx b/src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx
index f0455e589e7..2cb0efae0fe 100644
--- a/src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx
+++ b/src/app/legacy/containers/PageHandlers/withOptimizelyProvider/index.tsx
@@ -49,7 +49,7 @@ export const REFERRER_CATEGORIES = {
AT_PARAM_VALUES: ['social', 'social_flow', 'ws_whatsapp'],
};
-const getReferrer = () => {
+export const getReferrer = () => {
if (onClient()) {
const referrer = document?.referrer?.toLowerCase();
diff --git a/src/app/models/types/global.ts b/src/app/models/types/global.ts
index 32498de809d..fc30eb0d913 100644
--- a/src/app/models/types/global.ts
+++ b/src/app/models/types/global.ts
@@ -28,6 +28,7 @@ export type ComponentExperimentProps = {
sendOptimizelyEvents?: boolean;
experimentName?: string;
experimentVariant?: string;
+ referrer?: string | null;
};
export type ServerSideExperiment = {
diff --git a/src/app/pages/ArticlePage/ArticlePage.styles.ts b/src/app/pages/ArticlePage/ArticlePage.styles.ts
index 5ab3500ffe1..9e60c7080cb 100644
--- a/src/app/pages/ArticlePage/ArticlePage.styles.ts
+++ b/src/app/pages/ArticlePage/ArticlePage.styles.ts
@@ -44,13 +44,38 @@ export default {
}),
secondaryColumn: ({ mq }: Theme) =>
css({
+ display: 'none',
gridColumn: '1 / span 12',
-
[mq.GROUP_4_MIN_WIDTH]: {
+ display: 'block',
gridColumn: '9 / span 4',
marginTop: '2rem',
},
}),
+
+ hideOnDesktop: ({ mq }: Theme) =>
+ css({
+ [mq.GROUP_4_MIN_WIDTH]: {
+ display: 'none',
+ },
+ }),
+
+ hideBelowDesktopWidth: ({ mq }: Theme) =>
+ css({
+ display: 'none',
+ [mq.GROUP_4_MIN_WIDTH]: {
+ display: 'block',
+ },
+ }),
+
+ underArticleSection: ({ spacings }: Theme) =>
+ css({
+ display: 'block',
+ gridColumn: '9 / span 4',
+ marginTop: '2rem',
+ marginBottom: `${spacings.TRIPLE}rem`,
+ }),
+
pglColumn: () =>
css({
gridColumn: '1 / span 12',
diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx
index 9610c828c45..3b411f60fde 100644
--- a/src/app/pages/ArticlePage/ArticlePage.tsx
+++ b/src/app/pages/ArticlePage/ArticlePage.tsx
@@ -1,7 +1,10 @@
-import { use, useState } from 'react';
+import { use, useState, useEffect } from 'react';
import { useTheme } from '@emotion/react';
import useToggle from '#hooks/useToggle';
import { singleTextBlock } from '#app/models/blocks';
+import useOptimizelyVariation, {
+ ExperimentType,
+} from '#app/hooks/useOptimizelyVariation';
import OptimizelyPageMetrics from '#app/components/OptimizelyPageMetrics';
import ArticleMetadata from '#containers/ArticleMetadata';
import { RequestContext } from '#contexts/RequestContext';
@@ -16,6 +19,7 @@ import SocialEmbedContainer from '#containers/SocialEmbed';
import MediaLoader from '#app/components/MediaLoader';
import { MediaBlock } from '#app/components/MediaLoader/types';
import { PHOTO_GALLERY_PAGE, STORY_PAGE } from '#app/routes/utils/pageTypes';
+import { getReferrer } from '#app/legacy/containers/PageHandlers/withOptimizelyProvider';
import {
getArticleId,
@@ -77,6 +81,7 @@ import {
isPortraitVideo,
isPortraitVideoUnderHeadline,
} from '../utils/portraitVideo';
+import getUnderArticleComponents from './helpers';
const getImageComponent =
(preloadLeadImageToggle: boolean) => (props: ComponentToRenderProps) => (
@@ -129,10 +134,39 @@ const getMpuComponent =
(allowAdvertising: boolean) => (props: ComponentToRenderProps) =>
allowAdvertising ? : null;
-const getWsojComponent = (
- props: ComponentToRenderProps & { data: Recommendation[] },
-) => ;
-
+const getWsojComponent = ({
+ data,
+ blocks,
+ topStoriesContent,
+ featuresContent,
+ referrerVariant,
+ referrerExperimentName,
+ referrer,
+}: {
+ data: Recommendation[];
+ blocks: OptimoBlock[];
+ topStoriesContent?: unknown;
+ featuresContent?: unknown;
+ referrerVariant?: string | null;
+ referrerExperimentName?: string;
+ referrer?: string | null;
+}) => (
+
+);
const DisclaimerWithPaddingOverride = (props: ComponentToRenderProps) => (
);
@@ -165,19 +199,29 @@ const getVideoComponent =
};
const getContinueReadingButton =
- ({ showAllContent, setShowAllContent }: ContinueReadingButtonProps) =>
- () => {
- return (
-
- );
- };
+ ({
+ showAllContent,
+ setShowAllContent,
+ experimentProps,
+ }: ContinueReadingButtonProps) =>
+ () => (
+
+ );
const ArticlePage = ({ pageData }: { pageData: Article }) => {
const [showAllContent, setShowAllContent] = useState(false);
const { isApp, isAmp, isLite } = use(RequestContext);
+ // SSR-safe: always null on server, update on client
+ const [referrer, setReferrer] = useState(null);
+
+ useEffect(() => {
+ setReferrer(getReferrer());
+ }, []);
+
const {
articleAuthor,
isTrustProjectParticipant,
@@ -196,6 +240,13 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
palette: { GREY_2 },
} = useTheme();
+ // EXPERIMENT: Referrer Experiment
+ const referrerExperimentName = 'newswb_ws_oj_by_referrer';
+ const referrerVariant = useOptimizelyVariation({
+ experimentName: referrerExperimentName,
+ experimentType: ExperimentType.CLIENT_SIDE,
+ });
+
const allowAdvertising = pageData?.metadata?.allowAdvertising ?? false;
const adcampaign = pageData?.metadata?.adCampaignKeyword;
@@ -213,6 +264,8 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
const aboutTags = getAboutTags(pageData);
const topics = pageData?.metadata?.topics ?? [];
const blocks = pageData?.content?.model?.blocks ?? [];
+ const topStoriesContent = pageData?.secondaryColumn?.topStories;
+ const featuresContent = pageData?.secondaryColumn?.features;
const startsWithHeading = blocks?.[0]?.type === 'headline' || false;
const bylineBlock = blocks.find(
@@ -284,13 +337,29 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
group: gist,
links: ArticleLinksBlock,
mpu: getMpuComponent(allowAdvertising),
- wsoj: getWsojComponent,
+ wsoj: ({ data }: { data: Recommendation[] }) =>
+ getWsojComponent({
+ data,
+ blocks,
+ topStoriesContent,
+ featuresContent,
+ referrerVariant,
+ referrerExperimentName,
+ referrer,
+ }),
disclaimer: DisclaimerWithPaddingOverride,
podcastPromo: getPodcastPromoComponent(podcastPromoEnabled),
...(showContinueReadingButton && {
continueReading: getContinueReadingButton({
showAllContent,
setShowAllContent,
+ ...(referrerVariant && {
+ experimentProps: {
+ sendOptimizelyEvents: true,
+ experimentName: referrerExperimentName,
+ experimentVariant: referrerVariant,
+ },
+ }),
}),
}),
};
@@ -397,10 +466,37 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
/>
)}
-
+
{!isApp && !isPGL && }
+
+ {/* // EXPERIMENT: Referrer Experiment
+ Under-article components for mobile/tablet only */}
+ {getUnderArticleComponents({
+ referrerVariant: referrerVariant || '',
+ referrerExperimentName: referrerExperimentName || '',
+ topStoriesData: topStoriesContent,
+ featuresData: featuresContent,
+ articleBlocks: blocks,
+ grey2: GREY_2,
+ pageStyles: styles,
+ referrer,
+ }).map(component => component)}
+
{!isApp && !isPGL && (
{
size="default"
headingBackgroundColour={GREY_2}
mobileDivider={showTopics}
- eventTrackingData={{ componentName: 'most-read' }}
+ {...(referrerVariant && {
+ experimentProps: {
+ sendOptimizelyEvents: true,
+ experimentName: referrerExperimentName,
+ experimentVariant: referrerVariant,
+ },
+ })}
/>
)}
diff --git a/src/app/pages/ArticlePage/ContinueReadingButton/index.tsx b/src/app/pages/ArticlePage/ContinueReadingButton/index.tsx
index d00c09a382a..49f537f38fc 100644
--- a/src/app/pages/ArticlePage/ContinueReadingButton/index.tsx
+++ b/src/app/pages/ArticlePage/ContinueReadingButton/index.tsx
@@ -10,20 +10,27 @@ import styles from './index.styles';
export type ContinueReadingButtonProps = {
showAllContent: boolean;
setShowAllContent: Dispatch>;
-};
-
-const eventTrackingData: EventTrackingData = {
- componentName: 'continue-reading-button',
+ experimentProps?: {
+ sendOptimizelyEvents: boolean;
+ experimentName: string;
+ experimentVariant: string;
+ };
};
const ContinueReadingButton = ({
showAllContent,
setShowAllContent,
+ experimentProps,
}: ContinueReadingButtonProps) => {
const {
translations: { continueReading = 'Continue reading' },
} = use(ServiceContext);
+ const eventTrackingData: EventTrackingData = {
+ componentName: 'continue-reading-button',
+ ...(experimentProps && experimentProps),
+ };
+
const viewRef = useViewTracker(eventTrackingData);
const { onClick: clickTrackerHandler } =
useClickTrackerHandler(eventTrackingData);
diff --git a/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/TopStoriesItem/index.tsx b/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/TopStoriesItem/index.tsx
index 57fe0dda03b..1bf1258617f 100644
--- a/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/TopStoriesItem/index.tsx
+++ b/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/TopStoriesItem/index.tsx
@@ -10,7 +10,6 @@ const getArticleTopStoryItem = (item: TopStoryItem) => {
const overtypedHeadline = item?.headlines?.overtyped ?? '';
const mainHeadline = item?.headlines?.headline ?? '';
const headlineBlockText =
- // @ts-expect-error - nested block structure
item?.headlines?.promoHeadline?.blocks?.[0]?.model?.blocks?.[0]?.model
?.text ?? '';
diff --git a/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/types.ts b/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/types.ts
index 5c465f3d315..0d913860dba 100644
--- a/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/types.ts
+++ b/src/app/pages/ArticlePage/PagePromoSections/TopStoriesSection/types.ts
@@ -1,6 +1,25 @@
import { MetadataTaggings } from '#app/models/types/metadata';
import { OptimoBlock } from '#app/models/types/optimo';
+export type PromoHeadlineBlock = {
+ type: string;
+ model: {
+ blocks: Array<{
+ type: string;
+ model: {
+ text: string;
+ blocks?: Array<{
+ type: string;
+ model: {
+ text: string;
+ attributes?: unknown[];
+ };
+ }>;
+ };
+ }>;
+ };
+};
+
export type TopStoryItem = {
contentType?: string;
uri?: string;
@@ -82,10 +101,14 @@ export type TopStoryItem = {
headline: string;
seoHeadline: string;
promoHeadline: {
- blocks: OptimoBlock[];
+ blocks: PromoHeadlineBlock[];
+ };
+ };
+ images: {
+ defaultPromoImage?: {
+ blocks?: OptimoBlock[];
};
};
- images: object;
summary: {
blocks: OptimoBlock[];
};
diff --git a/src/app/pages/ArticlePage/SecondaryColumn.tsx b/src/app/pages/ArticlePage/SecondaryColumn.tsx
index e0e58be4be4..1109d3306b4 100644
--- a/src/app/pages/ArticlePage/SecondaryColumn.tsx
+++ b/src/app/pages/ArticlePage/SecondaryColumn.tsx
@@ -4,7 +4,15 @@ import { Article } from '#app/models/types/optimo';
import TopStoriesSection from './PagePromoSections/TopStoriesSection';
import styles from './ArticlePage.styles';
-const SecondaryColumn = ({ pageData }: { pageData: Article }) => {
+const SecondaryColumn = ({
+ pageData,
+ referrerVariant,
+ referrerExperimentName,
+}: {
+ pageData: Article;
+ referrerVariant?: string | null;
+ referrerExperimentName?: string;
+}) => {
const topStoriesContent = pageData?.secondaryColumn?.topStories;
const featuresContent = pageData?.secondaryColumn?.features;
const {
@@ -21,7 +29,17 @@ const SecondaryColumn = ({ pageData }: { pageData: Article }) => {
data-testid="top-stories"
data-experiment-position="secondaryColumn"
>
-
+
)}
{featuresContent && (
@@ -30,6 +48,14 @@ const SecondaryColumn = ({ pageData }: { pageData: Article }) => {
content={featuresContent}
parentColumns={{}}
sectionLabelBackground={GREY_2}
+ // EXPERIMENT: Referrer Experiment
+ {...(referrerVariant && {
+ experimentProps: {
+ sendOptimizelyEvents: true,
+ experimentName: referrerExperimentName,
+ experimentVariant: referrerVariant,
+ },
+ })}
/>
)}
diff --git a/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap b/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap
index dadbd758e1a..66d55928a5a 100644
--- a/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap
+++ b/src/app/pages/ArticlePage/__snapshots__/index.test.tsx.snap
@@ -324,8 +324,35 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
}
-@media (max-width: 24.9375rem) {
+.emotion-12 {
+ display: none;
+}
+
+@media (min-width: 63rem) {
.emotion-12 {
+ display: block;
+ }
+}
+
+.emotion-13 {
+ grid-column: 1/span 12;
+}
+
+.emotion-14 {
+ display: block;
+ grid-column: 9/span 4;
+ margin-top: 2rem;
+ margin-bottom: 1.5rem;
+}
+
+@media (min-width: 63rem) {
+ .emotion-14 {
+ display: none;
+ }
+}
+
+@media (max-width: 24.9375rem) {
+ .emotion-15 {
margin: 0 0.5rem;
padding-bottom: 1.5rem;
}
@@ -333,7 +360,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
@media (min-width: 25rem) {
@media (max-width: 62.9375rem) {
- .emotion-12 {
+ .emotion-15 {
margin: 0 1rem;
padding-bottom: 2rem;
}
@@ -341,21 +368,21 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-12 {
+ .emotion-15 {
margin: 0 1rem;
padding-bottom: 2.5rem;
}
}
@media (min-width: 80rem) {
- .emotion-12 {
+ .emotion-15 {
margin: 0 auto;
padding: 0 1rem 1.5rem;
max-width: 80rem;
}
}
-.emotion-13 {
+.emotion-16 {
position: relative;
z-index: 0;
color: #141414;
@@ -363,36 +390,36 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 37.5rem) {
- .emotion-13 {
+ .emotion-16 {
margin-top: 1.5rem;
}
}
@media (min-width: 63rem) {
- .emotion-13 {
+ .emotion-16 {
margin-bottom: 1.5rem;
}
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-13 {
+ .emotion-16 {
margin-bottom: 1rem;
}
}
-.emotion-15 {
+.emotion-18 {
margin: 0;
padding: 0;
scroll-margin-top: 1rem;
}
-.emotion-15:focus-visible {
+.emotion-18:focus-visible {
outline: 0.1875rem solid #000000;
box-shadow: 0 0 0 0.1875rem #FFFFFF;
outline-offset: 0.1875rem;
}
-.emotion-17 {
+.emotion-20 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -402,7 +429,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
flex-direction: column;
}
-.emotion-19 {
+.emotion-22 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -422,7 +449,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 37.5rem) {
- .emotion-19 {
+ .emotion-22 {
-webkit-align-items: stretch;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
@@ -430,7 +457,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
}
-.emotion-21 {
+.emotion-24 {
font-size: 1.125rem;
line-height: 1.375rem;
font-family: Helmet,Freesans,Helvetica,Arial,sans-serif;
@@ -450,32 +477,32 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-21 {
+ .emotion-24 {
font-size: 1.25rem;
line-height: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-21 {
+ .emotion-24 {
font-size: 1.5rem;
line-height: 1.75rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-21 {
+ .emotion-24 {
margin: 0;
}
}
@media (min-width: 37.5rem) {
- .emotion-21 {
+ .emotion-24 {
padding-right: 1rem;
}
}
-.emotion-23 {
+.emotion-26 {
list-style-type: none;
margin: 0;
padding: 0;
@@ -484,7 +511,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@supports (display: grid) {
- .emotion-23 {
+ .emotion-26 {
display: grid;
position: initial;
width: initial;
@@ -492,7 +519,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (max-width: 14.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 0.5rem;
@@ -500,7 +527,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 0.5rem;
@@ -508,7 +535,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 0.5rem;
@@ -516,7 +543,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 1rem;
@@ -524,7 +551,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(8, 1fr);
grid-column-end: span 8;
grid-column-gap: 1rem;
@@ -532,7 +559,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 80rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(20, 1fr);
grid-column-end: span 20;
grid-column-gap: 1rem;
@@ -541,24 +568,24 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 80rem) {
- .emotion-23 {
+ .emotion-26 {
grid-auto-flow: row;
}
}
@media (min-width: 37.5rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-rows: repeat(3, auto);
}
}
-.emotion-25 {
+.emotion-28 {
position: relative;
padding-bottom: 1.5rem;
}
@media (max-width: 14.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(100%);
display: inline-block;
vertical-align: top;
@@ -566,7 +593,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(100%);
display: inline-block;
vertical-align: top;
@@ -574,7 +601,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(100%);
display: inline-block;
vertical-align: top;
@@ -582,7 +609,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(50%);
display: inline-block;
vertical-align: top;
@@ -590,7 +617,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(50%);
display: inline-block;
vertical-align: top;
@@ -598,7 +625,7 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 80rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(20%);
display: inline-block;
vertical-align: top;
@@ -606,56 +633,56 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@supports (display: grid) {
- .emotion-25 {
+ .emotion-28 {
display: block;
width: initial;
margin: 0;
}
@media (max-width: 14.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
}
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(3, 1fr);
grid-column-end: span 3;
}
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(4, 1fr);
grid-column-end: span 4;
}
}
@media (min-width: 80rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(4, 1fr);
grid-column-end: span 4;
}
}
}
-.emotion-27 {
+.emotion-30 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -668,50 +695,50 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (max-width: 14.9375rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 2rem;
}
}
@supports (grid-template-columns: fit-content(200px)) {
@media (min-width: 37.5rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 2rem;
}
}
}
@media (min-width: 80rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 2rem;
}
}
-.emotion-29 {
+.emotion-32 {
font-family: Helmet,Freesans,Helvetica,Arial,sans-serif;
font-style: normal;
font-weight: 400;
@@ -724,32 +751,32 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-29 {
+ .emotion-32 {
font-size: 2.5rem;
line-height: 2.75rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-29 {
+ .emotion-32 {
font-size: 3.5rem;
line-height: 3.75rem;
}
}
-.emotion-30 {
+.emotion-33 {
padding-top: 0.375rem;
padding-left: 1rem;
padding-right: 1rem;
}
@supports (grid-template-columns: fit-content(200px)) {
- .emotion-30 {
+ .emotion-33 {
padding-right: 0;
}
}
-.emotion-31 {
+.emotion-34 {
font-size: 0.9375rem;
line-height: 1.25rem;
font-family: Helmet,Freesans,Helvetica,Arial,sans-serif;
@@ -763,26 +790,26 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1rem;
line-height: 1.25rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1rem;
line-height: 1.25rem;
}
}
-.emotion-31:hover,
-.emotion-31:focus {
+.emotion-34:hover,
+.emotion-34:focus {
-webkit-text-decoration: underline;
text-decoration: underline;
}
-.emotion-31:before {
+.emotion-34:before {
bottom: 0;
content: '';
left: 0;
@@ -795,31 +822,31 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 25rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1.125rem;
line-height: 1.375rem;
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1.125rem;
line-height: 1.375rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1.25rem;
line-height: 1.5rem;
}
}
}
-.emotion-32 {
+.emotion-35 {
padding-top: 0.5rem;
}
-.emotion-33 {
+.emotion-36 {
font-size: 0.875rem;
line-height: 1.125rem;
color: #545658;
@@ -830,14 +857,14 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-33 {
+ .emotion-36 {
font-size: 0.875rem;
line-height: 1.125rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-33 {
+ .emotion-36 {
font-size: 0.8125rem;
line-height: 1rem;
}
@@ -887,31 +914,41 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
+
+
@@ -922,43 +959,43 @@ exports[`Article Page should render a ltr article (pidgin) with most read correc
-
1
Teams wey qualify for Afcon 2023 and how things stand for each group
-
2
'We dey hear gospel songs den screaming' - Woman tok of Uganda school attack
-
3
Super Eagles qualify for Nations Cup afta beating Sierra Leone for Monrovia
-
4
Forbes top ten list of highest paid athletes for 2023 and wetin dem earn
-
5
Tins to sabi about 2023 Sierra Leone general election
+
+
@@ -2383,43 +2457,43 @@ exports[`Article Page should render a news article correctly 1`] = `
-
1
Why the oxygen timeline on sub may not be so fixed
-
2
MasterChef contestant jailed for child abuse images
-
3
Musk and Zuckerberg agree to hold cage fight
-
4
Cindy Beale returns to EastEnders after 25 years
-
5
Mosquito-borne diseases risk increasing in Europe
-
6
Teenager guilty of murdering boy at train station
-
7
At least 30 migrants feared dead off Canary Islands
-
8
Bank boss warns interest rate hike hard for many
-
9
Ukraine strikes bridge to Crimea, says Russia
-
10
The woman who wants to end abortion in America
+
`;
@@ -3402,6 +3513,33 @@ exports[`Article Page should render a news article without headline correctly 1`
}
}
+.emotion-9 {
+ display: none;
+}
+
+@media (min-width: 63rem) {
+ .emotion-9 {
+ display: block;
+ }
+}
+
+.emotion-10 {
+ grid-column: 1/span 12;
+}
+
+.emotion-11 {
+ display: block;
+ grid-column: 9/span 4;
+ margin-top: 2rem;
+ margin-bottom: 1.5rem;
+}
+
+@media (min-width: 63rem) {
+ .emotion-11 {
+ display: none;
+ }
+}
+
+
`;
@@ -3771,8 +3919,35 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
}
-@media (max-width: 24.9375rem) {
+.emotion-12 {
+ display: none;
+}
+
+@media (min-width: 63rem) {
.emotion-12 {
+ display: block;
+ }
+}
+
+.emotion-13 {
+ grid-column: 1/span 12;
+}
+
+.emotion-14 {
+ display: block;
+ grid-column: 9/span 4;
+ margin-top: 2rem;
+ margin-bottom: 1.5rem;
+}
+
+@media (min-width: 63rem) {
+ .emotion-14 {
+ display: none;
+ }
+}
+
+@media (max-width: 24.9375rem) {
+ .emotion-15 {
margin: 0 0.5rem;
padding-bottom: 1.5rem;
}
@@ -3780,7 +3955,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
@media (min-width: 25rem) {
@media (max-width: 62.9375rem) {
- .emotion-12 {
+ .emotion-15 {
margin: 0 1rem;
padding-bottom: 2rem;
}
@@ -3788,21 +3963,21 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-12 {
+ .emotion-15 {
margin: 0 1rem;
padding-bottom: 2.5rem;
}
}
@media (min-width: 80rem) {
- .emotion-12 {
+ .emotion-15 {
margin: 0 auto;
padding: 0 1rem 1.5rem;
max-width: 80rem;
}
}
-.emotion-13 {
+.emotion-16 {
position: relative;
z-index: 0;
color: #141414;
@@ -3810,36 +3985,36 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 37.5rem) {
- .emotion-13 {
+ .emotion-16 {
margin-top: 1.5rem;
}
}
@media (min-width: 63rem) {
- .emotion-13 {
+ .emotion-16 {
margin-bottom: 1.5rem;
}
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-13 {
+ .emotion-16 {
margin-bottom: 1rem;
}
}
-.emotion-15 {
+.emotion-18 {
margin: 0;
padding: 0;
scroll-margin-top: 1rem;
}
-.emotion-15:focus-visible {
+.emotion-18:focus-visible {
outline: 0.1875rem solid #000000;
box-shadow: 0 0 0 0.1875rem #FFFFFF;
outline-offset: 0.1875rem;
}
-.emotion-17 {
+.emotion-20 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -3849,7 +4024,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
flex-direction: column;
}
-.emotion-19 {
+.emotion-22 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -3869,7 +4044,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 37.5rem) {
- .emotion-19 {
+ .emotion-22 {
-webkit-align-items: stretch;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
@@ -3877,7 +4052,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
}
-.emotion-21 {
+.emotion-24 {
font-size: 1.25rem;
line-height: 1.75rem;
font-family: "BBC Reith Qalam","Times New Roman",Arial,Verdana,Geneva,Helvetica,sans-serif;
@@ -3897,32 +4072,32 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-21 {
+ .emotion-24 {
font-size: 1.25rem;
line-height: 1.75rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-21 {
+ .emotion-24 {
font-size: 1.5rem;
line-height: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-21 {
+ .emotion-24 {
margin: 0;
}
}
@media (min-width: 37.5rem) {
- .emotion-21 {
+ .emotion-24 {
padding-left: 1rem;
}
}
-.emotion-23 {
+.emotion-26 {
list-style-type: none;
margin: 0;
padding: 0;
@@ -3931,7 +4106,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@supports (display: grid) {
- .emotion-23 {
+ .emotion-26 {
display: grid;
position: initial;
width: initial;
@@ -3939,7 +4114,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (max-width: 14.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 0.5rem;
@@ -3947,7 +4122,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 0.5rem;
@@ -3955,7 +4130,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 0.5rem;
@@ -3963,7 +4138,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
grid-column-gap: 1rem;
@@ -3971,7 +4146,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(8, 1fr);
grid-column-end: span 8;
grid-column-gap: 1rem;
@@ -3979,7 +4154,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 80rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-columns: repeat(20, 1fr);
grid-column-end: span 20;
grid-column-gap: 1rem;
@@ -3988,24 +4163,24 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 80rem) {
- .emotion-23 {
+ .emotion-26 {
grid-auto-flow: row;
}
}
@media (min-width: 37.5rem) {
- .emotion-23 {
+ .emotion-26 {
grid-template-rows: repeat(5, auto);
}
}
-.emotion-25 {
+.emotion-28 {
position: relative;
padding-bottom: 1.5rem;
}
@media (max-width: 14.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(100%);
display: inline-block;
vertical-align: top;
@@ -4013,7 +4188,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(100%);
display: inline-block;
vertical-align: top;
@@ -4021,7 +4196,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(100%);
display: inline-block;
vertical-align: top;
@@ -4029,7 +4204,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(50%);
display: inline-block;
vertical-align: top;
@@ -4037,7 +4212,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(50%);
display: inline-block;
vertical-align: top;
@@ -4045,7 +4220,7 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 80rem) {
- .emotion-25 {
+ .emotion-28 {
width: calc(20%);
display: inline-block;
vertical-align: top;
@@ -4053,56 +4228,56 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@supports (display: grid) {
- .emotion-25 {
+ .emotion-28 {
display: block;
width: initial;
margin: 0;
}
@media (max-width: 14.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(6, 1fr);
grid-column-end: span 6;
}
}
@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(3, 1fr);
grid-column-end: span 3;
}
}
@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(4, 1fr);
grid-column-end: span 4;
}
}
@media (min-width: 80rem) {
- .emotion-25 {
+ .emotion-28 {
grid-template-columns: repeat(4, 1fr);
grid-column-end: span 4;
}
}
}
-.emotion-27 {
+.emotion-30 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -4115,50 +4290,50 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (max-width: 14.9375rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1rem;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
@supports (grid-template-columns: fit-content(200px)) {
@media (min-width: 37.5rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
}
@media (min-width: 80rem) {
- .emotion-28 {
+ .emotion-31 {
min-width: 1.5rem;
}
}
-.emotion-29 {
+.emotion-32 {
font-family: "BBC Reith Qalam","Times New Roman",Arial,Verdana,Geneva,Helvetica,sans-serif;
font-style: normal;
font-weight: 400;
@@ -4171,32 +4346,32 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-29 {
+ .emotion-32 {
font-size: 2.5rem;
line-height: 2.75rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-29 {
+ .emotion-32 {
font-size: 3.5rem;
line-height: 3.75rem;
}
}
-.emotion-30 {
+.emotion-33 {
padding-top: 0.375rem;
padding-left: 1rem;
padding-right: 1rem;
}
@supports (grid-template-columns: fit-content(200px)) {
- .emotion-30 {
+ .emotion-33 {
padding-left: 0;
}
}
-.emotion-31 {
+.emotion-34 {
font-size: 0.9375rem;
line-height: 1.5rem;
font-family: "BBC Reith Qalam","Times New Roman",Arial,Verdana,Geneva,Helvetica,sans-serif;
@@ -4210,26 +4385,26 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1rem;
line-height: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1rem;
line-height: 1.5rem;
}
}
-.emotion-31:hover,
-.emotion-31:focus {
+.emotion-34:hover,
+.emotion-34:focus {
-webkit-text-decoration: underline;
text-decoration: underline;
}
-.emotion-31:before {
+.emotion-34:before {
bottom: 0;
content: '';
left: 0;
@@ -4242,31 +4417,31 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 25rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1.125rem;
line-height: 1.625rem;
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1.125rem;
line-height: 1.625rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-31 {
+ .emotion-34 {
font-size: 1.25rem;
line-height: 1.75rem;
}
}
}
-.emotion-32 {
+.emotion-35 {
padding-top: 0.5rem;
}
-.emotion-33 {
+.emotion-36 {
font-size: 0.875rem;
line-height: 1.25rem;
color: #545658;
@@ -4277,235 +4452,235 @@ exports[`Article Page should render a rtl article (persian) with most read corre
}
@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-33 {
+ .emotion-36 {
font-size: 0.875rem;
line-height: 1.25rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-33 {
+ .emotion-36 {
font-size: 0.8125rem;
line-height: 1.25rem;
}
}
@media (max-width: 14.9375rem) {
- .emotion-38 {
+ .emotion-41 {
min-width: 1rem;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-38 {
+ .emotion-41 {
min-width: 1.5rem;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-38 {
+ .emotion-41 {
min-width: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-38 {
+ .emotion-41 {
min-width: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-38 {
+ .emotion-41 {
min-width: 2rem;
}
}
@supports (grid-template-columns: fit-content(200px)) {
@media (min-width: 37.5rem) {
- .emotion-38 {
+ .emotion-41 {
min-width: 1.5rem;
}
}
}
@media (min-width: 80rem) {
- .emotion-38 {
+ .emotion-41 {
min-width: 1.5rem;
}
}
@media (max-width: 14.9375rem) {
- .emotion-68 {
+ .emotion-71 {
min-width: 1rem;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-68 {
+ .emotion-71 {
min-width: 1.5rem;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-68 {
+ .emotion-71 {
min-width: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-68 {
+ .emotion-71 {
min-width: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-68 {
+ .emotion-71 {
min-width: 1.5rem;
}
}
@supports (grid-template-columns: fit-content(200px)) {
@media (min-width: 37.5rem) {
- .emotion-68 {
+ .emotion-71 {
min-width: 1.5rem;
}
}
}
@media (min-width: 80rem) {
- .emotion-68 {
+ .emotion-71 {
min-width: 2rem;
}
}
@media (max-width: 14.9375rem) {
- .emotion-78 {
+ .emotion-81 {
min-width: 1rem;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-78 {
+ .emotion-81 {
min-width: 1.5rem;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-78 {
+ .emotion-81 {
min-width: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-78 {
+ .emotion-81 {
min-width: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-78 {
+ .emotion-81 {
min-width: 2rem;
}
}
@supports (grid-template-columns: fit-content(200px)) {
@media (min-width: 37.5rem) {
- .emotion-78 {
+ .emotion-81 {
min-width: 2rem;
}
}
}
@media (min-width: 80rem) {
- .emotion-78 {
+ .emotion-81 {
min-width: 1.5rem;
}
}
@media (max-width: 14.9375rem) {
- .emotion-88 {
+ .emotion-91 {
min-width: 1rem;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-88 {
+ .emotion-91 {
min-width: 1.5rem;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-88 {
+ .emotion-91 {
min-width: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-88 {
+ .emotion-91 {
min-width: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-88 {
+ .emotion-91 {
min-width: 1.5rem;
}
}
@supports (grid-template-columns: fit-content(200px)) {
@media (min-width: 37.5rem) {
- .emotion-88 {
+ .emotion-91 {
min-width: 2rem;
}
}
}
@media (min-width: 80rem) {
- .emotion-88 {
+ .emotion-91 {
min-width: 1.5rem;
}
}
@media (max-width: 14.9375rem) {
- .emotion-118 {
+ .emotion-121 {
min-width: 1rem;
}
}
@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-118 {
+ .emotion-121 {
min-width: 1.5rem;
}
}
@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-118 {
+ .emotion-121 {
min-width: 1.5rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-118 {
+ .emotion-121 {
min-width: 2rem;
}
}
@media (min-width: 37.5rem) {
- .emotion-118 {
+ .emotion-121 {
min-width: 2rem;
}
}
@supports (grid-template-columns: fit-content(200px)) {
@media (min-width: 37.5rem) {
- .emotion-118 {
+ .emotion-121 {
min-width: 2rem;
}
}
}
@media (min-width: 80rem) {
- .emotion-118 {
+ .emotion-121 {
min-width: 2rem;
}
}
@@ -4554,31 +4729,41 @@ exports[`Article Page should render a rtl article (persian) with most read corre
+
+
@@ -4589,43 +4774,43 @@ exports[`Article Page should render a rtl article (persian) with most read corre
-
۱
ایران در آستانه بحران بنزین؛ تابستانی دشوار برای دولت رئیسی در پیش است
-
۲
پرونده حمید نوری؛ وکلای مدافع: نوری فریب خورد
-
۳
اما رادوکانو: گاهی آرزو میکنم که ای کاش قهرمان اوپن آمریکا نمیشدم
-
۴
ملیپوش سابق هلند برای چاقو زدن به فامیلش به ۱۸ ماه حبس محکوم شد
-
۵
وزیر خارجه آمریکا در پکن؛ شی: پیشرفتهایی در روابط حاصل شده است، بلینکن: رقابت نباید بدل به دشمنی شود
-
۶
سگم سرطان پستان مرا تشخیص داد
-
۷
لوح ۲۸۰۰ ساله از ایتالیا به عراق بازگردانده شد
-
۸
نمایندگان کشورها و نهادها در اجلاس سازمان ملل خواستار پایان یافتن سیاستهای تبعیضآمیز طالبان علیه زنان شدند
-
۹
چرا موبایل داغ میکند و چطور آن را خنک نگه داریم؟
-
۱۰
هشتگ «داستان_ما_یکیست» در چهلمین سالگرد اعدام ۱۰ زن بهائی در شیراز