Skip to content

Commit 6aa5645

Browse files
itayodbrandonroberts
authored andcommitted
feat(store): support store config factory for feature (#1445)
Closes #1414
1 parent 089126e commit 6aa5645

File tree

4 files changed

+313
-13
lines changed

4 files changed

+313
-13
lines changed

modules/store/spec/fixtures/counter.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,16 @@ export function counterReducer(state = 0, action: Action) {
1616
return state;
1717
}
1818
}
19+
20+
export function counterReducer2(state = 0, action: Action) {
21+
switch (action.type) {
22+
case INCREMENT:
23+
return state + 1;
24+
case DECREMENT:
25+
return state - 1;
26+
case RESET:
27+
return 0;
28+
default:
29+
return state;
30+
}
31+
}

modules/store/spec/store.spec.ts

Lines changed: 241 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ReflectiveInjector } from '@angular/core';
1+
import { ReflectiveInjector, InjectionToken } from '@angular/core';
22
import { TestBed } from '@angular/core/testing';
33
import { hot } from 'jasmine-marbles';
44
import {
@@ -10,12 +10,16 @@ import {
1010
ReducerManagerDispatcher,
1111
UPDATE,
1212
REDUCER_FACTORY,
13+
ActionReducer,
14+
Action,
1315
} from '../';
16+
import { StoreConfig } from '../src/store_module';
1417
import {
1518
counterReducer,
1619
INCREMENT,
1720
DECREMENT,
1821
RESET,
22+
counterReducer2,
1923
} from './fixtures/counter';
2024
import Spy = jasmine.Spy;
2125
import any = jasmine.any;
@@ -33,15 +37,18 @@ describe('ngRx Store', () => {
3337
let store: Store<TestAppSchema>;
3438
let dispatcher: ActionsSubject;
3539

36-
function setup(initialState: any = { counter1: 0, counter2: 1 }) {
40+
function setup(
41+
initialState: any = { counter1: 0, counter2: 1 },
42+
metaReducers: any = []
43+
) {
3744
const reducers = {
3845
counter1: counterReducer,
3946
counter2: counterReducer,
4047
counter3: counterReducer,
4148
};
4249

4350
TestBed.configureTestingModule({
44-
imports: [StoreModule.forRoot(reducers, { initialState })],
51+
imports: [StoreModule.forRoot(reducers, { initialState, metaReducers })],
4552
});
4653

4754
store = TestBed.get(Store);
@@ -471,4 +478,235 @@ describe('ngRx Store', () => {
471478
mockStore.dispatch(action);
472479
});
473480
});
481+
482+
describe('Meta Reducers', () => {
483+
let metaReducerContainer: any;
484+
let metaReducerSpy1: Spy;
485+
let metaReducerSpy2: Spy;
486+
487+
beforeEach(() => {
488+
metaReducerContainer = (function() {
489+
function metaReducer1(reducer: ActionReducer<any, any>) {
490+
return function(state: any, action: Action) {
491+
return reducer(state, action);
492+
};
493+
}
494+
495+
function metaReducer2(reducer: ActionReducer<any, any>) {
496+
return function(state: any, action: Action) {
497+
return reducer(state, action);
498+
};
499+
}
500+
501+
return {
502+
metaReducer1: metaReducer1,
503+
metaReducer2: metaReducer2,
504+
};
505+
})();
506+
507+
metaReducerSpy1 = spyOn(
508+
metaReducerContainer,
509+
'metaReducer1'
510+
).and.callThrough();
511+
512+
metaReducerSpy2 = spyOn(
513+
metaReducerContainer,
514+
'metaReducer2'
515+
).and.callThrough();
516+
});
517+
518+
it('should create a meta reducer for root and call it through', () => {
519+
setup({}, [metaReducerContainer.metaReducer1]);
520+
const action = { type: INCREMENT };
521+
store.dispatch(action);
522+
expect(metaReducerSpy1).toHaveBeenCalled();
523+
});
524+
525+
it('should call two meta reducers', () => {
526+
setup({}, [
527+
metaReducerContainer.metaReducer1,
528+
metaReducerContainer.metaReducer2,
529+
]);
530+
const action = { type: INCREMENT };
531+
store.dispatch(action);
532+
533+
expect(metaReducerSpy1).toHaveBeenCalled();
534+
expect(metaReducerSpy2).toHaveBeenCalled();
535+
});
536+
537+
it('should create a meta reducer for feature and call it with the expected reducer', () => {
538+
TestBed.configureTestingModule({
539+
imports: [
540+
StoreModule.forRoot({}),
541+
StoreModule.forFeature('counter1', counterReducer, {
542+
metaReducers: [metaReducerContainer.metaReducer1],
543+
}),
544+
StoreModule.forFeature('counter2', counterReducer2, {
545+
metaReducers: [metaReducerContainer.metaReducer2],
546+
}),
547+
],
548+
});
549+
const mockStore = TestBed.get(Store);
550+
const action = { type: INCREMENT };
551+
mockStore.dispatch(action);
552+
553+
expect(metaReducerSpy1).toHaveBeenCalledWith(counterReducer);
554+
expect(metaReducerSpy2).toHaveBeenCalledWith(counterReducer2);
555+
});
556+
});
557+
558+
describe('Feature config token', () => {
559+
let FEATURE_CONFIG_TOKEN: InjectionToken<StoreConfig<any, any>>;
560+
let FEATURE_CONFIG2_TOKEN: InjectionToken<StoreConfig<any, any>>;
561+
562+
beforeEach(() => {
563+
FEATURE_CONFIG_TOKEN = new InjectionToken('Feature Config');
564+
FEATURE_CONFIG2_TOKEN = new InjectionToken('Feature Config2');
565+
});
566+
567+
it('should initial state with value', (done: DoneFn) => {
568+
const initialState = { counter1: 1 };
569+
const featureKey = 'counter';
570+
571+
TestBed.configureTestingModule({
572+
imports: [
573+
StoreModule.forRoot({}),
574+
StoreModule.forFeature(
575+
featureKey,
576+
counterReducer,
577+
FEATURE_CONFIG_TOKEN
578+
),
579+
],
580+
providers: [
581+
{
582+
provide: FEATURE_CONFIG_TOKEN,
583+
useValue: { initialState: initialState },
584+
},
585+
],
586+
});
587+
588+
const mockStore = TestBed.get(Store);
589+
590+
mockStore.pipe(take(1)).subscribe({
591+
next(val: any) {
592+
expect(val[featureKey]).toEqual(initialState);
593+
},
594+
error: done,
595+
complete: done,
596+
});
597+
});
598+
599+
it('should initial state with value for multi features', (done: DoneFn) => {
600+
const initialState = 1;
601+
const initialState2 = 2;
602+
const initialState3 = 3;
603+
const featureKey = 'counter';
604+
const featureKey2 = 'counter2';
605+
const featureKey3 = 'counter3';
606+
607+
TestBed.configureTestingModule({
608+
imports: [
609+
StoreModule.forRoot({}),
610+
StoreModule.forFeature(
611+
featureKey,
612+
counterReducer,
613+
FEATURE_CONFIG_TOKEN
614+
),
615+
StoreModule.forFeature(
616+
featureKey2,
617+
counterReducer,
618+
FEATURE_CONFIG2_TOKEN
619+
),
620+
StoreModule.forFeature(featureKey3, counterReducer, {
621+
initialState: initialState3,
622+
}),
623+
],
624+
providers: [
625+
{
626+
provide: FEATURE_CONFIG_TOKEN,
627+
useValue: { initialState: initialState },
628+
},
629+
{
630+
provide: FEATURE_CONFIG2_TOKEN,
631+
useValue: { initialState: initialState2 },
632+
},
633+
],
634+
});
635+
636+
const mockStore = TestBed.get(Store);
637+
638+
mockStore.pipe(take(1)).subscribe({
639+
next(val: any) {
640+
expect(val[featureKey]).toEqual(initialState);
641+
expect(val[featureKey2]).toEqual(initialState2);
642+
expect(val[featureKey3]).toEqual(initialState3);
643+
},
644+
error: done,
645+
complete: done,
646+
});
647+
});
648+
649+
it('should create a meta reducer with config injection token and call it with the expected reducer', () => {
650+
const metaReducerContainer = (function() {
651+
function metaReducer1(reducer: ActionReducer<any, any>) {
652+
return function(state: any, action: Action) {
653+
return reducer(state, action);
654+
};
655+
}
656+
657+
function metaReducer2(reducer: ActionReducer<any, any>) {
658+
return function(state: any, action: Action) {
659+
return reducer(state, action);
660+
};
661+
}
662+
663+
return {
664+
metaReducer1: metaReducer1,
665+
metaReducer2: metaReducer2,
666+
};
667+
})();
668+
669+
const metaReducerSpy1 = spyOn(
670+
metaReducerContainer,
671+
'metaReducer1'
672+
).and.callThrough();
673+
674+
const metaReducerSpy2 = spyOn(
675+
metaReducerContainer,
676+
'metaReducer2'
677+
).and.callThrough();
678+
679+
TestBed.configureTestingModule({
680+
imports: [
681+
StoreModule.forRoot({}),
682+
StoreModule.forFeature(
683+
'counter1',
684+
counterReducer,
685+
FEATURE_CONFIG_TOKEN
686+
),
687+
StoreModule.forFeature(
688+
'counter2',
689+
counterReducer2,
690+
FEATURE_CONFIG2_TOKEN
691+
),
692+
],
693+
providers: [
694+
{
695+
provide: FEATURE_CONFIG_TOKEN,
696+
useValue: { metaReducers: [metaReducerContainer.metaReducer1] },
697+
},
698+
{
699+
provide: FEATURE_CONFIG2_TOKEN,
700+
useValue: { metaReducers: [metaReducerContainer.metaReducer2] },
701+
},
702+
],
703+
});
704+
const mockStore = TestBed.get(Store);
705+
const action = { type: INCREMENT };
706+
mockStore.dispatch(action);
707+
708+
expect(metaReducerSpy1).toHaveBeenCalledWith(counterReducer);
709+
expect(metaReducerSpy2).toHaveBeenCalledWith(counterReducer2);
710+
});
711+
});
474712
});

0 commit comments

Comments
 (0)