Skip to content

Commit

Permalink
feat: add child component to productDetailsTab (#15808)
Browse files Browse the repository at this point in the history
A child component is injected within ProductDetails tab, below the product details text. 
This change has been made to allow PDF component to be displayed below product details.
  • Loading branch information
FollowTheFlo committed Jun 2, 2022
1 parent 31d0633 commit 58fc267
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 12 deletions.
4 changes: 4 additions & 0 deletions projects/core/src/model/cms.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export interface CmsComponent {
styleClasses?: string;
}

export interface CmsComponentWithChildren extends CmsComponent {
children?: string;
}

export enum PageType {
CONTENT_PAGE = 'ContentPage',
PRODUCT_PAGE = 'ProductPage',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
<ng-container *ngIf="product$ | async as product">
<div class="container" [innerHTML]="product?.description"></div>
</ng-container>
<ng-container *ngIf="children$ | async as children">
<ng-container *ngFor="let child of children">
<ng-container *ngIf="child">
<ng-template [cxOutlet]="child.flexType" [cxOutletContext]="{}">
<div class="container">
<ng-container [cxComponentWrapper]="child"></ng-container>
</div>
</ng-template>
</ng-container>
</ng-container>
</ng-container>
Original file line number Diff line number Diff line change
@@ -1,17 +1,67 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { Product } from '@spartacus/core';
import { Observable, of } from 'rxjs';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { CmsComponentWithChildren, CmsService, Product } from '@spartacus/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { CmsComponentData } from '../../../../cms-structure/page/model/cms-component-data';
import { CurrentProductService } from '../../current-product.service';
import { ProductDetailsTabComponent } from './product-details-tab.component';

const mockProduct: Product = { name: 'mockProduct' };
const mockCmsComponentWithChildren: CmsComponentWithChildren = {
name: 'Product Details Tab',
typeCode: 'CMSFlexComponent',
uid: 'testUid',
children: 'TestPDFComponent',
};

const mockCmsComponentWithNoChildren: CmsComponentWithChildren = {
name: 'Product Details Tab',
typeCode: 'CMSFlexComponent',
uid: 'testUid',
};

const mockPDFComponent = {
ui: 'TestPDFComponent',
uuid: 'PDFComponent',
typeCode: 'PDFDocumentComponent',
name: 'TestPDFName',
container: false,
pdfFile: {
code: 'test-pdf',
mime: 'application/pdf',
url: '/medias/test.pdf?context=bWFzdGVyfGl',
},
synchronizationBlocked: false,
title: 'ProductDetails',
parents: 'ProductDetailsTabComponent',
height: '200',
modifiedTime: '2022-05-20T13:07:22.277Z',
};

class MockCurrentProductService {
getProduct(): Observable<Product> {
return of(mockProduct);
}
}

const data$: BehaviorSubject<CmsComponentWithChildren> = new BehaviorSubject(
mockCmsComponentWithChildren
);

class MockCmsComponentData {
get data$(): Observable<CmsComponentWithChildren> {
return data$.asObservable();
}
}

class MockCmsService {
getComponentData(component: string): Observable<any | null> {
if (component === 'TestPDFComponent') {
return of(mockPDFComponent);
}
return of(null);
}
}

describe('ProductDetailsTabComponent', () => {
let productDetailsTabComponent: ProductDetailsTabComponent;
let fixture: ComponentFixture<ProductDetailsTabComponent>;
Expand All @@ -25,6 +75,14 @@ describe('ProductDetailsTabComponent', () => {
provide: CurrentProductService,
useClass: MockCurrentProductService,
},
{
provide: CmsComponentData,
useClass: MockCmsComponentData,
},
{
provide: CmsService,
useClass: MockCmsService,
},
],
}).compileComponents();
})
Expand All @@ -41,10 +99,47 @@ describe('ProductDetailsTabComponent', () => {

it('should get product', () => {
productDetailsTabComponent.ngOnInit();
let result: Product;
let result: Product | null | undefined;
productDetailsTabComponent.product$.subscribe(
(product) => (result = product)
);
expect(result).toEqual(mockProduct);
});

it('should get undefined children when child cmsComponents are unknown', () => {
let result: any[] | undefined;
data$.next({
...mockCmsComponentWithChildren,
children: 'testCpntOne testCpntTwo',
});

productDetailsTabComponent.children$
.subscribe((children) => (result = children))
.unsubscribe();
expect(result).toEqual([undefined, undefined]);
});

it('should get children containing cms PDFComponent', () => {
let result: any[] | undefined;
data$.next({ ...mockCmsComponentWithChildren });
productDetailsTabComponent.children$
.subscribe((children) => (result = children))
.unsubscribe();

expect(result).toContain({
...mockPDFComponent,
flexType: mockPDFComponent.typeCode,
});
});

it('should get undefined children when cmsComponent has no children property', () => {
data$.next({
...mockCmsComponentWithNoChildren,
});
let result: any[] | undefined;
productDetailsTabComponent.children$
.subscribe((children) => (result = children))
.unsubscribe();
expect(result).toEqual([undefined]);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Product } from '@spartacus/core';
import { Observable } from 'rxjs';
import { CmsComponentWithChildren, CmsService, Product } from '@spartacus/core';
import { combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { CmsComponentData } from '../../../../cms-structure/page/model/cms-component-data';
import { CurrentProductService } from '../../current-product.service';

@Component({
Expand All @@ -11,7 +13,35 @@ import { CurrentProductService } from '../../current-product.service';
export class ProductDetailsTabComponent implements OnInit {
product$: Observable<Product | null>;

constructor(protected currentProductService: CurrentProductService) {}
constructor(
protected currentProductService: CurrentProductService,
protected componentData: CmsComponentData<CmsComponentWithChildren>,
protected cmsService: CmsService
) {}
children$: Observable<any[]> = this.componentData.data$.pipe(
switchMap((data) =>
combineLatest(
(data?.children ?? '').split(' ').map((component) =>
this.cmsService.getComponentData<any>(component).pipe(
distinctUntilChanged(),
map((child) => {
if (!child) {
return undefined;
}
if (!child.flexType) {
child = {
...child,
flexType: child.typeCode,
};
}

return child;
})
)
)
)
)
);

ngOnInit() {
this.product$ = this.currentProductService.getProduct();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { NgModule } from '@angular/core';
import { CmsConfig, provideDefaultConfig } from '@spartacus/core';
import { OutletModule } from './../../../../cms-structure/outlet/outlet.module';
import { PageComponentModule } from './../../../../cms-structure/page/component/page-component.module';
import { ProductDetailsTabComponent } from './product-details-tab.component';

@NgModule({
imports: [CommonModule],
imports: [CommonModule, PageComponentModule, OutletModule],
providers: [
provideDefaultConfig(<CmsConfig>{
cmsComponents: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
%cx-pdf {
.pdf-container {
padding: 0.938rem;
padding-top: 0.938rem;
font-size: var(--cx-font-size, 0.875rem);
a {
color: var(--cx-color-text);
line-height: 1.1875rem;
text-decoration: underline;
font-weight: 600;
}
cx-icon {
margin-inline-start: 0.625rem;
margin-inline-start: 0.3rem;
background-color: transparent;
border: none;
text-decoration: none;
Expand Down

0 comments on commit 58fc267

Please sign in to comment.