diff --git a/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js b/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js
index 310bb401f68..006836c36cd 100644
--- a/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js
+++ b/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.js
@@ -26,7 +26,7 @@ const RECENT_AUDIO_EPISODES = 'episodes-audio';
const PODCAST_LINKS = 'third-party';
const LATEST_MEDIA = 'latest';
const RECOMMENDATIONS = 'midarticle-mostread';
-const SCROLLABLE_PROMO = 'edoj';
+const ARTICLE_LINKS_BLOCK = 'edoj';
const BILLBOARD = 'billboard';
const SOCIAL_EMBED = 'social-consent-banner';
const LIVE_MEDIA = 'live-header-media';
@@ -52,7 +52,7 @@ export const COMPONENTS = {
RELATED_CONTENT,
RELATED_TOPICS,
SCROLLABLE_NAVIGATION,
- SCROLLABLE_PROMO,
+ ARTICLE_LINKS_BLOCK,
SHARE,
SOCIAL_EMBED,
TOP_STORIES,
diff --git a/scripts/bundleSize/bundleSizeConfig.js b/scripts/bundleSize/bundleSizeConfig.js
index f739f087d5d..309ec3f9d0b 100644
--- a/scripts/bundleSize/bundleSizeConfig.js
+++ b/scripts/bundleSize/bundleSizeConfig.js
@@ -9,5 +9,5 @@
export const VARIANCE = 5;
-export const MIN_SIZE = 922;
+export const MIN_SIZE = 921;
export const MAX_SIZE = 1292;
diff --git a/src/app/components/ArticleLinksBlock/Promo/index.styles.tsx b/src/app/components/ArticleLinksBlock/Promo/index.styles.tsx
new file mode 100644
index 00000000000..5c77dfa05ce
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/Promo/index.styles.tsx
@@ -0,0 +1,70 @@
+import { OPERA_MINI_CLASSNAME } from '#app/lib/utilities/addOperaMiniClassScript';
+import pixelsToRem from '#app/utilities/pixelsToRem';
+import { css, Theme } from '@emotion/react';
+
+export default {
+ link: ({ fontSizes, fontVariants, isDarkUi, palette }: Theme) =>
+ css({
+ ...fontSizes.pica,
+ ...fontVariants.serifBold,
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ textDecoration: 'none',
+ overflowX: 'hidden',
+ overflowY: 'hidden',
+ WebkitLineClamp: 4,
+ WebkitBoxOrient: 'vertical',
+ color: isDarkUi ? palette.GREY_10 : palette.GREY_8,
+
+ '&:hover, &:focus': {
+ textDecoration: 'underline',
+ },
+
+ '&:visited': {
+ color: palette.GREY_6,
+ },
+
+ '&::before': {
+ bottom: 0,
+ content: '""',
+ left: 0,
+ overflow: 'hidden',
+ position: 'absolute',
+ right: 0,
+ top: 0,
+ zIndex: 1,
+ },
+ }),
+ promoBox: ({ isDarkUi, mq, palette, spacings }: Theme) =>
+ css({
+ position: 'relative',
+ backgroundColor: isDarkUi ? palette.GREY_3 : palette.WHITE,
+ padding: `${spacings.DOUBLE}rem`,
+ paddingInlineEnd: `${pixelsToRem(38)}rem`,
+ height: 'auto',
+ display: 'block',
+ width: '100%',
+ [`.${OPERA_MINI_CLASSNAME} &`]: {
+ position: 'relative',
+ backgroundColor: isDarkUi ? palette.GREY_3 : palette.WHITE,
+ padding: `${spacings.DOUBLE}rem`,
+ marginBottom: `${spacings.DOUBLE}rem`,
+ width: `calc(100% - ${spacings.FULL}rem)`,
+ [mq.GROUP_2_MIN_WIDTH]: {
+ width: `calc(50% - ${spacings.DOUBLE}rem)`,
+ },
+ },
+ }),
+ timestamp: ({ isDarkUi, palette, spacings }: Theme) =>
+ css({
+ marginTop: `${spacings.FULL}rem`,
+ color: isDarkUi ? palette.GREY_6 : undefined,
+ }),
+ chevron: () =>
+ css({
+ position: 'absolute',
+ top: '50%',
+ transform: 'translateY(-50%)',
+ insetInlineEnd: `${pixelsToRem(12)}rem`,
+ }),
+};
diff --git a/src/app/components/ArticleLinksBlock/Promo/index.test.tsx b/src/app/components/ArticleLinksBlock/Promo/index.test.tsx
new file mode 100644
index 00000000000..c182248fd4f
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/Promo/index.test.tsx
@@ -0,0 +1,45 @@
+import { render } from '../../react-testing-library-with-providers';
+import { PromoSingleBlock, oneLinkWithTimestamp } from '../helpers/fixtureData';
+import Promo from '.';
+import { ServiceContextProvider } from '../../../contexts/ServiceContext';
+
+const ArticleLinksBlock = ({ block }) => (
+
+
+
+);
+
+describe('Article Links Block', () => {
+ it('should render a link', () => {
+ const { queryByRole } = render(
+ ,
+ );
+ expect(queryByRole('link')).toBeInTheDocument();
+ });
+
+ it('should extract and render the correct title', () => {
+ const { getByText } = render(
+ ,
+ );
+ expect(
+ getByText(
+ 'This is a very long headline. I am creating this for a test purpose. I love creating these type of tests. I really do not know what to write.',
+ ),
+ ).toBeTruthy();
+ });
+
+ it('should extract and render the correct href', () => {
+ const { queryByRole } = render(
+ ,
+ );
+ const link = queryByRole('link') as HTMLAnchorElement;
+ expect(link?.href).toEqual('https://www.bbc.com/mundo');
+ });
+
+ it('should render timestamp if timestamp is available', () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.getElementsByTagName('time')[0]).toBeInTheDocument();
+ });
+});
diff --git a/src/app/components/ArticleLinksBlock/Promo/index.tsx b/src/app/components/ArticleLinksBlock/Promo/index.tsx
new file mode 100644
index 00000000000..9676918d455
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/Promo/index.tsx
@@ -0,0 +1,56 @@
+import { use } from 'react';
+import filterForBlockType from '#lib/utilities/blockHandlers';
+import PromoTimestamp from '#components/Promo/timestamp';
+import { OptimoBlock } from '#app/models/types/optimo';
+import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler';
+import { Chevron, ChevronOrientation } from '#app/components/icons';
+import { ServiceContext } from '../../../contexts/ServiceContext';
+import styles from './index.styles';
+
+interface PromoProps {
+ block: OptimoBlock;
+ clickTracker?: ReturnType;
+}
+
+const Promo = ({ block, clickTracker }: PromoProps) => {
+ const { serviceDatetimeLocale, dir } = use(ServiceContext);
+
+ const textBlock = filterForBlockType(
+ (block?.model as { blocks?: Record })?.blocks || {},
+ 'text',
+ );
+ const aresLinkBlock = filterForBlockType(
+ (block?.model as { blocks?: Record })?.blocks || {},
+ 'aresLink',
+ );
+ const timestamp =
+ aresLinkBlock?.model?.blocks?.[0]?.model?.timestamp ?? undefined;
+
+ const href =
+ textBlock?.model?.blocks?.[0]?.model?.blocks?.[0]?.model?.locator ?? '';
+ const title =
+ textBlock?.model?.blocks?.[0]?.model?.blocks?.[0]?.model?.text ?? '';
+
+ return (
+
+ );
+};
+
+export default Promo;
diff --git a/src/app/components/ArticleLinksBlock/PromoList/index.styles.tsx b/src/app/components/ArticleLinksBlock/PromoList/index.styles.tsx
new file mode 100644
index 00000000000..b8cb3cb9c83
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/PromoList/index.styles.tsx
@@ -0,0 +1,60 @@
+import { OPERA_MINI_CLASSNAME } from '#app/lib/utilities/addOperaMiniClassScript';
+import pixelsToRem from '#app/utilities/pixelsToRem';
+import { css, Theme } from '@emotion/react';
+
+export default {
+ promo: ({ spacings }: Theme) =>
+ css({
+ '& ul': {
+ listStyle: 'none',
+ paddingInlineStart: '0',
+ margin: '0',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: `${spacings.FULL}rem`,
+ width: '100%',
+ },
+ [`.${OPERA_MINI_CLASSNAME} &`]: {
+ listStyle: 'none',
+ paddingInlineStart: '0',
+ margin: '0',
+ },
+ }),
+ list: ({ mq, spacings }: Theme) =>
+ css({
+ display: 'flex',
+ flexShrink: 0,
+ marginInline: `${spacings.FULL}rem`,
+ ':last-of-type': {
+ marginBottom: `${spacings.TRIPLE}rem`,
+ },
+ [mq.FORCED_COLOURS]: {
+ border: `solid ${pixelsToRem(3)}rem transparent`,
+ },
+
+ [mq.GROUP_2_MIN_WIDTH]: {
+ marginInline: `${spacings.DOUBLE}rem`,
+ },
+
+ [mq.GROUP_3_MIN_WIDTH]: {
+ marginInline: `${spacings.DOUBLE}rem`,
+ },
+
+ [mq.GROUP_4_MIN_WIDTH]: {
+ marginInline: 0,
+ },
+ [`.${OPERA_MINI_CLASSNAME} &`]: {
+ [mq.GROUP_0_MAX_WIDTH]: {
+ marginInline: `${spacings.FULL}rem`,
+ },
+
+ [mq.GROUP_2_MIN_WIDTH]: {
+ marginInline: `${spacings.DOUBLE}rem`,
+ },
+
+ [mq.GROUP_4_MIN_WIDTH]: {
+ marginInline: 0,
+ },
+ },
+ }),
+};
diff --git a/src/app/components/ArticleLinksBlock/PromoList/index.tsx b/src/app/components/ArticleLinksBlock/PromoList/index.tsx
new file mode 100644
index 00000000000..391b45d644b
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/PromoList/index.tsx
@@ -0,0 +1,35 @@
+import { OptimoBlock } from '#app/models/types/optimo';
+import { ViewTracker } from '#app/lib/analyticsUtils/types';
+import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler';
+import Promo from '../Promo';
+import styles from './index.styles';
+
+interface PromoListProps {
+ blocks: OptimoBlock[];
+ viewTracker?: ViewTracker;
+ clickTracker?: ReturnType;
+}
+
+const PromoList = ({ blocks, viewTracker, clickTracker }: PromoListProps) => {
+ const listBlocks = blocks.slice(0, 3);
+
+ return (
+
+
+ {listBlocks.map((block, index) => {
+ return (
+ -
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default PromoList;
diff --git a/src/app/components/ArticleLinksBlock/README.md b/src/app/components/ArticleLinksBlock/README.md
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/app/components/ArticleLinksBlock/__snapshots__/index.test.tsx.snap b/src/app/components/ArticleLinksBlock/__snapshots__/index.test.tsx.snap
new file mode 100644
index 00000000000..8a55fd91be0
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,1146 @@
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+
+exports[`Article Links Block Mid Page Article Links Block it should match a11y snapshot for list 1`] = `
+.emotion-0 {
+ position: relative;
+}
+
+.emotion-2 {
+ font-family: ReithSans,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ background-color: #FFFFFF;
+ border: 0.125rem solid #222222;
+ color: #222222;
+ display: block;
+ left: 0;
+ line-height: 1;
+ padding: 0.75rem;
+ position: absolute;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ top: 0;
+ z-index: 10;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-2 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-2 {
+ font-size: 0.8125rem;
+ line-height: 1rem;
+ }
+}
+
+.emotion-2:not(:focus):not(:active) {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+.emotion-4 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ font-family: ReithSans,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 400;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ background-color: #F6F6F6;
+ width: 100%;
+ height: 2rem;
+ color: #3F3F42;
+ padding: 0 0.5rem;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-4 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-4 {
+ font-size: 0.8125rem;
+ line-height: 1rem;
+ }
+}
+
+@media screen and (forced-colors: active) {
+ .emotion-4 {
+ border: solid 0.1875rem transparent;
+ border-bottom: transparent;
+ }
+}
+
+@media (max-width: 14.9375rem) {
+ .emotion-4 {
+ margin-inline: 0.5rem;
+ margin: 0;
+ }
+}
+
+@media (min-width: 25rem) {
+ .emotion-4 {
+ margin-inline: 1rem;
+ padding: 0 1rem;
+ margin: 0 -0.2rem;
+ }
+}
+
+.emotion-5 ul {
+ list-style: none;
+ -webkit-padding-start: 0;
+ padding-inline-start: 0;
+ margin: 0;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ gap: 0.5rem;
+ width: 100%;
+}
+
+.is-opera-mini .emotion-5 {
+ list-style: none;
+ -webkit-padding-start: 0;
+ padding-inline-start: 0;
+ margin: 0;
+}
+
+.emotion-6 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+ margin-inline: 0.5rem;
+}
+
+.emotion-6:last-of-type {
+ margin-bottom: 1.5rem;
+}
+
+@media screen and (forced-colors: active) {
+ .emotion-6 {
+ border: solid 0.1875rem transparent;
+ }
+}
+
+@media (min-width: 25rem) {
+ .emotion-6 {
+ margin-inline: 1rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-6 {
+ margin-inline: 1rem;
+ }
+}
+
+@media (min-width: 63rem) {
+ .emotion-6 {
+ margin-inline: 0;
+ }
+}
+
+@media (max-width: 14.9375rem) {
+ .is-opera-mini .emotion-6 {
+ margin-inline: 0.5rem;
+ }
+}
+
+@media (min-width: 25rem) {
+ .is-opera-mini .emotion-6 {
+ margin-inline: 1rem;
+ }
+}
+
+@media (min-width: 63rem) {
+ .is-opera-mini .emotion-6 {
+ margin-inline: 0;
+ }
+}
+
+.emotion-7 {
+ position: relative;
+ background-color: #FFFFFF;
+ padding: 1rem;
+ -webkit-padding-end: 2.375rem;
+ padding-inline-end: 2.375rem;
+ height: auto;
+ display: block;
+ width: 100%;
+}
+
+.is-opera-mini .emotion-7 {
+ position: relative;
+ background-color: #FFFFFF;
+ padding: 1rem;
+ margin-bottom: 1rem;
+ width: calc(100% - 0.5rem);
+}
+
+@media (min-width: 25rem) {
+ .is-opera-mini .emotion-7 {
+ width: calc(50% - 1rem);
+ }
+}
+
+.emotion-8 {
+ font-size: 0.9375rem;
+ line-height: 1.25rem;
+ font-family: ReithSerif,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ display: inline-block;
+ vertical-align: middle;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ color: #202224;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-8 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-8 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+.emotion-8:hover,
+.emotion-8:focus {
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+}
+
+.emotion-8:visited {
+ color: #545658;
+}
+
+.emotion-8::before {
+ bottom: 0;
+ content: "";
+ left: 0;
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 1;
+}
+
+.emotion-9 {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -moz-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ inset-inline-end: 0.75rem;
+}
+
+.emotion-18 {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+
+`;
+
+exports[`Article Links Block Mid Page Article Links Block it should match a11y snapshot for list with no title 1`] = `
+.emotion-0 {
+ position: relative;
+}
+
+.emotion-2 {
+ font-family: ReithSans,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ background-color: #FFFFFF;
+ border: 0.125rem solid #222222;
+ color: #222222;
+ display: block;
+ left: 0;
+ line-height: 1;
+ padding: 0.75rem;
+ position: absolute;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ top: 0;
+ z-index: 10;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-2 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-2 {
+ font-size: 0.8125rem;
+ line-height: 1rem;
+ }
+}
+
+.emotion-2:not(:focus):not(:active) {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+.emotion-4 {
+ background: #F6F6F6;
+ padding: 0 0.5rem 1rem;
+ margin: 0;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ overflow-x: auto;
+ width: 100%;
+}
+
+@media (min-width: 25rem) {
+ .emotion-4 {
+ padding: 0 1rem 1rem;
+ margin: 0 -0.2rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-4 {
+ padding: 0 1rem 1rem;
+ }
+}
+
+.emotion-5 {
+ position: relative;
+ background-color: #FFFFFF;
+ padding: 1rem;
+ -webkit-padding-end: 2.375rem;
+ padding-inline-end: 2.375rem;
+ height: auto;
+ display: block;
+ width: 100%;
+}
+
+.is-opera-mini .emotion-5 {
+ position: relative;
+ background-color: #FFFFFF;
+ padding: 1rem;
+ margin-bottom: 1rem;
+ width: calc(100% - 0.5rem);
+}
+
+@media (min-width: 25rem) {
+ .is-opera-mini .emotion-5 {
+ width: calc(50% - 1rem);
+ }
+}
+
+.emotion-6 {
+ font-size: 0.9375rem;
+ line-height: 1.25rem;
+ font-family: ReithSerif,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ display: inline-block;
+ vertical-align: middle;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ color: #202224;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-6 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-6 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+.emotion-6:hover,
+.emotion-6:focus {
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+}
+
+.emotion-6:visited {
+ color: #545658;
+}
+
+.emotion-6::before {
+ bottom: 0;
+ content: "";
+ left: 0;
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 1;
+}
+
+.emotion-7 {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -moz-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ inset-inline-end: 0.75rem;
+}
+
+.emotion-8 {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+
+`;
+
+exports[`Article Links Block Mid Page Article Links Block it should match a11y snapshot for single card 1`] = `
+.emotion-0 {
+ position: relative;
+}
+
+.emotion-2 {
+ font-family: ReithSans,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ background-color: #FFFFFF;
+ border: 0.125rem solid #222222;
+ color: #222222;
+ display: block;
+ left: 0;
+ line-height: 1;
+ padding: 0.75rem;
+ position: absolute;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ top: 0;
+ z-index: 10;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-2 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-2 {
+ font-size: 0.8125rem;
+ line-height: 1rem;
+ }
+}
+
+.emotion-2:not(:focus):not(:active) {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+.emotion-4 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ font-family: ReithSans,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 400;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ background-color: #F6F6F6;
+ width: 100%;
+ height: 2rem;
+ color: #3F3F42;
+ padding: 0 0.5rem;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-4 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-4 {
+ font-size: 0.8125rem;
+ line-height: 1rem;
+ }
+}
+
+@media screen and (forced-colors: active) {
+ .emotion-4 {
+ border: solid 0.1875rem transparent;
+ border-bottom: transparent;
+ }
+}
+
+@media (max-width: 14.9375rem) {
+ .emotion-4 {
+ margin-inline: 0.5rem;
+ margin: 0;
+ }
+}
+
+@media (min-width: 25rem) {
+ .emotion-4 {
+ margin-inline: 1rem;
+ padding: 0 1rem;
+ margin: 0 -0.2rem;
+ }
+}
+
+.emotion-5 {
+ background: #F6F6F6;
+ padding: 0 0.5rem 1rem;
+ margin: 0;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ overflow-x: auto;
+ width: 100%;
+}
+
+@media (min-width: 25rem) {
+ .emotion-5 {
+ padding: 0 1rem 1rem;
+ margin: 0 -0.2rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-5 {
+ padding: 0 1rem 1rem;
+ }
+}
+
+.emotion-6 {
+ position: relative;
+ background-color: #FFFFFF;
+ padding: 1rem;
+ -webkit-padding-end: 2.375rem;
+ padding-inline-end: 2.375rem;
+ height: auto;
+ display: block;
+ width: 100%;
+}
+
+.is-opera-mini .emotion-6 {
+ position: relative;
+ background-color: #FFFFFF;
+ padding: 1rem;
+ margin-bottom: 1rem;
+ width: calc(100% - 0.5rem);
+}
+
+@media (min-width: 25rem) {
+ .is-opera-mini .emotion-6 {
+ width: calc(50% - 1rem);
+ }
+}
+
+.emotion-7 {
+ font-size: 0.9375rem;
+ line-height: 1.25rem;
+ font-family: ReithSerif,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ display: inline-block;
+ vertical-align: middle;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ color: #202224;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-7 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-7 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+.emotion-7:hover,
+.emotion-7:focus {
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+}
+
+.emotion-7:visited {
+ color: #545658;
+}
+
+.emotion-7::before {
+ bottom: 0;
+ content: "";
+ left: 0;
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 1;
+}
+
+.emotion-8 {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -moz-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ inset-inline-end: 0.75rem;
+}
+
+.emotion-9 {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+
+`;
+
+exports[`Article Links Block Mid Page Article Links Block it should match snapshot when in dark ui mode 1`] = `
+.emotion-0 {
+ position: relative;
+}
+
+.emotion-2 {
+ font-family: ReithSans,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ background-color: #FFFFFF;
+ border: 0.125rem solid #222222;
+ color: #222222;
+ display: block;
+ left: 0;
+ line-height: 1;
+ padding: 0.75rem;
+ position: absolute;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ top: 0;
+ z-index: 10;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-2 {
+ font-size: 0.875rem;
+ line-height: 1.125rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-2 {
+ font-size: 0.8125rem;
+ line-height: 1rem;
+ }
+}
+
+.emotion-2:not(:focus):not(:active) {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+.emotion-4 {
+ background: #F6F6F6;
+ padding: 0 0.5rem 1rem;
+ margin: 0;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ overflow-x: auto;
+ width: 100%;
+}
+
+@media (min-width: 25rem) {
+ .emotion-4 {
+ padding: 0 1rem 1rem;
+ margin: 0 -0.2rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-4 {
+ padding: 0 1rem 1rem;
+ }
+}
+
+.emotion-5 {
+ position: relative;
+ background-color: #E6E8EA;
+ padding: 1rem;
+ -webkit-padding-end: 2.375rem;
+ padding-inline-end: 2.375rem;
+ height: auto;
+ display: block;
+ width: 100%;
+}
+
+.is-opera-mini .emotion-5 {
+ position: relative;
+ background-color: #E6E8EA;
+ padding: 1rem;
+ margin-bottom: 1rem;
+ width: calc(100% - 0.5rem);
+}
+
+@media (min-width: 25rem) {
+ .is-opera-mini .emotion-5 {
+ width: calc(50% - 1rem);
+ }
+}
+
+.emotion-6 {
+ font-size: 0.9375rem;
+ line-height: 1.25rem;
+ font-family: ReithSerif,Helvetica,Arial,sans-serif;
+ font-style: normal;
+ font-weight: 700;
+ display: inline-block;
+ vertical-align: middle;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ color: #141414;
+}
+
+@media (min-width: 20rem) and (max-width: 37.4375rem) {
+ .emotion-6 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+@media (min-width: 37.5rem) {
+ .emotion-6 {
+ font-size: 1rem;
+ line-height: 1.25rem;
+ }
+}
+
+.emotion-6:hover,
+.emotion-6:focus {
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+}
+
+.emotion-6:visited {
+ color: #545658;
+}
+
+.emotion-6::before {
+ bottom: 0;
+ content: "";
+ left: 0;
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 1;
+}
+
+.emotion-7 {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -moz-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ inset-inline-end: 0.75rem;
+}
+
+.emotion-8 {
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ clip: rect(1px, 1px, 1px, 1px);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ width: 1px;
+ margin: 0;
+}
+
+
+`;
diff --git a/src/app/legacy/components/ScrollablePromo/fixtures.js b/src/app/components/ArticleLinksBlock/fixtures.ts
similarity index 100%
rename from src/app/legacy/components/ScrollablePromo/fixtures.js
rename to src/app/components/ArticleLinksBlock/fixtures.ts
diff --git a/src/app/legacy/components/ScrollablePromo/helpers/fixtureData.js b/src/app/components/ArticleLinksBlock/helpers/fixtureData.ts
similarity index 100%
rename from src/app/legacy/components/ScrollablePromo/helpers/fixtureData.js
rename to src/app/components/ArticleLinksBlock/helpers/fixtureData.ts
diff --git a/src/app/components/ArticleLinksBlock/index.stories.tsx b/src/app/components/ArticleLinksBlock/index.stories.tsx
new file mode 100644
index 00000000000..ca2c893654c
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/index.stories.tsx
@@ -0,0 +1,68 @@
+import ArticleLinksBlock from '.';
+import {
+ threeLinks,
+ oneLinkOnly,
+ oneLinkWithNoTitle,
+ oneLinkWithTimestamp,
+ moreThanThreeLinks,
+ twoLinksWithNoImages,
+ truncatedTextInSingleLink,
+ arabicText,
+} from './helpers/fixtureData';
+import metadata from './metadata.json';
+import readme from './README.md';
+import { OptimoBlock } from '#app/models/types/optimo';
+import { Direction } from '#app/models/types/global';
+import { GREY_2 } from '../ThemeProvider/palette';
+
+interface Props {
+ blocks: OptimoBlock[];
+ dir?: Direction;
+}
+
+const Component = ({ blocks, dir = 'ltr' }: Props) => (
+
+);
+
+export default {
+ title: 'Components/Article Links Block',
+ Component,
+ parameters: {
+ docs: { readme },
+ metadata,
+ },
+};
+
+export const ThreeLinks = () => ;
+
+export const OnlyOneLink = () => ;
+
+export const OneLinkWithNoTitle = () => (
+
+);
+
+export const MoreThanThreeLinks = () => (
+
+);
+
+export const NoImagesInData = () => ;
+
+export const TruncatedTextInSingleLink = () => (
+
+);
+
+export const ArabicText = () => ;
+
+ArabicText.globals = {
+ service: { service: 'arabic' },
+};
+
+export const WithTimestamp = () => ;
diff --git a/src/app/components/ArticleLinksBlock/index.styles.tsx b/src/app/components/ArticleLinksBlock/index.styles.tsx
new file mode 100644
index 00000000000..14633b0236f
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/index.styles.tsx
@@ -0,0 +1,54 @@
+import pixelsToRem from '#app/utilities/pixelsToRem';
+import { css, Theme } from '@emotion/react';
+
+export default {
+ promoContainer: ({ palette, mq, spacings }: Theme) =>
+ css({
+ background: palette.GREY_2,
+ padding: `0 ${spacings.FULL}rem ${spacings.DOUBLE}rem`,
+ margin: 0,
+ display: 'flex',
+ overflowX: 'auto',
+ width: '100%',
+ [mq.GROUP_2_MIN_WIDTH]: {
+ padding: `0 ${spacings.DOUBLE}rem ${spacings.DOUBLE}rem`,
+ margin: '0 -0.2rem',
+ },
+ [mq.GROUP_3_MIN_WIDTH]: {
+ padding: `0 ${spacings.DOUBLE}rem ${spacings.DOUBLE}rem`,
+ },
+ }),
+ labelComponent: ({
+ isDarkUi,
+ fontSizes,
+ fontVariants,
+ mq,
+ palette,
+ spacings,
+ }: Theme) =>
+ css({
+ ...fontSizes.brevier,
+ ...fontVariants.sansRegular,
+ display: 'flex',
+ alignItems: 'center',
+ backgroundColor: palette.GREY_2,
+ width: '100%',
+ height: `${spacings.QUADRUPLE}rem`,
+ color: isDarkUi ? palette.GREY_2 : palette.SHADOW,
+ padding: `0 ${spacings.FULL}rem`,
+ [mq.FORCED_COLOURS]: {
+ border: `solid ${pixelsToRem(3)}rem transparent`,
+ borderBottom: 'transparent',
+ },
+
+ [mq.GROUP_0_MAX_WIDTH]: {
+ marginInline: `${spacings.FULL}rem`,
+ margin: 0,
+ },
+ [mq.GROUP_2_MIN_WIDTH]: {
+ marginInline: `${spacings.DOUBLE}rem`,
+ padding: `0 ${spacings.DOUBLE}rem`,
+ margin: '0 -0.2rem',
+ },
+ }),
+};
diff --git a/src/app/components/ArticleLinksBlock/index.test.tsx b/src/app/components/ArticleLinksBlock/index.test.tsx
new file mode 100644
index 00000000000..5688de0c8b4
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/index.test.tsx
@@ -0,0 +1,166 @@
+import * as viewTracking from '#hooks/useViewTracker';
+import * as clickTracking from '#hooks/useClickTrackerHandler';
+import { render } from '../react-testing-library-with-providers';
+import {
+ threeLinks,
+ oneLinkOnly,
+ oneLinkWithNoTitle,
+ moreThanThreeLinks,
+} from './helpers/fixtureData';
+import ArticleLinksBlock from '.';
+import { edOjA, edOjB } from './fixtures';
+import { MEDIA_ARTICLE_PAGE } from '../../routes/utils/pageTypes';
+
+describe('Article Links Block', () => {
+ describe('Mid Page Article Links Block', () => {
+ it('should return null if no data is passed', () => {
+ const { container } = render();
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ it('should render max 3 promo items', () => {
+ const { getAllByRole } = render(
+ ,
+ );
+ expect(getAllByRole('listitem').length).toEqual(3);
+ });
+
+ it('should render single promo item', () => {
+ const { container } = render();
+ expect(container.childElementCount).toEqual(1);
+ });
+
+ it('should render single promo item with a title', () => {
+ const { container, getByTestId } = render(
+ ,
+ );
+ expect(container.childElementCount).toEqual(1);
+ expect(getByTestId('eoj-recommendations-heading')).toBeInTheDocument();
+ });
+
+ it('should render single promo item without a title', () => {
+ const { container, queryByTestId } = render(
+ ,
+ );
+ expect(container.childElementCount).toEqual(1);
+ expect(
+ queryByTestId('eoj-recommendations-heading'),
+ ).not.toBeInTheDocument();
+ });
+
+ it('should not render a list when there is only one promo', () => {
+ const { queryByRole } = render(
+ ,
+ );
+
+ expect(queryByRole('list')).not.toBeInTheDocument();
+ expect(queryByRole('listitem')).not.toBeInTheDocument();
+ });
+
+ it('should render unordered list if more than 1 item', () => {
+ const { queryByRole, getAllByRole } = render(
+ ,
+ );
+ expect(queryByRole('list')).toBeInTheDocument();
+ expect(getAllByRole('listitem').length).toEqual(3);
+ });
+
+ describe('event tracking in editorial onward journeys', () => {
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should call the view tracking hook with the correct params with one editorial onward journey', () => {
+ const viewTrackerSpy = jest.spyOn(viewTracking, 'default');
+
+ render(
+ ,
+ );
+
+ expect(viewTrackerSpy).toHaveBeenCalledWith({
+ componentName: 'edoj1',
+ format: 'CHD=edoj',
+ });
+ });
+
+ it('should call the view tracking hook with the correct params with multiple editorial onward journeys', () => {
+ const viewTrackerSpy = jest.spyOn(viewTracking, 'default');
+ render(
+ ,
+ );
+ render(
+ ,
+ );
+
+ expect(viewTrackerSpy).toHaveBeenCalledTimes(4);
+ expect(viewTrackerSpy).toHaveBeenCalledWith({
+ componentName: 'edoj1',
+ format: 'CHD=edoj',
+ });
+ expect(viewTrackerSpy).toHaveBeenCalledWith({
+ componentName: 'edoj2',
+ format: 'CHD=edoj',
+ });
+ });
+
+ it('should call the click tracking hook with one editorial onward journey', () => {
+ const clickTrackerSpy = jest.spyOn(clickTracking, 'default');
+ render(
+ ,
+ );
+
+ expect(clickTrackerSpy).toHaveBeenCalledWith({
+ componentName: 'edoj1',
+ format: 'CHD=edoj',
+ });
+ });
+
+ it('should call the click tracking hook with multiple editorial onward journeys', () => {
+ const clickTrackerSpy = jest.spyOn(clickTracking, 'default');
+ render(
+ ,
+ );
+ render(
+ ,
+ );
+
+ expect(clickTrackerSpy).toHaveBeenCalledTimes(4);
+ expect(clickTrackerSpy).toHaveBeenCalledWith({
+ componentName: 'edoj1',
+ format: 'CHD=edoj',
+ });
+ expect(clickTrackerSpy).toHaveBeenCalledWith({
+ componentName: 'edoj2',
+ format: 'CHD=edoj',
+ });
+ });
+ });
+
+ it('it should match a11y snapshot for single card', () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
+
+ it('it should match a11y snapshot for list', () => {
+ const { container } = render();
+ expect(container).toMatchSnapshot();
+ });
+
+ it('it should match a11y snapshot for list with no title', () => {
+ const { container } = render(
+ ,
+ );
+ expect(container).toMatchSnapshot();
+ });
+
+ it('it should match snapshot when in dark ui mode', () => {
+ const { container } = render(
+ ,
+ {
+ pageType: MEDIA_ARTICLE_PAGE,
+ },
+ );
+ expect(container).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/app/components/ArticleLinksBlock/index.tsx b/src/app/components/ArticleLinksBlock/index.tsx
new file mode 100644
index 00000000000..99243a22d87
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/index.tsx
@@ -0,0 +1,102 @@
+import { use } from 'react';
+import useViewTracker from '#hooks/useViewTracker';
+import idSanitiser from '#lib/utilities/idSanitiser';
+import { OptimoBlock } from '#app/models/types/optimo';
+import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler';
+import SkipLinkWrapper from '#app/legacy/components/SkipLinkWrapper';
+import { ServiceContext } from '../../contexts/ServiceContext';
+import Promo from './Promo';
+import PromoList from './PromoList';
+import styles from './index.styles';
+
+interface ArticleLinksBlockProps {
+ blocks: OptimoBlock[];
+ blockGroupIndex?: number | null;
+}
+
+const ArticleLinksBlock = ({
+ blocks,
+ blockGroupIndex = null,
+}: ArticleLinksBlockProps) => {
+ const { translations, recommendations, service } = use(ServiceContext);
+
+ const eventTrackingData = {
+ componentName: `edoj${blockGroupIndex}`,
+ format: 'CHD=edoj',
+ };
+
+ const viewTracker = useViewTracker(eventTrackingData);
+ const clickTracker = useClickTrackerHandler(eventTrackingData);
+
+ if (!blocks || blocks.length === 0) return null;
+
+ const title =
+ blocks[0]?.type === 'title'
+ ? // @ts-expect-error - deeply nested
+ (blocks[0]?.model?.blocks?.[0].model?.blocks?.[0]?.model?.text ??
+ undefined)
+ : undefined;
+
+ const blocksWithoutTitle =
+ blocks[0]?.type === 'title' ? blocks.slice(1) : blocks;
+
+ const isSingleItem = blocksWithoutTitle.length === 1;
+
+ const ariaLabel = title ? idSanitiser(title) : undefined;
+
+ const a11yAttributes = {
+ role: 'region',
+ ...(ariaLabel
+ ? { 'aria-labelledby': ariaLabel }
+ : {
+ 'aria-label': translations?.relatedContent ?? 'Related Content',
+ }),
+ };
+
+ const { skipLink } = recommendations || {};
+
+ const { text, endTextVisuallyHidden } = skipLink || {
+ text: 'Skip content and continue reading',
+ endTextVisuallyHidden: 'End of content',
+ };
+
+ const terms = { '%title%': title || 'content' };
+
+ const endTextId = `end-of-article-links-block`;
+
+ const skipLinkProps = {
+ endTextId,
+ terms,
+ text: text.replace('%title%', terms['%title%']),
+ endTextVisuallyHidden,
+ };
+
+ return (
+
+
+ {typeof title === 'string' && title.length > 0 && (
+
+ {title}
+
+ )}
+ {isSingleItem ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+export default ArticleLinksBlock;
diff --git a/src/app/components/ArticleLinksBlock/metadata.json b/src/app/components/ArticleLinksBlock/metadata.json
new file mode 100644
index 00000000000..1335762526c
--- /dev/null
+++ b/src/app/components/ArticleLinksBlock/metadata.json
@@ -0,0 +1,29 @@
+{
+ "alpha": false,
+ "lastUpdated": {
+ "day": 18,
+ "month": "December",
+ "year": 2025
+ },
+ "uxAccessibilityDoc": {
+ "done": false,
+ "reference": {
+ "url": "",
+ "label": "Screen Reader UX"
+ }
+ },
+ "acceptanceCriteria": {
+ "done": false,
+ "reference": {
+ "url": "",
+ "label": "Accessibility Acceptance Criteria"
+ }
+ },
+ "swarm": {
+ "done": false,
+ "reference": {
+ "url": "",
+ "label": "Accessibility Swarm Notes"
+ }
+ }
+}
diff --git a/src/app/legacy/components/ScrollablePromo/Promo/index.jsx b/src/app/legacy/components/ScrollablePromo/Promo/index.jsx
deleted file mode 100644
index b123a3606b7..00000000000
--- a/src/app/legacy/components/ScrollablePromo/Promo/index.jsx
+++ /dev/null
@@ -1,198 +0,0 @@
-import { use } from 'react';
-import styled from '@emotion/styled';
-import path from 'ramda/src/path';
-import { Link } from '#psammead/psammead-story-promo/src';
-import {
- GEL_SPACING,
- GEL_SPACING_DBL,
- GEL_SPACING_TRPL,
-} from '#psammead/gel-foundations/src/spacings';
-import {
- GEL_GROUP_0_SCREEN_WIDTH_MIN,
- GEL_GROUP_1_SCREEN_WIDTH_MIN,
- GEL_GROUP_2_SCREEN_WIDTH_MIN,
- GEL_GROUP_3_SCREEN_WIDTH_MIN,
- GEL_GROUP_4_SCREEN_WIDTH_MIN,
-} from '#psammead/gel-foundations/src/breakpoints';
-import filterForBlockType from '#lib/utilities/blockHandlers';
-import useOperaMiniDetection from '#hooks/useOperaMiniDetection';
-import PromoTimestamp from '#components/Promo/timestamp';
-import LiveLabel from '../../../../components/LiveLabel';
-import { ServiceContext } from '../../../../contexts/ServiceContext';
-
-const StyledLink = styled(Link)`
- ${({ theme: { fontSizes } }) => fontSizes.pica};
- ${({ theme: { fontVariants } }) => fontVariants.serifBold};
- width: 100%;
- text-decoration: none;
-
- overflow-x: hidden;
- overflow-y: hidden;
-
- ${({ experimentVariant }) =>
- !experimentVariant &&
- ` -webkit-line-clamp: 4;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- `}
-
- &:hover,
- &:focus {
- text-decoration: underline;
- }
-
- color: ${({ theme }) =>
- theme.isDarkUi ? theme.palette.GREY_10 : theme.palette.GREY_8};
- &:visited {
- color: ${props => props.theme.palette.GREY_6};
- }
-`;
-
-const PromoBox = styled.div`
- position: relative;
- background-color: ${({ theme }) =>
- theme.isDarkUi ? theme.palette.GREY_3 : theme.palette.WHITE};
- padding: ${GEL_SPACING_DBL};
- margin-bottom: ${GEL_SPACING_TRPL};
- height: auto;
- @media (min-width: ${GEL_GROUP_0_SCREEN_WIDTH_MIN}) {
- width: 14.8125rem;
- }
- @media (min-width: ${GEL_GROUP_3_SCREEN_WIDTH_MIN}) {
- width: 11.125rem;
- }
- @media (min-width: ${GEL_GROUP_4_SCREEN_WIDTH_MIN}) {
- width: 12.6875rem;
- }
- ${({ experimentVariant }) =>
- experimentVariant &&
- `
- display: block;
- margin-bottom: 0;
- @media (min-width: ${GEL_GROUP_0_SCREEN_WIDTH_MIN}) {
- width: 11.5rem;
- }
- @media (min-width: ${GEL_GROUP_1_SCREEN_WIDTH_MIN}) {
- width: 17rem;
- }
- @media (min-width: ${GEL_GROUP_3_SCREEN_WIDTH_MIN}) {
- width: 15.5rem;
- }
- padding: ${GEL_SPACING};
- `}
-`;
-
-const OperaPromoBox = styled.div`
- position: relative;
- background-color: ${({ theme }) =>
- theme.isDarkUi ? theme.palette.GREY_3 : theme.palette.WHITE};
- padding: ${GEL_SPACING_DBL};
- margin-bottom: ${GEL_SPACING_DBL};
- width: calc(100% - ${GEL_SPACING});
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}) {
- width: calc(100% - ${GEL_SPACING_DBL});
- }
-`;
-
-const TimeStamp = styled(PromoTimestamp)`
- margin-top: ${GEL_SPACING};
- color: ${({ theme }) => theme.isDarkUi && theme.palette.GREY_6};
-`;
-
-const Promo = ({ block, experimentVariant, clickTracker }) => {
- const { script, service, serviceDatetimeLocale } = use(ServiceContext);
- let title;
- let href;
- let textBlock;
- let aresLinkBlock;
- let timestamp;
- let isLive;
-
- switch (experimentVariant) {
- case 'top-bar-top-stories':
- case 'read-more-a-and-top-stories': {
- const overtypedHeadline = block?.headlines?.overtyped ?? '';
- const mainHeadline = block?.headlines?.headline ?? '';
- const headlineBlockText =
- block?.headlines?.promoHeadline?.blocks?.[0]?.model?.blocks?.[0]?.model
- ?.text ?? '';
- const name = block?.name ?? '';
-
- title =
- overtypedHeadline ||
- mainHeadline ||
- headlineBlockText ||
- name ||
- block.headline ||
- '';
-
- const canonicalUrl = block?.locators?.canonicalUrl ?? '';
- const assetUri = block?.locators?.assetUri ?? '';
- const uri = block?.uri ?? '';
-
- href =
- canonicalUrl ||
- assetUri ||
- uri ||
- (block.destinationUrl
- ? `https://www.bbc.com${block.destinationUrl}`
- : '');
- isLive = block.isLive;
- break;
- }
- case 'top-bar-most-read':
- title = block.title;
- href = block.href;
- break;
- default:
- textBlock = filterForBlockType(block?.model?.blocks || {}, 'text');
- aresLinkBlock = filterForBlockType(
- block?.model?.blocks || {},
- 'aresLink',
- );
- timestamp = path(
- ['model', 'blocks', '0', 'model', 'timestamp'],
- aresLinkBlock,
- );
- href =
- path(
- ['model', 'blocks', '0', 'model', 'blocks', '0', 'model', 'locator'],
- textBlock,
- ) || '';
- title =
- path(
- ['model', 'blocks', '0', 'model', 'blocks', '0', 'model', 'text'],
- textBlock,
- ) || '';
- break;
- }
-
- const isOperaMini = useOperaMiniDetection();
-
- const WrapperPromoBox = isOperaMini ? OperaPromoBox : PromoBox;
-
- return (
-
-
- {isLive && }
- {title}
-
- {timestamp && !experimentVariant && (
-
- {timestamp}
-
- )}
-
- );
-};
-
-export default Promo;
diff --git a/src/app/legacy/components/ScrollablePromo/Promo/index.test.jsx b/src/app/legacy/components/ScrollablePromo/Promo/index.test.jsx
deleted file mode 100644
index 69e4cd67c04..00000000000
--- a/src/app/legacy/components/ScrollablePromo/Promo/index.test.jsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { render } from '../../../../components/react-testing-library-with-providers';
-import {
- PromoSingleBlock,
- oneLinkWithTimestamp,
- topStoriesBlocks,
- mostReadBlocks,
- topStoriesBlocksWithLiveItem,
-} from '../helpers/fixtureData';
-import Promo from '.';
-import { ServiceContextProvider } from '../../../../contexts/ServiceContext';
-
-const ScrollablePromo = ({ block, experimentVariant }) => (
-
- {}}
- experimentVariant={experimentVariant}
- />
-
-);
-
-describe('ScrollablePromo', () => {
- it('should render a link', () => {
- const { queryByRole } = render(
- ,
- );
- expect(queryByRole('link')).toBeInTheDocument();
- });
-
- it('should extract and render the correct title', () => {
- const { getByText } = render();
- expect(
- getByText(
- 'This is a very long headline. I am creating this for a test purpose. I love creating these type of tests. I really do not know what to write.',
- ),
- ).toBeTruthy();
- });
-
- it('should extract and render the correct href', () => {
- const { queryByRole } = render(
- ,
- );
- expect(queryByRole('link').href).toEqual('https://www.bbc.com/mundo');
- });
-
- it('should render timestamp if timestamp is available', () => {
- const { container } = render(
- ,
- );
- expect(container.getElementsByTagName('time')[0]).toBeInTheDocument();
- });
-
- describe('OJ Top Bar Promo', () => {
- it('should display Top Stories content when experimentVariant is top-bar-top-stories', () => {
- const { container } = render(
- ,
- );
- const expectedHeadline =
- topStoriesBlocks[0].headlines.promoHeadline.blocks[0].model.blocks[0]
- .model.text;
- expect(container).toHaveTextContent(expectedHeadline);
- });
-
- it('should display Top Stories content when experimentVariant is read-more-a-and-top-stories', () => {
- const { container } = render(
- ,
- );
- const expectedHeadline =
- topStoriesBlocks[0].headlines.promoHeadline.blocks[0].model.blocks[0]
- .model.text;
- expect(container).toHaveTextContent(expectedHeadline);
- });
-
- it('should display Most Read content when experimentVariant is top-bar-most-read', () => {
- const { container } = render(
- ,
- );
- const expectedHeadline = mostReadBlocks[0].title;
- expect(container).toHaveTextContent(expectedHeadline);
- });
-
- it('should render a link on Top Stories article headline when experimentVariant is top-bar-top-stories', () => {
- const { queryByRole } = render(
- ,
- );
- expect(queryByRole('link')).toBeInTheDocument();
- });
-
- it('should render a link on Most Read article headline when experimentVariant is top-bar-most-read', () => {
- const { queryByRole } = render(
- ,
- );
- expect(queryByRole('link')).toBeInTheDocument();
- });
-
- it('should not display a timestamp when experimentVariant is top-bar-top-stories or top-bar-most-read', () => {
- const { queryByTestId } = render(
- ,
- );
- expect(queryByTestId('timestamp')).not.toBeInTheDocument();
- });
-
- it('should display a LiveLabel when returning Top Stories', () => {
- const { container } = render(
- ,
- );
- expect(
- container.querySelector('[class*="liveLabelPulse"]'),
- ).toBeInTheDocument();
- expect(
- container.querySelector('[class*="liveLabelText"]'),
- ).toBeInTheDocument();
- });
- });
-});
diff --git a/src/app/legacy/components/ScrollablePromo/PromoList/index.jsx b/src/app/legacy/components/ScrollablePromo/PromoList/index.jsx
deleted file mode 100644
index aa8cb854588..00000000000
--- a/src/app/legacy/components/ScrollablePromo/PromoList/index.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { use } from 'react';
-import styled from '@emotion/styled';
-import {
- GEL_SPACING,
- GEL_SPACING_DBL,
-} from '#psammead/gel-foundations/src/spacings';
-import {
- GEL_GROUP_0_SCREEN_WIDTH_MIN,
- GEL_GROUP_2_SCREEN_WIDTH_MIN,
- GEL_GROUP_4_SCREEN_WIDTH_MIN,
-} from '#psammead/gel-foundations/src/breakpoints';
-import useOperaMiniDetection from '#hooks/useOperaMiniDetection';
-import { ServiceContext } from '../../../../contexts/ServiceContext';
-import Promo from '../Promo';
-
-const StandardScrollPromo = styled.ul`
- list-style: none;
- ${({ dir }) => `padding-${dir === 'ltr' ? 'left' : 'right'}: 0;`}
- margin: 0;
- display: flex;
- overflow-x: scroll;
- /* Avoid using smooth scrolling as it causes accessibility issues */
- scroll-behavior: auto;
- -webkit-overflow-scrolling: touch;
-
- /* Hide scrollbar */
- scrollbar-width: none;
- -ms-overflow-style: none;
- &::-webkit-scrollbar {
- display: none;
- }
-`;
-
-const OperaScrollPromo = styled.ul`
- list-style: none;
- ${({ dir }) => `padding-${dir === 'ltr' ? 'left' : 'right'}: 0;`}
- margin: 0;
-`;
-
-const StyledList = styled.li`
- display: flex;
- flex-shrink: 0;
-
- ${({ dir, experimentVariant }) =>
- `
- @media (min-width: ${GEL_GROUP_0_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? 'left' : 'right'}: ${GEL_SPACING};
- &:first-child {
- margin-${dir === 'ltr' ? 'left' : 'right'}: ${experimentVariant && experimentVariant !== 'off' ? 0 : GEL_SPACING};
- }
- &:last-child {
- margin-${dir === 'ltr' ? 'right' : 'left'}: ${GEL_SPACING};
- }
- }
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: ${GEL_SPACING_DBL};
-
- &:first-child {
- margin-${dir === 'ltr' ? 'left' : 'right'}: ${experimentVariant && experimentVariant !== 'off' ? 0 : GEL_SPACING_DBL};
- }
- }
- @media (min-width: ${GEL_GROUP_4_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: ${GEL_SPACING_DBL};
- &:first-child {
- margin-${dir === 'ltr' ? 'left' : 'right'}: 0;
- }
- }
- `}
-`;
-
-const OperaStyledList = styled.li`
- ${({ dir }) => `@media (min-width: ${GEL_GROUP_0_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? 'left' : 'right'}: ${GEL_SPACING};
- }
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: ${GEL_SPACING_DBL};
- }
- @media (min-width: ${GEL_GROUP_4_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: 0;}`}
-`;
-
-const PromoList = ({
- blocks,
- experimentVariant,
- viewTracker,
- clickTracker,
- a11yAttributes,
-}) => {
- const { dir } = use(ServiceContext);
- const isOperaMini = useOperaMiniDetection();
- const listBlocks =
- experimentVariant === 'top-bar-most-read'
- ? blocks.slice(0, 5)
- : blocks.slice(0, 3);
-
- const ScrollPromo = isOperaMini ? OperaScrollPromo : StandardScrollPromo;
- const List = isOperaMini ? OperaStyledList : StyledList;
-
- return (
-
- {listBlocks.map((block, index) => {
- return (
-
-
-
- );
- })}
-
- );
-};
-
-export default PromoList;
diff --git a/src/app/legacy/components/ScrollablePromo/__snapshots__/index.test.jsx.snap b/src/app/legacy/components/ScrollablePromo/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index 93894e197a0..00000000000
--- a/src/app/legacy/components/ScrollablePromo/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,1110 +0,0 @@
-// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-
-exports[`ScrollablePromo Mid Page ScrollablePromo it should match a11y snapshot for list 1`] = `
-@media (max-width: 14.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- margin-left: 20%;
- }
-}
-
-@media (min-width: 80rem) {
- .emotion-0 {
- margin-left: 40%;
- }
-}
-
-@supports (display: grid) {
- .emotion-0 {
- display: block;
- width: initial;
- margin: 0;
- }
-
- @media (max-width: 14.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 2;
- }
- }
-
- @media (min-width: 80rem) {
- .emotion-0 {
- grid-template-columns: repeat(10, 1fr);
- grid-column-end: span 10;
- grid-column-start: 5;
- }
- }
-}
-
-.emotion-2 {
- display: block;
- font-size: 1.125rem;
- line-height: 1.375rem;
- font-family: ReithSans,Helvetica,Arial,sans-serif;
- font-style: normal;
- font-weight: 400;
- margin-bottom: 1rem;
- color: #3F3F42;
-}
-
-@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-2 {
- font-size: 1.25rem;
- line-height: 1.5rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-2 {
- font-size: 1.5rem;
- line-height: 1.75rem;
- }
-}
-
-@media (min-width: 0rem) {
- .emotion-2 {
- margin-left: 0.5rem;
- }
-}
-
-@media (min-width: 25rem) {
- .emotion-2 {
- margin-left: 1rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-2 {
- margin-left: 0;
- }
-}
-
-.emotion-4 {
- list-style: none;
- padding-left: 0;
- margin: 0;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- overflow-x: scroll;
- scroll-behavior: auto;
- -webkit-overflow-scrolling: touch;
- scrollbar-width: none;
- -ms-overflow-style: none;
-}
-
-.emotion-4::-webkit-scrollbar {
- display: none;
-}
-
-.emotion-6 {
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-flex-shrink: 0;
- -ms-flex-negative: 0;
- flex-shrink: 0;
-}
-
-@media (min-width: 0rem) {
- .emotion-6 {
- margin-left: 0.5rem;
- }
-
- .emotion-6:first-child {
- margin-left: 0.5rem;
- }
-
- .emotion-6:last-child {
- margin-right: 0.5rem;
- }
-}
-
-@media (min-width: 25rem) {
- .emotion-6 {
- margin-left: 1rem;
- }
-
- .emotion-6:first-child {
- margin-left: 1rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-6 {
- margin-left: 1rem;
- }
-
- .emotion-6:first-child {
- margin-left: 0;
- }
-}
-
-.emotion-8 {
- position: relative;
- background-color: #FFFFFF;
- padding: 1rem;
- margin-bottom: 1.5rem;
- height: auto;
-}
-
-@media (min-width: 0rem) {
- .emotion-8 {
- width: 14.8125rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-8 {
- width: 11.125rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-8 {
- width: 12.6875rem;
- }
-}
-
-.emotion-10 {
- position: static;
- color: #222222;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-wrap: break-word;
- display: inline-block;
- font-size: 0.9375rem;
- line-height: 1.25rem;
- font-family: ReithSerif,Helvetica,Arial,sans-serif;
- font-style: normal;
- font-weight: 700;
- width: 100%;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-x: hidden;
- overflow-y: hidden;
- -webkit-line-clamp: 4;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- color: #202224;
-}
-
-.emotion-10:before {
- bottom: 0;
- content: '';
- left: 0;
- overflow: hidden;
- position: absolute;
- right: 0;
- top: 0;
- white-space: nowrap;
- z-index: 1;
-}
-
-.emotion-10:hover,
-.emotion-10:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-10:visited {
- color: #6E6E73;
-}
-
-@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-10 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-10 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-.emotion-10:hover,
-.emotion-10:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-10:visited {
- color: #545658;
-}
-
-
-
-
- Show all links (no images)
-
-
-
-
-`;
-
-exports[`ScrollablePromo Mid Page ScrollablePromo it should match a11y snapshot for list with no title 1`] = `
-@media (max-width: 14.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- margin-left: 20%;
- }
-}
-
-@media (min-width: 80rem) {
- .emotion-0 {
- margin-left: 40%;
- }
-}
-
-@supports (display: grid) {
- .emotion-0 {
- display: block;
- width: initial;
- margin: 0;
- }
-
- @media (max-width: 14.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 2;
- }
- }
-
- @media (min-width: 80rem) {
- .emotion-0 {
- grid-template-columns: repeat(10, 1fr);
- grid-column-end: span 10;
- grid-column-start: 5;
- }
- }
-}
-
-.emotion-2 {
- margin-left: 0.5rem;
-}
-
-@media (min-width: 25rem) {
- .emotion-2 {
- margin-left: 1rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-2 {
- margin-left: 0;
- }
-}
-
-.emotion-4 {
- position: relative;
- background-color: #FFFFFF;
- padding: 1rem;
- margin-bottom: 1.5rem;
- height: auto;
-}
-
-@media (min-width: 0rem) {
- .emotion-4 {
- width: 14.8125rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-4 {
- width: 11.125rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-4 {
- width: 12.6875rem;
- }
-}
-
-.emotion-6 {
- position: static;
- color: #222222;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-wrap: break-word;
- display: inline-block;
- font-size: 0.9375rem;
- line-height: 1.25rem;
- font-family: ReithSerif,Helvetica,Arial,sans-serif;
- font-style: normal;
- font-weight: 700;
- width: 100%;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-x: hidden;
- overflow-y: hidden;
- -webkit-line-clamp: 4;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- color: #202224;
-}
-
-.emotion-6:before {
- bottom: 0;
- content: '';
- left: 0;
- overflow: hidden;
- position: absolute;
- right: 0;
- top: 0;
- white-space: nowrap;
- z-index: 1;
-}
-
-.emotion-6:hover,
-.emotion-6:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-6:visited {
- color: #6E6E73;
-}
-
-@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-6 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-6 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-.emotion-6:hover,
-.emotion-6:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-6:visited {
- color: #545658;
-}
-
-
-`;
-
-exports[`ScrollablePromo Mid Page ScrollablePromo it should match a11y snapshot for single card 1`] = `
-@media (max-width: 14.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- margin-left: 20%;
- }
-}
-
-@media (min-width: 80rem) {
- .emotion-0 {
- margin-left: 40%;
- }
-}
-
-@supports (display: grid) {
- .emotion-0 {
- display: block;
- width: initial;
- margin: 0;
- }
-
- @media (max-width: 14.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 2;
- }
- }
-
- @media (min-width: 80rem) {
- .emotion-0 {
- grid-template-columns: repeat(10, 1fr);
- grid-column-end: span 10;
- grid-column-start: 5;
- }
- }
-}
-
-.emotion-2 {
- display: block;
- font-size: 1.125rem;
- line-height: 1.375rem;
- font-family: ReithSans,Helvetica,Arial,sans-serif;
- font-style: normal;
- font-weight: 400;
- margin-bottom: 1rem;
- color: #3F3F42;
-}
-
-@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-2 {
- font-size: 1.25rem;
- line-height: 1.5rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-2 {
- font-size: 1.5rem;
- line-height: 1.75rem;
- }
-}
-
-@media (min-width: 0rem) {
- .emotion-2 {
- margin-left: 0.5rem;
- }
-}
-
-@media (min-width: 25rem) {
- .emotion-2 {
- margin-left: 1rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-2 {
- margin-left: 0;
- }
-}
-
-.emotion-4 {
- margin-left: 0.5rem;
-}
-
-@media (min-width: 25rem) {
- .emotion-4 {
- margin-left: 1rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-4 {
- margin-left: 0;
- }
-}
-
-.emotion-6 {
- position: relative;
- background-color: #FFFFFF;
- padding: 1rem;
- margin-bottom: 1.5rem;
- height: auto;
-}
-
-@media (min-width: 0rem) {
- .emotion-6 {
- width: 14.8125rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-6 {
- width: 11.125rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-6 {
- width: 12.6875rem;
- }
-}
-
-.emotion-8 {
- position: static;
- color: #222222;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-wrap: break-word;
- display: inline-block;
- font-size: 0.9375rem;
- line-height: 1.25rem;
- font-family: ReithSerif,Helvetica,Arial,sans-serif;
- font-style: normal;
- font-weight: 700;
- width: 100%;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-x: hidden;
- overflow-y: hidden;
- -webkit-line-clamp: 4;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- color: #202224;
-}
-
-.emotion-8:before {
- bottom: 0;
- content: '';
- left: 0;
- overflow: hidden;
- position: absolute;
- right: 0;
- top: 0;
- white-space: nowrap;
- z-index: 1;
-}
-
-.emotion-8:hover,
-.emotion-8:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-8:visited {
- color: #6E6E73;
-}
-
-@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-8 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-8 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-.emotion-8:hover,
-.emotion-8:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-8:visited {
- color: #545658;
-}
-
-
-`;
-
-exports[`ScrollablePromo Mid Page ScrollablePromo it should match snapshot when in dark ui mode 1`] = `
-@media (max-width: 14.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- margin-left: 0%;
- }
-}
-
-@media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- margin-left: 20%;
- }
-}
-
-@media (min-width: 80rem) {
- .emotion-0 {
- margin-left: 40%;
- }
-}
-
-@supports (display: grid) {
- .emotion-0 {
- display: block;
- width: initial;
- margin: 0;
- }
-
- @media (max-width: 14.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 15rem) and (max-width: 24.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 25rem) and (max-width: 37.4375rem) {
- .emotion-0 {
- grid-template-columns: repeat(6, 1fr);
- grid-column-end: span 6;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 37.5rem) and (max-width: 62.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 1;
- }
- }
-
- @media (min-width: 63rem) and (max-width: 79.9375rem) {
- .emotion-0 {
- grid-template-columns: repeat(5, 1fr);
- grid-column-end: span 5;
- grid-column-start: 2;
- }
- }
-
- @media (min-width: 80rem) {
- .emotion-0 {
- grid-template-columns: repeat(10, 1fr);
- grid-column-end: span 10;
- grid-column-start: 5;
- }
- }
-}
-
-.emotion-2 {
- margin-left: 0.5rem;
-}
-
-@media (min-width: 25rem) {
- .emotion-2 {
- margin-left: 1rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-2 {
- margin-left: 0;
- }
-}
-
-.emotion-4 {
- position: relative;
- background-color: #E6E8EA;
- padding: 1rem;
- margin-bottom: 1.5rem;
- height: auto;
-}
-
-@media (min-width: 0rem) {
- .emotion-4 {
- width: 14.8125rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-4 {
- width: 11.125rem;
- }
-}
-
-@media (min-width: 63rem) {
- .emotion-4 {
- width: 12.6875rem;
- }
-}
-
-.emotion-6 {
- position: static;
- color: #222222;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-wrap: break-word;
- display: inline-block;
- font-size: 0.9375rem;
- line-height: 1.25rem;
- font-family: ReithSerif,Helvetica,Arial,sans-serif;
- font-style: normal;
- font-weight: 700;
- width: 100%;
- -webkit-text-decoration: none;
- text-decoration: none;
- overflow-x: hidden;
- overflow-y: hidden;
- -webkit-line-clamp: 4;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- color: #141414;
-}
-
-.emotion-6:before {
- bottom: 0;
- content: '';
- left: 0;
- overflow: hidden;
- position: absolute;
- right: 0;
- top: 0;
- white-space: nowrap;
- z-index: 1;
-}
-
-.emotion-6:hover,
-.emotion-6:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-6:visited {
- color: #6E6E73;
-}
-
-@media (min-width: 20rem) and (max-width: 37.4375rem) {
- .emotion-6 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-@media (min-width: 37.5rem) {
- .emotion-6 {
- font-size: 1rem;
- line-height: 1.25rem;
- }
-}
-
-.emotion-6:hover,
-.emotion-6:focus {
- -webkit-text-decoration: underline;
- text-decoration: underline;
-}
-
-.emotion-6:visited {
- color: #545658;
-}
-
-
-`;
diff --git a/src/app/legacy/components/ScrollablePromo/index.jsx b/src/app/legacy/components/ScrollablePromo/index.jsx
deleted file mode 100644
index 95fbf921f22..00000000000
--- a/src/app/legacy/components/ScrollablePromo/index.jsx
+++ /dev/null
@@ -1,260 +0,0 @@
-import { use } from 'react';
-import {
- GEL_SPACING,
- GEL_SPACING_DBL,
- GEL_SPACING_QUAD,
-} from '#psammead/gel-foundations/src/spacings';
-
-import styled from '@emotion/styled';
-import path from 'ramda/src/path';
-import pathOr from 'ramda/src/pathOr';
-import isEmpty from 'ramda/src/isEmpty';
-import tail from 'ramda/src/tail';
-import {
- GEL_GROUP_0_SCREEN_WIDTH_MIN,
- GEL_GROUP_2_SCREEN_WIDTH_MIN,
- GEL_GROUP_3_SCREEN_WIDTH_MAX,
- GEL_GROUP_3_SCREEN_WIDTH_MIN,
- GEL_GROUP_4_SCREEN_WIDTH_MIN,
-} from '#psammead/gel-foundations/src/breakpoints';
-import { GridItemMediumNoMargin } from '#components/Grid';
-import useViewTracker from '#hooks/useViewTracker';
-import useClickTrackerHandler from '#hooks/useClickTrackerHandler';
-import idSanitiser from '#lib/utilities/idSanitiser';
-import { GREY_2 } from '#app/components/ThemeProvider/palette';
-import { ServiceContext } from '../../../contexts/ServiceContext';
-import Promo from './Promo';
-import PromoList from './PromoList';
-
-const PromoWrapper = styled.div`
- ${({ dir }) => `margin-${dir === 'ltr' ? 'left' : 'right'}: ${GEL_SPACING};`}
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}) {
- ${({ dir }) =>
- `margin-${dir === 'ltr' ? 'left' : 'right'}: ${GEL_SPACING_DBL};`}
- }
- @media (min-width: ${GEL_GROUP_4_SCREEN_WIDTH_MIN}) {
- ${({ dir }) => `margin-${dir === 'ltr' ? 'left' : 'right'}: 0;`}
- }
-`;
-
-const ScrollablePromoContainer = styled.div`
- background: ${GREY_2};
- padding: ${GEL_SPACING};
- display: flex;
- overflow-x: auto;
- -ms-overflow-style: none;
- scrollbar-width: none;
- &::-webkit-scrollbar {
- display: none;
- }
- ${({ experimentVariant }) =>
- experimentVariant &&
- experimentVariant !== 'off' &&
- `
- padding: 0 ${GEL_SPACING} ${GEL_SPACING_DBL};
- margin: 0rem;
-
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}) {
- padding: 0 ${GEL_SPACING_DBL} ${GEL_SPACING_DBL};
- margin: 0 -0.2rem;
- }
-
- @media (min-width: ${GEL_GROUP_3_SCREEN_WIDTH_MIN}) {
- margin: 0 -0.8rem;
- }
-
- @media (min-width: ${GEL_GROUP_3_SCREEN_WIDTH_MAX}) {
- display: none;
- }
-
- width: 100vw;
- `}
-`;
-
-const LabelComponent = styled.strong`
- display: block;
- ${({ theme: { fontSizes } }) => fontSizes.doublePica};
- ${({ theme: { fontVariants } }) => fontVariants.sansRegular};
- margin-bottom: ${GEL_SPACING_DBL};
- color: ${({ theme }) =>
- theme.isDarkUi ? theme.palette.GREY_2 : theme.palette.SHADOW};
-
- ${({ dir }) =>
- `
- @media (min-width: ${GEL_GROUP_0_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? 'left' : 'right'}: ${GEL_SPACING};
- }
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: ${GEL_SPACING_DBL};
- }
- @media (min-width: ${GEL_GROUP_4_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: 0;
- }
-`}
-`;
-
-const LabelComponentOJTopBar = styled(({ ariaLabel, ...props }) => (
-
-))`
- ${({ theme: { fontSizes } }) => fontSizes.brevier};
- ${({ theme: { fontVariants } }) => fontVariants.sansRegular};
- display: inline-block;
- margin-bottom: ${GEL_SPACING_DBL};
- color: ${({ theme }) =>
- theme.isDarkUi ? theme.palette.GREY_2 : theme.palette.SHADOW};
-
- ${({ dir }) =>
- `
- @media (min-width: ${GEL_GROUP_0_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? 'left' : 'right'}: ${GEL_SPACING};
- }
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: ${GEL_SPACING_DBL};
- }
- @media (min-width: ${GEL_GROUP_4_SCREEN_WIDTH_MIN}){
- margin-${dir === 'ltr' ? `left` : `right`}: 0;
- }
- `}
-
- padding: 0 ${GEL_SPACING};
-
- @media (min-width: ${GEL_GROUP_0_SCREEN_WIDTH_MIN}) {
- margin: 0rem;
- }
-
- @media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}) {
- padding: 0 ${GEL_SPACING_DBL};
- margin: 0 -0.2rem;
- }
-
- @media (min-width: ${GEL_GROUP_3_SCREEN_WIDTH_MIN}) {
- margin: 0 -0.8rem;
- }
-
- @media (min-width: ${GEL_GROUP_3_SCREEN_WIDTH_MAX}) {
- display: none;
- }
-
- display: flex;
- align-items: center;
- height: ${GEL_SPACING_QUAD};
- background: ${GREY_2};
- width: 100vw;
-`;
-
-const ScrollablePromo = ({
- blocks,
- blockGroupIndex = null,
- experimentVariant = null,
-}) => {
- const { script, service, dir, translations, mostRead } = use(ServiceContext);
-
- const eventTrackingData = {
- componentName: `edoj${blockGroupIndex}`,
- format: 'CHD=edoj',
- ...(experimentVariant && {
- componentName: 'top-bar-oj',
- sendOptimizelyEvents: true,
- viewThreshold: 0,
- }),
- };
-
- const viewTracker = useViewTracker(eventTrackingData);
- const clickTracker = useClickTrackerHandler(eventTrackingData);
-
- if (!blocks || isEmpty(blocks)) {
- return null;
- }
-
- let title;
- if (
- ['top-bar-top-stories', 'read-more-a-and-top-stories'].includes(
- experimentVariant,
- )
- ) {
- title = translations.topStoriesTitle || 'Top Stories';
- } else if (experimentVariant === 'top-bar-most-read') {
- title = mostRead.header || 'Most Read';
- } else {
- title =
- blocks[0].type === 'title' &&
- path(
- ['0', 'model', 'blocks', '0', 'model', 'blocks', '0', 'model', 'text'],
- blocks,
- );
- }
-
- const blocksWithoutTitle = blocks[0].type === 'title' ? tail(blocks) : blocks;
-
- const isSingleItem = blocksWithoutTitle.length === 1;
-
- const ariaLabel = title && idSanitiser(title);
-
- const a11yAttributes = {
- ...(!experimentVariant && {
- as: 'section',
- role: 'region',
- }),
- ...(ariaLabel
- ? { 'aria-labelledby': ariaLabel }
- : {
- 'aria-label': pathOr(
- 'Related Content',
- ['relatedContent'],
- translations,
- ),
- }),
- };
-
- return experimentVariant ? (
- <>
-
- {title}
-
-
-
-
-
-
- >
- ) : (
-
- {title && (
-
- {title}
-
- )}
- {isSingleItem ? (
-
-
-
- ) : (
-
- )}
-
- );
-};
-
-export default ScrollablePromo;
diff --git a/src/app/legacy/components/ScrollablePromo/index.stories.jsx b/src/app/legacy/components/ScrollablePromo/index.stories.jsx
deleted file mode 100644
index de5a43b26fd..00000000000
--- a/src/app/legacy/components/ScrollablePromo/index.stories.jsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import styled from '@emotion/styled';
-import { ServiceContextProvider } from '#contexts/ServiceContext';
-import ScrollablePromo from '.';
-import {
- threeLinks,
- oneLinkOnly,
- oneLinkWithNoTitle,
- oneLinkWithTimestamp,
- moreThanThreeLinks,
- twoLinksWithNoImages,
- truncatedTextInSingleLink,
- arabicText,
- topStoriesBlocks,
- topStoriesBlocksWithLiveItem,
- mostReadBlocks,
- mostReadBlocksRTL,
-} from './helpers/fixtureData';
-
-const BackGround = styled.div`
- background-color: #f6f6f6;
- padding: 2rem;
-`;
-
-const ScrollablePromoComponent = ({
- data,
- service,
- experimentVariant = null,
-}) => (
-
-
-
-);
-
-export default {
- title: 'Components/Scrollable Promo',
- ScrollablePromoComponent,
-};
-
-export const ThreeLinks = (_, { service }) => (
-
-
-
-);
-
-export const OnlyOneLink = (_, { service }) => (
-
-
-
-);
-
-export const OneLinkWithNoTitle = (_, { service }) => (
-
-
-
-);
-
-export const MoreThanThreeLinks = (_, { service }) => (
-
-
-
-);
-
-export const NoImagesInData = (_, { service }) => (
-
-
-
-);
-
-export const TruncatedTextInSingleLink = (_, { service }) => (
-
-
-
-);
-
-export const ArabicText = () => (
-
-
-
-);
-
-ArabicText.globals = {
- service: { service: 'arabic' },
-};
-
-export const WithTimestamp = (_, { service }) => (
-
-
-
-);
-
-export const OJTopBarTopStories = (_, { service }) => (
-
-);
-
-export const OJTopBarTopStoriesWithLiveLabel = (_, { service }) => (
-
-);
-
-export const OJTopBarMostRead = (_, { service }) => (
-
-);
-
-export const OJTopBarMostReadRTL = () => (
-
-);
diff --git a/src/app/legacy/components/ScrollablePromo/index.test.jsx b/src/app/legacy/components/ScrollablePromo/index.test.jsx
deleted file mode 100644
index 63ce2abb127..00000000000
--- a/src/app/legacy/components/ScrollablePromo/index.test.jsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import * as viewTracking from '#hooks/useViewTracker';
-import * as clickTracking from '#hooks/useClickTrackerHandler';
-import { render } from '../../../components/react-testing-library-with-providers';
-import {
- threeLinks,
- oneLinkOnly,
- oneLinkWithNoTitle,
- moreThanThreeLinks,
- topStoriesBlocks,
- mostReadBlocks,
-} from './helpers/fixtureData';
-import ScrollablePromo from '.';
-import { edOjA, edOjB } from './fixtures';
-import { MEDIA_ARTICLE_PAGE } from '../../../routes/utils/pageTypes';
-
-describe('ScrollablePromo', () => {
- describe('Mid Page ScrollablePromo', () => {
- it('should return null if no data is passed', () => {
- const { container } = render();
- expect(container).toBeEmptyDOMElement();
- });
-
- it('should render max 3 promo items', () => {
- const { getAllByRole } = render(
- ,
- );
- expect(getAllByRole('listitem').length).toEqual(3);
- });
-
- it('should render single promo item', () => {
- const { container } = render();
- expect(container.childElementCount).toEqual(1);
- });
-
- it('should render single promo item with a title', () => {
- const { container, getByTestId } = render(
- ,
- );
- expect(container.childElementCount).toEqual(1);
- expect(getByTestId('eoj-recommendations-heading')).toBeInTheDocument();
- });
-
- it('should render single promo item without a title', () => {
- const { container, queryByTestId } = render(
- ,
- );
- expect(container.childElementCount).toEqual(1);
- expect(
- queryByTestId('eoj-recommendations-heading'),
- ).not.toBeInTheDocument();
- });
-
- it('should not render a list when there is only one promo', () => {
- const { queryByRole } = render();
-
- expect(queryByRole('list')).not.toBeInTheDocument();
- expect(queryByRole('listitem')).not.toBeInTheDocument();
- });
-
- it('should render unordered list if more than 1 item', () => {
- const { queryByRole, getAllByRole } = render(
- ,
- );
- expect(queryByRole('list')).toBeInTheDocument();
- expect(getAllByRole('listitem').length).toEqual(3);
- });
-
- describe('event tracking in editorial onward journeys', () => {
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it('should call the view tracking hook with the correct params with one editorial onward journey', () => {
- const viewTrackerSpy = jest.spyOn(viewTracking, 'default');
-
- render(
- ,
- );
-
- expect(viewTrackerSpy).toHaveBeenCalledWith({
- componentName: 'edoj1',
- format: 'CHD=edoj',
- });
- });
-
- it('should call the view tracking hook with the correct params with multiple editorial onward journeys', () => {
- const viewTrackerSpy = jest.spyOn(viewTracking, 'default');
- render(
- ,
- );
- render(
- ,
- );
-
- expect(viewTrackerSpy).toHaveBeenCalledTimes(4);
- expect(viewTrackerSpy).toHaveBeenCalledWith({
- componentName: 'edoj1',
- format: 'CHD=edoj',
- });
- expect(viewTrackerSpy).toHaveBeenCalledWith({
- componentName: 'edoj2',
- format: 'CHD=edoj',
- });
- });
-
- it('should call the click tracking hook with one editorial onward journey', () => {
- const clickTrackerSpy = jest.spyOn(clickTracking, 'default');
- render(
- ,
- );
-
- expect(clickTrackerSpy).toHaveBeenCalledWith({
- componentName: 'edoj1',
- format: 'CHD=edoj',
- });
- });
-
- it('should call the click tracking hook with multiple editorial onward journeys', () => {
- const clickTrackerSpy = jest.spyOn(clickTracking, 'default');
- render(
- ,
- );
- render(
- ,
- );
-
- expect(clickTrackerSpy).toHaveBeenCalledTimes(4);
- expect(clickTrackerSpy).toHaveBeenCalledWith({
- componentName: 'edoj1',
- format: 'CHD=edoj',
- });
- expect(clickTrackerSpy).toHaveBeenCalledWith({
- componentName: 'edoj2',
- format: 'CHD=edoj',
- });
- });
- });
-
- it('it should match a11y snapshot for single card', () => {
- const { container } = render();
- expect(container).toMatchSnapshot();
- });
-
- it('it should match a11y snapshot for list', () => {
- const { container } = render();
- expect(container).toMatchSnapshot();
- });
-
- it('it should match a11y snapshot for list with no title', () => {
- const { container } = render(
- ,
- );
- expect(container).toMatchSnapshot();
- });
-
- it('it should match snapshot when in dark ui mode', () => {
- const { container } = render(
- ,
- {
- pageType: MEDIA_ARTICLE_PAGE,
- },
- );
- expect(container).toMatchSnapshot();
- });
- });
-
- describe('OJ Top Bar ScrollablePromo', () => {
- it('it should display Top Stories label when experimentVariant is top-bar-top-stories', () => {
- const { getByText, queryByText } = render(
- ,
- );
- expect(getByText('Top Stories')).toBeVisible();
- expect(queryByText('Popular Reads')).toBeNull();
- });
-
- it('it should display Top Stories label when experimentVariant is read-more-a-and-top-stories', () => {
- const { getByText, queryByText } = render(
- ,
- );
- expect(getByText('Top Stories')).toBeVisible();
- expect(queryByText('Popular Reads')).toBeNull();
- });
-
- it('it should display Most Read label when experimentVariant is top-bar-most-read', () => {
- const { getByText, queryByText } = render(
- ,
- );
- expect(getByText('Popular Reads')).toBeVisible();
- expect(queryByText('Top Stories')).toBeNull();
- });
-
- it('it should display 3 promo items with Top Stories when experimentVariant is top-bar-top-stories', () => {
- const { getAllByRole } = render(
- ,
- );
- expect(getAllByRole('listitem')).toHaveLength(3);
- });
-
- it('it should display 5 promo items with Most Read when experimentVariant is top-bar-most-read', () => {
- const { getAllByRole } = render(
- ,
- );
- expect(getAllByRole('listitem')).toHaveLength(5);
- });
-
- it('it should display Top Stories content when experimentVariant is top-bar-top-stories', () => {
- const { getAllByRole } = render(
- ,
- );
- const expectedFirstHeadline =
- topStoriesBlocks[0].headlines.promoHeadline.blocks[0].model.blocks[0]
- .model.text;
- expect(getAllByRole('listitem')[0]).toHaveTextContent(
- expectedFirstHeadline,
- );
- });
-
- it('it should display Most Read content when experimentVariant is top-bar-most-read', () => {
- const { getAllByRole } = render(
- ,
- );
- const expectedFirstHeadline = mostReadBlocks[0].title;
- expect(getAllByRole('listitem')[0]).toHaveTextContent(
- expectedFirstHeadline,
- );
- });
- });
-});
diff --git a/src/app/legacy/containers/Navigation/index.stories.jsx b/src/app/legacy/containers/Navigation/index.stories.jsx
index c80af4e4bbe..f3723da0dda 100644
--- a/src/app/legacy/containers/Navigation/index.stories.jsx
+++ b/src/app/legacy/containers/Navigation/index.stories.jsx
@@ -3,7 +3,7 @@ import { HOME_PAGE } from '#app/routes/utils/pageTypes';
import {
topStoriesBlocks,
mostReadBlocks,
-} from '../../components/ScrollablePromo/helpers/fixtureData';
+} from '../../../components/ArticleLinksBlock/helpers/fixtureData';
import AmpDecorator from '../../../../../.storybook/helpers/ampDecorator';
import Navigation from '.';
diff --git a/src/app/pages/ArticlePage/ArticlePage.tsx b/src/app/pages/ArticlePage/ArticlePage.tsx
index b118859846d..b8ccb724345 100644
--- a/src/app/pages/ArticlePage/ArticlePage.tsx
+++ b/src/app/pages/ArticlePage/ArticlePage.tsx
@@ -41,7 +41,7 @@ import {
import { Translations } from '#app/models/types/translations';
import { Recommendation } from '#app/models/types/onwardJourney';
-import ScrollablePromo from '#components/ScrollablePromo';
+import ArticleLinksBlock from '#app/components/ArticleLinksBlock';
import Recommendations from '#app/components/Recommendations';
import ReadTimeArticle from '#app/components/ReadTime';
import PWAPromotionalBanner from '#app/components/PWAPromotionalBanner';
@@ -281,7 +281,7 @@ const ArticlePage = ({ pageData }: { pageData: Article }) => {
embedImages: EmbedImages,
embedUploader: Uploader,
group: gist,
- links: ScrollablePromo,
+ links: ArticleLinksBlock,
mpu: getMpuComponent(allowAdvertising),
wsoj: getWsojComponent,
disclaimer: DisclaimerWithPaddingOverride,
diff --git a/src/app/pages/MediaArticlePage/MediaArticlePage.tsx b/src/app/pages/MediaArticlePage/MediaArticlePage.tsx
index d04867e6fdc..36e6bffd6a8 100644
--- a/src/app/pages/MediaArticlePage/MediaArticlePage.tsx
+++ b/src/app/pages/MediaArticlePage/MediaArticlePage.tsx
@@ -24,7 +24,7 @@ import {
} from '../../lib/utilities/parseAssetData';
import filterForBlockType from '../../lib/utilities/blockHandlers';
-import ScrollablePromo from '../../legacy/components/ScrollablePromo';
+import ArticleLinksBlock from '../../components/ArticleLinksBlock';
import headings from '../../legacy/containers/Headings';
import visuallyHiddenHeadline from '../../legacy/containers/VisuallyHiddenHeadline';
@@ -121,7 +121,9 @@ const getBylineComponent =
) : null;
-const Links = (props: ComponentToRenderProps) => ;
+const Links = (props: ComponentToRenderProps) => (
+
+);
const getImageComponent =
(preloadLeadImageToggle: boolean) => (props: ComponentToRenderProps) => (
diff --git a/ws-nextjs-app/cypress/e2e/articlePage/index.cy.ts b/ws-nextjs-app/cypress/e2e/articlePage/index.cy.ts
index c4d717f97af..6ec150ee036 100644
--- a/ws-nextjs-app/cypress/e2e/articlePage/index.cy.ts
+++ b/ws-nextjs-app/cypress/e2e/articlePage/index.cy.ts
@@ -57,9 +57,9 @@ import {
assertSocialEmbedComponentView,
} from '../specialFeatures/atiAnalytics/assertions/socialEmbed';
import {
- assertScrollablePromoComponentClick,
- assertScrollablePromoComponentView,
-} from '../specialFeatures/atiAnalytics/assertions/scrollablePromo';
+ assertArticleLinksBlockComponentClick,
+ assertArticleLinksBlockComponentView,
+} from '../specialFeatures/atiAnalytics/assertions/articleLinksBlock';
import getPathWithSuffix from '../../support/helpers/getPathWithSuffix';
import { assertLiteSiteSummaryComponentToMainSiteClick } from '../specialFeatures/atiAnalytics/assertions/liteSiteSummary';
@@ -376,8 +376,8 @@ const atiAnalyticsTestSuites = [
assertRelatedContentComponentClick,
assertTopStoriesComponentView,
assertTopStoriesComponentClick,
- assertScrollablePromoComponentClick,
- assertScrollablePromoComponentView,
+ assertArticleLinksBlockComponentClick,
+ assertArticleLinksBlockComponentView,
],
},
{
diff --git a/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/scrollablePromo.ts b/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/articleLinksBlock.ts
similarity index 69%
rename from ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/scrollablePromo.ts
rename to ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/articleLinksBlock.ts
index f3d6524e146..1af709fbd8c 100644
--- a/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/scrollablePromo.ts
+++ b/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/assertions/articleLinksBlock.ts
@@ -6,9 +6,9 @@ import {
} from '../../../../../../cypress/e2e/specialFeatures/atiAnalytics/assertions';
import { AtiAssertionFnProps } from './type';
-const { SCROLLABLE_PROMO } = COMPONENTS;
+const { ARTICLE_LINKS_BLOCK } = COMPONENTS;
-export const assertScrollablePromoComponentView = ({
+export const assertArticleLinksBlockComponentView = ({
pageIdentifier,
path,
applicationType,
@@ -19,15 +19,15 @@ export const assertScrollablePromoComponentView = ({
cy.visit(path);
// This duplicate line of code has been added intentionally to get cypress to scroll to the bottom.
- cy.get('[data-e2e="scrollable-promos"]').first().scrollIntoView({
+ cy.get('[data-e2e="article-links-block"]').first().scrollIntoView({
duration: 1000,
});
- cy.get('[data-e2e="scrollable-promos"]').first().scrollIntoView({
+ cy.get('[data-e2e="article-links-block"]').first().scrollIntoView({
duration: 1000,
});
assertATIComponentViewEvent({
- component: SCROLLABLE_PROMO,
+ component: ARTICLE_LINKS_BLOCK,
pageIdentifier,
applicationType,
siteId,
@@ -35,7 +35,7 @@ export const assertScrollablePromoComponentView = ({
});
};
-export const assertScrollablePromoComponentClick = ({
+export const assertArticleLinksBlockComponentClick = ({
pageIdentifier,
path,
applicationType,
@@ -45,15 +45,15 @@ export const assertScrollablePromoComponentClick = ({
interceptATIAnalyticsBeacons();
cy.visit(path);
- cy.get('[data-e2e="scrollable-promos"]').first().scrollIntoView({
+ cy.get('[data-e2e="article-links-block"]').first().scrollIntoView({
duration: 1000,
});
// Click on first item
- cy.get('[data-e2e="scrollable-promos"]').find('a').first().click();
+ cy.get('[data-e2e="article-links-block"]').find('a').first().click();
assertATIComponentClickEvent({
- component: SCROLLABLE_PROMO,
+ component: ARTICLE_LINKS_BLOCK,
pageIdentifier,
applicationType,
siteId,
diff --git a/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.ts b/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.ts
index f5089b14a3c..a8542b63999 100644
--- a/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.ts
+++ b/ws-nextjs-app/cypress/e2e/specialFeatures/atiAnalytics/helpers/index.ts
@@ -28,7 +28,7 @@ const RECENT_AUDIO_EPISODES = 'episodes-audio';
const PODCAST_LINKS = 'third-party';
const LATEST_MEDIA = 'latest';
const RECOMMENDATIONS = 'midarticle-mostread';
-const SCROLLABLE_PROMO = 'edoj';
+const ARTICLE_LINKS_BLOCK = 'edoj';
const BILLBOARD = 'billboard';
const SOCIAL_EMBED = 'social-consent-banner';
const LIVE_MEDIA = 'live-header-media';
@@ -56,7 +56,7 @@ export const COMPONENTS = {
RELATED_CONTENT,
RELATED_TOPICS,
SCROLLABLE_NAVIGATION,
- SCROLLABLE_PROMO,
+ ARTICLE_LINKS_BLOCK,
SHARE,
SOCIAL_EMBED,
TOP_STORIES,