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 {