Skip to content

Commit

Permalink
test(atomic): atomic-commerce-no-products (#4098)
Browse files Browse the repository at this point in the history
* Add E2E tests for `atomic-commerce-no-products`
* Include "No Results" message in `atomic-aria-live`

https://coveord.atlassian.net/browse/KIT-3252
  • Loading branch information
y-lakhdar authored Jun 26, 2024
1 parent 8cd544d commit 6ad5665
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
buildProductListing,
} from '@coveo/headless/commerce';
import {Component, State, h} from '@stencil/core';
import {AriaLiveRegion} from '../../../utils/accessibility-utils';
import {
BindStateToController,
InitializableComponent,
Expand All @@ -16,6 +17,7 @@ import {NoItemsGuard} from '../../common/no-items/guard';
import {MagnifyingGlass} from '../../common/no-items/magnifying-glass';
import {NoItems} from '../../common/no-items/no-items';
import {SearchTips} from '../../common/no-items/tips';
import {getSummary} from '../../common/no-items/utils';
import {CommerceBindings} from '../atomic-commerce-interface/atomic-commerce-interface';

/**
Expand Down Expand Up @@ -44,6 +46,8 @@ export class AtomicCommerceNoProducts
@State()
private summaryState!: SearchSummaryState | ProductListingSummaryState;
@State() public error!: Error;
@AriaLiveRegion('no-products')
protected ariaMessage!: string;

public initialize() {
const controller =
Expand All @@ -56,6 +60,13 @@ export class AtomicCommerceNoProducts
const {
bindings: {i18n},
} = this;

this.ariaMessage = getSummary(
i18n,
'query' in this.summaryState ? this.summaryState.query : '',
this.summary.state.hasProducts
);

return (
<NoItemsGuard
isLoading={this.summaryState.isLoading}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
playExecuteFirstSearch,
wrapInCommerceInterface,
} from '@coveo/atomic/storybookUtils/commerce-interface-wrapper';
import {parameters} from '@coveo/atomic/storybookUtils/common-meta-parameters';
import {renderComponent} from '@coveo/atomic/storybookUtils/render-component';
import type {Meta, StoryObj as Story} from '@storybook/web-components';
import {html} from 'lit/static-html.js';

const {play} = wrapInCommerceInterface({skipFirstSearch: true});
const {decorator, play: preprocessedPlayed} = wrapInCommerceInterface({
skipFirstSearch: true,
engineConfig: {
preprocessRequest: (r) => {
const parsed = JSON.parse(r.body as string);
parsed.query = 'NOT @URI';
r.body = JSON.stringify(parsed);
return r;
},
},
});

const meta: Meta = {
component: 'atomic-commerce-no-products',
title: 'Atomic-Commerce/NoProduct',
id: 'atomic-commerce-no-products',
render: renderComponent,
decorators: [decorator],
parameters,
play: preprocessedPlayed,
};

export default meta;

export const Default: Story = {
name: 'atomic-commerce-no-products',
decorators: [
(story) =>
html` <atomic-commerce-layout>
<atomic-layout-section section="search">
<atomic-commerce-search-box
role="searchbox"
></atomic-commerce-search-box>
</atomic-layout-section>
<atomic-layout-section section="main">
<atomic-layout-section section="products">
${story()}
</atomic-layout-section>
</atomic-layout-section>
</atomic-commerce-layout>`,
],
play: async (context) => {
await preprocessedPlayed(context);
await playExecuteFirstSearch(context);
},
};

export const WithResults: Story = {
name: 'With Results',
tags: ['test'],
decorators: [
(story) =>
html` <atomic-commerce-layout>
<atomic-layout-section section="search">
<atomic-commerce-search-box
role="searchbox"
></atomic-commerce-search-box>
</atomic-layout-section>
<atomic-layout-section section="main">
<atomic-layout-section section="products">
${story()}
</atomic-layout-section>
</atomic-layout-section>
</atomic-commerce-layout>`,
],
play: async (context) => {
await play(context);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {test, expect} from './fixture';

test.describe('when there are results', () => {
test.beforeEach(async ({page}) => {
await page.goto(
'http://localhost:4400/iframe.html?id=atomic-commerce-no-products--with-results&viewMode=story'
);
});

test('should not be visible', async ({noProducts}) => {
await expect(noProducts.ariaLive()).not.toBeVisible();
});

test.describe('after executing a search query that yields no results', () => {
test.beforeEach(async ({searchBox}) => {
await searchBox.hydrated.waitFor();
// eslint-disable-next-line @cspell/spellchecker
await searchBox.searchInput.fill('gahaiusdhgaiuewjfsf');
await searchBox.submitButton.click();
});

test('should have aria live', async ({noProducts}) => {
// eslint-disable-next-line @cspell/spellchecker
await expect(noProducts.ariaLive('gahaiusdhgaiuewjfsf')).toBeVisible();
});

test('should display no result message', async ({noProducts}) => {
// eslint-disable-next-line @cspell/spellchecker
await expect(noProducts.message('gahaiusdhgaiuewjfsf')).toBeVisible();
});
});

test.describe('after executing a search query that returns results', () => {
test.beforeEach(async ({searchBox}) => {
await searchBox.hydrated.waitFor();
await searchBox.searchInput.fill('kayak');
await searchBox.submitButton.click();
});

test('should remove aria live', async ({noProducts}) => {
await expect(noProducts.ariaLive()).not.toBeVisible();
await expect(noProducts.ariaLive('kayak')).not.toBeVisible();
});

test('should remove no result message', async ({noProducts}) => {
await expect(noProducts.message()).not.toBeVisible();
});
});
});

test.describe('when there are no results', () => {
test.beforeEach(async ({page}) => {
await page.goto(
'http://localhost:4400/iframe.html?id=atomic-commerce-no-products--default&viewMode=story'
);
});

test('should be present in the page', async ({noProducts}) => {
await expect(noProducts.ariaLive()).toBeVisible();
});

test('should display no result message', async ({noProducts}) => {
await expect(noProducts.message()).toBeVisible();
});

test('should display search tips', async ({noProducts}) => {
await expect(noProducts.searchTips()).toBeVisible();
});

test.describe('when the query contains HTML characters', () => {
test.beforeEach(async ({searchBox}) => {
await searchBox.hydrated.waitFor();
await searchBox.searchInput.fill('<div>$@#()-^!query</div>');
await searchBox.submitButton.click();
});

test('should display the query with HTML characters', async ({
noProducts,
}) => {
await expect(
noProducts.message('<div>$@#()-^!query</div>')
).toBeVisible();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {test as base} from '@playwright/test';
import {
AxeFixture,
makeAxeBuilder,
} from '../../../../../playwright-utils/base-fixture';
import {SearchBoxPageObject as SearchBox} from '../../atomic-commerce-search-box/e2e/page-object';
import {NoProductsPageObject as NoProducts} from './page-object';

type MyFixtures = {
searchBox: SearchBox;
noProducts: NoProducts;
};

export const test = base.extend<MyFixtures & AxeFixture>({
makeAxeBuilder,
searchBox: async ({page}, use) => {
await use(new SearchBox(page));
},
noProducts: async ({page}, use) => {
await use(new NoProducts(page));
},
});
export {expect} from '@playwright/test';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type {Page} from '@playwright/test';

export class NoProductsPageObject {
private page: Page;
constructor(page: Page) {
this.page = page;
}

searchTips() {
return this.page.locator('[part=search-tips]');
}

ariaLive(query?: string) {
const text = query
? `We couldn't find anything for ${query}`
: 'No results';

return this.page.getByRole('status').filter({hasText: text});
}

message(query?: string) {
return this.page.locator('[part="no-results"]', {
hasText: query
? `We couldn't find anything for “${query}”`
: 'No results',
});
}
}

0 comments on commit 6ad5665

Please sign in to comment.