From ca5e89d00517dbd7080a6d1fba60cc9ecc07a4f0 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Sun, 1 Dec 2024 12:48:09 -0500 Subject: [PATCH 01/33] fix: disable attribute key restriction for opf (#19660) CXSPA-8983 --- .../opf-resource-loader.service.spec.ts | 1 + .../services/opf-resource-loader.service.ts | 14 +++++++++- .../src/util/script-loader.service.spec.ts | 26 +++++++++++++++++++ .../core/src/util/script-loader.service.ts | 8 +++++- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts index fb230644040..3ee1c5ae9cd 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts @@ -176,6 +176,7 @@ describe('OpfResourceLoaderService', () => { const mockStylesResources = { url: 'style-url', sri: 'fake-hash-code', + attributes: [{ key: 'mock-key', value: 'mock-value' }], type: OpfDynamicScriptResourceType.STYLES, }; diff --git a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts index aa7e9a32b8d..efaf936bfcf 100644 --- a/integration-libs/opf/base/root/services/opf-resource-loader.service.ts +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.ts @@ -11,6 +11,7 @@ import { ScriptLoader } from '@spartacus/core'; import { OpfDynamicScriptResource, OpfDynamicScriptResourceType, + OpfKeyValueMap, } from '../model'; @Injectable({ @@ -24,12 +25,13 @@ export class OpfResourceLoaderService { protected readonly OPF_RESOURCE_ATTRIBUTE_KEY = 'data-opf-resource'; protected embedStyles(embedOptions: { + attributes?: OpfKeyValueMap[]; src: string; sri?: string; callback?: EventListener; errorCallback: EventListener; }): void { - const { src, sri, callback, errorCallback } = embedOptions; + const { attributes, src, sri, callback, errorCallback } = embedOptions; const link: HTMLLinkElement = this.document.createElement('link'); link.href = src; @@ -39,6 +41,14 @@ export class OpfResourceLoaderService { if (sri) { link.integrity = sri; } + if (attributes?.length) { + attributes.forEach((attribute) => { + const { key, value } = attribute; + if (!(key in link)) { + link.setAttribute(key, value); + } + }); + } if (callback) { link.addEventListener('load', callback); @@ -88,6 +98,7 @@ export class OpfResourceLoaderService { attributes: attributes, callback: () => resolve(), errorCallback: () => reject(), + disableKeyRestriction: true, }); } else { resolve(); @@ -105,6 +116,7 @@ export class OpfResourceLoaderService { return new Promise((resolve, reject) => { if (resource.url && !this.hasStyles(resource.url)) { this.embedStyles({ + attributes: resource?.attributes, src: resource.url, sri: resource?.sri, callback: () => resolve(), diff --git a/projects/core/src/util/script-loader.service.spec.ts b/projects/core/src/util/script-loader.service.spec.ts index a6f36b466bf..12fd05f5e3f 100644 --- a/projects/core/src/util/script-loader.service.spec.ts +++ b/projects/core/src/util/script-loader.service.spec.ts @@ -78,6 +78,7 @@ describe('ScriptLoader', () => { attributes: { type: 'text/javascript', 'data-custom-attr': 'custom-attribute-value', + 'mock-attr-key': 'mock-attr-value', }, }); expect(documentMock.createElement).toHaveBeenCalledWith('script'); @@ -86,6 +87,31 @@ describe('ScriptLoader', () => { expect(jsDomElement.getAttribute('data-custom-attr')).toEqual( 'custom-attribute-value' ); + expect(jsDomElement.getAttribute('mock-attr-key')).toBeFalsy(); + }); + + it('should add script with unrestricted custom attributes', () => { + spyOn(documentMock, 'createElement').and.returnValue(jsDomElement); + + scriptLoader.embedScript({ + disableKeyRestriction: true, + src: SCRIPT_LOAD_URL, + params: undefined, + attributes: { + type: 'text/javascript', + 'data-custom-attr': 'custom-attribute-value', + 'mock-attr-key': 'mock-attr-value', + }, + }); + expect(documentMock.createElement).toHaveBeenCalledWith('script'); + expect(jsDomElement.src).toEqual(SCRIPT_LOAD_URL); + expect(jsDomElement.type).toEqual('text/javascript'); + expect(jsDomElement.getAttribute('data-custom-attr')).toEqual( + 'custom-attribute-value' + ); + expect(jsDomElement.getAttribute('mock-attr-key')).toEqual( + 'mock-attr-value' + ); }); it('should be able to add script in body element', () => { diff --git a/projects/core/src/util/script-loader.service.ts b/projects/core/src/util/script-loader.service.ts index 4464f97d53f..fc67470ae23 100644 --- a/projects/core/src/util/script-loader.service.ts +++ b/projects/core/src/util/script-loader.service.ts @@ -31,6 +31,7 @@ export class ScriptLoader { * callback: a function to be invoked after the script has been loaded * errorCallback: function to be invoked after error during script loading * placement: HTML body or head where script will be placed + * disableKeyRestriction: disable the custom attributes restriction which requires key to start with 'data-' */ public embedScript(embedOptions: { src: string; @@ -39,6 +40,7 @@ export class ScriptLoader { callback?: EventListener; errorCallback?: EventListener; placement?: ScriptPlacement; + disableKeyRestriction?: boolean; }): void { const { src, @@ -47,6 +49,7 @@ export class ScriptLoader { callback, errorCallback, placement = ScriptPlacement.HEAD, + disableKeyRestriction, } = embedOptions; const isSSR = isPlatformServer(this.platformId); @@ -67,7 +70,10 @@ export class ScriptLoader { if (attributes) { Object.keys(attributes).forEach((key) => { // custom attributes - if (key.startsWith('data-')) { + if ( + key.startsWith('data-') || + (disableKeyRestriction && !(key in script)) + ) { script.setAttribute(key, attributes[key as keyof object]); } else { (script as any)[key] = attributes[key as keyof object]; From 20683ab55d316145c230e553e9630450811b559b Mon Sep 17 00:00:00 2001 From: Mateusz Kolasa Date: Mon, 2 Dec 2024 01:06:45 +0100 Subject: [PATCH 02/33] fix: do not call `deleteUserAddress` for unlogged users (#19649) CXSPA-8873 --- .../opf-quick-buy-transaction.service.spec.ts | 16 +++++++++++++++- .../opf-quick-buy-transaction.service.ts | 19 +++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.spec.ts b/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.spec.ts index 05a83b69eb2..6041f83e713 100644 --- a/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.spec.ts +++ b/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.spec.ts @@ -427,9 +427,11 @@ describe('OpfQuickBuyTransactionService', () => { }); describe('deleteUserAddresses', () => { - it('should call deleteUserAddress for each address ID', () => { + it('should call `deleteUserAddress` for each address ID for logged in users', () => { const addrIds = ['addr1', 'addr2', 'addr3']; + authService.isUserLoggedIn.and.returnValue(of(true)); + service.deleteUserAddresses(addrIds); addrIds.forEach((addrId) => { @@ -439,9 +441,21 @@ describe('OpfQuickBuyTransactionService', () => { }); }); + it('should not call `deleteUserAddress` for unauthenticated users', () => { + const addrIds = ['addr1', 'addr2', 'addr3']; + + authService.isUserLoggedIn.and.returnValue(of(false)); + + service.deleteUserAddresses(addrIds); + + expect(userAddressService.deleteUserAddress).toHaveBeenCalledTimes(0); + }); + it('should disable global messages for address deletion success', () => { const addrIds = ['addr1', 'addr2']; + authService.isUserLoggedIn.and.returnValue(of(true)); + service.deleteUserAddresses(addrIds); expect(opfGlobalMessageService.disableGlobalMessage).toHaveBeenCalledWith( diff --git a/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.ts b/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.ts index 523dacab745..13669266b91 100644 --- a/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.ts +++ b/integration-libs/opf/quick-buy/core/services/opf-quick-buy-transaction.service.ts @@ -176,12 +176,19 @@ export class OpfQuickBuyTransactionService { } deleteUserAddresses(addrIds: string[]): void { - this.opfGlobalMessageService.disableGlobalMessage([ - 'addressForm.userAddressDeleteSuccess', - ]); - addrIds.forEach((addrId) => { - this.userAddressService.deleteUserAddress(addrId); - }); + this.authService + .isUserLoggedIn() + .pipe(take(1)) + .subscribe((isUserLoggedIn) => { + if (isUserLoggedIn) { + this.opfGlobalMessageService.disableGlobalMessage([ + 'addressForm.userAddressDeleteSuccess', + ]); + addrIds.forEach((addrId) => { + this.userAddressService.deleteUserAddress(addrId); + }); + } + }); } createCartGuestUser(): Observable { From 5d66fa2a0678988bbd36a727b8aaca5f4690078a Mon Sep 17 00:00:00 2001 From: steinsebastian <54309425+steinsebastian@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:31:41 +0100 Subject: [PATCH 03/33] fix: Remove unnecessary visible text for screen-readers (#19672) Closes CXSPA-9063 --- ...single-selection-bundle-dropdown.component.html | 14 -------------- ...bute-single-selection-bundle-dropdown.module.ts | 7 +------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/feature-libs/product-configurator/rulebased/components/attribute/types/single-selection-bundle-dropdown/configurator-attribute-single-selection-bundle-dropdown.component.html b/feature-libs/product-configurator/rulebased/components/attribute/types/single-selection-bundle-dropdown/configurator-attribute-single-selection-bundle-dropdown.component.html index b354ac6a8fa..7d2bbe4f57d 100644 --- a/feature-libs/product-configurator/rulebased/components/attribute/types/single-selection-bundle-dropdown/configurator-attribute-single-selection-bundle-dropdown.component.html +++ b/feature-libs/product-configurator/rulebased/components/attribute/types/single-selection-bundle-dropdown/configurator-attribute-single-selection-bundle-dropdown.component.html @@ -1,19 +1,5 @@
-