Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CTA Scripts feature #17991

Merged
merged 35 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ddfaf96
add cta-scripts cms cpnt
FollowTheFlo Oct 11, 2023
aed0c6c
Merge branch 'epic/opf' into feature/CXSPA-4532
FollowTheFlo Oct 11, 2023
3c93b44
add service
FollowTheFlo Oct 11, 2023
7983ac9
implement script list + button + css
FollowTheFlo Oct 13, 2023
787f93e
modif terminology - unit tests
FollowTheFlo Oct 16, 2023
99e4f45
add cta script for pdp - remove logs - error handling
FollowTheFlo Oct 16, 2023
ad897cd
add unit tests
FollowTheFlo Oct 16, 2023
0d6d509
add unit tests - improve rxjs piping
FollowTheFlo Oct 19, 2023
ef918e3
Add license header
github-actions[bot] Oct 19, 2023
0589a1d
improve logic and naming
FollowTheFlo Oct 19, 2023
d8a190d
simplify removeScriptTags fct
FollowTheFlo Oct 19, 2023
dba6fda
fix sonar probs
FollowTheFlo Oct 19, 2023
1783342
improve unit tests - simplify cpnt logic
FollowTheFlo Oct 19, 2023
03c6766
add changeDetection
FollowTheFlo Oct 19, 2023
cf74564
remove css of cta wrapper as redundant
FollowTheFlo Oct 20, 2023
9fd5e10
rename cta-button to cta-element
FollowTheFlo Oct 20, 2023
ec9d4f9
re-organize folders
FollowTheFlo Oct 20, 2023
8ee7d50
simplify rxjs piping
FollowTheFlo Oct 20, 2023
40c490f
refactor unit test to avoid redunduncy
FollowTheFlo Oct 20, 2023
376174a
remove comments
FollowTheFlo Oct 20, 2023
03df28c
fix sonar warning
FollowTheFlo Oct 20, 2023
ad53c0d
adjust naming and modules
FollowTheFlo Oct 20, 2023
7fd6890
reject when empty resources
FollowTheFlo Oct 20, 2023
fd0bb5d
add unit test for resource-loader service
FollowTheFlo Oct 23, 2023
9c0a7ba
Merge branch 'epic/opf' into feature/CXSPA-4532
FollowTheFlo Oct 23, 2023
56142cc
rename interface with more generic name
FollowTheFlo Oct 23, 2023
733cd11
remove rejects to only support happy scenario
FollowTheFlo Oct 23, 2023
f8dcc10
fix unit test
FollowTheFlo Oct 23, 2023
9336252
Merge branch 'epic/opf' into feature/CXSPA-4532
FollowTheFlo Oct 25, 2023
9513819
Merge branch 'epic/opf' into feature/CXSPA-4532
FollowTheFlo Oct 25, 2023
9a6bace
add cxSafeHtml pipe
FollowTheFlo Oct 25, 2023
1f33ef8
replace if/switch-case by mapping
FollowTheFlo Oct 25, 2023
49dffc1
remove console logs from unit tests
FollowTheFlo Oct 26, 2023
7c89c2f
fix sonar
FollowTheFlo Oct 26, 2023
1f0e5b0
Merge branch 'epic/opf' into feature/CXSPA-4532
FollowTheFlo Oct 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration-libs/opf/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 !default;
cx-opf-checkout-payment-wrapper, cx-opf-error-modal, cx-opf-cta-element !default;

$skipComponentStyles: () !default;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
*/

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],
imports: [OpfErrorModalModule, OpfCtaScriptsModule, OpfCtaElementModule],
providers: [],
})
export class OpfBaseComponentsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

export * from './opf-cta-element.component';
export * from './opf-cta-element.module';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div *ngIf="ctaScriptHtml" [innerHTML]="renderHtml(ctaScriptHtml)"></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DomSanitizer } from '@angular/platform-browser';
import { OpfCtaElementComponent } from './opf-cta-element.component';

describe('OpfCtaButton', () => {
let component: OpfCtaElementComponent;
let fixture: ComponentFixture<OpfCtaElementComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [OpfCtaElementComponent],
});
fixture = TestBed.createComponent(OpfCtaElementComponent);
component = fixture.componentInstance;
// opfCtaScriptsService = TestBed.inject(OpfCtaScriptsService);
FollowTheFlo marked this conversation as resolved.
Show resolved Hide resolved
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should bypass sanitizer', () => {
const htmlInput =
"<div><h2>Thanks for purchasing our great products</h2><h3>Please use promo code:<b>123abc</b> for your next purchase<h3></div><script>console.log('CTA Script #1 is running')</script><script>console.log('CTA Script #2 is running')</script>";
const domSanitizer: DomSanitizer = TestBed.inject(DomSanitizer);
spyOn(domSanitizer, 'bypassSecurityTrustHtml').and.stub();
component.renderHtml(htmlInput);
expect(domSanitizer.bypassSecurityTrustHtml).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import {
ChangeDetectionStrategy,
Component,
Input,
inject,
} from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
selector: 'cx-opf-cta-element',
templateUrl: './opf-cta-element.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OpfCtaElementComponent {
protected sanitizer = inject(DomSanitizer);

@Input() ctaScriptHtml: string;

renderHtml(html: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(html);
FollowTheFlo marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { OpfCtaElementComponent } from './opf-cta-element.component';

@NgModule({
declarations: [OpfCtaElementComponent],
imports: [CommonModule],
exports: [OpfCtaElementComponent],
})
export class OpfCtaElementModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

export * from './opf-cta-scripts.component';
export * from './opf-cta-scripts.module';
export * from './opf-cta-scripts.service';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<ng-container *ngIf="ctaHtmls$ | async as ctaHtmls; else loading">
<ng-container *ngIf="ctaHtmls?.length">
<cx-opf-cta-element
*ngFor="let ctaHtml of ctaHtmls"
[ctaScriptHtml]="ctaHtml"
></cx-opf-cta-element>
</ng-container>
</ng-container>
<ng-template #loading>
<div class="cx-spinner">
<cx-spinner></cx-spinner>
</div>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { OpfCtaWrapperComponent as OpfCtaScriptsComponent } from './opf-cta-scripts.component';
import { OpfCtaScriptsService } from './opf-cta-scripts.service';
import createSpy = jasmine.createSpy;

const mockHtmlsList = [
'<div style="border-style: solid;text-align:center;border-radius:10px;align-content:center;background-color:yellow;color:black"><h2>Thanks for purchasing our great products</h2><h3>Please use promo code:<b>123abc</b> for your next purchase<h3></div><script>console.log(\'CTA Script #1 is running\')</script>',
'<div style="border-style: solid;text-align:center;border-radius:10px;align-content:center;background-color:yellow;color:black"><h2>Thanks again for purchasing our great products</h2><h3>Please use promo code:<b>123abc</b> for your next purchase<h3></div><script>console.log(\'CTA Script #2 is running\')</script>',
];
const ctaElementSelector = 'cx-opf-cta-element';
describe('OpfCtaScriptsComponent', () => {
let component: OpfCtaScriptsComponent;
let fixture: ComponentFixture<OpfCtaScriptsComponent>;
let opfCtaScriptsService: jasmine.SpyObj<OpfCtaScriptsService>;

const createComponentInstance = () => {
fixture = TestBed.createComponent(OpfCtaScriptsComponent);
component = fixture.componentInstance;
};
beforeEach(() => {
opfCtaScriptsService = jasmine.createSpyObj('OpfCtaScriptsService', [
'getCtaHtmlslList',
]);

TestBed.configureTestingModule({
declarations: [OpfCtaScriptsComponent],
providers: [
{ provide: OpfCtaScriptsService, useValue: opfCtaScriptsService },
],
}).compileComponents();
});

beforeEach(() => {
opfCtaScriptsService.getCtaHtmlslList.and.returnValue(of(mockHtmlsList));
createComponentInstance();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should return Htmls list and display ctaButton elements', (done) => {
component.ctaHtmls$.subscribe((htmlList) => {
expect(htmlList[0]).toBeTruthy();
fixture.detectChanges();
expect(
fixture.nativeElement.querySelectorAll(ctaElementSelector).length
).toEqual(2);
done();
});
});

it('should isError be true when error is thrown', (done) => {
opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue(
throwError('error')
);
createComponentInstance();
Copy link
Contributor Author

@FollowTheFlo FollowTheFlo Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to have opfCtaScriptsService.getCtaHtmlslList new value generated (line 55), I had to force creation of new component. I couldn't find other solutions.
It works if we mock getCtaHtmlslList with a rxJs Subject but it then causes issue with removing subscriptions, needs to pipe with take(1) and side effect if both 'it' would run in parallel...

component.ctaHtmls$.subscribe((htmlList) => {
expect(htmlList).toEqual([]);
fixture.detectChanges();
expect(
fixture.nativeElement.querySelector(ctaElementSelector)
).toBeFalsy();
done();
});
});

it('should display spinner when html list is undefined', (done) => {
opfCtaScriptsService.getCtaHtmlslList = createSpy().and.returnValue(
of(undefined)
);
createComponentInstance();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same situation as line 58 commented above

fixture.detectChanges();
expect(fixture.nativeElement.querySelector('cx-spinner')).toBeTruthy();
done();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { OpfCtaScriptsService } from './opf-cta-scripts.service';

@Component({
selector: 'cx-opf-cta-scripts',
templateUrl: './opf-cta-scripts.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OpfCtaWrapperComponent {
protected opfCtaScriptService = inject(OpfCtaScriptsService);

ctaHtmls$ = this.opfCtaScriptService.getCtaHtmlslList().pipe(
catchError(() => {
return of([]);
})
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2023 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import {
CmsConfig,
FeaturesConfig,
provideDefaultConfig,
} from '@spartacus/core';
import { SpinnerModule } from '@spartacus/storefront';
import { OpfCtaElementModule } from '../opf-cta-element';
import { OpfCtaWrapperComponent as OpfCtaScriptsComponent } from './opf-cta-scripts.component';

@NgModule({
declarations: [OpfCtaScriptsComponent],
providers: [
provideDefaultConfig(<CmsConfig | FeaturesConfig>{
cmsComponents: {
OpfCtaScriptsComponent: {
component: OpfCtaScriptsComponent,
},
},
}),
],
exports: [OpfCtaScriptsComponent],
imports: [CommonModule, OpfCtaElementModule, SpinnerModule],
})
export class OpfCtaScriptsModule {}
Loading
Loading