From ddfaf96f2398136a8df7602c2a008cbd201cfdec Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Wed, 11 Oct 2023 12:11:53 -0400 Subject: [PATCH 01/30] add cta-scripts cms cpnt --- .../order-overview.component.html | 2 + .../components/opf-base-components.module.ts | 3 +- .../base/components/opf-cta-scripts/index.ts | 2 + .../opf-cta-scripts.component.html | 3 + .../opf-cta-scripts.component.spec.ts | 0 .../opf-cta-scripts.component.ts | 61 +++++++++++++++++++ .../opf-cta-scripts/opf-cta-scripts.module.ts | 28 +++++++++ .../opf-cta-scripts.service.ts | 0 .../core/connectors/opf-payment.adapter.ts | 6 ++ .../core/connectors/opf-payment.connector.ts | 8 +++ .../base/core/facade/opf-payment.service.ts | 15 +++++ .../opf/base/core/tokens/tokens.ts | 5 ++ .../opf/base/occ/adapters/occ-opf.adapter.ts | 32 ++++++++++ .../base/occ/config/default-occ-opf-config.ts | 1 + .../base/occ/model/occ-opf-endpoints.model.ts | 2 + .../opf-payment-verification.service.spec.ts | 4 +- .../opf-payment-verification.service.ts | 4 +- .../base/root/facade/opf-payment.facade.ts | 7 +++ .../base/root/model/opf-quick-buy.model.ts | 43 +++++++++++++ .../opf/base/root/model/opf.model.ts | 10 +-- .../opf/base/root/opf-base-root.module.ts | 15 +++++ .../services/opf-resource-loader.service.ts | 28 ++++----- 22 files changed, 254 insertions(+), 25 deletions(-) create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/index.ts create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts diff --git a/feature-libs/order/components/order-details/order-overview/order-overview.component.html b/feature-libs/order/components/order-details/order-overview/order-overview.component.html index 4d850012f8c..d64b1f82d25 100644 --- a/feature-libs/order/components/order-details/order-overview/order-overview.component.html +++ b/feature-libs/order/components/order-details/order-overview/order-overview.component.html @@ -1,3 +1,4 @@ +
@@ -184,4 +185,5 @@
+
diff --git a/integration-libs/opf/base/components/opf-base-components.module.ts b/integration-libs/opf/base/components/opf-base-components.module.ts index 6d45b6e82bc..78258462971 100644 --- a/integration-libs/opf/base/components/opf-base-components.module.ts +++ b/integration-libs/opf/base/components/opf-base-components.module.ts @@ -5,10 +5,11 @@ */ import { NgModule } from '@angular/core'; +import { OpfCtaScriptsModule } from './opf-cta-scripts'; import { OpfErrorModalModule } from './opf-error-modal/opf-error-modal.module'; @NgModule({ - imports: [OpfErrorModalModule], + imports: [OpfErrorModalModule, OpfCtaScriptsModule], providers: [], }) export class OpfBaseComponentsModule {} diff --git a/integration-libs/opf/base/components/opf-cta-scripts/index.ts b/integration-libs/opf/base/components/opf-cta-scripts/index.ts new file mode 100644 index 00000000000..52a647c6551 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-scripts/index.ts @@ -0,0 +1,2 @@ +export * from './opf-cta-scripts.component'; +export * from './opf-cta-scripts.module'; diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html new file mode 100644 index 00000000000..718edca49b6 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html @@ -0,0 +1,3 @@ +

CTA Scripts Component

+ + diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts new file mode 100644 index 00000000000..b98f8dbd14d --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { + CtaScriptsLocation, + CtaScriptsRequest, + OpfPaymentFacade, +} from '@spartacus/opf/base/root'; +import { OrderFacade } from '@spartacus/order/root'; +import { Order } from 'feature-libs/order/root/model'; +import { throwError } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; + +@Component({ + selector: 'cx-opf-cta-scripts', + templateUrl: './opf-cta-scripts.component.html', +}) +export class OpfCtaScriptsComponent implements OnInit { + protected opfPaymentFacade = inject(OpfPaymentFacade); + protected orderDetailsService = inject(OrderFacade); + + ngOnInit() { + this.orderDetailsService + .getOrderDetails() + .pipe( + switchMap((order) => { + if (!order) { + return throwError({ error: 'Order obj not found' }); + } + console.log('flo order', order); + const obj: CtaScriptsRequest = { + orderId: order?.code, + ctaProductItems: this.getProductItems(order), + paymentAccountIds: [12, 11, 200, 52, 101], + scriptLocations: [ + CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE, + ], + }; + return this.opfPaymentFacade.ctaScripts(obj); + }) + ) + .subscribe((ctaScriptsResponse) => { + console.log('ctaScriptsResponse', ctaScriptsResponse); + }); + } + + protected getProductItems( + order: Order + ): { productId: string; quantity: number }[] | [] { + return !!order.entries + ? order.entries + ?.filter((item) => { + return !!item?.product?.code && !!item?.quantity; + }) + .map((item) => { + return { + productId: item.product?.code as string, + quantity: item.quantity as number, + }; + }) + : []; + } +} diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts new file mode 100644 index 00000000000..438280426b9 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts @@ -0,0 +1,28 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { + CmsConfig, + FeaturesConfig, + provideDefaultConfig, +} from '@spartacus/core'; +import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; + +@NgModule({ + declarations: [OpfCtaScriptsComponent], + providers: [ + // provideOutlet({ + // id: 'cx-order-overview.top', + // component: OpfCtaScriptsComponent, + // }), + provideDefaultConfig({ + cmsComponents: { + OpfCtaScriptsComponent: { + component: OpfCtaScriptsComponent, + }, + }, + }), + ], + exports: [OpfCtaScriptsComponent], + imports: [CommonModule], +}) +export class OpfCtaScriptsModule {} diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts b/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts index 273fb9c3d3a..9c4786ced33 100644 --- a/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts +++ b/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts @@ -6,6 +6,8 @@ import { AfterRedirectScriptResponse, + CtaScriptsRequest, + CtaScriptsResponse, OpfPaymentVerificationPayload, OpfPaymentVerificationResponse, SubmitCompleteRequest, @@ -48,4 +50,8 @@ export abstract class OpfPaymentAdapter { abstract afterRedirectScripts( paymentSessionId: string ): Observable; + + abstract ctaScripts( + ctaScriptsRequest: CtaScriptsRequest + ): Observable; } diff --git a/integration-libs/opf/base/core/connectors/opf-payment.connector.ts b/integration-libs/opf/base/core/connectors/opf-payment.connector.ts index 67fa5a6b067..b9c85efba92 100644 --- a/integration-libs/opf/base/core/connectors/opf-payment.connector.ts +++ b/integration-libs/opf/base/core/connectors/opf-payment.connector.ts @@ -7,6 +7,8 @@ import { Injectable } from '@angular/core'; import { AfterRedirectScriptResponse, + CtaScriptsRequest, + CtaScriptsResponse, OpfPaymentVerificationPayload, OpfPaymentVerificationResponse, SubmitCompleteRequest, @@ -54,4 +56,10 @@ export class OpfPaymentConnector { ): Observable { return this.adapter.afterRedirectScripts(paymentSessionId); } + + public ctaScripts( + ctaScriptsRequest: CtaScriptsRequest + ): Observable { + return this.adapter.ctaScripts(ctaScriptsRequest); + } } diff --git a/integration-libs/opf/base/core/facade/opf-payment.service.ts b/integration-libs/opf/base/core/facade/opf-payment.service.ts index 963142f2256..1aeb30ce269 100644 --- a/integration-libs/opf/base/core/facade/opf-payment.service.ts +++ b/integration-libs/opf/base/core/facade/opf-payment.service.ts @@ -8,6 +8,8 @@ import { Injectable } from '@angular/core'; import { Command, CommandService } from '@spartacus/core'; import { AfterRedirectScriptResponse, + CtaScriptsRequest, + CtaScriptsResponse, OpfPaymentFacade, OpfPaymentVerificationPayload, OpfPaymentVerificationResponse, @@ -66,6 +68,15 @@ export class OpfPaymentService implements OpfPaymentFacade { ); }); + protected ctaScriptsCommand: Command< + { + ctaScriptsRequest: CtaScriptsRequest; + }, + CtaScriptsResponse + > = this.commandService.create((payload) => { + return this.opfPaymentConnector.ctaScripts(payload.ctaScriptsRequest); + }); + constructor( protected commandService: CommandService, protected opfPaymentConnector: OpfPaymentConnector, @@ -97,4 +108,8 @@ export class OpfPaymentService implements OpfPaymentFacade { afterRedirectScripts(paymentSessionId: string) { return this.afterRedirectScriptsCommand.execute({ paymentSessionId }); } + + ctaScripts(ctaScriptsRequest: CtaScriptsRequest) { + return this.ctaScriptsCommand.execute({ ctaScriptsRequest }); + } } diff --git a/integration-libs/opf/base/core/tokens/tokens.ts b/integration-libs/opf/base/core/tokens/tokens.ts index b6c37de5946..48b4a12fdd8 100644 --- a/integration-libs/opf/base/core/tokens/tokens.ts +++ b/integration-libs/opf/base/core/tokens/tokens.ts @@ -8,6 +8,7 @@ import { InjectionToken } from '@angular/core'; import { Converter } from '@spartacus/core'; import { AfterRedirectScriptResponse, + CtaScriptsResponse, OpfPaymentVerificationResponse, SubmitCompleteResponse, SubmitResponse, @@ -28,3 +29,7 @@ export const OPF_PAYMENT_SUBMIT_COMPLETE_NORMALIZER = new InjectionToken< export const OPF_AFTER_REDIRECT_SCRIPTS_NORMALIZER = new InjectionToken< Converter >('OpfAfterRedirectScriptsNormalizer'); + +export const OPF_CTA_SCRIPTS_NORMALIZER = new InjectionToken< + Converter +>('OpfCtaScriptsNormalizer'); diff --git a/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts b/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts index 95bda4c1896..4f1e5dc9d42 100644 --- a/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts +++ b/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts @@ -14,6 +14,7 @@ import { } from '@spartacus/core'; import { OPF_AFTER_REDIRECT_SCRIPTS_NORMALIZER, + OPF_CTA_SCRIPTS_NORMALIZER, OPF_PAYMENT_SUBMIT_COMPLETE_NORMALIZER, OPF_PAYMENT_SUBMIT_NORMALIZER, OPF_PAYMENT_VERIFICATION_NORMALIZER, @@ -22,6 +23,8 @@ import { } from '@spartacus/opf/base/core'; import { AfterRedirectScriptResponse, + CtaScriptsRequest, + CtaScriptsResponse, OPF_CC_OTP_KEY, OPF_CC_PUBLIC_KEY, OpfConfig, @@ -162,6 +165,31 @@ export class OccOpfPaymentAdapter implements OpfPaymentAdapter { ); } + ctaScripts( + ctaScriptsRequest: CtaScriptsRequest + ): Observable { + const headers = new HttpHeaders(this.header).set( + OPF_CC_PUBLIC_KEY, + this.config.opf?.commerceCloudPublicKey || '' + ); + + const url = this.getCtaScriptsEndpoint(); + + return this.http + .post(url, ctaScriptsRequest, { headers }) + .pipe( + catchError((error) => throwError(normalizeHttpError(error))), + backOff({ + shouldRetry: isJaloError, + }), + backOff({ + shouldRetry: isHttp500Error, + maxTries: 2, + }), + this.converter.pipeable(OPF_CTA_SCRIPTS_NORMALIZER) + ); + } + protected verifyPaymentEndpoint(paymentSessionId: string): string { return this.opfEndpointsService.buildUrl('verifyPayment', { urlParams: { paymentSessionId }, @@ -185,4 +213,8 @@ export class OccOpfPaymentAdapter implements OpfPaymentAdapter { urlParams: { paymentSessionId }, }); } + + protected getCtaScriptsEndpoint(): string { + return this.opfEndpointsService.buildUrl('ctaScripts'); + } } diff --git a/integration-libs/opf/base/occ/config/default-occ-opf-config.ts b/integration-libs/opf/base/occ/config/default-occ-opf-config.ts index 3761444c5ca..95346ad840c 100644 --- a/integration-libs/opf/base/occ/config/default-occ-opf-config.ts +++ b/integration-libs/opf/base/occ/config/default-occ-opf-config.ts @@ -15,6 +15,7 @@ export const defaultOccOpfConfig: OccConfig = { submitCompletePayment: 'payments/${paymentSessionId}/submit-complete', afterRedirectScripts: 'payments/${paymentSessionId}/after-redirect-scripts', + ctaScripts: 'payments/cta-scripts-rendering', }, }, }, diff --git a/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts b/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts index fb1a17b1943..9906487f9cf 100644 --- a/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts +++ b/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts @@ -25,5 +25,7 @@ declare module '@spartacus/core' { * Endpoint to fetch dynamic script for Hosted Fields pattern and PageRedirection sub-pattern. */ afterRedirectScripts?: string | OccEndpoint; + + ctaScripts?: string | OccEndpoint; } } diff --git a/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.spec.ts b/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.spec.ts index 53353326d78..3354208e597 100644 --- a/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.spec.ts +++ b/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.spec.ts @@ -21,7 +21,7 @@ import { OpfPaymentFacade, } from '../../facade'; import { - AfterRedirectDynamicScript, + OpfDynamicScript, OpfPaymentMetadata, OpfPaymentVerificationResponse, OpfPaymentVerificationResult, @@ -287,7 +287,7 @@ describe('OpfPaymentVerificationService', () => { }); describe('runHostedFieldsPattern', () => { - const dynamicScriptMock: AfterRedirectDynamicScript = { + const dynamicScriptMock: OpfDynamicScript = { cssUrls: [{ url: 'css url test', sri: 'css sri test' }], jsUrls: [{ url: 'js url test', sri: 'js sri test' }], html: 'html test', diff --git a/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.ts b/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.ts index 1c5c27a3669..d8e2e56fd6d 100644 --- a/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.ts +++ b/integration-libs/opf/base/root/components/opf-payment-verification/opf-payment-verification.service.ts @@ -27,8 +27,8 @@ import { OpfPaymentVerificationResult, } from '../../model'; import { - AfterRedirectDynamicScript, KeyValuePair, + OpfDynamicScript, OpfPaymentMetadata, TargetPage, } from '../../model/opf.model'; @@ -220,7 +220,7 @@ export class OpfPaymentVerificationService { } protected renderAfterRedirectScripts( - script: AfterRedirectDynamicScript + script: OpfDynamicScript ): Promise { const html = script?.html; diff --git a/integration-libs/opf/base/root/facade/opf-payment.facade.ts b/integration-libs/opf/base/root/facade/opf-payment.facade.ts index 3132614b390..dd2ae911038 100644 --- a/integration-libs/opf/base/root/facade/opf-payment.facade.ts +++ b/integration-libs/opf/base/root/facade/opf-payment.facade.ts @@ -10,6 +10,8 @@ import { Observable } from 'rxjs'; import { OPF_BASE_FEATURE } from '../feature-name'; import { AfterRedirectScriptResponse, + CtaScriptsRequest, + CtaScriptsResponse, OpfPaymentVerificationPayload, OpfPaymentVerificationResponse, SubmitCompleteInput, @@ -27,6 +29,7 @@ import { 'submitPayment', 'submitCompletePayment', 'afterRedirectScripts', + 'ctaScripts', ], }), }) @@ -66,4 +69,8 @@ export abstract class OpfPaymentFacade { abstract afterRedirectScripts( paymentSessionId: string ): Observable; + + abstract ctaScripts( + ctaScriptsRequest: CtaScriptsRequest + ): Observable; } diff --git a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts index e3a868de653..2df03cbe961 100644 --- a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts +++ b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts @@ -4,6 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { OpfDynamicScript } from './opf.model'; + export interface DigitalWalletQuickBuy { description?: string; provider?: OpfProviderType; @@ -17,3 +19,44 @@ export enum OpfProviderType { APPLE_PAY = 'APPLE_PAY', GOOGLE_PAY = 'GOOGLE_PAY', } +export interface CtaScriptsRequest { + paymentAccountIds?: Array; + orderId?: string; + ctaProductItems?: Array; + scriptLocations?: Array; + additionalData?: Array<{ + key: + | 'divisionId' + | 'experienceId' + | 'currency' + | 'fulfillmentLocationId' + | 'locale' + | 'scriptIdentifier'; + value: string; + }>; +} + +export interface CTAProductItem { + productId: string; + quantity: number; + fulfillmentLocationId?: string; +} + +export enum CtaScriptsLocation { + CART_MESSAGING = 'CART_MESSAGING', + PDP_MESSAGING = 'PDP_MESSAGING', + PDP_QUICK_BUY = 'PDP_QUICK_BUY', + CART_QUICK_BUY = 'CART_QUICK_BUY', + CHECKOUT_QUICK_BUY = 'CHECKOUT_QUICK_BUY', + ORDER_CONFIRMATION_PAYMENT_GUIDE = 'ORDER_CONFIRMATION_PAYMENT_GUIDE', + ORDER_HISTORY_PAYMENT_GUIDE = 'ORDER_HISTORY_PAYMENT_GUIDE', +} + +export interface CtaScriptsResponse { + value: Array; +} + +export interface CtaScript { + accountId: string; + dynamicScript: OpfDynamicScript; +} diff --git a/integration-libs/opf/base/root/model/opf.model.ts b/integration-libs/opf/base/root/model/opf.model.ts index 2aa5aba3b13..647c6b73dbb 100644 --- a/integration-libs/opf/base/root/model/opf.model.ts +++ b/integration-libs/opf/base/root/model/opf.model.ts @@ -133,16 +133,16 @@ export interface SubmitCompleteInput { } export interface AfterRedirectScriptResponse { - afterRedirectScript: AfterRedirectDynamicScript; + afterRedirectScript: OpfDynamicScript; } -export interface AfterRedirectDynamicScript { - cssUrls?: AfterRedirectDynamicScriptResource[]; - jsUrls?: AfterRedirectDynamicScriptResource[]; +export interface OpfDynamicScript { + cssUrls?: OpfDynamicScriptResource[]; + jsUrls?: OpfDynamicScriptResource[]; html?: string; } -export interface AfterRedirectDynamicScriptResource { +export interface OpfDynamicScriptResource { url?: string; sri?: string; attributes?: KeyValuePair[]; diff --git a/integration-libs/opf/base/root/opf-base-root.module.ts b/integration-libs/opf/base/root/opf-base-root.module.ts index ee5e896f7d9..7e6972ab2d8 100644 --- a/integration-libs/opf/base/root/opf-base-root.module.ts +++ b/integration-libs/opf/base/root/opf-base-root.module.ts @@ -7,15 +7,18 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { + CmsConfig, MODULE_INITIALIZER, provideConfigValidator, provideDefaultConfig, + provideDefaultConfigFactory, } from '@spartacus/core'; import { OpfPaymentVerificationComponent } from './components/opf-payment-verification'; import { defaultOpfRoutingConfig } from './config'; import { defaultOpfConfig } from './config/default-opf-config'; import { opfConfigValidator } from './config/opf-config-validator'; import { OpfEventModule } from './events/opf-event.module'; +import { OPF_BASE_FEATURE } from './feature-name'; import { OpfStatePersistenceService } from './services/opf-state-persistence.service'; export function opfStatePersistenceFactory( @@ -24,6 +27,17 @@ export function opfStatePersistenceFactory( return () => opfStatePersistenceService.initSync(); } +export function defaultOpfCtaScriptsComponentsConfig(): CmsConfig { + const config: CmsConfig = { + featureModules: { + [OPF_BASE_FEATURE]: { + cmsComponents: ['OpfCtaScriptsComponent'], + }, + }, + }; + return config; +} + @NgModule({ imports: [ RouterModule.forChild([ @@ -58,6 +72,7 @@ export function opfStatePersistenceFactory( // TODO OPF: uncomment once proper type and routing is set up provideDefaultConfig(defaultOpfRoutingConfig), provideConfigValidator(opfConfigValidator), + provideDefaultConfigFactory(defaultOpfCtaScriptsComponentsConfig), ], }) export class OpfBaseRootModule {} 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 823c1407512..32ea561ad4d 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 @@ -10,8 +10,8 @@ import { ScriptLoader } from '@spartacus/core'; import { throwError } from 'rxjs'; import { - AfterRedirectDynamicScriptResource, AfterRedirectDynamicScriptResourceType, + OpfDynamicScriptResource, } from '../model'; @Injectable({ @@ -27,7 +27,7 @@ export class OpfResourceLoaderService extends ScriptLoader { protected readonly OPF_RESOURCE_ATTRIBUTE_KEY = 'data-opf-resource'; - protected loadedResources: AfterRedirectDynamicScriptResource[] = []; + protected loadedResources: OpfDynamicScriptResource[] = []; protected embedStyles(embedOptions: { src: string; @@ -71,15 +71,13 @@ export class OpfResourceLoaderService extends ScriptLoader { return throwError(`Error while loading external ${src} resource.`); } - protected isResourceLoadingCompleted( - resources: AfterRedirectDynamicScriptResource[] - ) { + protected isResourceLoadingCompleted(resources: OpfDynamicScriptResource[]) { return resources.length === this.loadedResources.length; } protected markResourceAsLoaded( - resource: AfterRedirectDynamicScriptResource, - resources: AfterRedirectDynamicScriptResource[], + resource: OpfDynamicScriptResource, + resources: OpfDynamicScriptResource[], resolve: (value: void | PromiseLike) => void ) { this.loadedResources.push(resource); @@ -89,8 +87,8 @@ export class OpfResourceLoaderService extends ScriptLoader { } protected loadScript( - resource: AfterRedirectDynamicScriptResource, - resources: AfterRedirectDynamicScriptResource[], + resource: OpfDynamicScriptResource, + resources: OpfDynamicScriptResource[], resolve: (value: void | PromiseLike) => void ) { if (resource.url && !this.hasScript(resource.url)) { @@ -110,8 +108,8 @@ export class OpfResourceLoaderService extends ScriptLoader { } protected loadStyles( - resource: AfterRedirectDynamicScriptResource, - resources: AfterRedirectDynamicScriptResource[], + resource: OpfDynamicScriptResource, + resources: OpfDynamicScriptResource[], resolve: (value: void | PromiseLike) => void ) { if (resource.url && !this.hasStyles(resource.url)) { @@ -144,10 +142,10 @@ export class OpfResourceLoaderService extends ScriptLoader { } loadProviderResources( - scripts: AfterRedirectDynamicScriptResource[] = [], - styles: AfterRedirectDynamicScriptResource[] = [] + scripts: OpfDynamicScriptResource[] = [], + styles: OpfDynamicScriptResource[] = [] ): Promise { - const resources: AfterRedirectDynamicScriptResource[] = [ + const resources: OpfDynamicScriptResource[] = [ ...scripts.map((script) => ({ ...script, type: AfterRedirectDynamicScriptResourceType.SCRIPT, @@ -161,7 +159,7 @@ export class OpfResourceLoaderService extends ScriptLoader { return new Promise((resolve) => { this.loadedResources = []; - resources.forEach((resource: AfterRedirectDynamicScriptResource) => { + resources.forEach((resource: OpfDynamicScriptResource) => { if (!resource.url) { this.markResourceAsLoaded(resource, resources, resolve); } else { From 3c93b446ca943ebf4e7991db2090d7399a2b9886 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Wed, 11 Oct 2023 18:20:34 -0400 Subject: [PATCH 02/30] add service --- .../base/components/opf-cta-scripts/index.ts | 1 + .../opf-cta-scripts.component.ts | 79 ++++------- .../opf-cta-scripts.service.ts | 132 ++++++++++++++++++ .../opf/base/occ/adapters/occ-opf.adapter.ts | 2 +- .../base/root/model/opf-quick-buy.model.ts | 7 + 5 files changed, 169 insertions(+), 52 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta-scripts/index.ts b/integration-libs/opf/base/components/opf-cta-scripts/index.ts index 52a647c6551..1dc8c0aa120 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/index.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/index.ts @@ -1,2 +1,3 @@ export * from './opf-cta-scripts.component'; export * from './opf-cta-scripts.module'; +export * from './opf-cta-scripts.service'; diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts index b98f8dbd14d..3fc115c9337 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts @@ -1,61 +1,38 @@ -import { Component, OnInit, inject } from '@angular/core'; -import { - CtaScriptsLocation, - CtaScriptsRequest, - OpfPaymentFacade, -} from '@spartacus/opf/base/root'; -import { OrderFacade } from '@spartacus/order/root'; -import { Order } from 'feature-libs/order/root/model'; -import { throwError } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { Component, OnDestroy, OnInit, inject } from '@angular/core'; +import { CmsService } from '@spartacus/core'; +import { Subscription } from 'rxjs'; +import { OpfCtaScriptsService } from './opf-cta-scripts.service'; @Component({ selector: 'cx-opf-cta-scripts', templateUrl: './opf-cta-scripts.component.html', }) -export class OpfCtaScriptsComponent implements OnInit { - protected opfPaymentFacade = inject(OpfPaymentFacade); - protected orderDetailsService = inject(OrderFacade); +export class OpfCtaScriptsComponent implements OnInit, OnDestroy { + protected opfCtaScriptService = inject(OpfCtaScriptsService); + protected cmsService = inject(CmsService); - ngOnInit() { - this.orderDetailsService - .getOrderDetails() - .pipe( - switchMap((order) => { - if (!order) { - return throwError({ error: 'Order obj not found' }); - } - console.log('flo order', order); - const obj: CtaScriptsRequest = { - orderId: order?.code, - ctaProductItems: this.getProductItems(order), - paymentAccountIds: [12, 11, 200, 52, 101], - scriptLocations: [ - CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE, - ], - }; - return this.opfPaymentFacade.ctaScripts(obj); - }) - ) - .subscribe((ctaScriptsResponse) => { - console.log('ctaScriptsResponse', ctaScriptsResponse); - }); + protected sub: Subscription; + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + } } - protected getProductItems( - order: Order - ): { productId: string; quantity: number }[] | [] { - return !!order.entries - ? order.entries - ?.filter((item) => { - return !!item?.product?.code && !!item?.quantity; - }) - .map((item) => { - return { - productId: item.product?.code as string, - quantity: item.quantity as number, - }; - }) - : []; + ngOnInit() { + console.log('ngOnInit'); + + // this.cmsService.getCurrentPage().subscribe((cmsPage) => { + // console.log('cmsPage', cmsPage); + // }); + + this.sub = this.opfCtaScriptService.getCtaScripts().subscribe({ + next: (ctaScripts) => { + console.log('ctaScripts', ctaScripts); + }, + error: (error) => { + console.log('cta error', error); + }, + }); } } diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts index e69de29bb2d..e8cf7c1f35e 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts @@ -0,0 +1,132 @@ +import { Injectable, inject } from '@angular/core'; +import { + CmsService, + GlobalMessageService, + GlobalMessageType, + QueryState, +} from '@spartacus/core'; +import { Order, OrderFacade, OrderHistoryFacade } from '@spartacus/order/root'; +import { Observable, of, throwError } from 'rxjs'; +import { filter, map, switchMap, take, tap } from 'rxjs/operators'; + +import { + ActiveConfiguration, + CmsPageLocation, + CtaScriptsLocation, + CtaScriptsRequest, + OpfPaymentFacade, +} from '@spartacus/opf/base/root'; + +@Injectable({ + providedIn: 'root', +}) +export class OpfCtaScriptsService { + protected opfPaymentFacade = inject(OpfPaymentFacade); + protected orderDetailsService = inject(OrderFacade); + protected orderHistoryService = inject(OrderHistoryFacade); + protected globalMessageService = inject(GlobalMessageService); + protected cmsService = inject(CmsService); + + getCtaScripts() { + let _paymentAccountIds: number[]; + let _scriptLocation: CtaScriptsLocation; + return this.getPaymentAccountIds().pipe( + switchMap((paymentAccountIds) => { + console.log('paymentAccountIds', paymentAccountIds); + _paymentAccountIds = paymentAccountIds; + return this.getScriptLocation(); + }), + switchMap((scriptsLocation) => { + _scriptLocation = scriptsLocation; + return this.getOrderDetails(scriptsLocation); + }), + switchMap((order) => { + console.log('flo order', order); + const ctaScripts: CtaScriptsRequest = { + orderId: order?.code, + ctaProductItems: this.getProductItems(order), + paymentAccountIds: _paymentAccountIds, + scriptLocations: [_scriptLocation], + }; + + return this.opfPaymentFacade.ctaScripts(ctaScripts); + }) + ); + } + + protected getScriptLocation(): Observable { + return this.cmsService.getCurrentPage().pipe( + take(1), + switchMap((page) => { + switch (page.pageId) { + case CmsPageLocation.ORDER_PAGE: + return of(CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE); + case CmsPageLocation.ORDER_CONFIRMATION_PAGE: + return of(CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE); + default: + return throwError({ error: 'Page not valid' }); + } + }) + ); + } + + protected getOrderDetails(scriptsLocation: CtaScriptsLocation) { + const order$ = + scriptsLocation == CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE + ? this.orderDetailsService.getOrderDetails() + : this.orderHistoryService.getOrderDetails(); + return order$.pipe( + filter((order) => !!order?.entries), + switchMap((order) => { + if (!order) { + return throwError({ error: 'Order obj not found' }); + } + if (!order?.entries) { + return throwError({ error: 'Order entries not found' }); + } + return of(order); + }) + ); + } + + protected getPaymentAccountIds() { + return this.opfPaymentFacade.getActiveConfigurationsState().pipe( + tap((state: QueryState) => { + console.log('onTap'); + if (state.error) { + this.displayError('loadActiveConfigurations'); + } else if (!state.loading && !Boolean(state.data?.length)) { + this.displayError('noActiveConfigurations'); + } + }), + filter( + (state) => !state.loading && !state.error && Boolean(state.data?.length) + ), + map((state) => state.data?.map((val) => val.id) as number[]) + ); + } + + protected getProductItems( + order: Order + ): { productId: string; quantity: number }[] | [] { + return !!order.entries + ? order.entries + ?.filter((item) => { + return !!item?.product?.code && !!item?.quantity; + }) + .map((item) => { + return { + productId: item.product?.code as string, + quantity: item.quantity as number, + }; + }) + : []; + } + + protected displayError(errorKey: string): void { + this.globalMessageService.add( + { key: `opf.checkout.errors.${errorKey}` }, + GlobalMessageType.MSG_TYPE_ERROR + ); + } +} diff --git a/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts b/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts index ffa2973e2e0..05c38fca7b5 100644 --- a/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts +++ b/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts @@ -168,7 +168,7 @@ export class OccOpfPaymentAdapter implements OpfPaymentAdapter { } getActiveConfigurations(): Observable { - const headers = new HttpHeaders().set( + const headers = new HttpHeaders(this.header).set( OPF_CC_PUBLIC_KEY, this.config.opf?.commerceCloudPublicKey || '' ); diff --git a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts index 2df03cbe961..84c4dca865c 100644 --- a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts +++ b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts @@ -52,6 +52,13 @@ export enum CtaScriptsLocation { ORDER_HISTORY_PAYMENT_GUIDE = 'ORDER_HISTORY_PAYMENT_GUIDE', } +export enum CmsPageLocation { + ORDER_CONFIRMATION_PAGE = 'orderConfirmationPage', + ORDER_PAGE = 'order', + PDP_PAGE = 'productDetails', + CART_PAGE = 'cartPage', +} + export interface CtaScriptsResponse { value: Array; } From 7983ac98b46ab2b319a5b9d6f64c6921872f4fb0 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 13 Oct 2023 18:01:42 -0400 Subject: [PATCH 03/30] implement script list + button + css --- integration-libs/opf/_index.scss | 3 +- .../components/opf-base-components.module.ts | 3 +- .../base/components/opf-cta-button/index.ts | 2 + .../opf-cta-button.component.html | 1 + .../opf-cta-button.component.ts | 85 +++++++ .../opf-cta-button/opf-cta-button.module.ts | 10 + .../opf-cta-scripts.component.html | 16 +- .../opf-cta-scripts.component.ts | 35 +-- .../opf-cta-scripts/opf-cta-scripts.module.ts | 4 +- .../opf-cta-scripts.service.ts | 230 +++++++++++++++--- .../services/opf-resource-loader.service.ts | 26 +- .../opf/base/styles/components/_index.scss | 2 + .../styles/components/_opf-cta-button.scss | 5 + .../styles/components/_opf-cta-scripts.scss | 7 + 14 files changed, 360 insertions(+), 69 deletions(-) create mode 100644 integration-libs/opf/base/components/opf-cta-button/index.ts create mode 100644 integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html create mode 100644 integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts create mode 100644 integration-libs/opf/base/components/opf-cta-button/opf-cta-button.module.ts create mode 100644 integration-libs/opf/base/styles/components/_opf-cta-button.scss create mode 100644 integration-libs/opf/base/styles/components/_opf-cta-scripts.scss diff --git a/integration-libs/opf/_index.scss b/integration-libs/opf/_index.scss index 63dc6729038..087bf872986 100644 --- a/integration-libs/opf/_index.scss +++ b/integration-libs/opf/_index.scss @@ -8,7 +8,8 @@ $opf-components-allowlist: cx-opf-checkout-payment-and-review, cx-opf-checkout-payments, cx-opf-checkout-billing-address-form, - cx-opf-checkout-payment-wrapper, cx-opf-error-modal !default; + cx-opf-checkout-payment-wrapper, cx-opf-error-modal, cx-opf-cta-button, + cx-opf-cta-scripts !default; $skipComponentStyles: () !default; diff --git a/integration-libs/opf/base/components/opf-base-components.module.ts b/integration-libs/opf/base/components/opf-base-components.module.ts index 78258462971..85dd480101f 100644 --- a/integration-libs/opf/base/components/opf-base-components.module.ts +++ b/integration-libs/opf/base/components/opf-base-components.module.ts @@ -5,11 +5,12 @@ */ import { NgModule } from '@angular/core'; +import { OpfCtaButtonModule } from './opf-cta-button'; import { OpfCtaScriptsModule } from './opf-cta-scripts'; import { OpfErrorModalModule } from './opf-error-modal/opf-error-modal.module'; @NgModule({ - imports: [OpfErrorModalModule, OpfCtaScriptsModule], + imports: [OpfErrorModalModule, OpfCtaScriptsModule, OpfCtaButtonModule], providers: [], }) export class OpfBaseComponentsModule {} diff --git a/integration-libs/opf/base/components/opf-cta-button/index.ts b/integration-libs/opf/base/components/opf-cta-button/index.ts new file mode 100644 index 00000000000..17889b36f11 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-button/index.ts @@ -0,0 +1,2 @@ +export * from './opf-cta-button.component'; +export * from './opf-cta-button.module'; diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html new file mode 100644 index 00000000000..d7455c0527d --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html @@ -0,0 +1 @@ +
diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts new file mode 100644 index 00000000000..05215dc1741 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts @@ -0,0 +1,85 @@ +import { AfterViewInit, Component, Input, OnInit, inject } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { OpfResourceLoaderService } from '@spartacus/opf/base/root'; +import { OpfDynamicScript } from '../../root/model'; + +@Component({ + selector: 'cx-opf-cta-button', + templateUrl: './opf-cta-button.component.html', +}) +export class OpfCtaButtonComponent implements OnInit, AfterViewInit { + protected opfResourceLoaderService = inject(OpfResourceLoaderService); + protected sanitizer = inject(DomSanitizer); + // _ctaScript$: Observable; + // ready$ = new BehaviorSubject(false); + + @Input() ctaScriptHtml: string; + + htmlSnippet?: string; + + constructor() { + console.log('constructor'); + // this.renderCtaScripts(this.ctaScript).then(() => { + // this.ready = true; + // }); + } + ngOnDestroy(): void { + console.log('ngOnDestroy Button', this.ctaScriptHtml); + } + + ngAfterViewInit(): void { + console.log('after view init'); + } + + ngOnInit(): void { + console.log('ngOnInit'); + // this.renderCtaScripts(this.ctaScript) + // .then(() => { + // this.ready$.next(true); + // }) + // .catch((error) => console.log('error render', error)); + // this.renderCtaScripts(this.ctaScript).then((htmlSnippet) => { + // this.htmlSnippet = htmlSnippet; + // }); + // this._ctaScript$ = this.ctaScript$.pipe( + // tap((script) => { + // console.log(); + // return this.renderCtaScripts(script); + // }), + // concatMap((script) => of(script.html)) + // ); + } + + renderHtml(html?: string): SafeHtml { + // const trustedHtml = this.sanitizer.bypassSecurityTrustHtml(html as string); + // console.log('trustedHtml', trustedHtml); + console.log('renderHtml', html); + + return html as string; + } + + renderCtaScripts(script: OpfDynamicScript): Promise { + const html = script?.html; + console.log('renderCtaScripts:', html); + return new Promise((resolve: (value: string | undefined) => void) => { + this.opfResourceLoaderService + .loadProviderResources(script.jsUrls, script.cssUrls) + .then(() => { + if (html) { + setTimeout(() => { + console.log('flo ok:', html); + this.opfResourceLoaderService.executeScriptFromHtml(html); + resolve(html); + }); + } else { + console.log('flo ko:', html); + resolve(undefined); + } + }) + .catch(() => { + console.log('flo error:'); + resolve(undefined); + }); + }); + } +} diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.module.ts b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.module.ts new file mode 100644 index 00000000000..84d0bfb6fa9 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.module.ts @@ -0,0 +1,10 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { OpfCtaButtonComponent } from './opf-cta-button.component'; + +@NgModule({ + declarations: [OpfCtaButtonComponent], + imports: [CommonModule], + exports: [OpfCtaButtonComponent], +}) +export class OpfCtaButtonModule {} diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html index 718edca49b6..ff62d118549 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html @@ -1,3 +1,17 @@ -

CTA Scripts Component

+ + + + + + +
+ +
+
+ + diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts index 3fc115c9337..5eaf6784bd3 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts @@ -1,38 +1,15 @@ -import { Component, OnDestroy, OnInit, inject } from '@angular/core'; -import { CmsService } from '@spartacus/core'; -import { Subscription } from 'rxjs'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { OpfResourceLoaderService } from '@spartacus/opf/base/root'; import { OpfCtaScriptsService } from './opf-cta-scripts.service'; @Component({ selector: 'cx-opf-cta-scripts', templateUrl: './opf-cta-scripts.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class OpfCtaScriptsComponent implements OnInit, OnDestroy { +export class OpfCtaScriptsComponent { protected opfCtaScriptService = inject(OpfCtaScriptsService); - protected cmsService = inject(CmsService); + protected opfResourceLoaderService = inject(OpfResourceLoaderService); - protected sub: Subscription; - - ngOnDestroy() { - if (this.sub) { - this.sub.unsubscribe(); - } - } - - ngOnInit() { - console.log('ngOnInit'); - - // this.cmsService.getCurrentPage().subscribe((cmsPage) => { - // console.log('cmsPage', cmsPage); - // }); - - this.sub = this.opfCtaScriptService.getCtaScripts().subscribe({ - next: (ctaScripts) => { - console.log('ctaScripts', ctaScripts); - }, - error: (error) => { - console.log('cta error', error); - }, - }); - } + ctaHtmlList$ = this.opfCtaScriptService.getCtaHtmlslList(); } diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts index 438280426b9..d2852426659 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts @@ -5,6 +5,8 @@ import { FeaturesConfig, provideDefaultConfig, } from '@spartacus/core'; +import { SpinnerModule } from '@spartacus/storefront'; +import { OpfCtaButtonModule } from '../opf-cta-button/opf-cta-button.module'; import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; @NgModule({ @@ -23,6 +25,6 @@ import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; }), ], exports: [OpfCtaScriptsComponent], - imports: [CommonModule], + imports: [CommonModule, OpfCtaButtonModule, SpinnerModule], }) export class OpfCtaScriptsModule {} diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts index e8cf7c1f35e..1e374754034 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts @@ -3,18 +3,28 @@ import { CmsService, GlobalMessageService, GlobalMessageType, - QueryState, } from '@spartacus/core'; import { Order, OrderFacade, OrderHistoryFacade } from '@spartacus/order/root'; -import { Observable, of, throwError } from 'rxjs'; -import { filter, map, switchMap, take, tap } from 'rxjs/operators'; +import { Observable, from, of, throwError } from 'rxjs'; +import { + concatMap, + filter, + finalize, + last, + map, + switchMap, + take, + tap, +} from 'rxjs/operators'; import { - ActiveConfiguration, CmsPageLocation, CtaScriptsLocation, CtaScriptsRequest, + CtaScriptsResponse, + OpfDynamicScript, OpfPaymentFacade, + OpfResourceLoaderService, } from '@spartacus/opf/base/root'; @Injectable({ @@ -25,31 +35,179 @@ export class OpfCtaScriptsService { protected orderDetailsService = inject(OrderFacade); protected orderHistoryService = inject(OrderHistoryFacade); protected globalMessageService = inject(GlobalMessageService); + protected opfResourceLoaderService = inject(OpfResourceLoaderService); protected cmsService = inject(CmsService); - getCtaScripts() { - let _paymentAccountIds: number[]; - let _scriptLocation: CtaScriptsLocation; + protected mock = { + value: [ + { + paymentAccountId: 1, + dynamicScript: { + html: "

CTA Html snippet #1

", + cssUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css', + sri: '', + }, + ], + jsUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js', + sri: '', + }, + ], + }, + }, + { + paymentAccountId: 2, + dynamicScript: { + html: "

CTA Html snippet #2

", + cssUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.2/adyen.css', + sri: '', + }, + ], + jsUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.2/adyen.js', + sri: '', + }, + ], + }, + }, + { + paymentAccountId: 3, + dynamicScript: { + html: "

CTA Html snippet #3

", + cssUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.3/adyen.css', + sri: '', + }, + ], + jsUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.3/adyen.js', + sri: '', + }, + ], + }, + }, + { + paymentAccountId: 4, + dynamicScript: { + html: "

CTA Html snippet #4

", + cssUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.x/adyen.css', + sri: '', + }, + ], + jsUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.0/adyen.js', + sri: '', + }, + ], + }, + }, + { + paymentAccountId: 5, + dynamicScript: { + html: "

CTA Html snippet #5

", + cssUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.1.x/adyen.css', + sri: '', + }, + ], + jsUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.1.0/adyen.js', + sri: '', + }, + ], + }, + }, + ], + }; + + getCtaHtmlslList(): Observable { + return this.fillCtaScriptRequest().pipe( + switchMap((ctaScriptsRequest) => + this.fetchCtaScriptsList(ctaScriptsRequest) + ), + switchMap((scriptslist) => this.runCtaScriptsList(scriptslist)), + finalize(() => { + this.clearResources(); + }) + ); + } + + protected clearResources() { + this.opfResourceLoaderService.clearAllProviderResources(); + } + + protected fetchCtaScriptsList(ctaScriptsRequest: CtaScriptsRequest) { + return this.opfPaymentFacade.ctaScripts(ctaScriptsRequest).pipe( + map((ctaScriptsResponse: CtaScriptsResponse) => { + console.log('ctaScriptsResponse', ctaScriptsResponse); + // mock for test purpose until PSP ready + const list = this.mock.value.map( + (ctaScript) => ctaScript.dynamicScript + ); + return list; + }), + take(1) + ); + } + + protected fillCtaScriptRequest() { + let paymentAccountIds: number[]; + let scriptLocation: CtaScriptsLocation; return this.getPaymentAccountIds().pipe( - switchMap((paymentAccountIds) => { - console.log('paymentAccountIds', paymentAccountIds); - _paymentAccountIds = paymentAccountIds; + concatMap((paymentAccountIds) => { + paymentAccountIds = paymentAccountIds; return this.getScriptLocation(); }), - switchMap((scriptsLocation) => { - _scriptLocation = scriptsLocation; + concatMap((scriptsLocation) => { + scriptLocation = scriptsLocation; return this.getOrderDetails(scriptsLocation); }), - switchMap((order) => { - console.log('flo order', order); - const ctaScripts: CtaScriptsRequest = { + map((order) => { + const ctaScriptsRequest: CtaScriptsRequest = { orderId: order?.code, ctaProductItems: this.getProductItems(order), - paymentAccountIds: _paymentAccountIds, - scriptLocations: [_scriptLocation], + paymentAccountIds: paymentAccountIds, + scriptLocations: [scriptLocation], }; - return this.opfPaymentFacade.ctaScripts(ctaScripts); + return ctaScriptsRequest; + }) + ); + } + + protected runCtaScriptsList(scripts: OpfDynamicScript[]) { + let loadedCtaHtmls: string[]; + return of(scripts).pipe( + tap(() => { + loadedCtaHtmls = []; + }), + concatMap((scripts) => { + return from(scripts); + }), + concatMap((script) => from(this.loadAndRunScript(script))), + tap((script) => { + console.log('in tap', script); + if (script?.html) { + loadedCtaHtmls.push(script.html); + } + }), + last(), + map(() => { + console.log('in last'); + return loadedCtaHtmls; }) ); } @@ -57,7 +215,7 @@ export class OpfCtaScriptsService { protected getScriptLocation(): Observable { return this.cmsService.getCurrentPage().pipe( take(1), - switchMap((page) => { + concatMap((page) => { switch (page.pageId) { case CmsPageLocation.ORDER_PAGE: return of(CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE); @@ -77,7 +235,7 @@ export class OpfCtaScriptsService { : this.orderHistoryService.getOrderDetails(); return order$.pipe( filter((order) => !!order?.entries), - switchMap((order) => { + concatMap((order) => { if (!order) { return throwError({ error: 'Order obj not found' }); } @@ -91,14 +249,6 @@ export class OpfCtaScriptsService { protected getPaymentAccountIds() { return this.opfPaymentFacade.getActiveConfigurationsState().pipe( - tap((state: QueryState) => { - console.log('onTap'); - if (state.error) { - this.displayError('loadActiveConfigurations'); - } else if (!state.loading && !Boolean(state.data?.length)) { - this.displayError('noActiveConfigurations'); - } - }), filter( (state) => !state.loading && !state.error && Boolean(state.data?.length) ), @@ -129,4 +279,28 @@ export class OpfCtaScriptsService { GlobalMessageType.MSG_TYPE_ERROR ); } + + loadAndRunScript( + script: OpfDynamicScript + ): Promise { + const html = script?.html; + + return new Promise( + (resolve: (value: OpfDynamicScript | undefined) => void) => { + this.opfResourceLoaderService + .loadProviderResources(script.jsUrls, script.cssUrls) + .then(() => { + if (html) { + this.opfResourceLoaderService.executeScriptFromHtml(html); + resolve(script); + } else { + resolve(undefined); + } + }) + .catch(() => { + resolve(undefined); + }); + } + ); + } } 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 32ea561ad4d..804175aa74b 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 @@ -89,7 +89,8 @@ export class OpfResourceLoaderService extends ScriptLoader { protected loadScript( resource: OpfDynamicScriptResource, resources: OpfDynamicScriptResource[], - resolve: (value: void | PromiseLike) => void + resolve: (value: void | PromiseLike) => void, + reject: (value: void | PromiseLike) => void ) { if (resource.url && !this.hasScript(resource.url)) { super.embedScript({ @@ -99,8 +100,13 @@ export class OpfResourceLoaderService extends ScriptLoader { [this.OPF_RESOURCE_ATTRIBUTE_KEY]: true, }, - callback: () => this.markResourceAsLoaded(resource, resources, resolve), - errorCallback: () => this.handleLoadingResourceError(resource.url), + callback: () => { + this.markResourceAsLoaded(resource, resources, resolve); + }, + errorCallback: () => { + this.handleLoadingResourceError(resource.url); + reject(); + }, }); } else { this.markResourceAsLoaded(resource, resources, resolve); @@ -110,13 +116,17 @@ export class OpfResourceLoaderService extends ScriptLoader { protected loadStyles( resource: OpfDynamicScriptResource, resources: OpfDynamicScriptResource[], - resolve: (value: void | PromiseLike) => void + resolve: (value: void | PromiseLike) => void, + reject: (value: void | PromiseLike) => void ) { if (resource.url && !this.hasStyles(resource.url)) { this.embedStyles({ src: resource.url, callback: () => this.markResourceAsLoaded(resource, resources, resolve), - errorCallback: () => this.handleLoadingResourceError(resource.url), + errorCallback: () => { + this.handleLoadingResourceError(resource.url); + reject(); + }, }); } else { this.markResourceAsLoaded(resource, resources, resolve); @@ -156,7 +166,7 @@ export class OpfResourceLoaderService extends ScriptLoader { })), ]; - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this.loadedResources = []; resources.forEach((resource: OpfDynamicScriptResource) => { @@ -165,10 +175,10 @@ export class OpfResourceLoaderService extends ScriptLoader { } else { switch (resource.type) { case AfterRedirectDynamicScriptResourceType.SCRIPT: - this.loadScript(resource, resources, resolve); + this.loadScript(resource, resources, resolve, reject); break; case AfterRedirectDynamicScriptResourceType.STYLES: - this.loadStyles(resource, resources, resolve); + this.loadStyles(resource, resources, resolve, reject); break; default: break; diff --git a/integration-libs/opf/base/styles/components/_index.scss b/integration-libs/opf/base/styles/components/_index.scss index f79abba97cd..66f1e36dee9 100644 --- a/integration-libs/opf/base/styles/components/_index.scss +++ b/integration-libs/opf/base/styles/components/_index.scss @@ -1 +1,3 @@ @import './opf-error-modal'; +@import './opf-cta-button'; +@import './opf-cta-scripts'; diff --git a/integration-libs/opf/base/styles/components/_opf-cta-button.scss b/integration-libs/opf/base/styles/components/_opf-cta-button.scss new file mode 100644 index 00000000000..e7259918190 --- /dev/null +++ b/integration-libs/opf/base/styles/components/_opf-cta-button.scss @@ -0,0 +1,5 @@ +%cx-opf-cta-button { + display: block; + margin: 0.5rem 0 0.5rem 0; + border-style: solid; +} diff --git a/integration-libs/opf/base/styles/components/_opf-cta-scripts.scss b/integration-libs/opf/base/styles/components/_opf-cta-scripts.scss new file mode 100644 index 00000000000..d6c568e0f83 --- /dev/null +++ b/integration-libs/opf/base/styles/components/_opf-cta-scripts.scss @@ -0,0 +1,7 @@ +%cx-opf-cta-scripts { + text-align: center; + font-size: var(--cx-font-size, 1rem); + color: var(--cx-color-dark); + display: block; + margin: 0.5rem 0 0.5rem 0; +} From 787f93e76410d1bcf2107f5e539f8a84b976c33d Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Sun, 15 Oct 2023 21:03:18 -0400 Subject: [PATCH 04/30] modif terminology - unit tests --- .../opf-cta-scripts.service.spec.ts | 99 +++++++++++++++++++ .../opf-cta-scripts.service.ts | 18 +--- .../core/connectors/opf-payment.adapter.ts | 8 +- .../connectors/opf-payment.connector.spec.ts | 7 ++ .../core/connectors/opf-payment.connector.ts | 4 +- .../core/facade/opf-payment.service.spec.ts | 81 +++++++++++++-- .../base/core/facade/opf-payment.service.ts | 4 +- .../opf/base/occ/adapters/occ-opf.adapter.ts | 4 +- .../base/occ/config/default-occ-opf-config.ts | 2 +- .../base/occ/model/occ-opf-endpoints.model.ts | 6 +- .../base/root/facade/opf-payment.facade.ts | 4 +- .../base/root/model/opf-quick-buy.model.ts | 2 +- 12 files changed, 202 insertions(+), 37 deletions(-) create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts new file mode 100644 index 00000000000..bc2bb8da765 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts @@ -0,0 +1,99 @@ +// import { TestBed } from '@angular/core/testing'; +// import { CmsService } from '@spartacus/core'; +// import { OpfResourceLoaderService } from '@spartacus/opf/base/root'; +// import { OrderFacade, OrderHistoryFacade } from '@spartacus/order/root'; +// import { of } from 'rxjs'; +// import { OpfCtaScriptsService } from './opf-cta-scripts.service'; + +// describe('OpfCtaScriptsService', () => { +// let service: OpfCtaScriptsService; +// let orderFacadeMock: jasmine.SpyObj; +// let orderHistoryFacadeMock: jasmine.SpyObj; +// let opfResourceLoaderServiceMock: jasmine.SpyObj; +// let cmsServiceMock: jasmine.SpyObj; + +// beforeEach(() => { +// orderFacadeMock = jasmine.createSpyObj('OrderFacade', ['getOrderDetails']); +// orderHistoryFacadeMock = jasmine.createSpyObj('OrderHistoryFacade', [ +// 'getOrderDetails', +// ]); +// opfResourceLoaderServiceMock = jasmine.createSpyObj( +// 'OpfResourceLoaderService', +// [ +// 'executeScriptFromHtml', +// 'loadProviderResources', +// 'clearAllProviderResources', +// ] +// ); +// cmsServiceMock = jasmine.createSpyObj('CmsService', ['getCurrentPage']); +// TestBed.configureTestingModule({ +// providers: [ +// OpfCtaScriptsService, +// { provide: OrderFacade, useValue: orderFacadeMock }, +// { provide: OrderHistoryFacade, useValue: orderHistoryFacadeMock }, +// { +// provide: OpfResourceLoaderService, +// useValue: opfResourceLoaderServiceMock, +// }, +// { provide: CmsService, useValue: cmsServiceMock }, +// ], +// }); +// service = TestBed.inject(OpfCtaScriptsService); +// }); + +// it('should be created', () => { +// expect(service).toBeTruthy(); +// }); + +// it('should get ctaScripts', () => { +// orderFacadeMock.getOrderDetails.and.returnValue(of(mockOrder)); +// orderHistoryFacadeMock.getOrderDetails.and.returnValue(of(mockOrder)); + +// opfResourceLoaderServiceMock.executeScriptFromHtml.and.returnValue(); +// opfResourceLoaderServiceMock.loadProviderResources.and.returnValue(Promise); + +// service.getCtaHtmlslList().subscribe((htmlsList) => {}); +// expect(service).toBeTruthy(); +// }); + +// // const mockCtaScriptsResponse = { +// // value: [ +// // { +// // paymentAccountId: 1, +// // dynamicScript: { +// // html: "

CTA Html snippet #1

", +// // cssUrls: [ +// // { +// // url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css', +// // sri: '', +// // }, +// // ], +// // jsUrls: [ +// // { +// // url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js', +// // sri: '', +// // }, +// // ], +// // }, +// // }, +// // ], +// // }; + +// const mockOrder = { +// code: 'mockOrder', +// entries: [ +// { +// product: { +// code: '11', +// }, +// quantity: '1', +// }, +// { +// product: { +// code: '22', +// }, +// quantity: '1', +// }, +// ], +// }; +// }); diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts index 1e374754034..b7301b46d17 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts @@ -1,9 +1,5 @@ import { Injectable, inject } from '@angular/core'; -import { - CmsService, - GlobalMessageService, - GlobalMessageType, -} from '@spartacus/core'; +import { CmsService } from '@spartacus/core'; import { Order, OrderFacade, OrderHistoryFacade } from '@spartacus/order/root'; import { Observable, from, of, throwError } from 'rxjs'; import { @@ -34,7 +30,6 @@ export class OpfCtaScriptsService { protected opfPaymentFacade = inject(OpfPaymentFacade); protected orderDetailsService = inject(OrderFacade); protected orderHistoryService = inject(OrderHistoryFacade); - protected globalMessageService = inject(GlobalMessageService); protected opfResourceLoaderService = inject(OpfResourceLoaderService); protected cmsService = inject(CmsService); @@ -150,7 +145,7 @@ export class OpfCtaScriptsService { } protected fetchCtaScriptsList(ctaScriptsRequest: CtaScriptsRequest) { - return this.opfPaymentFacade.ctaScripts(ctaScriptsRequest).pipe( + return this.opfPaymentFacade.getCtaScripts(ctaScriptsRequest).pipe( map((ctaScriptsResponse: CtaScriptsResponse) => { console.log('ctaScriptsResponse', ctaScriptsResponse); // mock for test purpose until PSP ready @@ -273,14 +268,7 @@ export class OpfCtaScriptsService { : []; } - protected displayError(errorKey: string): void { - this.globalMessageService.add( - { key: `opf.checkout.errors.${errorKey}` }, - GlobalMessageType.MSG_TYPE_ERROR - ); - } - - loadAndRunScript( + protected loadAndRunScript( script: OpfDynamicScript ): Promise { const html = script?.html; diff --git a/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts b/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts index 487c9e32338..7f4df58539f 100644 --- a/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts +++ b/integration-libs/opf/base/core/connectors/opf-payment.adapter.ts @@ -48,6 +48,9 @@ export abstract class OpfPaymentAdapter { paymentSessionId: string ): Observable; + /** + * Abstract method used to get AfterRedirect scripts used in hosted-fields pattern + */ abstract afterRedirectScripts( paymentSessionId: string ): Observable; @@ -57,7 +60,10 @@ export abstract class OpfPaymentAdapter { */ abstract getActiveConfigurations(): Observable; - abstract ctaScripts( + /** + * Abstract method used to get CTA scripts list, used by QuickBuy functionality + */ + abstract getCtaScripts( ctaScriptsRequest: CtaScriptsRequest ): Observable; } diff --git a/integration-libs/opf/base/core/connectors/opf-payment.connector.spec.ts b/integration-libs/opf/base/core/connectors/opf-payment.connector.spec.ts index a0f5f9568df..ee9123aa0a8 100644 --- a/integration-libs/opf/base/core/connectors/opf-payment.connector.spec.ts +++ b/integration-libs/opf/base/core/connectors/opf-payment.connector.spec.ts @@ -11,6 +11,7 @@ class MockOpfPaymentAdapter implements OpfPaymentAdapter { submitCompletePayment = createSpy().and.returnValue(of({})); afterRedirectScripts = createSpy().and.returnValue(of({})); getActiveConfigurations = createSpy().and.returnValue(of({})); + getCtaScripts = createSpy().and.returnValue(of({})); } describe('OpfPaymentConnector', () => { @@ -67,4 +68,10 @@ describe('OpfPaymentConnector', () => { done(); }); }); + it('getCtaScripts should call adapter', (done) => { + service.getCtaScripts({}).subscribe(() => { + expect(adapter.getCtaScripts).toHaveBeenCalled(); + done(); + }); + }); }); diff --git a/integration-libs/opf/base/core/connectors/opf-payment.connector.ts b/integration-libs/opf/base/core/connectors/opf-payment.connector.ts index 510b7cb095d..cb84e3c077e 100644 --- a/integration-libs/opf/base/core/connectors/opf-payment.connector.ts +++ b/integration-libs/opf/base/core/connectors/opf-payment.connector.ts @@ -62,9 +62,9 @@ export class OpfPaymentConnector { return this.adapter.getActiveConfigurations(); } - public ctaScripts( + public getCtaScripts( ctaScriptsRequest: CtaScriptsRequest ): Observable { - return this.adapter.ctaScripts(ctaScriptsRequest); + return this.adapter.getCtaScripts(ctaScriptsRequest); } } diff --git a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts index 8a07d8c3665..f6c2eee2b0f 100644 --- a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts +++ b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts @@ -10,6 +10,9 @@ import { Observable, of } from 'rxjs'; import { ActiveConfiguration, AfterRedirectScriptResponse, + CtaScriptsLocation, + CtaScriptsRequest, + CtaScriptsResponse, OpfPaymentProviderType, OpfPaymentVerificationPayload, OpfPaymentVerificationResponse, @@ -39,6 +42,12 @@ class MockPaymentConnector implements Partial { getActiveConfigurations(): Observable { return of(mockActiveConfigurations); } + getCtaScripts( + ctaScriptsRequest: CtaScriptsRequest + ): Observable { + console.log(ctaScriptsRequest); + return of(MockCtaScriptsResponse); + } } class MockOpfPaymentHostedFieldsService { @@ -68,6 +77,48 @@ const mockActiveConfigurations: ActiveConfiguration[] = [ }, ]; +const MockCtaRequest: CtaScriptsRequest = { + orderId: '00259012', + ctaProductItems: [ + { + productId: '301233', + quantity: 1, + }, + { + productId: '2231913', + quantity: 1, + }, + { + productId: '1776948', + quantity: 1, + }, + ], + scriptLocations: [CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE], +}; + +const MockCtaScriptsResponse: CtaScriptsResponse = { + value: [ + { + paymentAccountId: '1', + dynamicScript: { + html: "

CTA Html snippet #1

", + cssUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css', + sri: '', + }, + ], + jsUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js', + sri: '', + }, + ], + }, + }, + ], +}; + describe('OpfPaymentService', () => { let service: OpfPaymentService; let paymentConnector: MockPaymentConnector; @@ -254,16 +305,28 @@ describe('OpfPaymentService', () => { expect(connectorSpy).toHaveBeenCalledWith(paymentSessionId); }); - describe(`getActiveConfigurationsState`, () => { - it(`should return mockActiveConfigurations data`, (done) => { - service.getActiveConfigurationsState().subscribe((state) => { - expect(state).toEqual({ - loading: false, - error: false, - data: mockActiveConfigurations, - }); - done(); + // describe(`getActiveConfigurationsState`, () => { + it(`should return mockActiveConfigurations data`, (done) => { + service.getActiveConfigurationsState().subscribe((state) => { + expect(state).toEqual({ + loading: false, + error: false, + data: mockActiveConfigurations, }); + done(); + }); + }); + + it(`should return ctaScripts data`, (done) => { + const connectorCtaSpy = spyOn( + paymentConnector, + 'getCtaScripts' + ).and.callThrough(); + + service.getCtaScripts(MockCtaRequest).subscribe(() => { + expect(connectorCtaSpy).toHaveBeenCalledWith(MockCtaRequest); + done(); }); }); + // }); }); diff --git a/integration-libs/opf/base/core/facade/opf-payment.service.ts b/integration-libs/opf/base/core/facade/opf-payment.service.ts index cc0e380256f..cd7ddc5dafd 100644 --- a/integration-libs/opf/base/core/facade/opf-payment.service.ts +++ b/integration-libs/opf/base/core/facade/opf-payment.service.ts @@ -81,7 +81,7 @@ export class OpfPaymentService implements OpfPaymentFacade { }, CtaScriptsResponse > = this.commandService.create((payload) => { - return this.opfPaymentConnector.ctaScripts(payload.ctaScriptsRequest); + return this.opfPaymentConnector.getCtaScripts(payload.ctaScriptsRequest); }); protected activeConfigurationsQuery: Query = @@ -128,7 +128,7 @@ export class OpfPaymentService implements OpfPaymentFacade { return this.activeConfigurationsQuery.getState(); } - ctaScripts(ctaScriptsRequest: CtaScriptsRequest) { + getCtaScripts(ctaScriptsRequest: CtaScriptsRequest) { return this.ctaScriptsCommand.execute({ ctaScriptsRequest }); } } diff --git a/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts b/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts index 05c38fca7b5..bf57723b869 100644 --- a/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts +++ b/integration-libs/opf/base/occ/adapters/occ-opf.adapter.ts @@ -186,7 +186,7 @@ export class OccOpfPaymentAdapter implements OpfPaymentAdapter { ); } - ctaScripts( + getCtaScripts( ctaScriptsRequest: CtaScriptsRequest ): Observable { const headers = new HttpHeaders(this.header).set( @@ -240,6 +240,6 @@ export class OccOpfPaymentAdapter implements OpfPaymentAdapter { } protected getCtaScriptsEndpoint(): string { - return this.opfEndpointsService.buildUrl('ctaScripts'); + return this.opfEndpointsService.buildUrl('getCtaScripts'); } } diff --git a/integration-libs/opf/base/occ/config/default-occ-opf-config.ts b/integration-libs/opf/base/occ/config/default-occ-opf-config.ts index 5486f6d5650..7f53b1bc3e3 100644 --- a/integration-libs/opf/base/occ/config/default-occ-opf-config.ts +++ b/integration-libs/opf/base/occ/config/default-occ-opf-config.ts @@ -16,7 +16,7 @@ export const defaultOccOpfConfig: OccConfig = { afterRedirectScripts: 'payments/${paymentSessionId}/after-redirect-scripts', getActiveConfigurations: 'active-configurations', - ctaScripts: 'payments/cta-scripts-rendering', + getCtaScripts: 'payments/cta-scripts-rendering', }, }, }, diff --git a/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts b/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts index d2509bded27..b38a21bff55 100644 --- a/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts +++ b/integration-libs/opf/base/occ/model/occ-opf-endpoints.model.ts @@ -29,7 +29,9 @@ declare module '@spartacus/core' { * Endpoint to get active payment configurations */ getActiveConfigurations?: string | OccEndpoint; - - ctaScripts?: string | OccEndpoint; + /** + * Endpoint to get CTA (Call To Action) Scripts used in QuickBuy functionality + */ + getCtaScripts?: string | OccEndpoint; } } diff --git a/integration-libs/opf/base/root/facade/opf-payment.facade.ts b/integration-libs/opf/base/root/facade/opf-payment.facade.ts index e0a8fd6e0e3..8049d7cecff 100644 --- a/integration-libs/opf/base/root/facade/opf-payment.facade.ts +++ b/integration-libs/opf/base/root/facade/opf-payment.facade.ts @@ -31,7 +31,7 @@ import { 'submitCompletePayment', 'afterRedirectScripts', 'getActiveConfigurationsState', - 'ctaScripts', + 'getCtaScripts', ], }), }) @@ -79,7 +79,7 @@ export abstract class OpfPaymentFacade { QueryState >; - abstract ctaScripts( + abstract getCtaScripts( ctaScriptsRequest: CtaScriptsRequest ): Observable; } diff --git a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts index 84c4dca865c..b03c6af6515 100644 --- a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts +++ b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts @@ -64,6 +64,6 @@ export interface CtaScriptsResponse { } export interface CtaScript { - accountId: string; + paymentAccountId: string; dynamicScript: OpfDynamicScript; } From 99e4f457d15d7a9c19f0dfd447c0b323d211e25a Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Mon, 16 Oct 2023 15:20:19 -0400 Subject: [PATCH 05/30] add cta script for pdp - remove logs - error handling --- .../opf-cta-button.component.html | 2 +- .../opf-cta-button.component.ts | 75 +------- .../opf-cta-scripts.component.html | 18 +- .../opf-cta-scripts.component.ts | 16 +- .../opf-cta-scripts.service.ts | 175 +++++++----------- .../base/components/opf-cta-scripts/test.json | 22 +++ .../styles/components/_opf-cta-button.scss | 1 - 7 files changed, 115 insertions(+), 194 deletions(-) create mode 100644 integration-libs/opf/base/components/opf-cta-scripts/test.json diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html index d7455c0527d..190f83d79c2 100644 --- a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html +++ b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html @@ -1 +1 @@ -
+
diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts index 05215dc1741..f17b29f0ef3 100644 --- a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts +++ b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts @@ -1,85 +1,16 @@ -import { AfterViewInit, Component, Input, OnInit, inject } from '@angular/core'; +import { Component, Input, inject } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import { OpfResourceLoaderService } from '@spartacus/opf/base/root'; -import { OpfDynamicScript } from '../../root/model'; @Component({ selector: 'cx-opf-cta-button', templateUrl: './opf-cta-button.component.html', }) -export class OpfCtaButtonComponent implements OnInit, AfterViewInit { - protected opfResourceLoaderService = inject(OpfResourceLoaderService); +export class OpfCtaButtonComponent { protected sanitizer = inject(DomSanitizer); - // _ctaScript$: Observable; - // ready$ = new BehaviorSubject(false); @Input() ctaScriptHtml: string; - htmlSnippet?: string; - - constructor() { - console.log('constructor'); - // this.renderCtaScripts(this.ctaScript).then(() => { - // this.ready = true; - // }); - } - ngOnDestroy(): void { - console.log('ngOnDestroy Button', this.ctaScriptHtml); - } - - ngAfterViewInit(): void { - console.log('after view init'); - } - - ngOnInit(): void { - console.log('ngOnInit'); - // this.renderCtaScripts(this.ctaScript) - // .then(() => { - // this.ready$.next(true); - // }) - // .catch((error) => console.log('error render', error)); - // this.renderCtaScripts(this.ctaScript).then((htmlSnippet) => { - // this.htmlSnippet = htmlSnippet; - // }); - // this._ctaScript$ = this.ctaScript$.pipe( - // tap((script) => { - // console.log(); - // return this.renderCtaScripts(script); - // }), - // concatMap((script) => of(script.html)) - // ); - } - renderHtml(html?: string): SafeHtml { - // const trustedHtml = this.sanitizer.bypassSecurityTrustHtml(html as string); - // console.log('trustedHtml', trustedHtml); - console.log('renderHtml', html); - - return html as string; - } - - renderCtaScripts(script: OpfDynamicScript): Promise { - const html = script?.html; - console.log('renderCtaScripts:', html); - return new Promise((resolve: (value: string | undefined) => void) => { - this.opfResourceLoaderService - .loadProviderResources(script.jsUrls, script.cssUrls) - .then(() => { - if (html) { - setTimeout(() => { - console.log('flo ok:', html); - this.opfResourceLoaderService.executeScriptFromHtml(html); - resolve(html); - }); - } else { - console.log('flo ko:', html); - resolve(undefined); - } - }) - .catch(() => { - console.log('flo error:'); - resolve(undefined); - }); - }); + return this.sanitizer.bypassSecurityTrustHtml(html as string); } } diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html index ff62d118549..0a9c1a50d34 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html @@ -1,17 +1,21 @@ - - - + + + + + -
+ +
Error:
+
diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts index 5eaf6784bd3..14053588efe 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts @@ -1,5 +1,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { OpfResourceLoaderService } from '@spartacus/opf/base/root'; +import { OpfPaymentErrorHandlerService } from '@spartacus/opf/base/core'; +import { BehaviorSubject, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { OpfCtaScriptsService } from './opf-cta-scripts.service'; @Component({ @@ -9,7 +11,15 @@ import { OpfCtaScriptsService } from './opf-cta-scripts.service'; }) export class OpfCtaScriptsComponent { protected opfCtaScriptService = inject(OpfCtaScriptsService); - protected opfResourceLoaderService = inject(OpfResourceLoaderService); + protected opfPaymentErrorHandlerService = inject( + OpfPaymentErrorHandlerService + ); + isError$ = new BehaviorSubject(false); - ctaHtmlList$ = this.opfCtaScriptService.getCtaHtmlslList(); + ctaHtmlList$ = this.opfCtaScriptService.getCtaHtmlslList().pipe( + catchError((error) => { + this.isError$.next(true); + return throwError(error); + }) + ); } diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts index b7301b46d17..ffd5ab1ea7b 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts @@ -1,5 +1,5 @@ import { Injectable, inject } from '@angular/core'; -import { CmsService } from '@spartacus/core'; +import { CmsService, Product, isNotNullable } from '@spartacus/core'; import { Order, OrderFacade, OrderHistoryFacade } from '@spartacus/order/root'; import { Observable, from, of, throwError } from 'rxjs'; import { @@ -22,6 +22,7 @@ import { OpfPaymentFacade, OpfResourceLoaderService, } from '@spartacus/opf/base/root'; +import { CurrentProductService } from '@spartacus/storefront'; @Injectable({ providedIn: 'root', @@ -32,101 +33,7 @@ export class OpfCtaScriptsService { protected orderHistoryService = inject(OrderHistoryFacade); protected opfResourceLoaderService = inject(OpfResourceLoaderService); protected cmsService = inject(CmsService); - - protected mock = { - value: [ - { - paymentAccountId: 1, - dynamicScript: { - html: "

CTA Html snippet #1

", - cssUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css', - sri: '', - }, - ], - jsUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js', - sri: '', - }, - ], - }, - }, - { - paymentAccountId: 2, - dynamicScript: { - html: "

CTA Html snippet #2

", - cssUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.2/adyen.css', - sri: '', - }, - ], - jsUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.2/adyen.js', - sri: '', - }, - ], - }, - }, - { - paymentAccountId: 3, - dynamicScript: { - html: "

CTA Html snippet #3

", - cssUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.3/adyen.css', - sri: '', - }, - ], - jsUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.3/adyen.js', - sri: '', - }, - ], - }, - }, - { - paymentAccountId: 4, - dynamicScript: { - html: "

CTA Html snippet #4

", - cssUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.x/adyen.css', - sri: '', - }, - ], - jsUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.0/adyen.js', - sri: '', - }, - ], - }, - }, - { - paymentAccountId: 5, - dynamicScript: { - html: "

CTA Html snippet #5

", - cssUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.1.x/adyen.css', - sri: '', - }, - ], - jsUrls: [ - { - url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.1.0/adyen.js', - sri: '', - }, - ], - }, - }, - ], - }; + protected currentProductService = inject(CurrentProductService); getCtaHtmlslList(): Observable { return this.fillCtaScriptRequest().pipe( @@ -144,15 +51,17 @@ export class OpfCtaScriptsService { this.opfResourceLoaderService.clearAllProviderResources(); } - protected fetchCtaScriptsList(ctaScriptsRequest: CtaScriptsRequest) { + protected fetchCtaScriptsList( + ctaScriptsRequest: CtaScriptsRequest + ): Observable { return this.opfPaymentFacade.getCtaScripts(ctaScriptsRequest).pipe( - map((ctaScriptsResponse: CtaScriptsResponse) => { - console.log('ctaScriptsResponse', ctaScriptsResponse); - // mock for test purpose until PSP ready - const list = this.mock.value.map( - (ctaScript) => ctaScript.dynamicScript + concatMap((ctaScriptsResponse: CtaScriptsResponse) => { + if (!ctaScriptsResponse?.value?.length) { + return throwError({ error: 'Unvalid CTA Scripts Response' }); + } + return of( + ctaScriptsResponse.value.map((ctaScript) => ctaScript.dynamicScript) ); - return list; }), take(1) ); @@ -160,16 +69,45 @@ export class OpfCtaScriptsService { protected fillCtaScriptRequest() { let paymentAccountIds: number[]; - let scriptLocation: CtaScriptsLocation; + return this.getPaymentAccountIds().pipe( concatMap((paymentAccountIds) => { paymentAccountIds = paymentAccountIds; return this.getScriptLocation(); }), - concatMap((scriptsLocation) => { - scriptLocation = scriptsLocation; - return this.getOrderDetails(scriptsLocation); - }), + concatMap((scriptsLocation: CtaScriptsLocation) => { + return this.fillRequestForTargetPage( + scriptsLocation, + paymentAccountIds + ); + }) + ); + } + + protected fillRequestForTargetPage( + scriptsLocation: CtaScriptsLocation, + paymentAccountIds: number[] + ): Observable { + if (scriptsLocation == CtaScriptsLocation.PDP_QUICK_BUY) { + return this.fillCtaRequestforPDP(scriptsLocation, paymentAccountIds); + } else if ( + scriptsLocation == CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE || + scriptsLocation == CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE + ) { + return this.fillCtaRequestforPagesWithOrder( + scriptsLocation, + paymentAccountIds + ); + } else { + return throwError('Invalid Script Location'); + } + } + + protected fillCtaRequestforPagesWithOrder( + scriptLocation: CtaScriptsLocation, + paymentAccountIds: number[] + ): Observable { + return this.getOrderDetails(scriptLocation).pipe( map((order) => { const ctaScriptsRequest: CtaScriptsRequest = { orderId: order?.code, @@ -183,6 +121,23 @@ export class OpfCtaScriptsService { ); } + protected fillCtaRequestforPDP( + scriptLocation: CtaScriptsLocation, + paymentAccountIds: number[] + ) { + return this.currentProductService.getProduct().pipe( + filter(isNotNullable), + map((product: Product) => { + return { + orderId: undefined, + ctaProductItems: [{ productId: product?.code, quantity: 1 }], + paymentAccountIds: paymentAccountIds, + scriptLocations: [scriptLocation], + } as CtaScriptsRequest; + }) + ); + } + protected runCtaScriptsList(scripts: OpfDynamicScript[]) { let loadedCtaHtmls: string[]; return of(scripts).pipe( @@ -194,14 +149,12 @@ export class OpfCtaScriptsService { }), concatMap((script) => from(this.loadAndRunScript(script))), tap((script) => { - console.log('in tap', script); if (script?.html) { loadedCtaHtmls.push(script.html); } }), last(), map(() => { - console.log('in last'); return loadedCtaHtmls; }) ); @@ -216,6 +169,8 @@ export class OpfCtaScriptsService { return of(CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE); case CmsPageLocation.ORDER_CONFIRMATION_PAGE: return of(CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE); + case CmsPageLocation.PDP_PAGE: + return of(CtaScriptsLocation.PDP_QUICK_BUY); default: return throwError({ error: 'Page not valid' }); } diff --git a/integration-libs/opf/base/components/opf-cta-scripts/test.json b/integration-libs/opf/base/components/opf-cta-scripts/test.json new file mode 100644 index 00000000000..6d526d99ede --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-scripts/test.json @@ -0,0 +1,22 @@ +{ + "value": [ + { + "paymentAccountId": 1, + "dynamicScript": { + "html": "

Please use promo code 123abc

This promo code is still valid for 19 days

", + "cssUrls": [ + { + "url": "https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css", + "sri": "" + } + ], + "jsUrls": [ + { + "url": "https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js", + "sri": "" + } + ] + } + } + ] +} diff --git a/integration-libs/opf/base/styles/components/_opf-cta-button.scss b/integration-libs/opf/base/styles/components/_opf-cta-button.scss index e7259918190..fce13582d77 100644 --- a/integration-libs/opf/base/styles/components/_opf-cta-button.scss +++ b/integration-libs/opf/base/styles/components/_opf-cta-button.scss @@ -1,5 +1,4 @@ %cx-opf-cta-button { display: block; margin: 0.5rem 0 0.5rem 0; - border-style: solid; } From ad897cd7e816091fa76d44aed92cbf026320e11f Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Mon, 16 Oct 2023 18:04:00 -0400 Subject: [PATCH 06/30] add unit tests --- .../opf-cta-scripts.service.spec.ts | 333 ++++++++++++------ .../opf-cta-scripts.service.ts | 48 +-- .../core/facade/opf-payment.service.spec.ts | 2 +- .../base/root/model/opf-quick-buy.model.ts | 2 +- 4 files changed, 255 insertions(+), 130 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts index bc2bb8da765..3c3cd0e685a 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts @@ -1,99 +1,234 @@ -// import { TestBed } from '@angular/core/testing'; -// import { CmsService } from '@spartacus/core'; -// import { OpfResourceLoaderService } from '@spartacus/opf/base/root'; -// import { OrderFacade, OrderHistoryFacade } from '@spartacus/order/root'; -// import { of } from 'rxjs'; -// import { OpfCtaScriptsService } from './opf-cta-scripts.service'; - -// describe('OpfCtaScriptsService', () => { -// let service: OpfCtaScriptsService; -// let orderFacadeMock: jasmine.SpyObj; -// let orderHistoryFacadeMock: jasmine.SpyObj; -// let opfResourceLoaderServiceMock: jasmine.SpyObj; -// let cmsServiceMock: jasmine.SpyObj; - -// beforeEach(() => { -// orderFacadeMock = jasmine.createSpyObj('OrderFacade', ['getOrderDetails']); -// orderHistoryFacadeMock = jasmine.createSpyObj('OrderHistoryFacade', [ -// 'getOrderDetails', -// ]); -// opfResourceLoaderServiceMock = jasmine.createSpyObj( -// 'OpfResourceLoaderService', -// [ -// 'executeScriptFromHtml', -// 'loadProviderResources', -// 'clearAllProviderResources', -// ] -// ); -// cmsServiceMock = jasmine.createSpyObj('CmsService', ['getCurrentPage']); -// TestBed.configureTestingModule({ -// providers: [ -// OpfCtaScriptsService, -// { provide: OrderFacade, useValue: orderFacadeMock }, -// { provide: OrderHistoryFacade, useValue: orderHistoryFacadeMock }, -// { -// provide: OpfResourceLoaderService, -// useValue: opfResourceLoaderServiceMock, -// }, -// { provide: CmsService, useValue: cmsServiceMock }, -// ], -// }); -// service = TestBed.inject(OpfCtaScriptsService); -// }); - -// it('should be created', () => { -// expect(service).toBeTruthy(); -// }); - -// it('should get ctaScripts', () => { -// orderFacadeMock.getOrderDetails.and.returnValue(of(mockOrder)); -// orderHistoryFacadeMock.getOrderDetails.and.returnValue(of(mockOrder)); - -// opfResourceLoaderServiceMock.executeScriptFromHtml.and.returnValue(); -// opfResourceLoaderServiceMock.loadProviderResources.and.returnValue(Promise); - -// service.getCtaHtmlslList().subscribe((htmlsList) => {}); -// expect(service).toBeTruthy(); -// }); - -// // const mockCtaScriptsResponse = { -// // value: [ -// // { -// // paymentAccountId: 1, -// // dynamicScript: { -// // html: "

CTA Html snippet #1

", -// // cssUrls: [ -// // { -// // url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css', -// // sri: '', -// // }, -// // ], -// // jsUrls: [ -// // { -// // url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js', -// // sri: '', -// // }, -// // ], -// // }, -// // }, -// // ], -// // }; - -// const mockOrder = { -// code: 'mockOrder', -// entries: [ -// { -// product: { -// code: '11', -// }, -// quantity: '1', -// }, -// { -// product: { -// code: '22', -// }, -// quantity: '1', -// }, -// ], -// }; -// }); +import { TestBed } from '@angular/core/testing'; +import { CmsService, Page, Product, QueryState } from '@spartacus/core'; +import { + ActiveConfiguration, + CtaScriptsResponse, + OpfPaymentFacade, + OpfPaymentProviderType, + OpfResourceLoaderService, +} from '@spartacus/opf/base/root'; +import { Order, OrderFacade, OrderHistoryFacade } from '@spartacus/order/root'; +import { CurrentProductService } from '@spartacus/storefront'; +import { of } from 'rxjs'; +import { OpfCtaScriptsService } from './opf-cta-scripts.service'; + +describe('OpfCtaScriptsService', () => { + let service: OpfCtaScriptsService; + let orderFacadeMock: jasmine.SpyObj; + let orderHistoryFacadeMock: jasmine.SpyObj; + let opfResourceLoaderServiceMock: jasmine.SpyObj; + let cmsServiceMock: jasmine.SpyObj; + let currentProductMock: jasmine.SpyObj; + let opfPaymentFacadeMock: jasmine.SpyObj; + beforeEach(() => { + orderFacadeMock = jasmine.createSpyObj('OrderFacade', ['getOrderDetails']); + orderHistoryFacadeMock = jasmine.createSpyObj('OrderHistoryFacade', [ + 'getOrderDetails', + ]); + opfResourceLoaderServiceMock = jasmine.createSpyObj( + 'OpfResourceLoaderService', + [ + 'executeScriptFromHtml', + 'loadProviderResources', + 'clearAllProviderResources', + ] + ); + cmsServiceMock = jasmine.createSpyObj('CmsService', ['getCurrentPage']); + currentProductMock = jasmine.createSpyObj('CurrentProductService', [ + 'getProduct', + ]); + opfPaymentFacadeMock = jasmine.createSpyObj('OpfPaymentFacade', [ + 'getCtaScripts', + 'getActiveConfigurationsState', + ]); + + TestBed.configureTestingModule({ + providers: [ + OpfCtaScriptsService, + { provide: OrderFacade, useValue: orderFacadeMock }, + { provide: OrderHistoryFacade, useValue: orderHistoryFacadeMock }, + { + provide: OpfResourceLoaderService, + useValue: opfResourceLoaderServiceMock, + }, + { provide: CmsService, useValue: cmsServiceMock }, + { provide: CurrentProductService, useValue: currentProductMock }, + { provide: OpfPaymentFacade, useValue: opfPaymentFacadeMock }, + ], + }); + service = TestBed.inject(OpfCtaScriptsService); + + orderFacadeMock.getOrderDetails.and.returnValue(of(mockOrder)); + orderHistoryFacadeMock.getOrderDetails.and.returnValue(of(mockOrder)); + + opfResourceLoaderServiceMock.executeScriptFromHtml.and.returnValue(); + opfResourceLoaderServiceMock.loadProviderResources.and.returnValue( + Promise.resolve() + ); + currentProductMock.getProduct.and.returnValue(of(mockProduct)); + cmsServiceMock.getCurrentPage.and.returnValue(of(mockPage)); + opfPaymentFacadeMock.getActiveConfigurationsState.and.returnValue( + of(activeConfigurationsMock) + ); + opfPaymentFacadeMock.getCtaScripts.and.returnValue( + of(ctaScriptsresponseMock) + ); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should call orderHistoryFacade for CTA on ConfirmationPage', (done) => { + service.getCtaHtmlslList().subscribe((htmlsList) => { + console.log('htmlsList', htmlsList); + expect(htmlsList[0]).toContain( + 'Thanks for purchasing our great products' + ); + expect(orderHistoryFacadeMock.getOrderDetails).not.toHaveBeenCalled(); + expect(orderFacadeMock.getOrderDetails).toHaveBeenCalled(); + done(); + }); + }); + + it('should call OrderFacade for CTA on PDP', (done) => { + cmsServiceMock.getCurrentPage.and.returnValue( + of({ ...mockPage, pageId: 'order' }) + ); + + service.getCtaHtmlslList().subscribe((htmlsList) => { + console.log('htmlsList', htmlsList); + expect(htmlsList[0]).toContain( + 'Thanks for purchasing our great products' + ); + expect(orderHistoryFacadeMock.getOrderDetails).toHaveBeenCalled(); + expect(orderFacadeMock.getOrderDetails).not.toHaveBeenCalled(); + done(); + }); + }); + + it('should call currentProductService for CTA on PDP', (done) => { + cmsServiceMock.getCurrentPage.and.returnValue( + of({ ...mockPage, pageId: 'productDetails' }) + ); + + service.getCtaHtmlslList().subscribe((htmlsList) => { + console.log('htmlsList', htmlsList); + expect(htmlsList[0]).toContain( + 'Thanks for purchasing our great products' + ); + expect(currentProductMock.getProduct).toHaveBeenCalled(); + expect(orderFacadeMock.getOrderDetails).not.toHaveBeenCalled(); + done(); + }); + }); + + it('should throw an error when empty CTA scripts response from OPF server', (done) => { + opfPaymentFacadeMock.getCtaScripts.and.returnValue(of({ value: [] })); + + service.getCtaHtmlslList().subscribe({ + error: (error) => { + expect(error).toEqual('Invalid CTA Scripts Response'); + + done(); + }, + }); + }); + + it('should throw an error when empty ScriptLocation is invalid', (done) => { + cmsServiceMock.getCurrentPage.and.returnValue( + of({ ...mockPage, pageId: 'testPage' }) + ); + + service.getCtaHtmlslList().subscribe({ + error: (error) => { + expect(error).toEqual('Invalid Script Location'); + + done(); + }, + }); + }); + + // it('should throw an error when order is empty', (done) => { + // orderFacadeMock.getOrderDetails.and.returnValue( + // of({ ...mockOrder, entries: [] }) + // ); + + // service.getCtaHtmlslList().subscribe({ + // error: (error) => { + // expect(error).toEqual('Invalid Script Location'); + + // done(); + // }, + // }); + // }); + + const ctaScriptsresponseMock: CtaScriptsResponse = { + value: [ + { + paymentAccountId: 1, + dynamicScript: { + html: '

Thanks for purchasing our great products

Please use promo code:123abc for your next purchase

', + cssUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css', + sri: '', + }, + ], + jsUrls: [ + { + url: 'https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js', + sri: '', + }, + ], + }, + }, + ], + }; + + const activeConfigurationsMock: QueryState< + ActiveConfiguration[] | undefined + > = { + loading: false, + error: false, + data: [ + { + id: 14, + providerType: OpfPaymentProviderType.PAYMENT_METHOD, + merchantId: 'SAP OPF', + displayName: 'Crypto with BitPay', + }, + ], + }; + + const mockPage: Page = { + pageId: 'orderConfirmationPage', + }; + + const mockProduct: Product = { + name: 'mockProduct', + code: 'code1', + stock: { + stockLevel: 333, + stockLevelStatus: 'inStock', + }, + }; + + const mockOrder: Order = { + code: 'mockOrder', + entries: [ + { + product: { + code: '11', + }, + quantity: 1, + }, + { + product: { + code: '22', + }, + quantity: 1, + }, + ], + }; +}); diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts index ffd5ab1ea7b..a6507637c99 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts @@ -13,6 +13,7 @@ import { tap, } from 'rxjs/operators'; +import { OrderEntry } from '@spartacus/cart/base/root'; import { CmsPageLocation, CtaScriptsLocation, @@ -57,7 +58,7 @@ export class OpfCtaScriptsService { return this.opfPaymentFacade.getCtaScripts(ctaScriptsRequest).pipe( concatMap((ctaScriptsResponse: CtaScriptsResponse) => { if (!ctaScriptsResponse?.value?.length) { - return throwError({ error: 'Unvalid CTA Scripts Response' }); + return throwError('Invalid CTA Scripts Response'); } return of( ctaScriptsResponse.value.map((ctaScript) => ctaScript.dynamicScript) @@ -75,7 +76,7 @@ export class OpfCtaScriptsService { paymentAccountIds = paymentAccountIds; return this.getScriptLocation(); }), - concatMap((scriptsLocation: CtaScriptsLocation) => { + concatMap((scriptsLocation: CtaScriptsLocation | null) => { return this.fillRequestForTargetPage( scriptsLocation, paymentAccountIds @@ -85,7 +86,7 @@ export class OpfCtaScriptsService { } protected fillRequestForTargetPage( - scriptsLocation: CtaScriptsLocation, + scriptsLocation: CtaScriptsLocation | null, paymentAccountIds: number[] ): Observable { if (scriptsLocation == CtaScriptsLocation.PDP_QUICK_BUY) { @@ -111,7 +112,7 @@ export class OpfCtaScriptsService { map((order) => { const ctaScriptsRequest: CtaScriptsRequest = { orderId: order?.code, - ctaProductItems: this.getProductItems(order), + ctaProductItems: this.getProductItems(order as Order), paymentAccountIds: paymentAccountIds, scriptLocations: [scriptLocation], }; @@ -160,7 +161,7 @@ export class OpfCtaScriptsService { ); } - protected getScriptLocation(): Observable { + protected getScriptLocation(): Observable { return this.cmsService.getCurrentPage().pipe( take(1), concatMap((page) => { @@ -172,7 +173,7 @@ export class OpfCtaScriptsService { case CmsPageLocation.PDP_PAGE: return of(CtaScriptsLocation.PDP_QUICK_BUY); default: - return throwError({ error: 'Page not valid' }); + return of(null); } }) ); @@ -184,17 +185,8 @@ export class OpfCtaScriptsService { ? this.orderDetailsService.getOrderDetails() : this.orderHistoryService.getOrderDetails(); return order$.pipe( - filter((order) => !!order?.entries), - concatMap((order) => { - if (!order) { - return throwError({ error: 'Order obj not found' }); - } - if (!order?.entries) { - return throwError({ error: 'Order entries not found' }); - } - return of(order); - }) - ); + filter((order) => !!order?.entries) + ) as Observable; } protected getPaymentAccountIds() { @@ -209,18 +201,16 @@ export class OpfCtaScriptsService { protected getProductItems( order: Order ): { productId: string; quantity: number }[] | [] { - return !!order.entries - ? order.entries - ?.filter((item) => { - return !!item?.product?.code && !!item?.quantity; - }) - .map((item) => { - return { - productId: item.product?.code as string, - quantity: item.quantity as number, - }; - }) - : []; + return (order.entries as OrderEntry[]) + .filter((item) => { + return !!item?.product?.code && !!item?.quantity; + }) + .map((item) => { + return { + productId: item.product?.code as string, + quantity: item.quantity as number, + }; + }); } protected loadAndRunScript( diff --git a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts index f6c2eee2b0f..868f1027686 100644 --- a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts +++ b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts @@ -99,7 +99,7 @@ const MockCtaRequest: CtaScriptsRequest = { const MockCtaScriptsResponse: CtaScriptsResponse = { value: [ { - paymentAccountId: '1', + paymentAccountId: 1, dynamicScript: { html: "

CTA Html snippet #1

", cssUrls: [ diff --git a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts index b03c6af6515..670080c27d3 100644 --- a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts +++ b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts @@ -64,6 +64,6 @@ export interface CtaScriptsResponse { } export interface CtaScript { - paymentAccountId: string; + paymentAccountId: number; dynamicScript: OpfDynamicScript; } From 0d6d50926221e257e0f26e5f9dd81bd2b0535198 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Thu, 19 Oct 2023 09:27:32 -0400 Subject: [PATCH 07/30] add unit tests - improve rxjs piping --- .../opf-cta-button.component.spec.ts | 30 ++++++++ .../opf-cta-scripts.component.spec.ts | 64 ++++++++++++++++ .../opf-cta-scripts.component.ts | 5 +- .../opf-cta-scripts.service.spec.ts | 74 +++++++++++++++---- .../opf-cta-scripts.service.ts | 35 +++++---- .../styles/components/_opf-cta-scripts.scss | 3 - 6 files changed, 175 insertions(+), 36 deletions(-) create mode 100644 integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.spec.ts diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.spec.ts b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.spec.ts new file mode 100644 index 00000000000..dec88929f54 --- /dev/null +++ b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DomSanitizer } from '@angular/platform-browser'; +import { OpfCtaButtonComponent } from './opf-cta-button.component'; + +describe('OpfCtaButton', () => { + let component: OpfCtaButtonComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [OpfCtaButtonComponent], + }); + fixture = TestBed.createComponent(OpfCtaButtonComponent); + component = fixture.componentInstance; + // opfCtaScriptsService = TestBed.inject(OpfCtaScriptsService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should bypass sanitizer', () => { + const htmlInput = + "

Thanks for purchasing our great products

Please use promo code:123abc for your next purchase

"; + const domSanitizer: DomSanitizer = TestBed.inject(DomSanitizer); + spyOn(domSanitizer, 'bypassSecurityTrustHtml').and.stub(); + component.renderHtml(htmlInput); + expect(domSanitizer.bypassSecurityTrustHtml).toHaveBeenCalled(); + }); +}); diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts index e69de29bb2d..2f30e330822 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts @@ -0,0 +1,64 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { of, throwError } from 'rxjs'; +import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; +import { OpfCtaScriptsService } from './opf-cta-scripts.service'; +import createSpy = jasmine.createSpy; + +const mockHtmlsList = [ + '

Thanks for purchasing our great products

Please use promo code:123abc for your next purchase

', +]; + +describe('OpfCtaScriptsComponent', () => { + let component: OpfCtaScriptsComponent; + let fixture: ComponentFixture; + let opfCtaScriptsService: jasmine.SpyObj; + + beforeEach(() => { + opfCtaScriptsService = jasmine.createSpyObj('OpfCtaScriptsService', [ + 'getCtaHtmlslList', + ]); + + TestBed.configureTestingModule({ + declarations: [OpfCtaScriptsComponent], + providers: [ + { provide: OpfCtaScriptsService, useValue: opfCtaScriptsService }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + opfCtaScriptsService.getCtaHtmlslList.and.returnValue(of(mockHtmlsList)); + fixture = TestBed.createComponent(OpfCtaScriptsComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should return Htmls list without error', () => { + component.ctaHtmlList$.subscribe((htmlList) => { + expect(htmlList[0]).toBeTruthy(); + component.isError$.asObservable().subscribe((isError) => { + expect(isError).toBeFalsy(); + }); + }); + }); + + it('should isError be true when error is thrown', (done) => { + opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue( + throwError('error') + ); + fixture = TestBed.createComponent(OpfCtaScriptsComponent); + component = fixture.componentInstance; + component.ctaHtmlList$.subscribe({ + error: (error) => { + expect(error).toEqual('error'); + component.isError$.asObservable().subscribe((isError) => { + expect(isError).toBeTruthy(); + done(); + }); + }, + }); + }); +}); diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts index 14053588efe..ff11755fd2f 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts @@ -1,5 +1,4 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { OpfPaymentErrorHandlerService } from '@spartacus/opf/base/core'; import { BehaviorSubject, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { OpfCtaScriptsService } from './opf-cta-scripts.service'; @@ -11,9 +10,7 @@ import { OpfCtaScriptsService } from './opf-cta-scripts.service'; }) export class OpfCtaScriptsComponent { protected opfCtaScriptService = inject(OpfCtaScriptsService); - protected opfPaymentErrorHandlerService = inject( - OpfPaymentErrorHandlerService - ); + isError$ = new BehaviorSubject(false); ctaHtmlList$ = this.opfCtaScriptService.getCtaHtmlslList().pipe( diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts index 3c3cd0e685a..6ff662be501 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts @@ -149,19 +149,67 @@ describe('OpfCtaScriptsService', () => { }); }); - // it('should throw an error when order is empty', (done) => { - // orderFacadeMock.getOrderDetails.and.returnValue( - // of({ ...mockOrder, entries: [] }) - // ); - - // service.getCtaHtmlslList().subscribe({ - // error: (error) => { - // expect(error).toEqual('Invalid Script Location'); - - // done(); - // }, - // }); - // }); + it('should not load html snippet when its associated resource files fail to load', (done) => { + opfResourceLoaderServiceMock.loadProviderResources.and.returnValue( + Promise.reject() + ); + + service.getCtaHtmlslList().subscribe({ + next: (htmlsList) => { + expect(htmlsList.length).toEqual(0); + + done(); + }, + }); + }); + + it('should not load html snippet when html returned from server is empty ', (done) => { + opfPaymentFacadeMock.getCtaScripts.and.returnValue( + of({ + ...ctaScriptsresponseMock, + value: [ + { + ...ctaScriptsresponseMock.value[0], + dynamicScript: { + ...ctaScriptsresponseMock.value[0].dynamicScript, + html: '', + }, + }, + ], + }) + ); + + service.getCtaHtmlslList().subscribe({ + next: (htmlsList) => { + expect(htmlsList.length).toEqual(0); + done(); + }, + }); + }); + + it('should remove all script tags from html snippet', (done) => { + opfPaymentFacadeMock.getCtaScripts.and.returnValue( + of({ + ...ctaScriptsresponseMock, + value: [ + { + ...ctaScriptsresponseMock.value[0], + dynamicScript: { + ...ctaScriptsresponseMock.value[0].dynamicScript, + html: "

Thanks for purchasing our great products

Please use promo code:123abc for your next purchase

", + }, + }, + ], + }) + ); + + service.getCtaHtmlslList().subscribe({ + next: (htmlsList) => { + expect(htmlsList[0]).not.toContain('', + '

Thanks again for purchasing our great products

Please use promo code:123abc for your next purchase

', ]; - +const ctaButtonSelector = 'cx-opf-cta-button'; describe('OpfCtaScriptsComponent', () => { let component: OpfCtaScriptsComponent; let fixture: ComponentFixture; @@ -36,12 +37,14 @@ describe('OpfCtaScriptsComponent', () => { expect(component).toBeTruthy(); }); - it('should return Htmls list without error', () => { - component.ctaHtmlList$.subscribe((htmlList) => { + it('should return Htmls list and display ctaButton elements', (done) => { + component.ctaHtmls$.subscribe((htmlList) => { expect(htmlList[0]).toBeTruthy(); - component.isError$.asObservable().subscribe((isError) => { - expect(isError).toBeFalsy(); - }); + fixture.detectChanges(); + expect( + fixture.nativeElement.querySelectorAll(ctaButtonSelector).length + ).toEqual(2); + done(); }); }); @@ -51,14 +54,24 @@ describe('OpfCtaScriptsComponent', () => { ); fixture = TestBed.createComponent(OpfCtaScriptsComponent); component = fixture.componentInstance; - component.ctaHtmlList$.subscribe({ - error: (error) => { - expect(error).toEqual('error'); - component.isError$.asObservable().subscribe((isError) => { - expect(isError).toBeTruthy(); - done(); - }); - }, + component.ctaHtmls$.subscribe((htmlList) => { + expect(htmlList).toEqual([]); + fixture.detectChanges(); + expect( + fixture.nativeElement.querySelector(ctaButtonSelector) + ).toBeFalsy(); + done(); }); }); + + it('should display spinner when html list is undefined', (done) => { + opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue( + of(undefined) + ); + fixture = TestBed.createComponent(OpfCtaScriptsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('cx-spinner')).toBeTruthy(); + done(); + }); }); diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts index a894a8b8d83..1b7ca592a16 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts @@ -5,7 +5,7 @@ */ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { BehaviorSubject, throwError } from 'rxjs'; +import { of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { OpfCtaScriptsService } from './opf-cta-scripts.service'; @@ -17,12 +17,9 @@ import { OpfCtaScriptsService } from './opf-cta-scripts.service'; export class OpfCtaScriptsComponent { protected opfCtaScriptService = inject(OpfCtaScriptsService); - isError$ = new BehaviorSubject(false); - - ctaHtmlList$ = this.opfCtaScriptService.getCtaHtmlslList().pipe( - catchError((error) => { - this.isError$.next(true); - return throwError(error); + ctaHtmls$ = this.opfCtaScriptService.getCtaHtmlslList().pipe( + catchError(() => { + return of([]); }) ); } diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts index 6756635146d..abd6b942905 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts @@ -18,10 +18,6 @@ import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; @NgModule({ declarations: [OpfCtaScriptsComponent], providers: [ - // provideOutlet({ - // id: 'cx-order-overview.top', - // component: OpfCtaScriptsComponent, - // }), provideDefaultConfig({ cmsComponents: { OpfCtaScriptsComponent: { diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts index e6eabe9c0f9..6003ed852f5 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts @@ -43,10 +43,8 @@ export class OpfCtaScriptsService { getCtaHtmlslList(): Observable { return this.fillCtaScriptRequest().pipe( - switchMap((ctaScriptsRequest) => - this.fetchCtaScriptsList(ctaScriptsRequest) - ), - switchMap((scriptslist) => this.runCtaScriptsList(scriptslist)), + switchMap((ctaScriptsRequest) => this.fetchCtaScripts(ctaScriptsRequest)), + switchMap((scriptslist) => this.runCtaScripts(scriptslist)), finalize(() => { this.clearResources(); }) @@ -57,7 +55,7 @@ export class OpfCtaScriptsService { this.opfResourceLoaderService.clearAllProviderResources(); } - protected fetchCtaScriptsList( + protected fetchCtaScripts( ctaScriptsRequest: CtaScriptsRequest ): Observable { return this.opfPaymentFacade.getCtaScripts(ctaScriptsRequest).pipe( @@ -144,7 +142,7 @@ export class OpfCtaScriptsService { ); } - protected runCtaScriptsList(scripts: OpfDynamicScript[]) { + protected runCtaScripts(scripts: OpfDynamicScript[]) { return from(scripts).pipe( concatMap((script) => from(this.loadAndRunScript(script))), reduce((loadedList: string[], script) => { From 03c6766bfc661cf77da975eae038d293387db088 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Thu, 19 Oct 2023 16:55:20 -0400 Subject: [PATCH 13/30] add changeDetection --- .../components/opf-cta-button/opf-cta-button.component.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts index dee8cc94ec3..5e9254fb558 100644 --- a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts +++ b/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts @@ -4,12 +4,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Component, Input, inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + inject, +} from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @Component({ selector: 'cx-opf-cta-button', templateUrl: './opf-cta-button.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class OpfCtaButtonComponent { protected sanitizer = inject(DomSanitizer); From cf745647e19634685e6faee35ca8d746197525ee Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 10:20:31 -0400 Subject: [PATCH 14/30] remove css of cta wrapper as redundant --- integration-libs/opf/_index.scss | 3 +-- integration-libs/opf/base/styles/components/_index.scss | 1 - .../opf/base/styles/components/_opf-cta-scripts.scss | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 integration-libs/opf/base/styles/components/_opf-cta-scripts.scss diff --git a/integration-libs/opf/_index.scss b/integration-libs/opf/_index.scss index 087bf872986..04f161190f8 100644 --- a/integration-libs/opf/_index.scss +++ b/integration-libs/opf/_index.scss @@ -8,8 +8,7 @@ $opf-components-allowlist: cx-opf-checkout-payment-and-review, cx-opf-checkout-payments, cx-opf-checkout-billing-address-form, - cx-opf-checkout-payment-wrapper, cx-opf-error-modal, cx-opf-cta-button, - cx-opf-cta-scripts !default; + cx-opf-checkout-payment-wrapper, cx-opf-error-modal, cx-opf-cta-button !default; $skipComponentStyles: () !default; diff --git a/integration-libs/opf/base/styles/components/_index.scss b/integration-libs/opf/base/styles/components/_index.scss index 66f1e36dee9..4e9a6d189d8 100644 --- a/integration-libs/opf/base/styles/components/_index.scss +++ b/integration-libs/opf/base/styles/components/_index.scss @@ -1,3 +1,2 @@ @import './opf-error-modal'; @import './opf-cta-button'; -@import './opf-cta-scripts'; diff --git a/integration-libs/opf/base/styles/components/_opf-cta-scripts.scss b/integration-libs/opf/base/styles/components/_opf-cta-scripts.scss deleted file mode 100644 index 7fbfdcf6ff8..00000000000 --- a/integration-libs/opf/base/styles/components/_opf-cta-scripts.scss +++ /dev/null @@ -1,4 +0,0 @@ -%cx-opf-cta-scripts { - display: block; - margin: 0.5rem 0 0.5rem 0; -} From 9fd5e107eec5883ff4218480eca8a6854b7726de Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 10:35:53 -0400 Subject: [PATCH 15/30] rename cta-button to cta-element --- .../base/components/opf-base-components.module.ts | 4 ++-- .../{opf-cta-button => opf-cta-element}/index.ts | 4 ++-- .../opf-cta-element.component.html} | 0 .../opf-cta-element.component.spec.ts} | 10 +++++----- .../opf-cta-element.component.ts} | 6 +++--- .../opf-cta-element.module.ts} | 8 ++++---- .../opf-cta-scripts/opf-cta-scripts.component.html | 4 ++-- .../opf-cta-scripts.component.spec.ts | 14 +++++++------- .../opf-cta-scripts/opf-cta-scripts.component.ts | 2 +- .../opf-cta-scripts/opf-cta-scripts.module.ts | 6 +++--- 10 files changed, 29 insertions(+), 29 deletions(-) rename integration-libs/opf/base/components/{opf-cta-button => opf-cta-element}/index.ts (59%) rename integration-libs/opf/base/components/{opf-cta-button/opf-cta-button.component.html => opf-cta-element/opf-cta-element.component.html} (100%) rename integration-libs/opf/base/components/{opf-cta-button/opf-cta-button.component.spec.ts => opf-cta-element/opf-cta-element.component.spec.ts} (77%) rename integration-libs/opf/base/components/{opf-cta-button/opf-cta-button.component.ts => opf-cta-element/opf-cta-element.component.ts} (81%) rename integration-libs/opf/base/components/{opf-cta-button/opf-cta-button.module.ts => opf-cta-element/opf-cta-element.module.ts} (58%) diff --git a/integration-libs/opf/base/components/opf-base-components.module.ts b/integration-libs/opf/base/components/opf-base-components.module.ts index 85dd480101f..d32b294d57f 100644 --- a/integration-libs/opf/base/components/opf-base-components.module.ts +++ b/integration-libs/opf/base/components/opf-base-components.module.ts @@ -5,12 +5,12 @@ */ import { NgModule } from '@angular/core'; -import { OpfCtaButtonModule } from './opf-cta-button'; +import { OpfCtaElementModule } from './opf-cta-element'; import { OpfCtaScriptsModule } from './opf-cta-scripts'; import { OpfErrorModalModule } from './opf-error-modal/opf-error-modal.module'; @NgModule({ - imports: [OpfErrorModalModule, OpfCtaScriptsModule, OpfCtaButtonModule], + imports: [OpfErrorModalModule, OpfCtaScriptsModule, OpfCtaElementModule], providers: [], }) export class OpfBaseComponentsModule {} diff --git a/integration-libs/opf/base/components/opf-cta-button/index.ts b/integration-libs/opf/base/components/opf-cta-element/index.ts similarity index 59% rename from integration-libs/opf/base/components/opf-cta-button/index.ts rename to integration-libs/opf/base/components/opf-cta-element/index.ts index b2b0bd1084d..50ca90e8760 100644 --- a/integration-libs/opf/base/components/opf-cta-button/index.ts +++ b/integration-libs/opf/base/components/opf-cta-element/index.ts @@ -4,5 +4,5 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './opf-cta-button.component'; -export * from './opf-cta-button.module'; +export * from './opf-cta-element.component'; +export * from './opf-cta-element.module'; diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html b/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.html similarity index 100% rename from integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.html rename to integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.html diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.spec.ts b/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.spec.ts similarity index 77% rename from integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.spec.ts rename to integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.spec.ts index dec88929f54..0324a128b8f 100644 --- a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.spec.ts +++ b/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.spec.ts @@ -1,16 +1,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DomSanitizer } from '@angular/platform-browser'; -import { OpfCtaButtonComponent } from './opf-cta-button.component'; +import { OpfCtaElementComponent } from './opf-cta-element.component'; describe('OpfCtaButton', () => { - let component: OpfCtaButtonComponent; - let fixture: ComponentFixture; + let component: OpfCtaElementComponent; + let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ - declarations: [OpfCtaButtonComponent], + declarations: [OpfCtaElementComponent], }); - fixture = TestBed.createComponent(OpfCtaButtonComponent); + fixture = TestBed.createComponent(OpfCtaElementComponent); component = fixture.componentInstance; // opfCtaScriptsService = TestBed.inject(OpfCtaScriptsService); }); diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts b/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.ts similarity index 81% rename from integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts rename to integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.ts index 5e9254fb558..da2aade3037 100644 --- a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.component.ts +++ b/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.ts @@ -13,11 +13,11 @@ import { import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @Component({ - selector: 'cx-opf-cta-button', - templateUrl: './opf-cta-button.component.html', + selector: 'cx-opf-cta-element', + templateUrl: './opf-cta-element.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class OpfCtaButtonComponent { +export class OpfCtaElementComponent { protected sanitizer = inject(DomSanitizer); @Input() ctaScriptHtml: string; diff --git a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.module.ts b/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.module.ts similarity index 58% rename from integration-libs/opf/base/components/opf-cta-button/opf-cta-button.module.ts rename to integration-libs/opf/base/components/opf-cta-element/opf-cta-element.module.ts index db06a840965..8ba875926ac 100644 --- a/integration-libs/opf/base/components/opf-cta-button/opf-cta-button.module.ts +++ b/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.module.ts @@ -6,11 +6,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { OpfCtaButtonComponent } from './opf-cta-button.component'; +import { OpfCtaElementComponent } from './opf-cta-element.component'; @NgModule({ - declarations: [OpfCtaButtonComponent], + declarations: [OpfCtaElementComponent], imports: [CommonModule], - exports: [OpfCtaButtonComponent], + exports: [OpfCtaElementComponent], }) -export class OpfCtaButtonModule {} +export class OpfCtaElementModule {} diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html index 8ae835937f1..a7578a5c230 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html @@ -1,9 +1,9 @@ - + > diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts index 757934d61ae..412add11beb 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; -import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; +import { OpfCtaWrapperComponent } from './opf-cta-scripts.component'; import { OpfCtaScriptsService } from './opf-cta-scripts.service'; import createSpy = jasmine.createSpy; @@ -10,8 +10,8 @@ const mockHtmlsList = [ ]; const ctaButtonSelector = 'cx-opf-cta-button'; describe('OpfCtaScriptsComponent', () => { - let component: OpfCtaScriptsComponent; - let fixture: ComponentFixture; + let component: OpfCtaWrapperComponent; + let fixture: ComponentFixture; let opfCtaScriptsService: jasmine.SpyObj; beforeEach(() => { @@ -20,7 +20,7 @@ describe('OpfCtaScriptsComponent', () => { ]); TestBed.configureTestingModule({ - declarations: [OpfCtaScriptsComponent], + declarations: [OpfCtaWrapperComponent], providers: [ { provide: OpfCtaScriptsService, useValue: opfCtaScriptsService }, ], @@ -29,7 +29,7 @@ describe('OpfCtaScriptsComponent', () => { beforeEach(() => { opfCtaScriptsService.getCtaHtmlslList.and.returnValue(of(mockHtmlsList)); - fixture = TestBed.createComponent(OpfCtaScriptsComponent); + fixture = TestBed.createComponent(OpfCtaWrapperComponent); component = fixture.componentInstance; }); @@ -52,7 +52,7 @@ describe('OpfCtaScriptsComponent', () => { opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue( throwError('error') ); - fixture = TestBed.createComponent(OpfCtaScriptsComponent); + fixture = TestBed.createComponent(OpfCtaWrapperComponent); component = fixture.componentInstance; component.ctaHtmls$.subscribe((htmlList) => { expect(htmlList).toEqual([]); @@ -68,7 +68,7 @@ describe('OpfCtaScriptsComponent', () => { opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue( of(undefined) ); - fixture = TestBed.createComponent(OpfCtaScriptsComponent); + fixture = TestBed.createComponent(OpfCtaWrapperComponent); component = fixture.componentInstance; fixture.detectChanges(); expect(fixture.nativeElement.querySelector('cx-spinner')).toBeTruthy(); diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts index 1b7ca592a16..50e961f1307 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts @@ -14,7 +14,7 @@ import { OpfCtaScriptsService } from './opf-cta-scripts.service'; templateUrl: './opf-cta-scripts.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class OpfCtaScriptsComponent { +export class OpfCtaWrapperComponent { protected opfCtaScriptService = inject(OpfCtaScriptsService); ctaHtmls$ = this.opfCtaScriptService.getCtaHtmlslList().pipe( diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts index abd6b942905..58dfffdc6a7 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts +++ b/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts @@ -12,8 +12,8 @@ import { provideDefaultConfig, } from '@spartacus/core'; import { SpinnerModule } from '@spartacus/storefront'; -import { OpfCtaButtonModule } from '../opf-cta-button/opf-cta-button.module'; -import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; +import { OpfCtaElementModule } from '../opf-cta-element'; +import { OpfCtaWrapperComponent as OpfCtaScriptsComponent } from './opf-cta-scripts.component'; @NgModule({ declarations: [OpfCtaScriptsComponent], @@ -27,6 +27,6 @@ import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; }), ], exports: [OpfCtaScriptsComponent], - imports: [CommonModule, OpfCtaButtonModule, SpinnerModule], + imports: [CommonModule, OpfCtaElementModule, SpinnerModule], }) export class OpfCtaScriptsModule {} From ec9d4f99c04050b8fcb6a83c3aee36375010f89d Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 10:49:22 -0400 Subject: [PATCH 16/30] re-organize folders --- integration-libs/opf/_index.scss | 2 +- .../components/opf-base-components.module.ts | 4 ++-- .../base/components/opf-cta-scripts/test.json | 22 ------------------- .../{ => opf-cta}/opf-cta-element/index.ts | 0 .../opf-cta-element.component.html | 0 .../opf-cta-element.component.spec.ts | 0 .../opf-cta-element.component.ts | 0 .../opf-cta-element/opf-cta-element.module.ts | 0 .../{ => opf-cta}/opf-cta-scripts/index.ts | 0 .../opf-cta-scripts.component.html | 0 .../opf-cta-scripts.component.spec.ts | 6 ++--- .../opf-cta-scripts.component.ts | 0 .../opf-cta-scripts/opf-cta-scripts.module.ts | 0 .../opf-cta-scripts.service.spec.ts | 0 .../opf-cta-scripts.service.ts | 0 .../opf/base/styles/components/_index.scss | 2 +- ...-cta-button.scss => _opf-cta-element.scss} | 2 +- 17 files changed, 8 insertions(+), 30 deletions(-) delete mode 100644 integration-libs/opf/base/components/opf-cta-scripts/test.json rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-element/index.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-element/opf-cta-element.component.html (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-element/opf-cta-element.component.spec.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-element/opf-cta-element.component.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-element/opf-cta-element.module.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-scripts/index.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-scripts/opf-cta-scripts.component.html (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-scripts/opf-cta-scripts.component.spec.ts (93%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-scripts/opf-cta-scripts.component.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-scripts/opf-cta-scripts.module.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-scripts/opf-cta-scripts.service.spec.ts (100%) rename integration-libs/opf/base/components/{ => opf-cta}/opf-cta-scripts/opf-cta-scripts.service.ts (100%) rename integration-libs/opf/base/styles/components/{_opf-cta-button.scss => _opf-cta-element.scss} (69%) diff --git a/integration-libs/opf/_index.scss b/integration-libs/opf/_index.scss index 04f161190f8..63a903cf6e6 100644 --- a/integration-libs/opf/_index.scss +++ b/integration-libs/opf/_index.scss @@ -8,7 +8,7 @@ $opf-components-allowlist: cx-opf-checkout-payment-and-review, cx-opf-checkout-payments, cx-opf-checkout-billing-address-form, - cx-opf-checkout-payment-wrapper, cx-opf-error-modal, cx-opf-cta-button !default; + cx-opf-checkout-payment-wrapper, cx-opf-error-modal, cx-opf-cta-element !default; $skipComponentStyles: () !default; diff --git a/integration-libs/opf/base/components/opf-base-components.module.ts b/integration-libs/opf/base/components/opf-base-components.module.ts index d32b294d57f..a6d7b226298 100644 --- a/integration-libs/opf/base/components/opf-base-components.module.ts +++ b/integration-libs/opf/base/components/opf-base-components.module.ts @@ -5,8 +5,8 @@ */ import { NgModule } from '@angular/core'; -import { OpfCtaElementModule } from './opf-cta-element'; -import { OpfCtaScriptsModule } from './opf-cta-scripts'; +import { OpfCtaElementModule } from './opf-cta/opf-cta-element'; +import { OpfCtaScriptsModule } from './opf-cta/opf-cta-scripts'; import { OpfErrorModalModule } from './opf-error-modal/opf-error-modal.module'; @NgModule({ diff --git a/integration-libs/opf/base/components/opf-cta-scripts/test.json b/integration-libs/opf/base/components/opf-cta-scripts/test.json deleted file mode 100644 index 6d526d99ede..00000000000 --- a/integration-libs/opf/base/components/opf-cta-scripts/test.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "value": [ - { - "paymentAccountId": 1, - "dynamicScript": { - "html": "

Please use promo code 123abc

This promo code is still valid for 19 days

", - "cssUrls": [ - { - "url": "https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.css", - "sri": "" - } - ], - "jsUrls": [ - { - "url": "https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/4.2.1/adyen.js", - "sri": "" - } - ] - } - } - ] -} diff --git a/integration-libs/opf/base/components/opf-cta-element/index.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-element/index.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-element/index.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-element/index.ts diff --git a/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.html b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.html similarity index 100% rename from integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.html rename to integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.html diff --git a/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.spec.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.spec.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.spec.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.spec.ts diff --git a/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-element/opf-cta-element.component.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.ts diff --git a/integration-libs/opf/base/components/opf-cta-element/opf-cta-element.module.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.module.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-element/opf-cta-element.module.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.module.ts diff --git a/integration-libs/opf/base/components/opf-cta-scripts/index.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/index.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-scripts/index.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-scripts/index.ts diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.html similarity index 100% rename from integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.html rename to integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.html diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts similarity index 93% rename from integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts index 412add11beb..1c58ba3e2ae 100644 --- a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.spec.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts @@ -8,7 +8,7 @@ const mockHtmlsList = [ '

Thanks for purchasing our great products

Please use promo code:123abc for your next purchase

', '

Thanks again for purchasing our great products

Please use promo code:123abc for your next purchase

', ]; -const ctaButtonSelector = 'cx-opf-cta-button'; +const ctaElementSelector = 'cx-opf-cta-element'; describe('OpfCtaScriptsComponent', () => { let component: OpfCtaWrapperComponent; let fixture: ComponentFixture; @@ -42,7 +42,7 @@ describe('OpfCtaScriptsComponent', () => { expect(htmlList[0]).toBeTruthy(); fixture.detectChanges(); expect( - fixture.nativeElement.querySelectorAll(ctaButtonSelector).length + fixture.nativeElement.querySelectorAll(ctaElementSelector).length ).toEqual(2); done(); }); @@ -58,7 +58,7 @@ describe('OpfCtaScriptsComponent', () => { expect(htmlList).toEqual([]); fixture.detectChanges(); expect( - fixture.nativeElement.querySelector(ctaButtonSelector) + fixture.nativeElement.querySelector(ctaElementSelector) ).toBeFalsy(); done(); }); diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.component.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.ts diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.module.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.module.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.module.ts diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.spec.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.spec.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.spec.ts diff --git a/integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts similarity index 100% rename from integration-libs/opf/base/components/opf-cta-scripts/opf-cta-scripts.service.ts rename to integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts diff --git a/integration-libs/opf/base/styles/components/_index.scss b/integration-libs/opf/base/styles/components/_index.scss index 4e9a6d189d8..7fab0bcd06e 100644 --- a/integration-libs/opf/base/styles/components/_index.scss +++ b/integration-libs/opf/base/styles/components/_index.scss @@ -1,2 +1,2 @@ @import './opf-error-modal'; -@import './opf-cta-button'; +@import './opf-cta-element'; diff --git a/integration-libs/opf/base/styles/components/_opf-cta-button.scss b/integration-libs/opf/base/styles/components/_opf-cta-element.scss similarity index 69% rename from integration-libs/opf/base/styles/components/_opf-cta-button.scss rename to integration-libs/opf/base/styles/components/_opf-cta-element.scss index fce13582d77..bf6c20ed03d 100644 --- a/integration-libs/opf/base/styles/components/_opf-cta-button.scss +++ b/integration-libs/opf/base/styles/components/_opf-cta-element.scss @@ -1,4 +1,4 @@ -%cx-opf-cta-button { +%cx-opf-cta-element { display: block; margin: 0.5rem 0 0.5rem 0; } From 8ee7d506fcd8ae225288da3ca09ac2c6cd9d0872 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 11:33:37 -0400 Subject: [PATCH 17/30] simplify rxjs piping --- .../opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts index 6003ed852f5..fb696e8b517 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts @@ -160,16 +160,16 @@ export class OpfCtaScriptsService { protected getScriptLocation(): Observable { return this.cmsService.getCurrentPage().pipe( take(1), - concatMap((page) => { + map((page) => { switch (page.pageId) { case CmsPageLocation.ORDER_PAGE: - return of(CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE); + return CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE; case CmsPageLocation.ORDER_CONFIRMATION_PAGE: - return of(CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE); + return CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE; case CmsPageLocation.PDP_PAGE: - return of(CtaScriptsLocation.PDP_QUICK_BUY); + return CtaScriptsLocation.PDP_QUICK_BUY; default: - return of(null); + return null; } }) ); From 40c490fc7fc35dcbeb6f38c0e96a6fd186c1c359 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 12:30:53 -0400 Subject: [PATCH 18/30] refactor unit test to avoid redunduncy --- .../opf-cta-scripts.component.spec.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts index 1c58ba3e2ae..279049c0427 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; -import { OpfCtaWrapperComponent } from './opf-cta-scripts.component'; +import { OpfCtaWrapperComponent as OpfCtaScriptsComponent } from './opf-cta-scripts.component'; import { OpfCtaScriptsService } from './opf-cta-scripts.service'; import createSpy = jasmine.createSpy; @@ -10,17 +10,21 @@ const mockHtmlsList = [ ]; const ctaElementSelector = 'cx-opf-cta-element'; describe('OpfCtaScriptsComponent', () => { - let component: OpfCtaWrapperComponent; - let fixture: ComponentFixture; + let component: OpfCtaScriptsComponent; + let fixture: ComponentFixture; let opfCtaScriptsService: jasmine.SpyObj; + const createComponentInstance = () => { + fixture = TestBed.createComponent(OpfCtaScriptsComponent); + component = fixture.componentInstance; + }; beforeEach(() => { opfCtaScriptsService = jasmine.createSpyObj('OpfCtaScriptsService', [ 'getCtaHtmlslList', ]); TestBed.configureTestingModule({ - declarations: [OpfCtaWrapperComponent], + declarations: [OpfCtaScriptsComponent], providers: [ { provide: OpfCtaScriptsService, useValue: opfCtaScriptsService }, ], @@ -29,8 +33,7 @@ describe('OpfCtaScriptsComponent', () => { beforeEach(() => { opfCtaScriptsService.getCtaHtmlslList.and.returnValue(of(mockHtmlsList)); - fixture = TestBed.createComponent(OpfCtaWrapperComponent); - component = fixture.componentInstance; + createComponentInstance(); }); it('should create', () => { @@ -52,8 +55,7 @@ describe('OpfCtaScriptsComponent', () => { opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue( throwError('error') ); - fixture = TestBed.createComponent(OpfCtaWrapperComponent); - component = fixture.componentInstance; + createComponentInstance(); component.ctaHtmls$.subscribe((htmlList) => { expect(htmlList).toEqual([]); fixture.detectChanges(); @@ -68,8 +70,7 @@ describe('OpfCtaScriptsComponent', () => { opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue( of(undefined) ); - fixture = TestBed.createComponent(OpfCtaWrapperComponent); - component = fixture.componentInstance; + createComponentInstance(); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('cx-spinner')).toBeTruthy(); done(); From 376174a87b4f8bbe57d5f51b9dd6f495f155bcd7 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 12:48:30 -0400 Subject: [PATCH 19/30] remove comments --- .../opf/base/core/facade/opf-payment.service.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts index 868f1027686..2436a0fa4b5 100644 --- a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts +++ b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts @@ -305,7 +305,6 @@ describe('OpfPaymentService', () => { expect(connectorSpy).toHaveBeenCalledWith(paymentSessionId); }); - // describe(`getActiveConfigurationsState`, () => { it(`should return mockActiveConfigurations data`, (done) => { service.getActiveConfigurationsState().subscribe((state) => { expect(state).toEqual({ @@ -328,5 +327,4 @@ describe('OpfPaymentService', () => { done(); }); }); - // }); }); From 03df28c6b566be50ca65bef6f9ef4ef80998f872 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 13:27:06 -0400 Subject: [PATCH 20/30] fix sonar warning --- .../opf/base/root/model/opf-quick-buy.model.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts index 670080c27d3..758bc9e3700 100644 --- a/integration-libs/opf/base/root/model/opf-quick-buy.model.ts +++ b/integration-libs/opf/base/root/model/opf-quick-buy.model.ts @@ -19,19 +19,21 @@ export enum OpfProviderType { APPLE_PAY = 'APPLE_PAY', GOOGLE_PAY = 'GOOGLE_PAY', } + +export type CtaAdditionalDataKey = + | 'divisionId' + | 'experienceId' + | 'currency' + | 'fulfillmentLocationId' + | 'locale' + | 'scriptIdentifier'; export interface CtaScriptsRequest { paymentAccountIds?: Array; orderId?: string; ctaProductItems?: Array; scriptLocations?: Array; additionalData?: Array<{ - key: - | 'divisionId' - | 'experienceId' - | 'currency' - | 'fulfillmentLocationId' - | 'locale' - | 'scriptIdentifier'; + key: CtaAdditionalDataKey; value: string; }>; } From ad53c0d32f4a00552623c2d15361edc035ebf15c Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 16:39:41 -0400 Subject: [PATCH 21/30] adjust naming and modules --- .../opf/base/components/opf-base-components.module.ts | 3 +-- .../opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts | 2 +- .../opf-cta/opf-cta-scripts/opf-cta-scripts.component.ts | 2 +- .../opf-cta/opf-cta-scripts/opf-cta-scripts.module.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/integration-libs/opf/base/components/opf-base-components.module.ts b/integration-libs/opf/base/components/opf-base-components.module.ts index a6d7b226298..0ca9ebb6e54 100644 --- a/integration-libs/opf/base/components/opf-base-components.module.ts +++ b/integration-libs/opf/base/components/opf-base-components.module.ts @@ -5,12 +5,11 @@ */ import { NgModule } from '@angular/core'; -import { OpfCtaElementModule } from './opf-cta/opf-cta-element'; import { OpfCtaScriptsModule } from './opf-cta/opf-cta-scripts'; import { OpfErrorModalModule } from './opf-error-modal/opf-error-modal.module'; @NgModule({ - imports: [OpfErrorModalModule, OpfCtaScriptsModule, OpfCtaElementModule], + imports: [OpfErrorModalModule, OpfCtaScriptsModule], providers: [], }) export class OpfBaseComponentsModule {} diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts index 279049c0427..fb508138263 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; -import { OpfCtaWrapperComponent as OpfCtaScriptsComponent } from './opf-cta-scripts.component'; +import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; import { OpfCtaScriptsService } from './opf-cta-scripts.service'; import createSpy = jasmine.createSpy; diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.ts index 50e961f1307..1b7ca592a16 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.component.ts @@ -14,7 +14,7 @@ import { OpfCtaScriptsService } from './opf-cta-scripts.service'; templateUrl: './opf-cta-scripts.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class OpfCtaWrapperComponent { +export class OpfCtaScriptsComponent { protected opfCtaScriptService = inject(OpfCtaScriptsService); ctaHtmls$ = this.opfCtaScriptService.getCtaHtmlslList().pipe( diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.module.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.module.ts index 58dfffdc6a7..36e22ceda41 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.module.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.module.ts @@ -13,7 +13,7 @@ import { } from '@spartacus/core'; import { SpinnerModule } from '@spartacus/storefront'; import { OpfCtaElementModule } from '../opf-cta-element'; -import { OpfCtaWrapperComponent as OpfCtaScriptsComponent } from './opf-cta-scripts.component'; +import { OpfCtaScriptsComponent } from './opf-cta-scripts.component'; @NgModule({ declarations: [OpfCtaScriptsComponent], From 7fd6890a46638095e6cbb868ee19dcf402dae0b5 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Fri, 20 Oct 2023 18:06:07 -0400 Subject: [PATCH 22/30] reject when empty resources --- .../opf/base/root/services/opf-resource-loader.service.ts | 3 +++ 1 file changed, 3 insertions(+) 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 804175aa74b..0b4012080fa 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 @@ -165,6 +165,9 @@ export class OpfResourceLoaderService extends ScriptLoader { type: AfterRedirectDynamicScriptResourceType.STYLES, })), ]; + if (resources.length === 0) { + return Promise.reject(); + } return new Promise((resolve, reject) => { this.loadedResources = []; From fd0bb5d782a93ed0488c4d2d1a0f3b18f88e39c9 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Mon, 23 Oct 2023 12:48:25 -0400 Subject: [PATCH 23/30] add unit test for resource-loader service --- .../opf/base/root/model/opf.model.ts | 4 +- .../opf-resource-loader.service.spec.ts | 372 ++++++++++++++++++ .../services/opf-resource-loader.service.ts | 13 +- 3 files changed, 379 insertions(+), 10 deletions(-) create mode 100644 integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts diff --git a/integration-libs/opf/base/root/model/opf.model.ts b/integration-libs/opf/base/root/model/opf.model.ts index 86d9ba3eaa1..97794c967eb 100644 --- a/integration-libs/opf/base/root/model/opf.model.ts +++ b/integration-libs/opf/base/root/model/opf.model.ts @@ -146,10 +146,10 @@ export interface OpfDynamicScriptResource { url?: string; sri?: string; attributes?: KeyValuePair[]; - type?: AfterRedirectDynamicScriptResourceType; + type?: OpfDynamicScriptResourceType; } -export enum AfterRedirectDynamicScriptResourceType { +export enum OpfDynamicScriptResourceType { SCRIPT = 'SCRIPT', STYLES = 'STYLES', } 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 new file mode 100644 index 00000000000..0a9d7d59456 --- /dev/null +++ b/integration-libs/opf/base/root/services/opf-resource-loader.service.spec.ts @@ -0,0 +1,372 @@ +import { DOCUMENT } from '@angular/common'; +import { PLATFORM_ID } from '@angular/core'; +import { TestBed, fakeAsync } from '@angular/core/testing'; +import { ScriptLoader } from '@spartacus/core'; +import { OpfDynamicScriptResourceType } from '../model'; +import { OpfResourceLoaderService } from './opf-resource-loader.service'; + +describe('OpfResourceLoaderService', () => { + let opfResourceLoaderService: OpfResourceLoaderService; + let mockDocument: any; + let mockPlatformId: Object; + + beforeEach(() => { + mockDocument = { + createElement: jasmine.createSpy('createElement').and.callFake(() => ({ + href: '', + rel: '', + type: '', + setAttribute: jasmine.createSpy('setAttribute'), + addEventListener: jasmine.createSpy('addEventListener'), + })), + head: { + appendChild: jasmine.createSpy('appendChild'), + }, + querySelector: jasmine.createSpy('querySelector'), + }; + + mockPlatformId = 'browser'; + + TestBed.configureTestingModule({ + providers: [ + OpfResourceLoaderService, + { provide: DOCUMENT, useValue: mockDocument }, + { provide: PLATFORM_ID, useValue: mockPlatformId }, + ], + }); + }); + + it('should be created', () => { + opfResourceLoaderService = TestBed.inject(OpfResourceLoaderService); + expect(opfResourceLoaderService).toBeTruthy(); + }); + + it('should create OpfResourceLoaderService instance', () => { + opfResourceLoaderService = TestBed.inject(OpfResourceLoaderService); + expect(opfResourceLoaderService instanceof ScriptLoader).toBe(true); + }); + + describe('loadProviderResources', () => { + beforeEach(() => { + opfResourceLoaderService = TestBed.inject(OpfResourceLoaderService); + }); + + it('should load provider resources successfully for both scripts and styles', fakeAsync(() => { + const mockScriptResource = { + url: 'script-url', + type: OpfDynamicScriptResourceType.SCRIPT, + }; + + const mockStyleResource = { + url: 'style-url', + type: OpfDynamicScriptResourceType.STYLES, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + + opfResourceLoaderService.loadProviderResources( + [mockScriptResource], + [mockStyleResource] + ); + + expect(opfResourceLoaderService['loadStyles']).toHaveBeenCalled(); + expect(opfResourceLoaderService['loadScript']).toHaveBeenCalled(); + })); + + it('should load provider resources successfully for scripts', fakeAsync(() => { + const mockScriptResource = { + url: 'script-url', + type: OpfDynamicScriptResourceType.SCRIPT, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + + opfResourceLoaderService.loadProviderResources([mockScriptResource]); + + expect(opfResourceLoaderService['loadStyles']).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadScript']).toHaveBeenCalled(); + })); + + it('should load provider resources successfully for styles', fakeAsync(() => { + const mockStyleResource = { + url: 'style-url', + type: OpfDynamicScriptResourceType.STYLES, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + + opfResourceLoaderService.loadProviderResources([], [mockStyleResource]); + + expect(opfResourceLoaderService['loadScript']).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadStyles']).toHaveBeenCalled(); + })); + + it('should load provider resources successfully for styles with no url', fakeAsync(() => { + const mockStyleResource = { + type: OpfDynamicScriptResourceType.STYLES, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + + opfResourceLoaderService.loadProviderResources([], [mockStyleResource]); + + expect(opfResourceLoaderService['loadScript']).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadStyles']).not.toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).toHaveBeenCalled(); + })); + + it('should not load provider resources when no resources are provided', fakeAsync(() => { + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + + opfResourceLoaderService.loadProviderResources(); + + expect(opfResourceLoaderService['loadScript']).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadStyles']).not.toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).not.toHaveBeenCalled(); + })); + + it('should mark resource as loaded when script is successfully loaded', fakeAsync(() => { + const mockScriptResource = { + url: 'script-url', + type: OpfDynamicScriptResourceType.SCRIPT, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + spyOn(ScriptLoader.prototype, 'embedScript').and.callFake( + (options: any) => { + options.callback?.(); + } + ); + + opfResourceLoaderService.loadProviderResources([mockScriptResource]); + + expect(opfResourceLoaderService['loadStyles']).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadScript']).toHaveBeenCalled(); + expect(ScriptLoader.prototype.embedScript).toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).toHaveBeenCalled(); + })); + + it('should handle resource loading error when script is not successfully loaded', fakeAsync(() => { + const mockScriptResource = { + url: 'script-url', + type: OpfDynamicScriptResourceType.SCRIPT, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + spyOn( + opfResourceLoaderService, + 'handleLoadingResourceError' + ).and.callThrough(); + spyOn(ScriptLoader.prototype, 'embedScript').and.callFake( + (options: any) => { + options.errorCallback?.(); + } + ); + + opfResourceLoaderService.loadProviderResources([mockScriptResource]); + + expect(opfResourceLoaderService['loadStyles']).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadScript']).toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).not.toHaveBeenCalled(); + expect(ScriptLoader.prototype.embedScript).toHaveBeenCalled(); + expect( + opfResourceLoaderService['handleLoadingResourceError'] + ).toHaveBeenCalled(); + })); + + it('should mark resource as loaded when style is successfully loaded', fakeAsync(() => { + const mockStylesResources = { + url: 'style-url', + type: OpfDynamicScriptResourceType.STYLES, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + spyOn(opfResourceLoaderService, 'embedStyles').and.callFake( + (options: any) => { + options.callback?.(); // Simulate script loading + } + ); + + opfResourceLoaderService.loadProviderResources([], [mockStylesResources]); + + expect(opfResourceLoaderService['loadScript']).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadStyles']).toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).toHaveBeenCalled(); + expect(opfResourceLoaderService['embedStyles']).toHaveBeenCalled(); + })); + + it('should handle resource loading error when style is not successfully loaded', fakeAsync(() => { + const mockStylesResources = { + url: 'style-url', + type: OpfDynamicScriptResourceType.STYLES, + }; + + spyOn(opfResourceLoaderService, 'loadScript').and.callThrough(); + spyOn(opfResourceLoaderService, 'loadStyles').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + spyOn( + opfResourceLoaderService, + 'handleLoadingResourceError' + ).and.callThrough(); + spyOn(opfResourceLoaderService, 'embedStyles').and.callFake( + (options: any) => { + options.errorCallback?.(); // Simulate script loading + } + ); + + opfResourceLoaderService.loadProviderResources([], [mockStylesResources]); + + expect(opfResourceLoaderService['loadScript']).not.toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).not.toHaveBeenCalled(); + expect(opfResourceLoaderService['loadStyles']).toHaveBeenCalled(); + expect(opfResourceLoaderService['embedStyles']).toHaveBeenCalled(); + expect( + opfResourceLoaderService['handleLoadingResourceError'] + ).toHaveBeenCalled(); + })); + + it('should not embed styles if there is no style in the element', fakeAsync(() => { + const mockStyleResource = { + url: 'style-url', + type: OpfDynamicScriptResourceType.STYLES, + }; + + spyOn(opfResourceLoaderService, 'embedStyles').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + mockDocument.querySelector = jasmine + .createSpy('querySelector') + .and.returnValue({} as Element); + + opfResourceLoaderService.loadProviderResources([], [mockStyleResource]); + + expect(opfResourceLoaderService['embedStyles']).not.toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).toHaveBeenCalled(); + })); + + it('should not embed script if there is no script in the element', fakeAsync(() => { + const mockScriptResource = { + url: 'script-url', + type: OpfDynamicScriptResourceType.SCRIPT, + }; + + spyOn(opfResourceLoaderService, 'embedScript').and.callThrough(); + spyOn( + opfResourceLoaderService, + 'markResourceAsLoaded' + ).and.callThrough(); + mockDocument.querySelector = jasmine + .createSpy('querySelector') + .and.returnValue({} as Element); + + opfResourceLoaderService.loadProviderResources([mockScriptResource]); + + expect(opfResourceLoaderService['embedScript']).not.toHaveBeenCalled(); + expect( + opfResourceLoaderService['markResourceAsLoaded'] + ).toHaveBeenCalled(); + })); + }); + + describe('loadProviderResources using server platform', () => { + beforeEach(() => { + TestBed.overrideProvider(PLATFORM_ID, { useValue: 'server' }); + opfResourceLoaderService = TestBed.inject(OpfResourceLoaderService); + }); + + it('should embed styles with SSR when platform is set to server', fakeAsync(() => { + const mockStyleResource = { + url: 'style-url', + type: OpfDynamicScriptResourceType.STYLES, + }; + + spyOn(opfResourceLoaderService, 'embedStyles').and.callThrough(); + + opfResourceLoaderService.loadProviderResources([], [mockStyleResource]); + + expect(opfResourceLoaderService['embedStyles']).toHaveBeenCalled(); + })); + }); + + describe('clearAllProviderResources', () => { + it('should clear all provider resources', () => { + opfResourceLoaderService = TestBed.inject(OpfResourceLoaderService); + + const mockLinkElement = { + remove: jasmine.createSpy('remove'), + }; + + mockDocument.querySelectorAll = jasmine + .createSpy('querySelectorAll') + .and.returnValue([mockLinkElement]); + + opfResourceLoaderService.clearAllProviderResources(); + + expect(mockLinkElement.remove).toHaveBeenCalled(); + }); + }); + + describe('executeHtml', () => { + it('should execute script from HTML correctly', () => { + opfResourceLoaderService = TestBed.inject(OpfResourceLoaderService); + + const mockScript = document.createElement('script'); + mockScript.innerText = 'console.log("Script executed");'; + spyOn(document, 'createElement').and.returnValue(mockScript); + spyOn(console, 'log'); + + opfResourceLoaderService.executeScriptFromHtml( + '' + ); + + expect(console.log).toHaveBeenCalledWith('Script executed'); + }); + }); +}); 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 0b4012080fa..27dade94368 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 @@ -10,8 +10,8 @@ import { ScriptLoader } from '@spartacus/core'; import { throwError } from 'rxjs'; import { - AfterRedirectDynamicScriptResourceType, OpfDynamicScriptResource, + OpfDynamicScriptResourceType, } from '../model'; @Injectable({ @@ -158,16 +158,13 @@ export class OpfResourceLoaderService extends ScriptLoader { const resources: OpfDynamicScriptResource[] = [ ...scripts.map((script) => ({ ...script, - type: AfterRedirectDynamicScriptResourceType.SCRIPT, + type: OpfDynamicScriptResourceType.SCRIPT, })), ...styles.map((style) => ({ ...style, - type: AfterRedirectDynamicScriptResourceType.STYLES, + type: OpfDynamicScriptResourceType.STYLES, })), ]; - if (resources.length === 0) { - return Promise.reject(); - } return new Promise((resolve, reject) => { this.loadedResources = []; @@ -177,10 +174,10 @@ export class OpfResourceLoaderService extends ScriptLoader { this.markResourceAsLoaded(resource, resources, resolve); } else { switch (resource.type) { - case AfterRedirectDynamicScriptResourceType.SCRIPT: + case OpfDynamicScriptResourceType.SCRIPT: this.loadScript(resource, resources, resolve, reject); break; - case AfterRedirectDynamicScriptResourceType.STYLES: + case OpfDynamicScriptResourceType.STYLES: this.loadStyles(resource, resources, resolve, reject); break; default: From 56142ccca9943981d559ded7845e6ba52cb0e00d Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Mon, 23 Oct 2023 13:08:11 -0400 Subject: [PATCH 24/30] rename interface with more generic name --- integration-libs/opf/checkout/root/model/opf-payment.model.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-libs/opf/checkout/root/model/opf-payment.model.ts b/integration-libs/opf/checkout/root/model/opf-payment.model.ts index bc04345e9df..86d93d05ec3 100644 --- a/integration-libs/opf/checkout/root/model/opf-payment.model.ts +++ b/integration-libs/opf/checkout/root/model/opf-payment.model.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AfterRedirectDynamicScript } from '@spartacus/opf/base/root'; +import { OpfDynamicScript } from '@spartacus/opf/base/root'; export interface PaymentInitiationConfig { otpKey?: string; @@ -46,7 +46,7 @@ export interface PaymentSessionData { paymentIntent?: string; pattern?: string; destination?: PaymentDestination; - dynamicScript?: AfterRedirectDynamicScript; + dynamicScript?: OpfDynamicScript; } export interface PaymentDestination { From 733cd114ad726b6920525794ed65ad0f660ccfd9 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Mon, 23 Oct 2023 15:01:29 -0400 Subject: [PATCH 25/30] remove rejects to only support happy scenario --- .../root/services/opf-resource-loader.service.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) 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 27dade94368..db3df6f9631 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 @@ -89,8 +89,7 @@ export class OpfResourceLoaderService extends ScriptLoader { protected loadScript( resource: OpfDynamicScriptResource, resources: OpfDynamicScriptResource[], - resolve: (value: void | PromiseLike) => void, - reject: (value: void | PromiseLike) => void + resolve: (value: void | PromiseLike) => void ) { if (resource.url && !this.hasScript(resource.url)) { super.embedScript({ @@ -105,7 +104,6 @@ export class OpfResourceLoaderService extends ScriptLoader { }, errorCallback: () => { this.handleLoadingResourceError(resource.url); - reject(); }, }); } else { @@ -116,8 +114,7 @@ export class OpfResourceLoaderService extends ScriptLoader { protected loadStyles( resource: OpfDynamicScriptResource, resources: OpfDynamicScriptResource[], - resolve: (value: void | PromiseLike) => void, - reject: (value: void | PromiseLike) => void + resolve: (value: void | PromiseLike) => void ) { if (resource.url && !this.hasStyles(resource.url)) { this.embedStyles({ @@ -125,7 +122,6 @@ export class OpfResourceLoaderService extends ScriptLoader { callback: () => this.markResourceAsLoaded(resource, resources, resolve), errorCallback: () => { this.handleLoadingResourceError(resource.url); - reject(); }, }); } else { @@ -166,7 +162,7 @@ export class OpfResourceLoaderService extends ScriptLoader { })), ]; - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.loadedResources = []; resources.forEach((resource: OpfDynamicScriptResource) => { @@ -175,10 +171,10 @@ export class OpfResourceLoaderService extends ScriptLoader { } else { switch (resource.type) { case OpfDynamicScriptResourceType.SCRIPT: - this.loadScript(resource, resources, resolve, reject); + this.loadScript(resource, resources, resolve); break; case OpfDynamicScriptResourceType.STYLES: - this.loadStyles(resource, resources, resolve, reject); + this.loadStyles(resource, resources, resolve); break; default: break; From f8dcc102765081aaa84058be237d779d8d494405 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Mon, 23 Oct 2023 15:01:53 -0400 Subject: [PATCH 26/30] fix unit test --- ...f-checkout-payment-wrapper.service.spec.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/integration-libs/opf/checkout/components/opf-checkout-payment-wrapper/opf-checkout-payment-wrapper.service.spec.ts b/integration-libs/opf/checkout/components/opf-checkout-payment-wrapper/opf-checkout-payment-wrapper.service.spec.ts index f64fa00a33c..88a7b2bef63 100644 --- a/integration-libs/opf/checkout/components/opf-checkout-payment-wrapper/opf-checkout-payment-wrapper.service.spec.ts +++ b/integration-libs/opf/checkout/components/opf-checkout-payment-wrapper/opf-checkout-payment-wrapper.service.spec.ts @@ -7,7 +7,7 @@ import { UserIdService, } from '@spartacus/core'; import { - AfterRedirectDynamicScriptResourceType, + OpfDynamicScriptResourceType, OpfOrderFacade, OpfOtpFacade, OpfResourceLoaderService, @@ -118,13 +118,13 @@ describe('OpfCheckoutPaymentWrapperService', () => { jsUrls: [ { url: 'script.js', - type: AfterRedirectDynamicScriptResourceType.SCRIPT, + type: OpfDynamicScriptResourceType.SCRIPT, }, ], cssUrls: [ { url: 'styles.css', - type: AfterRedirectDynamicScriptResourceType.STYLES, + type: OpfDynamicScriptResourceType.STYLES, }, ], }, @@ -165,13 +165,13 @@ describe('OpfCheckoutPaymentWrapperService', () => { [ { url: 'script.js', - type: AfterRedirectDynamicScriptResourceType.SCRIPT, + type: OpfDynamicScriptResourceType.SCRIPT, }, ], [ { url: 'styles.css', - type: AfterRedirectDynamicScriptResourceType.STYLES, + type: OpfDynamicScriptResourceType.STYLES, }, ] ); @@ -182,13 +182,13 @@ describe('OpfCheckoutPaymentWrapperService', () => { jsUrls: [ { url: 'script.js', - type: AfterRedirectDynamicScriptResourceType.SCRIPT, + type: OpfDynamicScriptResourceType.SCRIPT, }, ], cssUrls: [ { url: 'styles.css', - type: AfterRedirectDynamicScriptResourceType.STYLES, + type: OpfDynamicScriptResourceType.STYLES, }, ], }, @@ -302,13 +302,13 @@ describe('OpfCheckoutPaymentWrapperService', () => { jsUrls: [ { url: 'script.js', - type: AfterRedirectDynamicScriptResourceType.SCRIPT, + type: OpfDynamicScriptResourceType.SCRIPT, }, ], cssUrls: [ { url: 'styles.css', - type: AfterRedirectDynamicScriptResourceType.STYLES, + type: OpfDynamicScriptResourceType.STYLES, }, ], }, @@ -326,13 +326,13 @@ describe('OpfCheckoutPaymentWrapperService', () => { [ { url: 'script.js', - type: AfterRedirectDynamicScriptResourceType.SCRIPT, + type: OpfDynamicScriptResourceType.SCRIPT, }, ], [ { url: 'styles.css', - type: AfterRedirectDynamicScriptResourceType.STYLES, + type: OpfDynamicScriptResourceType.STYLES, }, ] ); From 9a6bace41e1e69ee0ee94ae97440a465777fe116 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Wed, 25 Oct 2023 17:22:57 -0400 Subject: [PATCH 27/30] add cxSafeHtml pipe --- .../opf-cta-element/opf-cta-element.component.html | 2 +- .../opf-cta-element.component.spec.ts | 11 ----------- .../opf-cta-element/opf-cta-element.component.ts | 14 +------------- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.html b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.html index 190f83d79c2..6e9ed00faba 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.html +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.html @@ -1 +1 @@ -
+
diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.spec.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.spec.ts index 0324a128b8f..462eceb577f 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.spec.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DomSanitizer } from '@angular/platform-browser'; import { OpfCtaElementComponent } from './opf-cta-element.component'; describe('OpfCtaButton', () => { @@ -12,19 +11,9 @@ describe('OpfCtaButton', () => { }); fixture = TestBed.createComponent(OpfCtaElementComponent); component = fixture.componentInstance; - // opfCtaScriptsService = TestBed.inject(OpfCtaScriptsService); }); it('should create', () => { expect(component).toBeTruthy(); }); - - it('should bypass sanitizer', () => { - const htmlInput = - "

Thanks for purchasing our great products

Please use promo code:123abc for your next purchase

"; - const domSanitizer: DomSanitizer = TestBed.inject(DomSanitizer); - spyOn(domSanitizer, 'bypassSecurityTrustHtml').and.stub(); - component.renderHtml(htmlInput); - expect(domSanitizer.bypassSecurityTrustHtml).toHaveBeenCalled(); - }); }); diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.ts index da2aade3037..2fde76ae273 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.component.ts @@ -4,13 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - ChangeDetectionStrategy, - Component, - Input, - inject, -} from '@angular/core'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; @Component({ selector: 'cx-opf-cta-element', @@ -18,11 +12,5 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class OpfCtaElementComponent { - protected sanitizer = inject(DomSanitizer); - @Input() ctaScriptHtml: string; - - renderHtml(html: string): SafeHtml { - return this.sanitizer.bypassSecurityTrustHtml(html); - } } From 1f33ef88f8172450a4e006d39138e42f0e0a8ba2 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Wed, 25 Oct 2023 17:23:54 -0400 Subject: [PATCH 28/30] replace if/switch-case by mapping --- .../opf-cta-element/opf-cta-element.module.ts | 3 +- .../opf-cta-scripts.service.ts | 78 ++++++++++++------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.module.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.module.ts index 8ba875926ac..58accaee2db 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.module.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-element/opf-cta-element.module.ts @@ -6,11 +6,12 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { SafeHtmlModule } from '@spartacus/storefront'; import { OpfCtaElementComponent } from './opf-cta-element.component'; @NgModule({ declarations: [OpfCtaElementComponent], - imports: [CommonModule], + imports: [CommonModule, SafeHtmlModule], exports: [OpfCtaElementComponent], }) export class OpfCtaElementModule {} diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts index fb696e8b517..1efef9afc63 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts @@ -63,9 +63,10 @@ export class OpfCtaScriptsService { if (!ctaScriptsResponse?.value?.length) { return throwError('Invalid CTA Scripts Response'); } - return of( - ctaScriptsResponse.value.map((ctaScript) => ctaScript.dynamicScript) + const dynamicScripts = ctaScriptsResponse.value.map( + (ctaScript) => ctaScript.dynamicScript ); + return of(dynamicScripts); }), take(1) ); @@ -79,7 +80,7 @@ export class OpfCtaScriptsService { paymentAccountIds = accIds; return this.getScriptLocation(); }), - concatMap((scriptsLocation: CtaScriptsLocation | null) => { + concatMap((scriptsLocation: CtaScriptsLocation | undefined) => { return this.fillRequestForTargetPage( scriptsLocation, paymentAccountIds @@ -89,22 +90,42 @@ export class OpfCtaScriptsService { } protected fillRequestForTargetPage( - scriptsLocation: CtaScriptsLocation | null, + scriptsLocation: CtaScriptsLocation | undefined, paymentAccountIds: number[] ): Observable { - if (scriptsLocation === CtaScriptsLocation.PDP_QUICK_BUY) { - return this.fillCtaRequestforPDP(scriptsLocation, paymentAccountIds); - } else if ( - scriptsLocation === CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE || - scriptsLocation === CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE - ) { - return this.fillCtaRequestforPagesWithOrder( - scriptsLocation, - paymentAccountIds - ); - } else { + if (!scriptsLocation) { return throwError('Invalid Script Location'); } + const locationToFunctionMap: Record< + CtaScriptsLocation, + () => Observable + > = { + [CtaScriptsLocation.PDP_QUICK_BUY]: () => + this.fillCtaRequestforPDP(scriptsLocation, paymentAccountIds), + [CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE]: () => + this.fillCtaRequestforPagesWithOrder( + scriptsLocation, + paymentAccountIds + ), + [CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE]: () => + this.fillCtaRequestforPagesWithOrder( + scriptsLocation, + paymentAccountIds + ), + [CtaScriptsLocation.CART_MESSAGING]: () => + throwError('to be implemented'), + [CtaScriptsLocation.CART_QUICK_BUY]: () => + throwError('to be implemented'), + [CtaScriptsLocation.CHECKOUT_QUICK_BUY]: () => + throwError('to be implemented'), + [CtaScriptsLocation.PDP_MESSAGING]: () => throwError('to be implemented'), + }; + + const selectedFunction = locationToFunctionMap[scriptsLocation]; + + return selectedFunction + ? selectedFunction() + : throwError('Invalid Script Location'); } protected fillCtaRequestforPagesWithOrder( @@ -157,21 +178,22 @@ export class OpfCtaScriptsService { ); } - protected getScriptLocation(): Observable { + protected getScriptLocation(): Observable { + const cmsToCtaLocationMap: Record = { + [CmsPageLocation.ORDER_PAGE]: + CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE, + [CmsPageLocation.ORDER_CONFIRMATION_PAGE]: + CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE, + [CmsPageLocation.PDP_PAGE]: CtaScriptsLocation.PDP_QUICK_BUY, + [CmsPageLocation.CART_PAGE]: CtaScriptsLocation.CART_QUICK_BUY, + }; return this.cmsService.getCurrentPage().pipe( take(1), - map((page) => { - switch (page.pageId) { - case CmsPageLocation.ORDER_PAGE: - return CtaScriptsLocation.ORDER_HISTORY_PAYMENT_GUIDE; - case CmsPageLocation.ORDER_CONFIRMATION_PAGE: - return CtaScriptsLocation.ORDER_CONFIRMATION_PAYMENT_GUIDE; - case CmsPageLocation.PDP_PAGE: - return CtaScriptsLocation.PDP_QUICK_BUY; - default: - return null; - } - }) + map((page) => + page.pageId + ? cmsToCtaLocationMap[page.pageId as CmsPageLocation] + : undefined + ) ); } From 49dffc1c9bc3a550e2051e6d79232b55fc93c862 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Thu, 26 Oct 2023 12:49:21 -0400 Subject: [PATCH 29/30] remove console logs from unit tests --- .../opf-cta-scripts/opf-cta-scripts.service.spec.ts | 5 ----- .../opf/base/core/facade/opf-payment.service.spec.ts | 11 ++++------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.spec.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.spec.ts index 6ff662be501..d3c7365ac69 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.spec.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.spec.ts @@ -81,7 +81,6 @@ describe('OpfCtaScriptsService', () => { it('should call orderHistoryFacade for CTA on ConfirmationPage', (done) => { service.getCtaHtmlslList().subscribe((htmlsList) => { - console.log('htmlsList', htmlsList); expect(htmlsList[0]).toContain( 'Thanks for purchasing our great products' ); @@ -97,7 +96,6 @@ describe('OpfCtaScriptsService', () => { ); service.getCtaHtmlslList().subscribe((htmlsList) => { - console.log('htmlsList', htmlsList); expect(htmlsList[0]).toContain( 'Thanks for purchasing our great products' ); @@ -113,7 +111,6 @@ describe('OpfCtaScriptsService', () => { ); service.getCtaHtmlslList().subscribe((htmlsList) => { - console.log('htmlsList', htmlsList); expect(htmlsList[0]).toContain( 'Thanks for purchasing our great products' ); @@ -143,7 +140,6 @@ describe('OpfCtaScriptsService', () => { service.getCtaHtmlslList().subscribe({ error: (error) => { expect(error).toEqual('Invalid Script Location'); - done(); }, }); @@ -157,7 +153,6 @@ describe('OpfCtaScriptsService', () => { service.getCtaHtmlslList().subscribe({ next: (htmlsList) => { expect(htmlsList.length).toEqual(0); - done(); }, }); diff --git a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts index 2436a0fa4b5..86ee052aa43 100644 --- a/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts +++ b/integration-libs/opf/base/core/facade/opf-payment.service.spec.ts @@ -25,27 +25,24 @@ import { OpfPaymentService } from './opf-payment.service'; class MockPaymentConnector implements Partial { verifyPayment( - paymentSessionId: string, - payload: OpfPaymentVerificationPayload + _paymentSessionId: string, + _payload: OpfPaymentVerificationPayload ): Observable { - console.log(paymentSessionId, payload); return of({ result: 'result', }) as Observable; } afterRedirectScripts( - paymentSessionId: string + _paymentSessionId: string ): Observable { - console.log(paymentSessionId); return of({ afterRedirectScript: {} }); } getActiveConfigurations(): Observable { return of(mockActiveConfigurations); } getCtaScripts( - ctaScriptsRequest: CtaScriptsRequest + _ctaScriptsRequest: CtaScriptsRequest ): Observable { - console.log(ctaScriptsRequest); return of(MockCtaScriptsResponse); } } From 7c89c2f5c9aee90b94529a8b9254ec0fb99bc240 Mon Sep 17 00:00:00 2001 From: Florent Letendre Date: Thu, 26 Oct 2023 15:15:48 -0400 Subject: [PATCH 30/30] fix sonar --- .../opf-cta-scripts/opf-cta-scripts.service.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts index 1efef9afc63..c859271a078 100644 --- a/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts +++ b/integration-libs/opf/base/components/opf-cta/opf-cta-scripts/opf-cta-scripts.service.ts @@ -96,6 +96,7 @@ export class OpfCtaScriptsService { if (!scriptsLocation) { return throwError('Invalid Script Location'); } + const toBeImplementedException = () => throwError('to be implemented'); const locationToFunctionMap: Record< CtaScriptsLocation, () => Observable @@ -112,13 +113,10 @@ export class OpfCtaScriptsService { scriptsLocation, paymentAccountIds ), - [CtaScriptsLocation.CART_MESSAGING]: () => - throwError('to be implemented'), - [CtaScriptsLocation.CART_QUICK_BUY]: () => - throwError('to be implemented'), - [CtaScriptsLocation.CHECKOUT_QUICK_BUY]: () => - throwError('to be implemented'), - [CtaScriptsLocation.PDP_MESSAGING]: () => throwError('to be implemented'), + [CtaScriptsLocation.CART_MESSAGING]: toBeImplementedException, + [CtaScriptsLocation.CART_QUICK_BUY]: toBeImplementedException, + [CtaScriptsLocation.CHECKOUT_QUICK_BUY]: toBeImplementedException, + [CtaScriptsLocation.PDP_MESSAGING]: toBeImplementedException, }; const selectedFunction = locationToFunctionMap[scriptsLocation];