From 3ea5ee930fa5c5ee088d84f85f00ddfb8a634194 Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Wed, 18 Dec 2019 16:44:07 +0100 Subject: [PATCH] fix: repair memoization of promotion selectors (#70) --- src/app/core/facades/shopping.facade.ts | 4 +- .../promotions/promotions.selectors.spec.ts | 29 ++++++++++++-- .../promotions/promotions.selectors.ts | 39 ++++++++++++------- .../basket-promotion.component.spec.ts | 5 ++- .../product-promotion.component.spec.ts | 3 +- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/app/core/facades/shopping.facade.ts b/src/app/core/facades/shopping.facade.ts index 72ff9b156e..473168cf34 100644 --- a/src/app/core/facades/shopping.facade.ts +++ b/src/app/core/facades/shopping.facade.ts @@ -192,13 +192,13 @@ export class ShoppingFacade { // PROMOTIONS promotion$(promotionId: string) { this.store.dispatch(new LoadPromotion({ promoId: promotionId })); - return this.store.pipe(select(getPromotion, { promoId: promotionId })); + return this.store.pipe(select(getPromotion(), { promoId: promotionId })); } promotions$(promotionIds: string[]) { promotionIds.forEach(promotionId => { this.store.dispatch(new LoadPromotion({ promoId: promotionId })); }); - return this.store.pipe(select(getPromotions, { promotionIds })); + return this.store.pipe(select(getPromotions(), { promotionIds })); } } diff --git a/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts b/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts index 64aec149b0..4fc75c01f7 100644 --- a/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts +++ b/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts @@ -63,16 +63,39 @@ describe('Promotions Selectors', () => { describe('state with a promotion', () => { beforeEach(() => { store$.dispatch(new LoadPromotionSuccess({ promotion: promo })); + store$.dispatch(new LoadPromotionSuccess({ promotion: promo1 })); }); describe('but no current router state', () => { it('should return the promotion information when used', () => { - expect(getPromotionEntities(store$.state)).toEqual({ [promo.id]: promo }); + expect(getPromotionEntities(store$.state)).toMatchInlineSnapshot(` + Object { + "id": Object { + "id": "id", + }, + "id1": Object { + "id": "id1", + }, + } + `); }); it('should return a promotion stub if promotion is selected', () => { - expect(getPromotion(store$.state, { promoId: promo.id })).toBeTruthy(); - expect(getPromotions(store$.state, { promotionIds: [promo.id, promo1.id] })).toBeTruthy(); + expect(getPromotion()(store$.state, { promoId: promo.id })).toMatchInlineSnapshot(` + Object { + "id": "id", + } + `); + expect(getPromotions()(store$.state, { promotionIds: [promo.id, promo1.id] })).toMatchInlineSnapshot(` + Array [ + Object { + "id": "id", + }, + Object { + "id": "id1", + }, + ] + `); }); }); }); diff --git a/src/app/core/store/shopping/promotions/promotions.selectors.ts b/src/app/core/store/shopping/promotions/promotions.selectors.ts index 3e8444e640..6a892c517a 100644 --- a/src/app/core/store/shopping/promotions/promotions.selectors.ts +++ b/src/app/core/store/shopping/promotions/promotions.selectors.ts @@ -1,5 +1,11 @@ -import { createSelector } from '@ngrx/store'; -import { memoize } from 'lodash-es'; +import { + DefaultProjectorFn, + MemoizedSelectorWithProps, + createSelector, + createSelectorFactory, + defaultMemoize, +} from '@ngrx/store'; +import { isEqual } from 'lodash-es'; import { Promotion } from 'ish-core/models/promotion/promotion.model'; import { ShoppingState, getShoppingState } from 'ish-core/store/shopping/shopping-store'; @@ -15,17 +21,20 @@ export const { selectEntities: getPromotionEntities, selectAll: getAllPromotions getPromotionsState ); -export const getPromotion = createSelector( - getPromotionEntities, - (entities, props: { promoId: string }) => entities[props.promoId] -); +export const getPromotion = () => + createSelector( + getPromotionEntities, + (entities, props: { promoId: string }): Promotion => entities[props.promoId] + ); -export const getPromotions = createSelector( - getAllPromotions, - memoize( - (promotions, props): Promotion[] => - props.promotionIds.map(id => promotions.find(p => p.id === id)).filter(x => !!x), - (promotions: Promotion[], props: { promotionIds: string[] }) => - `${props.promotionIds.join()}#${promotions.map(p => p.id).join()}` - ) -); +export const getPromotions = (): MemoizedSelectorWithProps< + object, + { promotionIds: string[] }, + Promotion[], + DefaultProjectorFn +> => + createSelectorFactory(projector => defaultMemoize(projector, isEqual, isEqual))( + getAllPromotions, + (promotions: Promotion[], props: { promotionIds: string[] }): Promotion[] => + props.promotionIds.map(id => promotions.find(p => p.id === id)).filter(x => !!x) + ); diff --git a/src/app/shared/components/basket/basket-promotion/basket-promotion.component.spec.ts b/src/app/shared/components/basket/basket-promotion/basket-promotion.component.spec.ts index 45db61822c..7141c06fd8 100644 --- a/src/app/shared/components/basket/basket-promotion/basket-promotion.component.spec.ts +++ b/src/app/shared/components/basket/basket-promotion/basket-promotion.component.spec.ts @@ -8,6 +8,7 @@ import { instance, mock, when } from 'ts-mockito'; import { ServerHtmlDirective } from 'ish-core/directives/server-html.directive'; import { ShoppingFacade } from 'ish-core/facades/shopping.facade'; import { BasketRebate } from 'ish-core/models/basket-rebate/basket-rebate.model'; +import { Promotion } from 'ish-core/models/promotion/promotion.model'; import { getICMBaseURL } from 'ish-core/store/configuration'; import { PromotionDetailsComponent } from 'ish-shared/components/promotion/promotion-details/promotion-details.component'; @@ -51,7 +52,7 @@ describe('Basket Promotion Component', () => { id: 'PROMO_UUID', title: 'MyPromotionTitle', disableMessages: false, - }) + } as Promotion) ); component.rebate = { @@ -74,7 +75,7 @@ describe('Basket Promotion Component', () => { id: 'PROMO_UUID', title: 'MyPromotionTitle', disableMessages: true, - }) + } as Promotion) ); component.rebate = { diff --git a/src/app/shared/components/product/product-promotion/product-promotion.component.spec.ts b/src/app/shared/components/product/product-promotion/product-promotion.component.spec.ts index 84129b8433..74d28af169 100644 --- a/src/app/shared/components/product/product-promotion/product-promotion.component.spec.ts +++ b/src/app/shared/components/product/product-promotion/product-promotion.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; +import { EMPTY, of } from 'rxjs'; import { anything, instance, mock, when } from 'ts-mockito'; import { ServerHtmlDirective } from 'ish-core/directives/server-html.directive'; @@ -19,6 +19,7 @@ describe('Product Promotion Component', () => { beforeEach(async(() => { shoppingFacade = mock(ShoppingFacade); + when(shoppingFacade.promotions$(anything())).thenReturn(EMPTY); TestBed.configureTestingModule({ declarations: [