diff --git a/packages/atomic/playwright.config.ts b/packages/atomic/playwright.config.ts index d2eedc4cc9e..43648a38c85 100644 --- a/packages/atomic/playwright.config.ts +++ b/packages/atomic/playwright.config.ts @@ -35,10 +35,16 @@ export default defineConfig({ // name: 'firefox', // use: {...devices['Desktop Firefox'], viewport: DEFAULT_DESKTOP_VIEWPORT}, // }, - // { - // name: 'webkit', - // use: {...devices['Desktop Safari'], viewport: DEFAULT_DESKTOP_VIEWPORT}, - // }, + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + viewport: DEFAULT_DESKTOP_VIEWPORT, + }, + expect: { + timeout: 30 * 1000, + }, + }, ], expect: { timeout: 7 * 1000, diff --git a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.ts b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.ts index 74c3c653873..3ba2e609297 100644 --- a/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.ts +++ b/packages/atomic/src/components/commerce/atomic-commerce-search-box/atomic-commerce-search-box.ts @@ -229,8 +229,8 @@ export class AtomicCommerceSearchBox return; } if ( - !('redirectTo' in this.searchBoxState) || - !('afterRedirection' in this.searchBox) + !this.isStandaloneSearchboxState(this.searchBoxState) || + !Object.hasOwn(this.searchBox, 'afterRedirection') ) { return; } @@ -247,7 +247,7 @@ export class AtomicCommerceSearchBox const storage = new SafeStorage(); storage.setJSON(StorageItems.STANDALONE_SEARCH_BOX_DATA, data); - this.searchBox.afterRedirection(); + (this.searchBox as StandaloneSearchBox).afterRedirection(); const event = new CustomEvent('redirect'); this.dispatchEvent(event); @@ -306,7 +306,13 @@ export class AtomicCommerceSearchBox private isStandaloneSearchBox( searchBox: SearchBox | StandaloneSearchBox ): searchBox is StandaloneSearchBox { - return 'updateRedirectUrl' in searchBox; + return Object.hasOwn(searchBox, 'updateRedirectUrl'); + } + + private isStandaloneSearchboxState( + state: SearchBoxState | StandaloneSearchBoxState + ): state is StandaloneSearchBoxState { + return Object.hasOwn(state, 'redirectTo'); } private updateBreakpoints = once(() => updateBreakpoints(this)); diff --git a/packages/atomic/src/components/commerce/atomic-product-children/atomic-product-children.spec.ts b/packages/atomic/src/components/commerce/atomic-product-children/atomic-product-children.spec.ts index 9af845a0d2d..09b5c42abce 100644 --- a/packages/atomic/src/components/commerce/atomic-product-children/atomic-product-children.spec.ts +++ b/packages/atomic/src/components/commerce/atomic-product-children/atomic-product-children.spec.ts @@ -2,6 +2,7 @@ import type {ChildProduct} from '@coveo/headless/commerce'; import {html} from 'lit'; import {ifDefined} from 'lit/directives/if-defined.js'; import {describe, expect, it, vi} from 'vitest'; +import {server} from 'vitest/browser'; import type {AtomicProduct} from '@/src/components/commerce/atomic-product/atomic-product'; import {closest} from '@/src/utils/dom-utils'; import {renderInAtomicProduct} from '@/vitest-utils/testing-helpers/fixtures/atomic/commerce/atomic-product-fixture'; @@ -162,51 +163,54 @@ describe('atomic-product-children', () => { }); }); - describe('when a child product is touched', () => { - const touchChild = async () => { - const {childProducts} = await renderProductChildren(); - childProducts[3].dispatchEvent( - new TouchEvent('touchstart', { - touches: [ - new Touch({ - identifier: 0, - target: childProducts[3], - clientX: 100, - clientY: 100, - screenX: 100, - screenY: 100, - pageX: 100, - pageY: 100, - radiusX: 10, - radiusY: 10, - rotationAngle: 0, - force: 0.5, - }), - ], - }) - ); - return childProducts; - }; - - it('should change the active child product', async () => { - const childProducts = await touchChild(); - await expect - .poll(() => childProducts[3]) - .toHaveClass('box-border rounded border border-primary'); - await expect - .poll(() => childProducts[0]) - .not.toHaveClass('box-border rounded border border-primary'); - }); + describe.skipIf(server.browser === 'webkit')( + 'when a child product is touched', + () => { + const touchChildProduct = async () => { + const {childProducts} = await renderProductChildren(); + childProducts[3].dispatchEvent( + new TouchEvent('touchstart', { + touches: [ + new Touch({ + identifier: 0, + target: childProducts[3], + clientX: 100, + clientY: 100, + screenX: 100, + screenY: 100, + pageX: 100, + pageY: 100, + radiusX: 10, + radiusY: 10, + rotationAngle: 0, + force: 0.5, + }), + ], + }) + ); + return childProducts; + }; + + it('should change the active child product', async () => { + const childProducts = await touchChildProduct(); + await expect + .poll(() => childProducts[3]) + .toHaveClass('box-border rounded border border-primary'); + await expect + .poll(() => childProducts[0]) + .not.toHaveClass('box-border rounded border border-primary'); + }); - it('should dispatch the "atomic/selectChildProduct" event', async () => { - const dispatchSpy = vi.spyOn( - AtomicProductChildren.prototype, - 'dispatchEvent' - ); - await touchChild(); - expect(dispatchSpy).toHaveBeenCalled(); - }); - }); + it('should dispatch the "atomic/selectChildProduct" event', async () => { + const dispatchSpy = vi.spyOn( + AtomicProductChildren.prototype, + 'dispatchEvent' + ); + await touchChildProduct(); + expect(dispatchSpy).toHaveBeenCalled(); + }); + } + ); describe('when a child product is clicked', () => { it('should stop propagation if the parent element is an tag', async () => { diff --git a/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx b/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx index b1b4f00bffd..54262178287 100644 --- a/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx +++ b/packages/atomic/src/components/search/atomic-search-box/atomic-search-box.tsx @@ -215,7 +215,7 @@ export class AtomicSearchBox implements InitializableComponent { private isStandaloneSearchBox( searchBox: SearchBox | StandaloneSearchBox ): searchBox is StandaloneSearchBox { - return 'redirectTo' in searchBox; + return Object.hasOwn(searchBox, 'redirectTo'); } public initialize() { @@ -252,10 +252,14 @@ export class AtomicSearchBox implements InitializableComponent { }); } + private isStandaloneSearchboxState(state: SearchBoxState | StandaloneSearchBoxState): state is StandaloneSearchBoxState { + return Object.hasOwn(state, 'redirectTo') + } + public componentWillUpdate() { if ( - !('redirectTo' in this.searchBoxState) || - !('afterRedirection' in this.searchBox) + !this.isStandaloneSearchboxState(this.searchBoxState) || + !Object.hasOwn(this.searchBox, 'afterRedirection') ) { return; } @@ -273,7 +277,7 @@ export class AtomicSearchBox implements InitializableComponent { const storage = new SafeStorage(); storage.setJSON(StorageItems.STANDALONE_SEARCH_BOX_DATA, data); - this.searchBox.afterRedirection(); + (this.searchBox as StandaloneSearchBox).afterRedirection(); const event = this.redirect.emit({redirectTo, value}); if (!event.defaultPrevented) { window.location.href = redirectTo; diff --git a/packages/atomic/src/components/search/result-template-components/quickview-iframe/quickview-iframe.tsx b/packages/atomic/src/components/search/result-template-components/quickview-iframe/quickview-iframe.tsx index a8a30c65d10..022d60e0791 100644 --- a/packages/atomic/src/components/search/result-template-components/quickview-iframe/quickview-iframe.tsx +++ b/packages/atomic/src/components/search/result-template-components/quickview-iframe/quickview-iframe.tsx @@ -59,7 +59,7 @@ export const QuickviewIframe: FunctionalComponent<{ // When a document is written with document.open/document.write/document.close // it is not synchronous and the content of the iframe is only available to be queried at the end of the current call stack. // This add a "wait" (setTimeout 0) before calling the `onSetIframeRef` from the parent modal quickview - const waitForIframeContentToBeWritten = () => { + const flushMicrotasks = () => { return new Promise((resolve) => setTimeout(resolve)); }; @@ -76,6 +76,10 @@ export const QuickviewIframe: FunctionalComponent<{ return; } + if(iframeRef.isConnected === false) { + await flushMicrotasks(); + } + const documentWriter = iframeRef.contentDocument; if (!documentWriter) { if (src) { @@ -97,7 +101,7 @@ export const QuickviewIframe: FunctionalComponent<{ writeDocument(documentWriter, content); ensureSameResultIsNotOverwritten(documentWriter, uniqueIdentifier); - await waitForIframeContentToBeWritten(); + await flushMicrotasks(); onSetIframeRef(iframeRef); }} > diff --git a/packages/atomic/vitest.config.js b/packages/atomic/vitest.config.js index 58cef6f2058..62ec084b9ad 100644 --- a/packages/atomic/vitest.config.js +++ b/packages/atomic/vitest.config.js @@ -113,6 +113,16 @@ const atomicDefault = defineConfig({ actionTimeout: 3000, }, }, + ...(process.env.CI + ? [ + { + browser: 'webkit', + context: { + actionTimeout: 3000, + }, + }, + ] + : []), ], }, },