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. 1
    Teams wey qualify for Afcon 2023 and how things stand for each group
  2. 2
    'We dey hear gospel songs den screaming' - Woman tok of Uganda school attack
  3. 3
    Super Eagles qualify for Nations Cup afta beating Sierra Leone for Monrovia
  4. 4
    Forbes top ten list of highest paid athletes for 2023 and wetin dem earn
  5. 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. 1
      Why the oxygen timeline on sub may not be so fixed
    2. 2
      MasterChef contestant jailed for child abuse images
    3. 3
      Musk and Zuckerberg agree to hold cage fight
    4. 4
      Cindy Beale returns to EastEnders after 25 years
    5. 5
      Mosquito-borne diseases risk increasing in Europe
    6. 6
      Teenager guilty of murdering boy at train station
    7. 7
      At least 30 migrants feared dead off Canary Islands
    8. 8
      Bank boss warns interest rate hike hard for many
    9. 9
      Ukraine strikes bridge to Crimea, says Russia
    10. 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

    1. ۱
      ایران در آستانه بحران بنزین؛ تابستانی دشوار برای دولت رئیسی در پیش است
    2. ۲
      پرونده حمید نوری؛ وکلای مدافع: نوری فریب خورد
    3. ۳
      اما رادوکانو: گاهی آرزو می‌کنم که ای کاش قهرمان اوپن آمریکا نمی‌شدم
    4. ۴
      ملی‌پوش سابق هلند برای چاقو زدن به فامیلش به ۱۸ ماه حبس محکوم شد
    5. ۵
      وزیر خارجه آمریکا در پکن؛ شی: پیشرفت‌هایی در روابط حاصل شده است، بلینکن: رقابت نباید بدل به دشمنی شود
    6. ۶
      سگم سرطان پستان مرا تشخیص داد
    7. ۷
      لوح ۲۸۰۰ ساله از ایتالیا به عراق بازگردانده شد
    8. ۸
      نمایندگان کشورها و نهادها در اجلاس سازمان ملل خواستار پایان یافتن سیاست‌های تبعیض‌آمیز طالبان علیه زنان شدند
    9. ۹
      چرا موبایل داغ می‌کند و چطور آن را خنک نگه داریم؟
    10. ۱۰
      هشتگ «داستان_ما_یکیست» در چهلمین سالگرد اعدام ۱۰ زن بهائی در شیراز