Skip to content

Commit

Permalink
feat: introduce content parameter state management - initial with fil…
Browse files Browse the repository at this point in the history
…tered product lists (#673)
  • Loading branch information
shauke committed Jun 10, 2021
1 parent 6cf2f8a commit 6aa7fb4
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/app/core/store/content/content-store.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { pageTreeReducer } from './page-tree/page-tree.reducer';
import { pageletsReducer } from './pagelets/pagelets.reducer';
import { PagesEffects } from './pages/pages.effects';
import { pagesReducer } from './pages/pages.reducer';
import { ParametersEffects } from './parameters/parameters.effects';
import { parametersReducer } from './parameters/parameters.reducer';
import { ViewcontextsEffects } from './viewcontexts/viewcontexts.effects';
import { viewcontextsReducer } from './viewcontexts/viewcontexts.reducer';

Expand All @@ -22,9 +24,10 @@ const contentReducers: ActionReducerMap<ContentState> = {
pages: pagesReducer,
viewcontexts: viewcontextsReducer,
pagetree: pageTreeReducer,
parameters: parametersReducer,
};

const contentEffects = [IncludesEffects, PagesEffects, ViewcontextsEffects, PageTreeEffects];
const contentEffects = [IncludesEffects, PagesEffects, ViewcontextsEffects, PageTreeEffects, ParametersEffects];

const metaReducers = [resetOnLogoutMeta];

Expand Down
7 changes: 6 additions & 1 deletion src/app/core/store/content/content-store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { anything, capture, instance, mock, spy, verify, when } from 'ts-mockito
import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry-point/content-pagelet-entry-point.model';
import { ContentPagelet } from 'ish-core/models/content-pagelet/content-pagelet.model';
import { CMSService } from 'ish-core/services/cms/cms.service';
import { FilterService } from 'ish-core/services/filter/filter.service';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
import { whenTruthy } from 'ish-core/utils/operators';

Expand All @@ -26,13 +27,17 @@ describe('Content Store', () => {

beforeEach(() => {
const cmsService = mock(CMSService);
const filterService = mock(FilterService);
when(cmsService.getContentInclude('id')).thenReturn(
of({ include: { ...include }, pagelets: [{ ...pagelet, id: '1' }] })
);

TestBed.configureTestingModule({
imports: [ContentStoreModule, CoreStoreModule.forTesting([], true)],
providers: [{ provide: CMSService, useFactory: () => instance(cmsService) }],
providers: [
{ provide: CMSService, useFactory: () => instance(cmsService) },
{ provide: FilterService, useFactory: () => instance(filterService) },
],
});

store$ = TestBed.inject(Store);
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/store/content/content-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IncludesState } from './includes/includes.reducer';
import { PageTreeState } from './page-tree/page-tree.reducer';
import { PageletsState } from './pagelets/pagelets.reducer';
import { PagesState } from './pages/pages.reducer';
import { ParametersState } from './parameters/parameters.reducer';
import { ViewcontextsState } from './viewcontexts/viewcontexts.reducer';

export interface ContentState {
Expand All @@ -12,6 +13,7 @@ export interface ContentState {
pages: PagesState;
viewcontexts: ViewcontextsState;
pagetree: PageTreeState;
parameters: ParametersState;
}

export const getContentState = createFeatureSelector<ContentState>('content');
3 changes: 3 additions & 0 deletions src/app/core/store/content/parameters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// API to access ngrx parameters state
export * from './parameters.actions';
export * from './parameters.selectors';
19 changes: 19 additions & 0 deletions src/app/core/store/content/parameters/parameters.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createAction } from '@ngrx/store';

import { httpError, payload } from 'ish-core/utils/ngrx-creators';
import { URLFormParams } from 'ish-core/utils/url-form-params';

export const loadParametersProductListFilter = createAction(
'[Content Configuration Parameters] Load Product List (Filter)',
payload<{ id: string; searchParameter: URLFormParams; amount?: number }>()
);

export const loadParametersProductListFilterFail = createAction(
'[Content Configuration Parameters API] Load Product List (Filter) Fail',
httpError()
);

export const loadParametersProductListFilterSuccess = createAction(
'[Content Configuration Parameters API] Load Product List (Filter) Success',
payload<{ id: string; productList: string[] }>()
);
58 changes: 58 additions & 0 deletions src/app/core/store/content/parameters/parameters.effects.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Action } from '@ngrx/store';
import { cold, hot } from 'jest-marbles';
import { Observable, of } from 'rxjs';
import { anything, instance, mock, when } from 'ts-mockito';

import { Product } from 'ish-core/models/product/product.model';
import { FilterService } from 'ish-core/services/filter/filter.service';
import { loadProductSuccess } from 'ish-core/store/shopping/products';
import { URLFormParams } from 'ish-core/utils/url-form-params';

import { loadParametersProductListFilter, loadParametersProductListFilterSuccess } from './parameters.actions';
import { ParametersEffects } from './parameters.effects';

describe('Parameters Effects', () => {
let actions$: Observable<Action>;
let effects: ParametersEffects;
let filterServiceMock: FilterService;

beforeEach(() => {
filterServiceMock = mock(FilterService);
TestBed.configureTestingModule({
providers: [
ParametersEffects,
provideMockActions(() => actions$),
{ provide: FilterService, useFactory: () => instance(filterServiceMock) },
],
});

effects = TestBed.inject(ParametersEffects);
});

describe('loadParameters$', () => {
it('should dispatch multiple actions when getFilteredProducts service is succesful', () => {
when(filterServiceMock.getFilteredProducts(anything(), anything(), anything(), anything())).thenReturn(
of({ total: 1, products: [{ name: 'test', sku: 'sku' } as Product], sortableAttributes: [] })
);
const action = loadParametersProductListFilter({
id: 'id',
searchParameter: {} as URLFormParams,
});
actions$ = hot('-a----a----a', { a: action });

const loadProductSuccessAction = loadProductSuccess({ product: { name: 'test', sku: 'sku' } as Product });
const loadParametersProductListFilterSuccessAction = loadParametersProductListFilterSuccess({
id: 'id',
productList: ['sku'],
});
const expected$ = cold('-(bc)-(bc)-(bc)', {
b: loadProductSuccessAction,
c: loadParametersProductListFilterSuccessAction,
});

expect(effects.loadParametersProductListFilter$).toBeObservable(expected$);
});
});
});
35 changes: 35 additions & 0 deletions src/app/core/store/content/parameters/parameters.effects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { concatMap, mergeMap } from 'rxjs/operators';

import { Product } from 'ish-core/models/product/product.model';
import { FilterService } from 'ish-core/services/filter/filter.service';
import { loadProductSuccess } from 'ish-core/store/shopping/products';
import { mapErrorToAction, mapToPayload } from 'ish-core/utils/operators';

import {
loadParametersProductListFilter,
loadParametersProductListFilterFail,
loadParametersProductListFilterSuccess,
} from './parameters.actions';

@Injectable()
export class ParametersEffects {
constructor(private actions$: Actions, private filterService: FilterService) {}

loadParametersProductListFilter$ = createEffect(() =>
this.actions$.pipe(
ofType(loadParametersProductListFilter),
mapToPayload(),
concatMap(({ id, searchParameter, amount }) =>
this.filterService.getFilteredProducts(searchParameter, 1, undefined, amount).pipe(
mergeMap(({ products }) => [
...products.map((product: Product) => loadProductSuccess({ product })),
loadParametersProductListFilterSuccess({ id, productList: products.map(p => p.sku) }),
]),
mapErrorToAction(loadParametersProductListFilterFail)
)
)
)
);
}
19 changes: 19 additions & 0 deletions src/app/core/store/content/parameters/parameters.reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createReducer, on } from '@ngrx/store';

import { loadParametersProductListFilterSuccess } from './parameters.actions';

export interface ParametersState {
productLists: { [id: string]: string[] };
}

const initialState: ParametersState = {
productLists: {},
};

export const parametersReducer = createReducer(
initialState,
on(loadParametersProductListFilterSuccess, (state, action) => ({
...state,
productLists: { ...state.productLists, [action.payload.id]: action.payload.productList },
}))
);
39 changes: 39 additions & 0 deletions src/app/core/store/content/parameters/parameters.selectors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { TestBed } from '@angular/core/testing';

import { ContentStoreModule } from 'ish-core/store/content/content-store.module';
import { CoreStoreModule } from 'ish-core/store/core/core-store.module';
import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing';

import { loadParametersProductListFilterSuccess } from './parameters.actions';
import { getParametersProductList } from './parameters.selectors';

describe('Parameters Selectors', () => {
let store$: StoreWithSnapshots;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ContentStoreModule.forTesting('parameters'), CoreStoreModule.forTesting()],
providers: [provideStoreSnapshots()],
});

store$ = TestBed.inject(StoreWithSnapshots);
});

describe('initial state', () => {
it('should not be loading when in initial state', () => {
expect(getParametersProductList('id')(store$.state)).toBeUndefined();
});
});

describe('loadParametersProductListFilterSuccess', () => {
const action = loadParametersProductListFilterSuccess({ id: 'id', productList: ['product'] });

beforeEach(() => {
store$.dispatch(action);
});

it('should set given productlist for id', () => {
expect(getParametersProductList('id')(store$.state)).toEqual(['product']);
});
});
});
14 changes: 14 additions & 0 deletions src/app/core/store/content/parameters/parameters.selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createSelector, createSelectorFactory, resultMemoize } from '@ngrx/store';

import { getContentState } from 'ish-core/store/content/content-store';
import { isArrayEqual } from 'ish-core/utils/functions';

const getParametersState = createSelector(getContentState, state => state.parameters);

const getParametersProductLists = createSelector(getParametersState, parameters => parameters.productLists);

export const getParametersProductList = (id: string) =>
createSelectorFactory<object, string[]>(projector => resultMemoize(projector, isArrayEqual))(
getParametersProductLists,
(productLists: { [id: string]: string[] }): string[] => productLists[id]
);

0 comments on commit 6aa7fb4

Please sign in to comment.