diff --git a/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.spec.ts b/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.spec.ts index 6d01bed8e7d..3beec7aa094 100644 --- a/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.spec.ts +++ b/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.spec.ts @@ -36,6 +36,7 @@ import { GROUP_ID_4, GROUP_ID_5, GROUP_ID_7, + GROUP_ID_8, PRODUCT_CODE, mockRouterState, productConfiguration, @@ -1204,6 +1205,44 @@ describe('ConfiguratorGroupMenuComponent', () => { ) ).toBe(true); }); + + it('should return `true` because a group with current group ID is part of the sub group hierarchy', () => { + expect( + component.containsSelectedGroup( + mockProductConfiguration.groups[3], // GROUP_ID_5 is parent of GROUP_ID_7 which in turn is parent of GROUP_ID_8 + GROUP_ID_8 + ) + ).toBe(true); + }); + }); + + describe('getTabIndex', () => { + it('should return `0` because current group id matches group itself', () => { + expect( + component.getTabIndex(mockProductConfiguration.groups[0], GROUP_ID_1) + ).toBe(0); + }); + + it("should return `-1` because current group id doesn't match group or its children", () => { + expect( + component.getTabIndex(mockProductConfiguration.groups[0], GROUP_ID_4) + ).toBe(-1); + }); + + it('should return `0` because current group id matches a direct child group id', () => { + expect( + component.getTabIndex(mockProductConfiguration.groups[2], GROUP_ID_4) + ).toBe(0); + }); + + it('should return `0` because current group id matches a in-direct child group id', () => { + expect( + component.getTabIndex( + mockProductConfiguration.groups[3], // GROUP_ID_5 is parent of GROUP_ID_7 which in turn is parent of GROUP_ID_8 + GROUP_ID_8 + ) + ).toBe(0); + }); }); describe('isGroupSelected', () => { diff --git a/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.ts b/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.ts index 1656a0993ed..16f2641f74e 100644 --- a/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.ts +++ b/feature-libs/product-configurator/rulebased/components/group-menu/configurator-group-menu.component.ts @@ -18,12 +18,12 @@ import { ConfiguratorRouterExtractorService, } from '@spartacus/product-configurator/common'; import { + BREAKPOINT, + BreakpointService, DirectionMode, DirectionService, HamburgerMenuService, ICON_TYPE, - BREAKPOINT, - BreakpointService, } from '@spartacus/storefront'; import { Observable, of } from 'rxjs'; import { filter, map, switchMap, take } from 'rxjs/operators'; @@ -503,13 +503,11 @@ export class ConfiguratorGroupMenuComponent { group: Configurator.Group, currentGroupId?: string ): boolean { - let isCurrentGroupFound = false; - group.subGroups?.forEach((subGroup) => { - if (this.isGroupSelected(subGroup.id, currentGroupId)) { - isCurrentGroupFound = true; - } - }); - return isCurrentGroupFound; + return !!group.subGroups?.find( + (subGroup) => + this.isGroupSelected(subGroup.id, currentGroupId) || + this.containsSelectedGroup(subGroup, currentGroupId) + ); } /** @@ -521,14 +519,10 @@ export class ConfiguratorGroupMenuComponent { * @returns {number} - tab index */ getTabIndex(group: Configurator.Group, currentGroupId: string): number { - if ( - !this.isGroupSelected(group.id, currentGroupId) && - !this.containsSelectedGroup(group, currentGroupId) - ) { - return -1; - } else { - return 0; - } + const isCurrentGroupPartOfGroupHierarchy = + this.isGroupSelected(group.id, currentGroupId) || + this.containsSelectedGroup(group, currentGroupId); + return isCurrentGroupPartOfGroupHierarchy ? 0 : -1; // 0 -> add to tab chain, -1 -> remove from tab chain } /** diff --git a/feature-libs/product-configurator/rulebased/components/service/configurator-storefront-utils.service.spec.ts b/feature-libs/product-configurator/rulebased/components/service/configurator-storefront-utils.service.spec.ts index 3f3609d4aff..71802a32209 100644 --- a/feature-libs/product-configurator/rulebased/components/service/configurator-storefront-utils.service.spec.ts +++ b/feature-libs/product-configurator/rulebased/components/service/configurator-storefront-utils.service.spec.ts @@ -20,6 +20,23 @@ import { Configurator } from '../../core/model/configurator.model'; import { ConfiguratorStorefrontUtilsService } from './configurator-storefront-utils.service'; import { ConfiguratorTestUtils } from '../../testing/configurator-test-utils'; +let mockedWindow: { + innerWidth?: number; + innerHeight?: number; + scrollY?: number; + scroll(): void; +} = { + innerWidth: 1000, + innerHeight: 1000, + scrollY: 1000, + scroll() {}, +}; +class MockedWindowRef extends WindowRef { + get nativeWindow(): Window | undefined { + return this.isBrowser() ? mockedWindow : undefined; + } +} + let isGroupVisited: Observable = of(false); const testSelector = 'test-configurator-overview-menu'; @@ -116,7 +133,6 @@ describe('ConfiguratorStorefrontUtilsService', () => { ); let windowRef: WindowRef; let keyboardFocusService: KeyboardFocusService; - let querySelectorOriginal: any; beforeEach(() => { mockRouterState.state.params.displayOnly = false; @@ -141,22 +157,26 @@ describe('ConfiguratorStorefrontUtilsService', () => { provide: ProductService, useClass: MockProductService, }, + { provide: WindowRef, useClass: MockedWindowRef }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], }); classUnderTest = TestBed.inject(ConfiguratorStorefrontUtilsService); fixture = TestBed.createComponent(MockComponent); htmlElem = fixture.nativeElement; - windowRef = TestBed.inject(WindowRef as Type); + windowRef = TestBed.inject(WindowRef); + mockedWindow.innerHeight = 1000; + mockedWindow.innerWidth = 1000; + mockedWindow.scrollY = 1000; keyboardFocusService = TestBed.inject( KeyboardFocusService as Type ); - querySelectorOriginal = document.querySelector; }); afterEach(() => { - document.querySelector = querySelectorOriginal; - document.body.removeChild(htmlElem); + if (htmlElem) { + document.body.removeChild(htmlElem); + } }); it('should be created', () => { @@ -174,9 +194,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should scroll to element', () => { const theElement = document.createElement('div'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(theElement); + spyOn(windowRef.document, 'querySelector').and.returnValue(theElement); spyOn(theElement, 'getBoundingClientRect').and.returnValue( new DOMRect(100, 2000, 100, 100) ); @@ -308,9 +326,9 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should delegate to keyboard focus service', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const focusedElements = createFocusedElements('ATTR', 2, 3); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(focusedElements); + spyOn(windowRef.document, 'querySelector').and.returnValue( + focusedElements[0] + ); spyOn(keyboardFocusService, 'findFocusable').and.returnValue( focusedElements ); @@ -320,9 +338,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should not delegate to keyboard focus service because form is undefined', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(undefined); + spyOn(windowRef.document, 'querySelector').and.returnValue(undefined); spyOn(keyboardFocusService, 'findFocusable').and.returnValue([]); classUnderTest.focusFirstActiveElement('elementSelector'); expect(keyboardFocusService.findFocusable).toHaveBeenCalledTimes(0); @@ -338,9 +354,9 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should not delegate to keyboard focus service because keyboard focus service returns no focusable elements', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const focusedElements = createFocusedElements('ATTR', 2, 3); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(focusedElements); + spyOn(windowRef.document, 'querySelector').and.returnValue( + focusedElements[0] + ); spyOn(keyboardFocusService, 'findFocusable').and.returnValue([]); classUnderTest.focusFirstActiveElement('elementSelector'); expect(keyboardFocusService.findFocusable).toHaveBeenCalledTimes(1); @@ -394,9 +410,9 @@ describe('ConfiguratorStorefrontUtilsService', () => { .queryAll(By.css('label')) .map((el) => el.nativeNode); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(focusedElements); + spyOn(windowRef.document, 'querySelector').and.returnValue( + focusedElements + ); }); it('should not set focus because attribute does not contain any values', () => { @@ -458,9 +474,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { spyOn(keyboardFocusService, 'findFocusable').and.returnValue( focusedElements ); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(undefined); + asSpy(windowRef.document.querySelector).and.returnValue(undefined); verify(focusedElements); }); @@ -526,9 +540,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should get HTML element based on query selector', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const theElement = document.createElement('elementMock'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(theElement); + spyOn(windowRef.document, 'querySelector').and.returnValue(theElement); expect(classUnderTest.getElement('elementMock')).toEqual(theElement); }); @@ -537,9 +549,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { describe('changeStyling', () => { it('should change styling of HTML element', () => { const theElement = document.createElement('elementMock'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(undefined); + spyOn(windowRef.document, 'querySelector').and.returnValue(undefined); classUnderTest.changeStyling('elementMock', 'position', 'sticky'); expect(theElement.style.position).not.toEqual('sticky'); @@ -547,9 +557,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should change styling of HTML element', () => { const theElement = document.createElement('elementMock'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(theElement); + spyOn(windowRef.document, 'querySelector').and.returnValue(theElement); classUnderTest.changeStyling('elementMock', 'position', 'sticky'); expect(theElement.style.position).toEqual('sticky'); @@ -561,9 +569,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const theElement = document.createElement('elementMock'); theElement.style.position = 'sticky'; - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(undefined); + spyOn(windowRef.document, 'querySelector').and.returnValue(undefined); classUnderTest.removeStyling('elementMock', 'position'); expect(theElement.style.position).toEqual('sticky'); @@ -573,9 +579,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const theElement = document.createElement('elementMock'); theElement.style.position = 'sticky'; - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(theElement); + spyOn(windowRef.document, 'querySelector').and.returnValue(theElement); classUnderTest.removeStyling('elementMock', 'position'); expect(theElement.style.position).toBe(''); @@ -605,9 +609,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const elements: Array = createElements('section', 10); - document.querySelectorAll = jasmine - .createSpy('section') - .and.returnValue(elements); + asSpy(windowRef.document.querySelectorAll).and.returnValue(elements); const htmlElements = classUnderTest.getElements('section'); @@ -627,7 +629,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should return number of pixels that the document is currently scrolled vertically', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); - spyOnProperty(window, 'scrollY').and.returnValue(250); + mockedWindow.scrollY = 250; const nativeWindow = windowRef.nativeWindow; if (nativeWindow) { expect(classUnderTest.getVerticallyScrolledPixels()).toBe(250); @@ -637,9 +639,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { describe('hasScrollbar', () => { it('should return false because element is undefined', () => { - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(undefined); + spyOn(windowRef.document, 'querySelector').and.returnValue(undefined); expect(classUnderTest.hasScrollbar('elementMock')).toBe(false); }); @@ -711,27 +711,25 @@ describe('ConfiguratorStorefrontUtilsService', () => { label.style.height = '10px'; }); - spyOnProperty(window, 'innerWidth').and.returnValue(100); + mockedWindow.innerWidth = 100; expect(classUnderTest['isInViewport'](form)).toBe(false); }); - // TODO: CXSPA-8270 - fix failing tests on Azure & GiHub - xit("should return true because window's innerWith is known", () => { + it("should return true because window's innerWith is known", () => { form.style.display = 'flex'; form.style.flexDirection = 'column'; - spyOnProperty(window, 'innerWidth').and.returnValue(1000); + mockedWindow.innerWidth = 1000; expect(classUnderTest['isInViewport'](form)).toBe(true); }); - // TODO: CXSPA-8270 - fix failing tests on Azure & GiHub - xit('should return true because clientWidth of element is known and its right is less than its width', () => { + it('should return true because clientWidth of element is known and its right is less than its width', () => { form.style.display = 'flex'; form.style.flexDirection = 'column'; - spyOnProperty(window, 'innerWidth').and.returnValue(undefined); + mockedWindow.innerWidth = undefined; expect(classUnderTest['isInViewport'](form)).toBe(true); }); @@ -741,7 +739,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { form.style.flexDirection = 'column'; form.style.height = '1000px'; - spyOnProperty(window, 'innerHeight').and.returnValue(undefined); + mockedWindow.innerHeight = undefined; expect(classUnderTest['isInViewport'](form)).toBe(true); }); @@ -765,15 +763,14 @@ describe('ConfiguratorStorefrontUtilsService', () => { expect(classUnderTest['getHeight']('unknown-query')).toBe(0); }); - it('should return zero because form is not im viewport', () => { - spyOnProperty(window, 'innerWidth').and.returnValue(100); + it('should return zero because form is not in viewport', () => { + mockedWindow.innerWidth = 100; expect(classUnderTest['getHeight']('cx-configurator-form')).toBe(0); }); - // TODO: CXSPA-8270 - fix failing tests on Azure & GiHub - xit('should return offsetHeight of the element because form is not im viewport', () => { - spyOnProperty(window, 'innerWidth').and.returnValue(1000); + it('should return offsetHeight of the element because form is not in viewport', () => { + mockedWindow.innerWidth = 1000; expect( classUnderTest['getHeight']('cx-configurator-form') @@ -804,7 +801,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { addToCart = document.createElement('cx-configurator-add-to-cart-button'); document.body.append(addToCart); - spyOnProperty(window, 'innerWidth').and.returnValue(1000); + mockedWindow.innerWidth = 1000; } it('should return zero because isBrowser is undefined', () => { @@ -877,9 +874,7 @@ describe('ConfiguratorStorefrontUtilsService', () => { it('should not ensure visibility of the element', () => { spyOn(windowRef, 'isBrowser').and.returnValue(false); ovMenu = document.createElement('cx-configurator-overview-menu'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(ovMenu); + spyOn(windowRef.document, 'querySelector').and.returnValue(ovMenu); classUnderTest.ensureElementVisible( 'cx-configurator-overview-menu', undefined @@ -988,4 +983,8 @@ describe('ConfiguratorStorefrontUtilsService', () => { expect(classUnderTest.isLastSelected('name', 'code')).toBe(false); }); }); + + function asSpy(f: any) { + return f; + } }); diff --git a/feature-libs/product/image-zoom/styles/_product-image-zoom-view.scss b/feature-libs/product/image-zoom/styles/_product-image-zoom-view.scss index 97f4aa38405..e7f64e11ec1 100644 --- a/feature-libs/product/image-zoom/styles/_product-image-zoom-view.scss +++ b/feature-libs/product/image-zoom/styles/_product-image-zoom-view.scss @@ -48,7 +48,9 @@ cx-product-image-zoom-view { height: calc(90vh - 200px); // TODO: (CXSPA-7492) - Remove feature flag next major release. @include forFeature('a11yKeyboardAccessibleZoom') { - height: unset; + &:has(picture) { + height: unset; + } } } diff --git a/feature-libs/quote/core/services/quote-storefront-utils.service.spec.ts b/feature-libs/quote/core/services/quote-storefront-utils.service.spec.ts index f58273d61e7..dbeb4c399ba 100644 --- a/feature-libs/quote/core/services/quote-storefront-utils.service.spec.ts +++ b/feature-libs/quote/core/services/quote-storefront-utils.service.spec.ts @@ -1,8 +1,20 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { WindowRef } from '@spartacus/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { WindowRef } from '@spartacus/core'; import { QuoteStorefrontUtilsService } from './quote-storefront-utils.service'; +const mockedWindowTemplate: { innerWidth?: number; innerHeight?: number } = { + innerWidth: 1000, + innerHeight: 1000, +}; + +let mockedWindow = mockedWindowTemplate; +class MockedWindowRef extends WindowRef { + get nativeWindow(): Window | undefined { + return this.isBrowser() ? mockedWindow : undefined; + } +} + @Component({ selector: 'cx-quote', template: ` @@ -20,24 +32,23 @@ describe('QuoteStorefrontUtilsService', () => { let fixture: ComponentFixture; let htmlElem: HTMLElement; let windowRef: WindowRef; - let querySelectorOriginal: any; beforeEach(() => { TestBed.configureTestingModule({ declarations: [MockQuoteComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [{ provide: WindowRef, useClass: MockedWindowRef }], }).compileComponents(); classUnderTest = TestBed.inject(QuoteStorefrontUtilsService); fixture = TestBed.createComponent(MockQuoteComponent); htmlElem = fixture.nativeElement; windowRef = TestBed.inject(WindowRef); + mockedWindow = structuredClone(mockedWindowTemplate); fixture.detectChanges(); - querySelectorOriginal = document.querySelector; }); afterEach(() => { - document.querySelector = querySelectorOriginal; if (htmlElem) { document.body.removeChild(htmlElem); } @@ -52,17 +63,13 @@ describe('QuoteStorefrontUtilsService', () => { it('should get HTML element based on query selector when running in browser and element exists', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const theElement = document.createElement('elementMock'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(theElement); + spyOn(windowRef.document, 'querySelector').and.returnValue(theElement); expect(classUnderTest.getElement('elementMock')).toEqual(theElement); }); it('should get null if element does not exist', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(null); + spyOn(windowRef.document, 'querySelector').and.returnValue(null); expect(classUnderTest.getElement('unknownElement')).toEqual(null); }); }); @@ -71,9 +78,7 @@ describe('QuoteStorefrontUtilsService', () => { it('should not change styling of HTML element if element does not exist', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const element = document.createElement('notExistingElement'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(undefined); + spyOn(windowRef.document, 'querySelector').and.returnValue(undefined); classUnderTest.changeStyling('notExistingElement', 'position', 'sticky'); expect(element.style.position).not.toEqual('sticky'); @@ -82,9 +87,7 @@ describe('QuoteStorefrontUtilsService', () => { it('should change styling of HTML element', () => { spyOn(windowRef, 'isBrowser').and.returnValue(true); const theElement = document.createElement('elementMock'); - document.querySelector = jasmine - .createSpy('HTML Element') - .and.returnValue(theElement); + spyOn(windowRef.document, 'querySelector').and.returnValue(theElement); classUnderTest.changeStyling('elementMock', 'position', 'sticky'); expect(theElement.style.position).toEqual('sticky'); }); @@ -121,27 +124,25 @@ describe('QuoteStorefrontUtilsService', () => { label.style.height = '10px'; }); - spyOnProperty(window, 'innerWidth').and.returnValue(100); + mockedWindow.innerWidth = 100; expect(classUnderTest['isInViewport'](list)).toBe(false); }); - // TODO: CXSPA-8270 - fix failing tests on Azure & GiHub - xit("should return 'true' because window's innerWith is known", () => { + it("should return 'true' because window's innerWith is known", () => { list.style.display = 'flex'; list.style.flexDirection = 'column'; - spyOnProperty(window, 'innerWidth').and.returnValue(1000); + mockedWindow.innerWidth = 1000; expect(classUnderTest['isInViewport'](list)).toBe(true); }); - // TODO: CXSPA-8270 - fix failing tests on Azure & GiHub - xit("should return 'true' because clientWidth of element is known and its right is less than its width", () => { + it("should return 'true' because clientWidth of element is known and its right is less than its width", () => { list.style.display = 'flex'; list.style.flexDirection = 'column'; - spyOnProperty(window, 'innerWidth').and.returnValue(undefined); + mockedWindow.innerWidth = undefined; expect(classUnderTest['isInViewport'](list)).toBe(true); }); @@ -151,7 +152,7 @@ describe('QuoteStorefrontUtilsService', () => { list.style.flexDirection = 'column'; list.style.height = '1000px'; - spyOnProperty(window, 'innerHeight').and.returnValue(undefined); + mockedWindow.innerHeight = undefined; expect(classUnderTest['isInViewport'](list)).toBe(true); }); @@ -176,14 +177,13 @@ describe('QuoteStorefrontUtilsService', () => { }); it('should return zero because component is not in viewport', () => { - spyOnProperty(window, 'innerWidth').and.returnValue(100); + mockedWindow.innerWidth = 100; expect(classUnderTest['getHeight']('cx-quote-list')).toBe(0); }); - // TODO: CXSPA-8270 - fix failing tests on Azure & GiHub - xit('should return offsetHeight of the element because component is not in viewport', () => { - spyOnProperty(window, 'innerWidth').and.returnValue(1000); + it('should return offsetHeight of the element because component is in viewport', () => { + mockedWindow.innerWidth = 1000; expect(classUnderTest['getHeight']('cx-quote-list')).toBeGreaterThan(0); }); diff --git a/integration-libs/epd-visualization/root/util/url-utils.spec.ts b/integration-libs/epd-visualization/root/util/url-utils.spec.ts index 20097972a99..183a925f487 100644 --- a/integration-libs/epd-visualization/root/util/url-utils.spec.ts +++ b/integration-libs/epd-visualization/root/util/url-utils.spec.ts @@ -19,10 +19,8 @@ describe('UrlUtils', () => { expect(isHttpOrHttps(new URL('http://www.google.com/'))).toBeTruthy(); }); - it('should return false for data: protocol', () => { - expect( - isHttpOrHttps(new URL('data://data:text/plain,some%20data/')) - ).toBeFalsy(); + it('should return false for non http-based protocol', () => { + expect(isHttpOrHttps(new URL('ws://example.com'))).toBeFalsy(); }); }); }); diff --git a/projects/storefrontapp-e2e-cypress/cypress/helpers/checkout-multi-dimensional.ts b/projects/storefrontapp-e2e-cypress/cypress/helpers/checkout-multi-dimensional.ts index d85e8cca5a8..f744a5a9e9b 100644 --- a/projects/storefrontapp-e2e-cypress/cypress/helpers/checkout-multi-dimensional.ts +++ b/projects/storefrontapp-e2e-cypress/cypress/helpers/checkout-multi-dimensional.ts @@ -4,20 +4,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { assertAddressForm } from './address-book'; -import { login } from './auth-forms'; -import * as guestCheckout from './checkout-as-guest'; -import * as checkout from './checkout-flow'; -import { validateUpdateProfileForm } from './update-profile'; +import { getSampleUser } from '../sample-data/checkout-flow'; import { cartWithMultipleVariantProducts, cartWithTotalVariantProduct, multiDBaseProduct, multiDProduct, } from '../sample-data/multi-dimensional-flow'; -import { getSampleUser } from '../sample-data/checkout-flow'; -import { searchForProduct } from './product-search'; +import { assertAddressForm } from './address-book'; import { addProductToCart } from './applied-promotions'; +import { login } from './auth-forms'; +import * as guestCheckout from './checkout-as-guest'; +import * as checkout from './checkout-flow'; +import { searchForProduct } from './product-search'; +import { validateUpdateProfileForm } from './update-profile'; export function testCheckoutMultiDAsGuest() { it('should perform checkout as guest, create an account and verify guest data', () => { @@ -160,9 +160,19 @@ export function testCheckoutMultiDAsGuestAndVerifyCart() { cy.findByText(/Sign in \/ Register/i).click(); cy.wait(`@${loginPage}`).its('response.statusCode').should('eq', 200); + cy.intercept( + 'GET', + `${Cypress.env('OCC_PREFIX')}/${Cypress.env( + 'BASE_SITE' + )}/users/current/carts?fields*` + ).as('carts'); + login(multiDUser.email, multiDUser.password); cy.get('cx-login div.cx-login-greet').should('exist'); + + cy.wait('@carts').its('response.statusCode').should('eq', 200); + cy.get('cx-mini-cart .count').contains('1'); const cartPage = checkout.waitForPage('/cart', 'getCartPage'); diff --git a/projects/storefrontlib/shared/components/ng-select-a11y/ng-select-a11y.directive.ts b/projects/storefrontlib/shared/components/ng-select-a11y/ng-select-a11y.directive.ts index 97410186f5c..0a9b1f51807 100644 --- a/projects/storefrontlib/shared/components/ng-select-a11y/ng-select-a11y.directive.ts +++ b/projects/storefrontlib/shared/components/ng-select-a11y/ng-select-a11y.directive.ts @@ -16,7 +16,9 @@ import { Optional, PLATFORM_ID, Renderer2, + SecurityContext, } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; import { FeatureConfigService, TranslationService } from '@spartacus/core'; import { filter, take } from 'rxjs'; import { BREAKPOINT, BreakpointService } from '../../../layout'; @@ -35,6 +37,7 @@ export class NgSelectA11yDirective implements AfterViewInit { @Input() cxNgSelectA11y: { ariaLabel?: string; ariaControls?: string }; protected translationService = inject(TranslationService); + protected domSanitizer = inject(DomSanitizer); private featureConfigService = inject(FeatureConfigService); @HostListener('open') @@ -107,7 +110,11 @@ export class NgSelectA11yDirective implements AfterViewInit { .subscribe((translation) => { options.forEach( (option: HTMLOptionElement, index: string | number) => { - const ariaLabel = `${option.innerText}, ${+index + 1} ${translation} ${options.length}`; + const sanitizedOptionText = this.domSanitizer.sanitize( + SecurityContext.HTML, + option.innerText + ); + const ariaLabel = `${sanitizedOptionText}, ${+index + 1} ${translation} ${options.length}`; this.renderer.setAttribute(option, ARIA_LABEL, ariaLabel); } ); @@ -125,9 +132,11 @@ export class NgSelectA11yDirective implements AfterViewInit { observer: MutationObserver, divCombobox: HTMLElement ) { - const valueLabel = - this.elementRef.nativeElement.querySelector('.ng-value-label')?.innerText; - if (valueLabel) { + const sanitizedValueLabel = this.domSanitizer.sanitize( + SecurityContext.HTML, + this.elementRef.nativeElement.querySelector('.ng-value-label')?.innerText + ); + if (sanitizedValueLabel) { const comboboxAriaLabel = divCombobox?.getAttribute(ARIA_LABEL) || ''; const valueElement = this.elementRef.nativeElement.querySelector('.ng-value'); @@ -135,7 +144,7 @@ export class NgSelectA11yDirective implements AfterViewInit { this.renderer.setAttribute( divCombobox, ARIA_LABEL, - comboboxAriaLabel + ', ' + valueLabel + comboboxAriaLabel + ', ' + sanitizedValueLabel ); } observer.disconnect();