diff --git a/packages/atomic/cypress/e2e/breadbox.cypress.ts b/packages/atomic/cypress/e2e/breadbox.cypress.ts index 1d390b1787b..1c50ff01070 100644 --- a/packages/atomic/cypress/e2e/breadbox.cypress.ts +++ b/packages/atomic/cypress/e2e/breadbox.cypress.ts @@ -271,7 +271,7 @@ describe('Breadbox Test Suites', () => { }); describe('when excluding from a standard facet', () => { - const selectionIndex = 2; + const selectionIndex = 1; function setupFacetWithMultipleExcludedValues() { new TestFixture() diff --git a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx index 2e5f573745d..949e299bf94 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx +++ b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.tsx @@ -203,22 +203,46 @@ export class AtomicCommerceSearchBox @AriaLiveRegion('search-suggestions', true) protected suggestionsAriaMessage!: string; + public disconnectedCallback = () => {}; + + private isStandaloneSearchBox( + searchBox: SearchBox | StandaloneSearchBox + ): searchBox is StandaloneSearchBox { + return 'redirectTo' in searchBox; + } public initialize() { - this.id = randomID('atomic-commerce-search-box-'); + this.id ??= randomID('atomic-commerce-search-box-'); + + this.initializeSearchboxController(); + this.initializeSuggestionManager(); + } + + private updateRedirectionUrl() { + if (this.isStandaloneSearchBox(this.searchBox) && this.redirectionUrl) { + this.searchBox.updateRedirectUrl(this.redirectionUrl); + } else { + this.registerNewSearchBoxController(); + } + } + + private registerNewSearchBoxController() { + this.disconnectedCallback(); + this.initialize(); + } + private initializeSearchboxController() { this.searchBox = this.redirectionUrl ? buildStandaloneSearchBox(this.bindings.engine, { options: { ...this.searchBoxOptions, redirectionUrl: this.redirectionUrl, + overwrite: true, }, }) : buildSearchBox(this.bindings.engine, { options: this.searchBoxOptions, }); - - this.initializeSuggestionManager(); } public componentWillUpdate() { @@ -271,7 +295,7 @@ export class AtomicCommerceSearchBox this.suggestionManager.forceUpdate(); } - public componentWillRender() { + private registerSearchboxSuggestionEvents() { this.searchBoxSuggestionEventsQueue.forEach((evt) => { this.suggestionManager.registerSuggestionsFromEvent( evt, @@ -283,7 +307,7 @@ export class AtomicCommerceSearchBox @Watch('redirectionUrl') watchRedirectionUrl() { - this.initialize(); + this.updateRedirectionUrl(); } private initializeSuggestionManager() { @@ -324,8 +348,8 @@ export class AtomicCommerceSearchBox return { ...this.bindings, id: this.id, - isStandalone: !!this.redirectionUrl, - searchBoxController: this.searchBox, + isStandalone: () => !!this.redirectionUrl, + searchBoxController: () => this.searchBox, numberOfQueries: this.numberOfQueries, clearFilters: this.clearFilters, }; @@ -675,6 +699,9 @@ export class AtomicCommerceSearchBox const isDisabled = this.isSearchDisabledForEndUser( this.searchBoxState.value ); + if (!this.suggestionManager.suggestions.length) { + this.registerSearchboxSuggestionEvents(); + } return ( diff --git a/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/atomic-commerce-search-box.e2e.ts b/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/atomic-commerce-search-box.e2e.ts index 56cb9dc3add..e28c87f6c32 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/atomic-commerce-search-box.e2e.ts +++ b/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/atomic-commerce-search-box.e2e.ts @@ -274,6 +274,35 @@ test.describe('with instant results & query suggestions', () => { await expect(searchBox.searchInput).toHaveValue('surf'); }); }); + + test.describe('after updating the redirect-url attribute', () => { + test.beforeEach(async ({searchBox}) => { + await searchBox.component.evaluate((node) => + node.setAttribute( + 'redirection-url', + './iframe.html?id=atomic-commerce-search-box--in-page&viewMode=story&args=enable-query-syntax:!true;suggestion-timeout:5000' + ) + ); + }); + + test('should redirect to the specified url after selecting a suggestion', async ({ + page, + searchBox, + }) => { + const suggestionText = await searchBox + .searchSuggestions() + .first() + .textContent(); + + expect(suggestionText).not.toBeNull(); + + await searchBox.searchSuggestions().first().click(); + await page.waitForURL( + '**/iframe.html?id=atomic-commerce-search-box--in-page*' + ); + await expect(searchBox.searchInput).toHaveValue(suggestionText ?? ''); + }); + }); }); }); diff --git a/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/page-object.ts b/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/page-object.ts index 9c0763a49c6..03023dfaae6 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/page-object.ts +++ b/packages/atomic/src/components/commerce/atomic-commerce-search-box/e2e/page-object.ts @@ -6,6 +6,10 @@ export class SearchBoxPageObject extends BasePageObject<'atomic-commerce-search- super(page, 'atomic-commerce-search-box'); } + get component() { + return this.page.locator('atomic-commerce-search-box'); + } + get submitButton() { return this.page.getByLabel('Search', {exact: true}); } diff --git a/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/atomic-product-image.e2e.ts b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/atomic-product-image.e2e.ts new file mode 100644 index 00000000000..c95f69122bf --- /dev/null +++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/atomic-product-image.e2e.ts @@ -0,0 +1,12 @@ +// import {test, expect} from './fixture'; + +// test.describe('default', async () => { +// 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', () => {}); +// }); +// }); 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 new file mode 100644 index 00000000000..ca73e2a7976 --- /dev/null +++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/fixture.ts @@ -0,0 +1,18 @@ +import {test as base} from '@playwright/test'; +import { + AxeFixture, + makeAxeBuilder, +} from '../../../../../../playwright-utils/base-fixture'; +import {ProductImageObject} from './page-object'; + +interface TestFixture { + productImage: ProductImageObject; +} + +export const test = base.extend({ + makeAxeBuilder, + productImage: async ({page}, use) => { + await use(new ProductImageObject(page)); + }, +}); +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 new file mode 100644 index 00000000000..30a89aebb3a --- /dev/null +++ b/packages/atomic/src/components/commerce/product-template-components/atomic-product-image/e2e/page-object.ts @@ -0,0 +1,9 @@ +import type {Page} from '@playwright/test'; +import {BasePageObject} from '../../../../../../playwright-utils/base-page-object'; + +export class ProductImageObject extends BasePageObject<'atomic-product-image'> { + constructor(page: Page) { + super(page, 'atomic-product-image'); + } + // TODO tests +} diff --git a/packages/atomic/src/components/commerce/product-template/atomic-product-template.new.stories.tsx b/packages/atomic/src/components/commerce/product-template/atomic-product-template.new.stories.tsx index 59ab137fb36..2d169ea774b 100644 --- a/packages/atomic/src/components/commerce/product-template/atomic-product-template.new.stories.tsx +++ b/packages/atomic/src/components/commerce/product-template/atomic-product-template.new.stories.tsx @@ -94,7 +94,7 @@ export const InASearchBoxInstantProducts: Story = { commerceInterfaceDecorator, ], play: async (context) => { - initializeCommerceInterface(context); + await initializeCommerceInterface(context); const {canvasElement, step} = context; const canvas = within(canvasElement); await step('Click Searchbox', async () => { diff --git a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx index d644140fc69..8a871088e16 100644 --- a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx +++ b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-instant-products/atomic-commerce-search-box-instant-products.tsx @@ -176,10 +176,10 @@ export class AtomicCommerceSearchBoxInstantProducts content: , onSelect: () => { this.bindings.clearSuggestions(); - this.bindings.searchBoxController.updateText( - this.instantProducts.state.query - ); - this.bindings.searchBoxController.submit(); + this.bindings + .searchBoxController() + .updateText(this.instantProducts.state.query); + this.bindings.searchBoxController().submit(); }, }); } @@ -226,7 +226,7 @@ export class AtomicCommerceSearchBoxInstantProducts private onSuggestedQueryChange() { if ( !this.bindings.getSuggestionElements().length && - !this.bindings.searchBoxController.state.value + !this.bindings.searchBoxController().state.value ) { console.warn( "There doesn't seem to be any query suggestions configured. Make sure to include either an atomic-commerce-search-box-query-suggestions or atomic-commerce-search-box-recent-queries in your search box in order to see some instant products." diff --git a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx index fe892b4f80d..ab5f5add258 100644 --- a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx +++ b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-query-suggestions/atomic-commerce-search-box-query-suggestions.tsx @@ -87,15 +87,16 @@ export class AtomicCommerceSearchBoxQuerySuggestions { } private renderItems(): SearchBoxSuggestionElement[] { - const hasQuery = this.bindings.searchBoxController.state.value !== ''; + const hasQuery = this.bindings.searchBoxController().state.value !== ''; const max = hasQuery ? this.maxWithQuery : this.maxWithoutQuery; - return this.bindings.searchBoxController.state.suggestions - .slice(0, max) + return this.bindings + .searchBoxController() + .state.suggestions.slice(0, max) .map((suggestion) => this.renderItem(suggestion)); } private renderItem(suggestion: Suggestion) { - const hasQuery = this.bindings.searchBoxController.state.value !== ''; + const hasQuery = this.bindings.searchBoxController().state.value !== ''; const partialItem = getPartialSearchBoxSuggestionElement( suggestion, this.bindings.i18n @@ -114,7 +115,9 @@ export class AtomicCommerceSearchBoxQuerySuggestions { ), onSelect: () => { - this.bindings.searchBoxController.selectSuggestion(suggestion.rawValue); + this.bindings + .searchBoxController() + .selectSuggestion(suggestion.rawValue); }, }; } diff --git a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx index b10687db4d6..9d7f67c8e9e 100644 --- a/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx +++ b/packages/atomic/src/components/commerce/search-box-suggestions/atomic-commerce-search-box-recent-queries/atomic-commerce-search-box-recent-queries.tsx @@ -123,7 +123,7 @@ export class AtomicCommerceSearchBoxRecentQueries { return []; } - const query = this.bindings.searchBoxController.state.value; + const query = this.bindings.searchBoxController().state.value; const hasQuery = query !== ''; const max = hasQuery ? this.maxWithQuery : this.maxWithoutQuery; const filteredQueries = this.recentQueriesList.state.queries @@ -157,7 +157,7 @@ export class AtomicCommerceSearchBoxRecentQueries { } private renderItem(value: string): SearchBoxSuggestionElement { - const query = this.bindings.searchBoxController.state.value; + const query = this.bindings.searchBoxController().state.value; const partialItem = getPartialRecentQueryElement(value, this.bindings.i18n); return { ...partialItem, @@ -169,9 +169,9 @@ export class AtomicCommerceSearchBoxRecentQueries { ), onSelect: () => { - if (this.bindings.isStandalone) { - this.bindings.searchBoxController.updateText(value); - this.bindings.searchBoxController.submit(); + if (this.bindings.isStandalone()) { + this.bindings.searchBoxController().updateText(value); + this.bindings.searchBoxController().submit(); return; } diff --git a/packages/atomic/src/components/common/image-carousel/image-carousel.tsx b/packages/atomic/src/components/common/image-carousel/image-carousel.tsx index 06f4063d248..6a812afccdc 100644 --- a/packages/atomic/src/components/common/image-carousel/image-carousel.tsx +++ b/packages/atomic/src/components/common/image-carousel/image-carousel.tsx @@ -27,7 +27,10 @@ export const ImageCarousel: FunctionalComponent<