From e0830751c3cac784568179d7bf15928ded384776 Mon Sep 17 00:00:00 2001 From: Radhep Sabapathipillai <34665674+RadhepS@users.noreply.github.com> Date: Mon, 21 Oct 2024 23:55:55 -0400 Subject: [PATCH 1/4] fix: update EPD visualization unit test to stop failures (#19421) closes: https://jira.tools.sap/browse/CXSPA-8696 --- .../epd-visualization/root/util/url-utils.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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(); }); }); }); From 103db4d0b2aa510c21a5281548b1e57efecedca2 Mon Sep 17 00:00:00 2001 From: PioBar <72926984+Pio-Bar@users.noreply.github.com> Date: Tue, 22 Oct 2024 06:40:36 +0200 Subject: [PATCH 2/4] fix: (CXSPA-1128) - Select a11y directive sanitized innerText (#19408) --- .../ng-select-a11y.directive.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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(); From e8eedb16d9c9bd37453748d07202dae040e75715 Mon Sep 17 00:00:00 2001 From: Ulrich Stellmacher Date: Tue, 22 Oct 2024 11:42:03 +0200 Subject: [PATCH 3/4] CXSPA-8270 fix random failing unit tests (#19417) --- ...figurator-storefront-utils.service.spec.ts | 123 +++++++++--------- .../quote-storefront-utils.service.spec.ts | 56 ++++---- 2 files changed, 89 insertions(+), 90 deletions(-) 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/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); }); From 071d8bfbd1b22ef3dbf46485479d26cb7183b304 Mon Sep 17 00:00:00 2001 From: Hak Woo Kim Date: Tue, 22 Oct 2024 08:46:26 -0400 Subject: [PATCH 4/4] chore: update customer's module (CXSPA-8588) (#19383) Co-authored-by: Hakwoo Kim --- projects/schematics/src/add-spartacus/spartacus-features.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/schematics/src/add-spartacus/spartacus-features.ts b/projects/schematics/src/add-spartacus/spartacus-features.ts index 38849bba7f2..38b76831280 100644 --- a/projects/schematics/src/add-spartacus/spartacus-features.ts +++ b/projects/schematics/src/add-spartacus/spartacus-features.ts @@ -95,6 +95,7 @@ function configureSpartacusModules( 'PageTitleModule', 'VideoModule', 'PDFModule', + 'SiteThemeSwitcherModule', ].forEach((content) => { addModuleImport(sourceFile, { import: [ @@ -116,6 +117,7 @@ function configureSpartacusModules( 'PageTitleModule', 'VideoModule', 'PDFModule', + 'SiteThemeSwitcherModule', ], }, ],