Skip to content

Commit

Permalink
test(atomic): add tests for atomic-commerce-recommendation-list (#4437)
Browse files Browse the repository at this point in the history
https://coveord.atlassian.net/browse/KIT-3248

---------

Co-authored-by: Frederic Beaudoin <[email protected]>
  • Loading branch information
alexprudhomme and fbeaudoincoveo authored Sep 27, 2024
1 parent ff39242 commit 74dff0d
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 12 deletions.
14 changes: 14 additions & 0 deletions packages/atomic/playwright-utils/base-page-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown> = {};
Object.entries(args as Record<string, unknown>).forEach(([key, value]) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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': `<atomic-product-template>
<template>
<atomic-product-section-name>
<atomic-product-link class="font-bold"></atomic-product-link>
</atomic-product-section-name>
<atomic-product-section-visual>
<atomic-product-image field="ec_thumbnails"></atomic-product-image>
</atomic-product-section-visual>
<atomic-product-section-metadata>
<atomic-product-text field="ec_brand" class="text-neutral-dark block"></atomic-product-text>
<atomic-product-rating field="ec_rating"></atomic-product-rating>
</atomic-product-section-metadata>
<atomic-product-section-emphasized>
<atomic-product-price currency="USD"></atomic-product-price>
</atomic-product-section-emphasized>
<atomic-product-section-children>
<atomic-product-children></atomic-product-children>
</atomic-product-section-children>
</template>
</atomic-product-template>`,
},
};
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': ` <atomic-product-template>
<template>
<atomic-product-section-visual>
<span>Visual Section</span>
</atomic-product-section-visual>
<atomic-product-section-badge>
<span>Badge Section</span>
</atomic-product-section-badge>
<atomic-product-section-actions>
<span>Actions Section</span>
</atomic-product-section-actions>
<atomic-product-section-title>
<span>Title Section</span>
</atomic-product-section-title>
<atomic-product-section-title-metadata>
<span>Title Metadata Section</span>
</atomic-product-section-title-metadata>
<atomic-product-section-emphasized>
<span>Emphasized Section</span>
</atomic-product-section-emphasized>
<atomic-product-section-excerpt>
<span>Excerpt Section</span>
</atomic-product-section-excerpt>
<atomic-product-section-bottom-metadata>
<span>Bottom Metadata Section</span>
</atomic-product-section-bottom-metadata>
</template>
</atomic-product-template>`,
},
};

export const AsCarousel: Story = {
args: {
'attributes-products-per-page': 3,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -409,7 +412,7 @@ export class AtomicCommerceRecommendationList
public render() {
if (this.hasNoProducts) {
this.bindings.store.unsetLoadingFlag(this.loadingFlag);
return;
return <Hidden></Hidden>;
}
return (
<Fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
Original file line number Diff line number Diff line change
@@ -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<MyFixtures & AxeFixture>({
makeAxeBuilder,
recommendationList: async ({page}, use) => {
await use(new RecommendationList(page));
},
});

export {expect} from '@playwright/test';
Original file line number Diff line number Diff line change
@@ -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');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand Down
9 changes: 2 additions & 7 deletions packages/atomic/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}

0 comments on commit 74dff0d

Please sign in to comment.