diff --git a/ab-testing/config/abTests.ts b/ab-testing/config/abTests.ts index 8205954e9fa..44a7b9b8927 100644 --- a/ab-testing/config/abTests.ts +++ b/ab-testing/config/abTests.ts @@ -69,16 +69,16 @@ const ABTests: ABTest[] = [ shouldForceMetricsCollection: false, }, { - name: "thefilter-at-a-glance-redesign", + name: "thefilter-at-a-glance-redesign-v2", description: "Testing redesigned at a glance component on The Filter articles", owners: ["thefilter.dev@guardian.co.uk"], - expirationDate: "2026-04-01", + expirationDate: "2026-05-06", type: "server", status: "ON", audienceSize: 0 / 100, audienceSpace: "C", - groups: ["control", "stacked", "carousel"], + groups: ["control", "carousel", "stacked-default", "stacked-expanded"], shouldForceMetricsCollection: false, }, { diff --git a/dotcom-rendering/src/components/ProductSummary.island.tsx b/dotcom-rendering/src/components/ProductSummary.island.tsx index 0b17b99de99..d72dd4af825 100644 --- a/dotcom-rendering/src/components/ProductSummary.island.tsx +++ b/dotcom-rendering/src/components/ProductSummary.island.tsx @@ -22,12 +22,26 @@ export const ProductSummary = ({ ); } + if (variant === 'stacked-default') { + return ( + + + + ); + } + return ( ); diff --git a/dotcom-rendering/src/components/StackedProducts.island.tsx b/dotcom-rendering/src/components/StackedProducts.island.tsx index c41cbc6d60f..7d491f1020e 100644 --- a/dotcom-rendering/src/components/StackedProducts.island.tsx +++ b/dotcom-rendering/src/components/StackedProducts.island.tsx @@ -33,10 +33,12 @@ export const StackedProducts = ({ products, heading, format, + showAllProducts, }: { products: ProductBlockElement[]; heading: string; format: ArticleFormat; + showAllProducts: boolean; }) => { const [isExpanded, setIsExpanded] = useState(false); return ( @@ -54,7 +56,7 @@ export const StackedProducts = ({ {heading} - {products.length > cardsShownByDefault && ( + {products.length > cardsShownByDefault && !showAllProducts && (

{isExpanded ? products.length @@ -82,7 +84,9 @@ export const StackedProducts = ({ data-component={`at-a-glance-stacked-card-${index + 1}`} style={{ display: - !isExpanded && index >= cardsShownByDefault + !isExpanded && + index >= cardsShownByDefault && + !showAllProducts ? 'none' : 'block', }} @@ -95,7 +99,7 @@ export const StackedProducts = ({ ))} - {products.length > cardsShownByDefault && ( + {products.length > cardsShownByDefault && !showAllProducts && ( setIsExpanded(!isExpanded)} cssOverrides={showAllButtonStyles} diff --git a/dotcom-rendering/src/components/StackedProducts.stories.tsx b/dotcom-rendering/src/components/StackedProducts.stories.tsx index 6ff737976d3..7e0e948c2ea 100644 --- a/dotcom-rendering/src/components/StackedProducts.stories.tsx +++ b/dotcom-rendering/src/components/StackedProducts.stories.tsx @@ -15,6 +15,7 @@ const meta = { display: ArticleDisplay.Standard, theme: Pillar.Lifestyle, }, + showAllProducts: false, }, decorators: [centreColumnDecorator], } satisfies Meta; @@ -34,5 +35,6 @@ export const FourProducts = { display: ArticleDisplay.Standard, theme: Pillar.Lifestyle, }, + showAllProducts: false, }, } satisfies Story; diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index 103a9fd1ccc..b3f1e4f6af8 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -4774,7 +4774,8 @@ "variant": { "enum": [ "carousel", - "stacked" + "stacked-default", + "stacked-expanded" ], "type": "string" } diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index 689661d7f7c..87de4403474 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -4256,7 +4256,8 @@ "variant": { "enum": [ "carousel", - "stacked" + "stacked-default", + "stacked-expanded" ], "type": "string" } diff --git a/dotcom-rendering/src/model/enhance-product-summary.test-helpers.ts b/dotcom-rendering/src/model/enhance-product-summary.test-helpers.ts index c17b60e49ad..e849eba292b 100644 --- a/dotcom-rendering/src/model/enhance-product-summary.test-helpers.ts +++ b/dotcom-rendering/src/model/enhance-product-summary.test-helpers.ts @@ -48,12 +48,22 @@ export const findCarousel = ( el.variant === 'carousel', ); -export const findStacked = ( +export const findStackedDefault = ( elements: FEElement[], ): ProductSummaryElement | undefined => elements.find( (el): el is ProductSummaryElement => el._type === 'model.dotcomrendering.pageElements.ProductSummaryElement' && - el.variant === 'stacked', + el.variant === 'stacked-default', + ); + +export const findStackedExpanded = ( + elements: FEElement[], +): ProductSummaryElement | undefined => + elements.find( + (el): el is ProductSummaryElement => + el._type === + 'model.dotcomrendering.pageElements.ProductSummaryElement' && + el.variant === 'stacked-expanded', ); diff --git a/dotcom-rendering/src/model/enhance-product-summary.test.ts b/dotcom-rendering/src/model/enhance-product-summary.test.ts index fd6bcc4be68..88455f113b5 100644 --- a/dotcom-rendering/src/model/enhance-product-summary.test.ts +++ b/dotcom-rendering/src/model/enhance-product-summary.test.ts @@ -3,7 +3,8 @@ import { atAGlanceHeading, dividerElement, findCarousel, - findStacked, + findStackedDefault, + findStackedExpanded, linkElement, productElement, textElement, @@ -222,7 +223,9 @@ describe('enhanceProductSummary', () => { const output = enhanceProductSummary({ pageId: allowedPageId, - serverSideABTests: { 'thefilter-at-a-glance-redesign': 'carousel' }, + serverSideABTests: { + 'thefilter-at-a-glance-redesign-v2': 'carousel', + }, renderingTarget: 'Web', filterAtAGlanceEnabled: true, })(input); @@ -232,7 +235,7 @@ describe('enhanceProductSummary', () => { expect(carousel).toBeDefined(); }); - it('enhances elements with a stacked product for allowlisted pages', () => { + it('enhances elements with a default stacked product component for allowlisted pages', () => { const allowedPageId = 'thefilter/test-article-example-for-product-summary'; @@ -264,14 +267,60 @@ describe('enhanceProductSummary', () => { const output = enhanceProductSummary({ pageId: allowedPageId, - serverSideABTests: { 'thefilter-at-a-glance-redesign': 'stacked' }, + serverSideABTests: { + 'thefilter-at-a-glance-redesign-v2': 'stacked-default', + }, renderingTarget: 'Web', filterAtAGlanceEnabled: true, })(input); - const stacked = findStacked(output); + const stackedDefault = findStackedDefault(output); - expect(stacked).toBeDefined(); + expect(stackedDefault).toBeDefined(); + }); + + it('enhances elements with an expanded stacked product component for allowlisted pages', () => { + const allowedPageId = + 'thefilter/test-article-example-for-product-summary'; + + const input = [ + atAGlanceHeading(), + linkElement( + 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', + 'Buy now', + ), + linkElement( + 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', + 'Buy now', + ), + linkElement( + 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', + 'Buy now', + ), + dividerElement(), + productElement([ + 'https://www.homebase.co.uk/en-uk/tower-airx-t17166-5l-grey-single-basket-air-fryer-digital-air-fryer/p/0757395', + ]), + productElement([ + 'https://www.lakeland.co.uk/27537/lakeland-slimline-air-fryer-black-8l', + ]), + productElement([ + 'https://ninjakitchen.co.uk/product/ninja-double-stack-xl-9-5l-air-fryer-sl400uk-zidSL400UK', + ]), + ]; + + const output = enhanceProductSummary({ + pageId: allowedPageId, + serverSideABTests: { + 'thefilter-at-a-glance-redesign-v2': 'stacked-expanded', + }, + renderingTarget: 'Web', + filterAtAGlanceEnabled: true, + })(input); + + const stackedExpanded = findStackedExpanded(output); + + expect(stackedExpanded).toBeDefined(); }); it('does not return stacked cards when the rendering target is apps', () => { @@ -306,14 +355,16 @@ describe('enhanceProductSummary', () => { const output = enhanceProductSummary({ pageId: allowedPageId, - serverSideABTests: { 'thefilter-at-a-glance-redesign': 'stacked' }, + serverSideABTests: { + 'thefilter-at-a-glance-redesign-v2': 'stacked-default', + }, renderingTarget: 'Apps', filterAtAGlanceEnabled: true, })(input); - const stacked = findStacked(output); + const stackedDefault = findStackedDefault(output); - expect(stacked).toBeUndefined(); + expect(stackedDefault).toBeUndefined(); }); it('does not return stacked cards when the filterAtAGlance switch is OFF', () => { @@ -348,13 +399,15 @@ describe('enhanceProductSummary', () => { const output = enhanceProductSummary({ pageId: allowedPageId, - serverSideABTests: { 'thefilter-at-a-glance-redesign': 'stacked' }, + serverSideABTests: { + 'thefilter-at-a-glance-redesign-v2': 'stacked-default', + }, renderingTarget: 'Web', filterAtAGlanceEnabled: false, })(input); - const stacked = findStacked(output); + const stackedDefault = findStackedDefault(output); - expect(stacked).toBeUndefined(); + expect(stackedDefault).toBeUndefined(); }); }); diff --git a/dotcom-rendering/src/model/enhance-product-summary.ts b/dotcom-rendering/src/model/enhance-product-summary.ts index 8f6d44f22d9..b389068bf4f 100644 --- a/dotcom-rendering/src/model/enhance-product-summary.ts +++ b/dotcom-rendering/src/model/enhance-product-summary.ts @@ -9,7 +9,7 @@ import { generateId } from './enhance-H2s'; * further up the rendering pipeline. */ -export type ABTestVariant = 'carousel' | 'stacked'; +export type ABTestVariant = 'carousel' | 'stacked-default' | 'stacked-expanded'; /** * List of page IDs eligible for product carousel enhancement. @@ -60,7 +60,11 @@ const isEligibleForSummary = (pageId: string) => { }; const isCarouselOrStacked = (string: string) => { - return string === 'carousel' || string === 'stacked'; + return ( + string === 'carousel' || + string === 'stacked-default' || + string === 'stacked-expanded' + ); }; // Extract URLs from 'At a glance' section elements @@ -202,7 +206,7 @@ export const enhanceProductSummary = }) => (elements: FEElement[]): FEElement[] => { const abTestVariant = - serverSideABTests?.['thefilter-at-a-glance-redesign']; + serverSideABTests?.['thefilter-at-a-glance-redesign-v2']; // do nothing if article is not on allow list / not in the test / variant is 'control' / renderingTarget is Apps / filterAtAGlance switch is OFF if ( diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts index 2b7c10cab2e..bf9f085f3ad 100644 --- a/dotcom-rendering/src/types/content.ts +++ b/dotcom-rendering/src/types/content.ts @@ -526,7 +526,7 @@ export interface ProductBlockElement { export interface ProductSummaryElement { _type: 'model.dotcomrendering.pageElements.ProductSummaryElement'; matchedProducts: ProductBlockElement[]; - variant: 'carousel' | 'stacked'; + variant: 'carousel' | 'stacked-default' | 'stacked-expanded'; } interface ProfileAtomBlockElement {