Skip to content

Commit

Permalink
feat: display only available products in product links carousels and …
Browse files Browse the repository at this point in the history
…lists (#626)


Co-authored-by: Danilo Hoffmann <hoffmann@evident.nl>
Co-authored-by: Silke <s.grueber@intershop.de>
Co-authored-by: Stefan Hauke <s.hauke@intershop.de>
  • Loading branch information
4 people authored May 26, 2021
1 parent 76c9074 commit 386a584
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
<div *ngIf="links.products.length" class="product-list-container">
<h2>{{ productLinkTitle }}</h2>
<ng-container *ngIf="productSKUs$ | async as productSKUs">
<div *ngIf="productSKUs.length" class="product-list-container">
<h2>{{ productLinkTitle }}</h2>

<div class="product-list">
<swiper [config]="swiperConfig">
<div *ngFor="let sku of links.products" class="swiper-slide">
<ng-template swiperSlide>
<ish-product-item ishProductContext [sku]="sku"></ish-product-item>
</ng-template>
</div>
</swiper>
<div class="swiper-pagination"></div>
<div class="product-list">
<swiper [config]="swiperConfig">
<div *ngFor="let sku of productSKUs" class="swiper-slide">
<ng-template swiperSlide>
<ish-product-item ishProductContext [sku]="sku"></ish-product-item>
</ng-template>
</div>
</swiper>
<div class="swiper-pagination"></div>
</div>
</div>
</div>
</ng-container>
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MockComponent, MockDirective } from 'ng-mocks';
import { of } from 'rxjs';
import { SwiperComponent } from 'swiper/angular';
import { anything, instance, mock, when } from 'ts-mockito';

import { ProductContextDirective } from 'ish-core/directives/product-context.directive';
import { ShoppingFacade } from 'ish-core/facades/shopping.facade';
import { ProductLinks } from 'ish-core/models/product-links/product-links.model';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
import { ProductItemComponent } from 'ish-shared/components/product/product-item/product-item.component';

import { ProductLinksCarouselComponent } from './product-links-carousel.component';
Expand All @@ -12,20 +16,24 @@ describe('Product Links Carousel Component', () => {
let component: ProductLinksCarouselComponent;
let fixture: ComponentFixture<ProductLinksCarouselComponent>;
let element: HTMLElement;
let shoppingFacade: ShoppingFacade;

beforeEach(async () => {
shoppingFacade = mock(ShoppingFacade);

await TestBed.configureTestingModule({
declarations: [
MockComponent(ProductItemComponent),
MockComponent(SwiperComponent),
MockDirective(ProductContextDirective),
ProductLinksCarouselComponent,
],
providers: [{ provide: ShoppingFacade, useFactory: () => instance(shoppingFacade) }],
}).compileComponents();
});

beforeEach(() => {
const productLink = { products: ['sku'], categories: ['catID'] } as ProductLinks;
const productLink = { products: ['sku1', 'sku2', 'sku3'], categories: ['catID'] } as ProductLinks;

fixture = TestBed.createComponent(ProductLinksCarouselComponent);
component = fixture.componentInstance;
Expand All @@ -36,7 +44,28 @@ describe('Product Links Carousel Component', () => {
it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => component.ngOnChanges()).not.toThrow();
expect(() => fixture.detectChanges()).not.toThrow();
expect(element.querySelector('swiper')).toBeTruthy();
});

it('should render all product slides if stocks filtering is off', () => {
component.displayOnlyAvailableProducts = false;
component.ngOnChanges();
fixture.detectChanges();

expect(element.querySelectorAll('.swiper-slide')).toHaveLength(3);
});

it('should render only available product slides if stocks filtering is on', () => {
when(shoppingFacade.product$(anything(), anything())).thenCall(sku =>
of({ sku, available: sku !== 'sku2' } as ProductView)
);

component.displayOnlyAvailableProducts = true;
component.ngOnChanges();
fixture.detectChanges();

expect(element.querySelectorAll('.swiper-slide')).toHaveLength(2);
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { ChangeDetectionStrategy, Component, Inject, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Inject, Input, OnChanges } from '@angular/core';
import { Observable, combineLatest, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { SwiperOptions } from 'swiper';
import SwiperCore, { Navigation, Pagination } from 'swiper/core';

import { LARGE_BREAKPOINT_WIDTH, MEDIUM_BREAKPOINT_WIDTH } from 'ish-core/configurations/injection-keys';
import { ShoppingFacade } from 'ish-core/facades/shopping.facade';
import { ProductLinks } from 'ish-core/models/product-links/product-links.model';
import { ProductCompletenessLevel } from 'ish-core/models/product/product.model';

SwiperCore.use([Navigation, Pagination]);

Expand All @@ -21,7 +25,7 @@ SwiperCore.use([Navigation, Pagination]);
templateUrl: './product-links-carousel.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductLinksCarouselComponent {
export class ProductLinksCarouselComponent implements OnChanges {
/**
* list of products which are assigned to the specific product link type
*/
Expand All @@ -30,6 +34,12 @@ export class ProductLinksCarouselComponent {
* title that should displayed for the specific product link type
*/
@Input() productLinkTitle: string;
/**
* display only available products if set to 'true'
*/
@Input() displayOnlyAvailableProducts = false;

productSKUs$: Observable<string[]>;

/**
* configuration of swiper carousel
Expand All @@ -39,7 +49,8 @@ export class ProductLinksCarouselComponent {

constructor(
@Inject(LARGE_BREAKPOINT_WIDTH) largeBreakpointWidth: number,
@Inject(MEDIUM_BREAKPOINT_WIDTH) mediumBreakpointWidth: number
@Inject(MEDIUM_BREAKPOINT_WIDTH) mediumBreakpointWidth: number,
private shoppingFacade: ShoppingFacade
) {
this.swiperConfig = {
direction: 'horizontal',
Expand Down Expand Up @@ -68,4 +79,12 @@ export class ProductLinksCarouselComponent {
},
};
}

ngOnChanges() {
this.productSKUs$ = this.displayOnlyAvailableProducts
? combineLatest(
this.links.products.map(sku => this.shoppingFacade.product$(sku, ProductCompletenessLevel.List))
).pipe(map(products => products.filter(p => p.available).map(p => p.sku)))
: of(this.links.products);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<div *ngIf="links.products.length" class="product-list-container">
<h2>{{ productLinkTitle }}</h2>
<div class="product-list">
<div *ngFor="let sku of links.products" class="product-list-item list-view">
<ish-product-item ishProductContext [sku]="sku" displayType="row"></ish-product-item>
<ng-container *ngIf="productSKUs$ | async as productSKUs">
<div *ngIf="productSKUs.length" class="product-list-container">
<h2>{{ productLinkTitle }}</h2>
<div class="product-list">
<div *ngFor="let sku of productSKUs" class="product-list-item list-view">
<ish-product-item ishProductContext [sku]="sku" displayType="row"></ish-product-item>
</div>
</div>
</div>
</div>
</ng-container>
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MockComponent, MockDirective } from 'ng-mocks';
import { of } from 'rxjs';
import { anything, instance, mock, when } from 'ts-mockito';

import { ProductContextDirective } from 'ish-core/directives/product-context.directive';
import { ShoppingFacade } from 'ish-core/facades/shopping.facade';
import { ProductLinks } from 'ish-core/models/product-links/product-links.model';
import { ProductView } from 'ish-core/models/product-view/product-view.model';
import { ProductItemComponent } from 'ish-shared/components/product/product-item/product-item.component';

import { ProductLinksListComponent } from './product-links-list.component';
Expand All @@ -11,19 +15,23 @@ describe('Product Links List Component', () => {
let component: ProductLinksListComponent;
let fixture: ComponentFixture<ProductLinksListComponent>;
let element: HTMLElement;
let shoppingFacade: ShoppingFacade;

beforeEach(async () => {
shoppingFacade = mock(ShoppingFacade);

await TestBed.configureTestingModule({
declarations: [
MockComponent(ProductItemComponent),
MockDirective(ProductContextDirective),
ProductLinksListComponent,
],
providers: [{ provide: ShoppingFacade, useFactory: () => instance(shoppingFacade) }],
}).compileComponents();
});

beforeEach(() => {
const productLink = { products: ['sku'], categories: ['catID'] } as ProductLinks;
const productLink = { products: ['sku1', 'sku2', 'sku3'], categories: ['catID'] } as ProductLinks;

fixture = TestBed.createComponent(ProductLinksListComponent);
component = fixture.componentInstance;
Expand All @@ -34,21 +42,28 @@ describe('Product Links List Component', () => {
it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => component.ngOnChanges()).not.toThrow();
expect(() => fixture.detectChanges()).not.toThrow();
expect(element).toMatchInlineSnapshot(`
<div class="product-list-container">
<h2></h2>
<div class="product-list">
<div class="product-list-item list-view">
<ish-product-item
displaytype="row"
ishproductcontext=""
ng-reflect-display-type="row"
ng-reflect-sku="sku"
></ish-product-item>
</div>
</div>
</div>
`);
expect(element.querySelector('.product-list')).toBeTruthy();
});

it('should render all product slides if stocks filtering is off', () => {
component.displayOnlyAvailableProducts = false;
component.ngOnChanges();
fixture.detectChanges();

expect(element.querySelectorAll('ish-product-item')).toHaveLength(3);
});

it('should render only available products if stocks filtering is on', () => {
when(shoppingFacade.product$(anything(), anything())).thenCall(sku =>
of({ sku, available: sku !== 'sku2' } as ProductView)
);

component.displayOnlyAvailableProducts = true;
component.ngOnChanges();
fixture.detectChanges();

expect(element.querySelectorAll('ish-product-item')).toHaveLength(2);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { Observable, combineLatest, of } from 'rxjs';
import { map } from 'rxjs/operators';

import { ShoppingFacade } from 'ish-core/facades/shopping.facade';
import { ProductLinks } from 'ish-core/models/product-links/product-links.model';
import { ProductCompletenessLevel } from 'ish-core/models/product/product.helper';

/**
* The Product Link List Component
Expand All @@ -16,7 +20,7 @@ import { ProductLinks } from 'ish-core/models/product-links/product-links.model'
templateUrl: './product-links-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductLinksListComponent {
export class ProductLinksListComponent implements OnChanges {
/**
* list of products which are assigned to the specific product link type
*/
Expand All @@ -25,4 +29,20 @@ export class ProductLinksListComponent {
* title that should displayed for the specific product link type
*/
@Input() productLinkTitle: string;
/**
* display only available products if set to 'true'
*/
@Input() displayOnlyAvailableProducts = false;

productSKUs$: Observable<string[]>;

constructor(private shoppingFacade: ShoppingFacade) {}

ngOnChanges() {
this.productSKUs$ = this.displayOnlyAvailableProducts
? combineLatest(
this.links.products.map(sku => this.shoppingFacade.product$(sku, ProductCompletenessLevel.List))
).pipe(map(products => products.filter(p => p.available).map(p => p.sku)))
: of(this.links.products);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ish-product-links-list
[links]="links.upselling"
[productLinkTitle]="'product.product_links.upselling.title' | translate"
[displayOnlyAvailableProducts]="true"
></ish-product-links-list>
</ng-container>
<ng-container *ngIf="links.spareparts?.products?.length">
Expand All @@ -15,6 +16,7 @@
<ish-product-links-carousel
[links]="links.crossselling"
[productLinkTitle]="'product.product_links.crossselling.title' | translate"
[displayOnlyAvailableProducts]="true"
></ish-product-links-carousel>
</ng-container>
<ng-container *ngIf="links.accessory?.products?.length">
Expand Down

0 comments on commit 386a584

Please sign in to comment.