diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.new.stories.tsx b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.new.stories.tsx
index 445d5efc2ff..a7ff0e7b741 100644
--- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.new.stories.tsx
+++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.new.stories.tsx
@@ -3,7 +3,13 @@ import {wrapInCommerceProductList} from '@coveo/atomic-storybook-utils/commerce/
import {wrapInProductTemplate} from '@coveo/atomic-storybook-utils/commerce/commerce-product-template-wrapper';
import {parameters} from '@coveo/atomic-storybook-utils/common/common-meta-parameters';
import {renderComponent} from '@coveo/atomic-storybook-utils/common/render-component';
-import type {Meta} from '@storybook/web-components';
+import type {Meta, StoryObj as Story} from '@storybook/web-components';
+import type {Decorator} from '@storybook/web-components';
+import {html} from 'lit-html';
+
+const styledDivDecorator: Decorator = (story) => {
+ return html`
${story()}
`;
+};
const {
decorator: commerceInterfaceDecorator,
@@ -14,7 +20,7 @@ const {
engineConfig: {
context: {
view: {
- url: 'https://sports.barca.group/browse/promotions/ui-kit-testing',
+ url: 'https://ui-kit.coveo/atomic/storybook/atomic-product-image',
},
language: 'en',
country: 'US',
@@ -34,6 +40,7 @@ const meta: Meta = {
productTemplateDecorator,
commerceProductListDecorator,
commerceInterfaceDecorator,
+ styledDivDecorator,
],
parameters,
play: initializeCommerceInterface,
@@ -41,55 +48,22 @@ const meta: Meta = {
export default meta;
-// export const Default: Story = {
-// name: 'atomic-product-image',
-// args: {
-// 'attributes-fallback': '2',
-// play: async (context) => {
-// await play(context);
-// await playExecuteFirstSearch(context);
-// },
-// },
-// };
-
-// const {play: playWithMultipleImages} = wrapInCommerceInterface({
-// engineConfig: {
-// ...engineConfig,
-// preprocessRequest: (r) => {
-// const parsed = JSON.parse(r.body as string);
-// parsed.query = 'https://sports.barca.group/pdp/SP00003_00001';
-// r.body = JSON.stringify(parsed);
-// return r;
-// },
-// },
-// });
-
-// export const WithMultipleImages: Story = {
-// name: 'With multiple images',
-// play: async (context) => {
-// await playWithMultipleImages(context);
-// },
-// };
+export const Default: Story = {
+ name: 'atomic-product-image',
+};
-// export const WithNoImage: Story = {
-// name: 'With no image',
-// args: {
-// 'attributes-field': 'ec_invalid_image_field',
-// },
-// play: async (context) => {
-// await play(context);
-// await playExecuteFirstSearch(context);
-// },
-// };
+export const withAFallbackImage: Story = {
+ name: 'With a fallback image',
+ args: {
+ 'attributes-field': 'invalid',
+ 'attributes-fallback': 'https://sports.barca.group/logos/barca.svg',
+ },
+};
-// export const WitCustomFallbackImage: Story = {
-// name: 'With custom fallback',
-// args: {
-// 'attributes-field': 'ec_invalid_image_field',
-// 'attributes-fallback': 'https://sports.barca.group/logos/barca.svg',
-// },
-// play: async (context) => {
-// await play(context);
-// await playExecuteFirstSearch(context);
-// },
-// };
+export const withAnAltTextField: Story = {
+ tags: ['test'],
+ name: 'With an alt text field',
+ args: {
+ 'attributes-image-alt-field': 'custom_alt_field',
+ },
+};
diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx
index 2d2b2c9e8de..04b1cf66bc6 100644
--- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx
+++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/atomic-product-image.tsx
@@ -171,6 +171,10 @@ export class AtomicProductImage implements InitializableComponent {
this.product,
this.imageAltField
);
+ // KIT-3620
+ // if (isNullOrUndefined(value)) {
+ // return null;
+ // }
if (Array.isArray(value)) {
return value.map((v) => `${v}`.trim());
@@ -209,9 +213,9 @@ export class AtomicProductImage implements InitializableComponent {
});
if (this.images.length === 0) {
this.validateUrl(this.fallback);
-
return (
{
-// test.describe('when clicking on the next button', async ({productImage}) => {
-// test.fixme('should navigate to the next image', () => {});
-// test.fixme('should not open the product', () => {});
-// });
-// test.describe('when clicking on the previous button', async ({productImage}) => {
-// test.fixme('should navigate to the previous image', () => {});
-// test.fixme('should not open the product', () => {});
-// });
-// });
+test.describe('default', async () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.load();
+ await productImage.noCarouselImage.waitFor();
+ });
-// default
+ test('should be accessible', async ({makeAxeBuilder}) => {
+ const accessibilityResults = await makeAxeBuilder().analyze();
+ expect(accessibilityResults.violations.length).toEqual(0);
+ });
-// accessible
+ test('should render the image', async ({productImage}) => {
+ expect(productImage.noCarouselImage).toBeVisible();
+ });
-// image alt field
+ test('should have a default alt text', async ({productImage}) => {
+ const altText = await productImage.noCarouselImage.getAttribute('alt');
+ expect(altText).toEqual('Image 1 out of 1 for Nublu Water Bottle');
+ });
-// fallback
+ test('should have a 1:1 aspect ratio', async ({productImage}) => {
+ const aspectRatio =
+ await productImage.noCarouselImage.getAttribute('class');
+ expect(aspectRatio).toEqual('aspect-square');
+ });
+});
+
+test.describe('with a fallback image', async () => {
+ const FALLBACK = 'https://sports.barca.group/logos/barca.svg';
+
+ test.describe('when the product image is missing', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.withCustomThumbnails([]);
+ await productImage.load({story: 'with-a-fallback-image'});
+ await productImage.noCarouselImage.waitFor();
+ });
+
+ test('should render the fallback image', async ({productImage}) => {
+ const src = await productImage.noCarouselImage.getAttribute('src');
+ expect(src).toContain(FALLBACK);
+ });
+
+ //KIT-3619
+ test.fixme('should have a 1:1 aspect ratio', async ({productImage}) => {
+ const aspectRatio =
+ await productImage.noCarouselImage.getAttribute('class');
+ expect(aspectRatio).toEqual('aspect-square');
+ });
+ });
-// as carousel (cant make a story for this)
+ test.describe('when the product image is invalid', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.withCustomThumbnails(['invalid-image']);
+ await productImage.load({story: 'with-a-fallback-image'});
+ });
-test('as carousel', async ({productImage, page}) => {
- await productImage.withMoreImages();
- await productImage.load();
- await page.waitForTimeout(10000);
+ test('should render the fallback image', async ({productImage}) => {
+ const src = await productImage.noCarouselImage.getAttribute('src');
+ expect(src).toContain(FALLBACK);
+ });
+
+ //KIT-3619
+ test.fixme('should have a 1:1 aspect ratio', async ({productImage}) => {
+ const aspectRatio =
+ await productImage.noCarouselImage.getAttribute('class');
+ expect(aspectRatio).toEqual('aspect-square');
+ });
+ });
+
+ test.describe('when the product image is not a string', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.withCustomThumbnails([1]);
+ await productImage.load({story: 'with-a-fallback-image'});
+ });
+
+ test('should render the fallback image', async ({productImage}) => {
+ const src = await productImage.noCarouselImage.getAttribute('src');
+ expect(src).toContain(FALLBACK);
+ });
+
+ //KIT-3619
+ test.fixme('should have a 1:1 aspect ratio', async ({productImage}) => {
+ const aspectRatio =
+ await productImage.noCarouselImage.getAttribute('class');
+ expect(aspectRatio).toEqual('aspect-square');
+ });
+ });
});
-test('when the image url is not valid, should render the component with fallback image & output error message', () => {});
-test('when the image url is not a string, should render the component with fallback image & output error message', () => {});
+test.describe('with an alt text field', async () => {
+ test.describe('when imageAltField is a valid string', () => {
+ const NO_CAROUSEL_CUSTOM_FIELD = 'Nublu Water Bottle';
+ const CAROUSEL_CUSTOM_FIELD = 'Blue Lagoon Mat';
+
+ test.beforeEach(async ({productImage}) => {
+ await productImage.withCustomField(
+ 'Nublu Water Bottle',
+ 'Blue Lagoon Mat'
+ );
+ await productImage.load({story: 'with-an-alt-text-field'});
+ await productImage.noCarouselImage.waitFor();
+ });
+
+ test('should be accessible', async ({makeAxeBuilder}) => {
+ const accessibilityResults = await makeAxeBuilder().analyze();
+ expect(accessibilityResults.violations.length).toEqual(0);
+ });
+
+ test('should use the same alt text for all images', async ({
+ productImage,
+ }) => {
+ const altNoCarousel =
+ await productImage.noCarouselImage.getAttribute('alt');
+ expect(altNoCarousel).toEqual(NO_CAROUSEL_CUSTOM_FIELD);
+
+ const altCarousel = await productImage.carouselImage.getAttribute('alt');
+ expect(altCarousel).toEqual(CAROUSEL_CUSTOM_FIELD);
+ });
+ });
+
+ test.describe('when imageAltField is an array of valid strings', () => {
+ const NO_CAROUSEL_CUSTOM_FIELDS = [
+ 'FIRST Nublu Water Bottle',
+ 'SECOND Nublu Water Bottle 2',
+ ];
+ const CAROUSEL_CUSTOM_FIELDS = [
+ 'FIRST Blue Lagoon Mat',
+ 'SECOND Blue Lagoon Mat',
+ ];
+
+ test.beforeEach(async ({productImage}) => {
+ await productImage.withCustomField(
+ NO_CAROUSEL_CUSTOM_FIELDS,
+ CAROUSEL_CUSTOM_FIELDS
+ );
+ await productImage.load({story: 'with-an-alt-text-field'});
+ await productImage.noCarouselImage.waitFor();
+ });
+
+ test('should be accessible', async ({makeAxeBuilder}) => {
+ const accessibilityResults = await makeAxeBuilder().analyze();
+ expect(accessibilityResults.violations.length).toEqual(0);
+ });
+
+ test('should correctly assign alt text for the first image', async ({
+ productImage,
+ }) => {
+ const noCarouselAlt =
+ await productImage.noCarouselImage.getAttribute('alt');
+ expect(noCarouselAlt).toContain(NO_CAROUSEL_CUSTOM_FIELDS[0]);
+
+ const carouselAlt = await productImage.carouselImage.getAttribute('alt');
+ expect(carouselAlt).toContain(CAROUSEL_CUSTOM_FIELDS[0]);
+ });
+
+ test('should correctly assign alt text for the last image', async ({
+ productImage,
+ }) => {
+ await productImage.nextButton.click();
+
+ await expect
+ .poll(async () => {
+ return await productImage.carouselImage.getAttribute('alt');
+ })
+ .toContain(CAROUSEL_CUSTOM_FIELDS[1]);
+ });
+ });
+
+ test.describe('when imageAltField is not specified', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.load();
+ await productImage.noCarouselImage.waitFor();
+ });
+
+ test('should be accessible', async ({makeAxeBuilder}) => {
+ const accessibilityResults = await makeAxeBuilder().analyze();
+ expect(accessibilityResults.violations.length).toEqual(0);
+ });
+
+ test('should generate default alt text for all images', async ({
+ productImage,
+ }) => {
+ expect(await productImage.noCarouselImage.getAttribute('alt')).toEqual(
+ 'Image 1 out of 1 for Nublu Water Bottle'
+ );
+ expect(await productImage.carouselImage.getAttribute('alt')).toEqual(
+ 'Image 1 out of 2 for Blue Lagoon Mat'
+ );
+ await productImage.nextButton.click();
+ await expect
+ .poll(async () => {
+ return await productImage.carouselImage.getAttribute('alt');
+ })
+ .toContain('Image 2 out of 2 for Blue Lagoon Mat');
+ });
+ });
+
+ test.describe('when imageAltField is invalid', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.load({story: 'with-an-alt-text-field'});
+ await productImage.noCarouselImage.waitFor();
+ });
+
+ test('should be accessible', async ({makeAxeBuilder}) => {
+ const accessibilityResults = await makeAxeBuilder().analyze();
+ expect(accessibilityResults.violations.length).toEqual(0);
+ });
+
+ //KIT-3612
+ test.fixme(
+ 'should use the default alt text for all images',
+ async ({productImage}) => {
+ expect(await productImage.noCarouselImage.getAttribute('alt')).toEqual(
+ 'Image 1 out of 1 for Nublu Water Bottle'
+ );
+ expect(await productImage.carouselImage.getAttribute('alt')).toEqual(
+ 'Image 1 out of 2 for Blue Lagoon Mat'
+ );
+ await productImage.nextButton.click();
+ await expect
+ .poll(async () => {
+ return await productImage.carouselImage.getAttribute('alt');
+ })
+ .toContain('Image 2 out of 2 for Blue Lagoon Mat');
+ }
+ );
+ });
+
+ test.describe('when imageAltField is an empty array', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.withCustomField([], []);
+ await productImage.load({story: 'with-an-alt-text-field'});
+ await productImage.noCarouselImage.waitFor();
+ });
+
+ test('should be accessible', async ({makeAxeBuilder}) => {
+ const accessibilityResults = await makeAxeBuilder().analyze();
+ expect(accessibilityResults.violations.length).toEqual(0);
+ });
+
+ test('should use the default alt text for all images', async ({
+ productImage,
+ }) => {
+ expect(await productImage.noCarouselImage.getAttribute('alt')).toEqual(
+ 'Image 1 out of 1 for Nublu Water Bottle'
+ );
+ expect(await productImage.carouselImage.getAttribute('alt')).toEqual(
+ 'Image 1 out of 2 for Blue Lagoon Mat'
+ );
+ await productImage.nextButton.click();
+ await expect
+ .poll(async () => {
+ return await productImage.carouselImage.getAttribute('alt');
+ })
+ .toContain('Image 2 out of 2 for Blue Lagoon Mat');
+ });
+ });
+});
+
+test.describe('as a carousel', async () => {
+ const URL =
+ 'http://localhost:4400/iframe.html?id=atomic-product-image--default&viewMode=story#sortCriteria=relevance';
+ const FIRST_IMAGE =
+ 'https://images.barca.group/Sports/mj/Trampolines%20%26%20Floats/Huge%20inflatable%20mats/3_Blue/df1a99488425_bottom_right.webp';
+ const SECOND_IMAGE =
+ 'https://images.barca.group/Sports/mj/Trampolines%20%26%20Floats/Huge%20inflatable%20mats/3_Blue/df1a99488425_bottom_left.webp';
+
+ test.beforeEach(async ({productImage}) => {
+ await productImage.load();
+ await productImage.carouselImage.waitFor();
+ });
+
+ test('should be accessible', async ({makeAxeBuilder}) => {
+ const accessibilityResults = await makeAxeBuilder().analyze();
+ expect(accessibilityResults.violations.length).toEqual(0);
+ });
+
+ test('should render the first image by default', async ({productImage}) => {
+ await expect(productImage.carouselImage).toBeVisible();
+ const src = await productImage.carouselImage.getAttribute('src');
+ expect(src).toContain(FIRST_IMAGE);
+ });
+
+ test.describe('when clicking the next button', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.nextButton.click();
+ });
+
+ test('should navigate to the next image', async ({productImage}) => {
+ await expect
+ .poll(async () => {
+ const src = await productImage.carouselImage.getAttribute('src');
+ return src;
+ })
+ .toContain(SECOND_IMAGE);
+ });
+
+ test('should not open the product', async ({page}) => {
+ expect(page.url()).toEqual(URL);
+ });
+ });
+
+ test.describe('when clicking the previous button', () => {
+ test.beforeEach(async ({productImage}) => {
+ await productImage.previousButton.click();
+ });
+
+ test('should navigate to the previous image', async ({productImage}) => {
+ await expect
+ .poll(async () => {
+ const src = await productImage.carouselImage.getAttribute('src');
+ return src;
+ })
+ .toContain(SECOND_IMAGE);
+ });
-test('when rendered as a single image', async ({productImage, page}) => {
- await productImage.withNoImage();
- await productImage.load();
- await page.waitForTimeout(10000);
+ test('should not open the product', async ({page}) => {
+ expect(page.url()).toEqual(URL);
+ });
+ });
});
diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/fixture.ts b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/fixture.ts
index ff7d1f91b90..7d1d031d2e3 100644
--- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/fixture.ts
+++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/fixture.ts
@@ -1,6 +1,4 @@
-import {SortBy} from '@coveo/headless/commerce';
-import {CommerceSuccessResponse} from '@coveo/headless/dist/definitions/api/commerce/common/response';
-import {test as base, Page} from '@playwright/test';
+import {test as base} from '@playwright/test';
import {
AxeFixture,
makeAxeBuilder,
@@ -17,53 +15,5 @@ export const test = base.extend({
await use(new ProductImageObject(page));
},
});
-export {expect} from '@playwright/test';
-export async function setImages(page: Page, urls: string[]) {
- await page.route('**/v2/search', async (route) => {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify({
- facets: [],
- pagination: {
- page: 0,
- perPage: 1,
- totalEntries: 1,
- totalPages: 1,
- },
- products: [
- {
- additionalFields: {},
- children: [],
- clickUri: '',
- ec_brand: '',
- ec_category: [],
- ec_color: '',
- ec_description: '',
- ec_gender: '',
- ec_images: urls,
- ec_in_stock: true,
- ec_item_group_id: '',
- ec_listing: '',
- ec_name: 'name',
- ec_price: 0,
- ec_product_id: '',
- ec_promo_price: 0,
- ec_rating: 0,
- ec_shortdesc: '',
- ec_thumbnails: urls,
- permanentid: 'permanentid',
- totalNumberOfChildren: 0,
- },
- ],
- responseId: '',
- sort: {
- appliedSort: {sortCriteria: SortBy.Relevance},
- availableSorts: [],
- },
- triggers: [],
- } as CommerceSuccessResponse),
- });
- });
-}
+export {expect} from '@playwright/test';
diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/page-object.ts b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/page-object.ts
index fcba569999a..f7bae26e7f8 100644
--- a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/page-object.ts
+++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/page-object.ts
@@ -6,15 +6,28 @@ export class ProductImageObject extends BasePageObject<'atomic-product-image'> {
super(page, 'atomic-product-image');
}
- async withMoreImages() {
+ get noCarouselImage() {
+ return this.page.getByRole('img').nth(0);
+ }
+
+ get carouselImage() {
+ return this.page.getByRole('img').nth(1);
+ }
+
+ get nextButton() {
+ return this.page.getByRole('button', {name: 'Next'});
+ }
+
+ get previousButton() {
+ return this.page.getByRole('button', {name: 'Previous'});
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ async withCustomThumbnails(thumbnails: any[]) {
await this.page.route('**/commerce/v2/listing', async (route) => {
const response = await route.fetch();
const body = await response.json();
- body.products[0].ec_thumbnails = [
- 'https://images.barca.group/Sports/mj/Sandals%20%26%20Shoes/Sandals/47_Blue_Women_Logo_Flip_Flop/7940686db76f_bottom_left.webp',
- 'https://images.barca.group/Sports/mj/Clothing/T-Shirts/29_Women_Blue_Cotton/2b1a880a2e30_bottom_left.webp',
- 'https://images.barca.group/Sports/mj/Clothing/T-Shirts/29_Women_Blue_Elastane/892ee4fe4145_bottom_left.webp',
- ];
+ body.products[0].ec_thumbnails = thumbnails;
await route.fulfill({
response,
@@ -24,11 +37,15 @@ export class ProductImageObject extends BasePageObject<'atomic-product-image'> {
return this;
}
- async withNoImage() {
+ async withCustomField(
+ fieldNoCarousel: string | string[],
+ fieldCarousel: string | string[]
+ ) {
await this.page.route('**/commerce/v2/listing', async (route) => {
const response = await route.fetch();
const body = await response.json();
- body.products[0].ec_thumbnails = [];
+ body.products[0].custom_alt_field = fieldNoCarousel;
+ body.products[1].custom_alt_field = fieldCarousel;
await route.fulfill({
response,