From 74dff0d3a9998a541e8ade9a00d6b65e51acbc29 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:19:01 -0400 Subject: [PATCH] test(atomic): add tests for atomic-commerce-recommendation-list (#4437) https://coveord.atlassian.net/browse/KIT-3248 --------- Co-authored-by: Frederic Beaudoin --- .../playwright-utils/base-page-object.ts | 14 +++ ...mmerce-recommendation-list.new.stories.tsx | 92 ++++++++++++++++ .../atomic-commerce-recommendation-list.tsx | 5 +- ...atomic-commerce-recommendation-list.e2e.ts | 102 ++++++++++++++++++ .../e2e/fixture.ts | 19 ++++ .../e2e/page-object.ts | 30 ++++++ .../e2e/atomic-recs-list.e2e.ts | 8 +- packages/atomic/tsconfig.json | 9 +- 8 files changed, 267 insertions(+), 12 deletions(-) create mode 100644 packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.new.stories.tsx create mode 100644 packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/atomic-commerce-recommendation-list.e2e.ts create mode 100644 packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/fixture.ts create mode 100644 packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/page-object.ts diff --git a/packages/atomic/playwright-utils/base-page-object.ts b/packages/atomic/playwright-utils/base-page-object.ts index 807285fd74c..47b6d569309 100644 --- a/packages/atomic/playwright-utils/base-page-object.ts +++ b/packages/atomic/playwright-utils/base-page-object.ts @@ -51,6 +51,20 @@ export class BasePageObject< return this; } + async noRecommendations() { + await this.page.route('**/commerce/v2/recommendations', async (route) => { + const response = await route.fetch(); + const body = await response.json(); + body.products = []; + await route.fulfill({ + response, + json: body, + }); + }); + + return this; + } + private camelToKebab(args: Component) { const toKebab: Record = {}; Object.entries(args as Record).forEach(([key, value]) => { diff --git a/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.new.stories.tsx b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.new.stories.tsx new file mode 100644 index 00000000000..2c03f361038 --- /dev/null +++ b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.new.stories.tsx @@ -0,0 +1,92 @@ +import {wrapInCommerceInterface} from '@coveo/atomic-storybook-utils/commerce/commerce-interface-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, StoryObj as Story} from '@storybook/web-components'; + +const {decorator, play} = wrapInCommerceInterface({skipFirstSearch: false}); + +const meta: Meta = { + component: 'atomic-commerce-recommendation-list', + title: 'Atomic-Commerce/Atomic Recommendation List', + id: 'atomic-commerce-recommendation-list', + render: renderComponent, + decorators: [decorator], + parameters, + play, + args: { + 'attributes-slot-id': 'd8118c04-ff59-4f03-baca-2fc5f3b81221', + 'slots-default': ` + + `, + }, +}; +export default meta; + +export const Default: Story = { + name: 'atomic-commerce-recommendation-list', +}; + +const {play: playNoFirstQuery} = wrapInCommerceInterface({ + skipFirstSearch: true, +}); +export const BeforeQuery: Story = { + tags: ['test'], + play: playNoFirstQuery, +}; + +export const WithFullTemplate: Story = { + tags: ['test'], + args: { + 'slots-default': ` + + `, + }, +}; + +export const AsCarousel: Story = { + args: { + 'attributes-products-per-page': 3, + }, +}; diff --git a/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.tsx b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.tsx index bc5d86a0908..019393485b3 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.tsx +++ b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/atomic-commerce-recommendation-list.tsx @@ -26,6 +26,7 @@ import {randomID} from '../../../utils/utils'; import {ResultsPlaceholdersGuard} from '../../common/atomic-result-placeholder/placeholders'; import {Carousel} from '../../common/carousel'; import {Heading} from '../../common/heading'; +import {Hidden} from '../../common/hidden'; import {DisplayGrid} from '../../common/item-list/display-grid'; import {DisplayWrapper} from '../../common/item-list/display-wrapper'; import {ItemDisplayGuard} from '../../common/item-list/item-display-guard'; @@ -56,6 +57,8 @@ import {SelectChildProductEventArgs} from '../product-template-components/atomic * @part indicators - The list of indicators. * @part indicator - A single indicator. * @part active-indicator - The active indicator. + * + * @slot default - The default slot where the product templates are defined. */ @Component({ tag: 'atomic-commerce-recommendation-list', @@ -409,7 +412,7 @@ export class AtomicCommerceRecommendationList public render() { if (this.hasNoProducts) { this.bindings.store.unsetLoadingFlag(this.loadingFlag); - return; + return ; } return ( diff --git a/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/atomic-commerce-recommendation-list.e2e.ts b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/atomic-commerce-recommendation-list.e2e.ts new file mode 100644 index 00000000000..14a89e99074 --- /dev/null +++ b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/atomic-commerce-recommendation-list.e2e.ts @@ -0,0 +1,102 @@ +import {test, expect} from './fixture'; + +test.describe('before query is loaded', () => { + test.beforeEach(async ({recommendationList}) => { + await recommendationList.load({story: 'before-query'}); + await recommendationList.hydrated.waitFor(); + }); + + test('should be a11y compliant', async ({makeAxeBuilder}) => { + const accessibilityResults = await makeAxeBuilder().analyze(); + expect(accessibilityResults.violations.length).toEqual(0); + }); + + test('should have placeholders', async ({recommendationList}) => { + await expect(recommendationList.placeholder.first()).toBeVisible(); + }); +}); + +test.describe('after query is loaded', () => { + test.beforeEach(async ({recommendationList}) => { + await recommendationList.load({story: 'default'}); + await recommendationList.hydrated.waitFor(); + }); + + test('should be a11y compliant', async ({makeAxeBuilder}) => { + const accessibilityResults = await makeAxeBuilder().analyze(); + expect(accessibilityResults.violations.length).toEqual(0); + }); + + test('should have recommendations', async ({recommendationList}) => { + await expect(recommendationList.recommendation.first()).toBeVisible(); + }); +}); + +test.describe('with a full result template', () => { + test.beforeEach(async ({recommendationList}) => { + await recommendationList.load({story: 'with-full-template'}); + await recommendationList.hydrated.waitFor(); + }); + + test('should be a11y compliant', async ({makeAxeBuilder}) => { + const accessibilityResults = await makeAxeBuilder().analyze(); + expect(accessibilityResults.violations.length).toEqual(0); + }); + + test('should have recommendations', async ({recommendationList}) => { + await expect(recommendationList.recommendation.first()).toBeVisible(); + }); +}); + +test.describe('with a carousel', () => { + test.beforeEach(async ({recommendationList}) => { + await recommendationList.load({story: 'as-carousel'}); + await recommendationList.hydrated.waitFor(); + }); + + test('should be a11y compliant', async ({makeAxeBuilder}) => { + const accessibilityResults = await makeAxeBuilder().analyze(); + expect(accessibilityResults.violations.length).toEqual(0); + }); + + test('should have recommendations', async ({recommendationList}) => { + await expect(recommendationList.recommendation.first()).toBeVisible(); + }); + + test('should support going forward and backward', async ({ + recommendationList, + }) => { + await recommendationList.nextButton.click(); + await expect(recommendationList.indicators.nth(1)).toHaveAttribute( + 'part', + 'indicator active-indicator' + ); + + await recommendationList.prevButton.click(); + await recommendationList.prevButton.click(); + await expect(recommendationList.indicators.nth(3)).toHaveAttribute( + 'part', + 'indicator active-indicator' + ); + + await recommendationList.nextButton.click(); + await expect(recommendationList.indicators.nth(0)).toHaveAttribute( + 'part', + 'indicator active-indicator' + ); + }); +}); + +test('with no recommendations returned by the API, should render placeholders', async ({ + recommendationList, +}) => { + await recommendationList.noRecommendations(); + await recommendationList.load({story: 'default'}); + await recommendationList.hydrated.waitFor({state: 'hidden'}); + await expect + .poll(async () => await recommendationList.recommendation.count()) + .toBe(0); + await expect + .poll(async () => await recommendationList.placeholder.count()) + .toBe(0); +}); diff --git a/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/fixture.ts b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/fixture.ts new file mode 100644 index 00000000000..ba7995b3704 --- /dev/null +++ b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/fixture.ts @@ -0,0 +1,19 @@ +import {test as base} from '@playwright/test'; +import { + AxeFixture, + makeAxeBuilder, +} from '../../../../../playwright-utils/base-fixture'; +import {AtomicCommerceRecommendationList as RecommendationList} from './page-object'; + +type MyFixtures = { + recommendationList: RecommendationList; +}; + +export const test = base.extend({ + makeAxeBuilder, + recommendationList: async ({page}, use) => { + await use(new RecommendationList(page)); + }, +}); + +export {expect} from '@playwright/test'; diff --git a/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/page-object.ts b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/page-object.ts new file mode 100644 index 00000000000..3f576bb3f01 --- /dev/null +++ b/packages/atomic/src/components/commerce/atomic-commerce-recommendation-list/e2e/page-object.ts @@ -0,0 +1,30 @@ +import type {Page} from '@playwright/test'; +import {BasePageObject} from '../../../../../playwright-utils/base-page-object'; + +export class AtomicCommerceRecommendationList extends BasePageObject<'atomic-commerce-recommendation-list'> { + constructor(page: Page) { + super(page, 'atomic-commerce-recommendation-list'); + } + + get placeholder() { + return this.page.locator('.placeholder'); + } + + get recommendation() { + return this.page.locator( + '[part="result-list-grid-clickable-container outline"]' + ); + } + + get indicators() { + return this.page.getByRole('listitem'); + } + + get nextButton() { + return this.page.getByLabel('Next'); + } + + get prevButton() { + return this.page.getByLabel('Previous'); + } +} diff --git a/packages/atomic/src/components/recommendations/atomic-recs-list/e2e/atomic-recs-list.e2e.ts b/packages/atomic/src/components/recommendations/atomic-recs-list/e2e/atomic-recs-list.e2e.ts index 9511e10b607..b72f0a53e27 100644 --- a/packages/atomic/src/components/recommendations/atomic-recs-list/e2e/atomic-recs-list.e2e.ts +++ b/packages/atomic/src/components/recommendations/atomic-recs-list/e2e/atomic-recs-list.e2e.ts @@ -6,7 +6,7 @@ test.describe('before query is loaded', () => { await recsList.hydrated.waitFor(); }); - test('should be ally compliant', async ({makeAxeBuilder}) => { + test('should be a11y compliant', async ({makeAxeBuilder}) => { const accessibilityResults = await makeAxeBuilder().analyze(); expect(accessibilityResults.violations.length).toEqual(0); }); @@ -22,7 +22,7 @@ test.describe('after query is loaded', () => { await recsList.hydrated.waitFor(); }); - test('should be ally compliant', async ({makeAxeBuilder}) => { + test('should be a11y compliant', async ({makeAxeBuilder}) => { const accessibilityResults = await makeAxeBuilder().analyze(); expect(accessibilityResults.violations.length).toEqual(0); }); @@ -38,7 +38,7 @@ test.describe('with a full result template', () => { await recsList.hydrated.waitFor(); }); - test('should be ally compliant', async ({makeAxeBuilder}) => { + test('should be a11y compliant', async ({makeAxeBuilder}) => { const accessibilityResults = await makeAxeBuilder().analyze(); expect(accessibilityResults.violations.length).toEqual(0); }); @@ -54,7 +54,7 @@ test.describe('with a carousel', () => { await recsList.hydrated.waitFor(); }); - test('should be ally compliant', async ({makeAxeBuilder}) => { + test('should be a11y compliant', async ({makeAxeBuilder}) => { const accessibilityResults = await makeAxeBuilder().analyze(); expect(accessibilityResults.violations.length).toEqual(0); }); diff --git a/packages/atomic/tsconfig.json b/packages/atomic/tsconfig.json index 3d5942e2885..ad57f4ba8ad 100644 --- a/packages/atomic/tsconfig.json +++ b/packages/atomic/tsconfig.json @@ -1,10 +1,5 @@ { "extends": "./tsconfig.stencil.json", - "exclude": [ - "node_modules", - "src/external-builds", - "**/*.stories.tsx", - "**/*.stories.ts", - "**/*.stories.js" - ] + + "exclude": ["node_modules", "src/external-builds"] }