Skip to content

Commit 5e5d7dd

Browse files
bfrickabrandonroberts
authored andcommitted
feat(Store): Added initial state function support for features. Added more tests (#85)
1 parent 920a5f4 commit 5e5d7dd

File tree

12 files changed

+570
-160
lines changed

12 files changed

+570
-160
lines changed

modules/store/spec/integration.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('ngRx Integration spec', () => {
139139
return todos.filter(predicate);
140140
};
141141

142-
let currentlyVisibleTodos: any;
142+
let currentlyVisibleTodos: Todo[] = [];
143143

144144
Observable.combineLatest(
145145
store.select('visibilityFilter'),

modules/store/spec/modules.spec.ts

Lines changed: 137 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import 'rxjs/add/operator/take';
22
import { TestBed } from '@angular/core/testing';
33
import { NgModule, InjectionToken } from '@angular/core';
4-
import { StoreModule, Store, ActionReducer, ActionReducerMap } from '../';
4+
import {
5+
StoreModule,
6+
Store,
7+
ActionReducer,
8+
ActionReducerMap,
9+
combineReducers,
10+
} from '../';
11+
import createSpy = jasmine.createSpy;
512

6-
describe('Nested Store Modules', () => {
13+
describe(`Store Modules`, () => {
714
type RootState = { fruit: string };
815
type FeatureAState = number;
916
type FeatureBState = { list: number[]; index: number };
@@ -14,57 +21,144 @@ describe('Nested Store Modules', () => {
1421
const reducersToken = new InjectionToken<ActionReducerMap<RootState>>(
1522
'Root Reducers'
1623
);
17-
const rootFruitReducer: ActionReducer<string> = () => 'apple';
18-
const featureAReducer: ActionReducer<FeatureAState> = () => 5;
19-
const featureBListReducer: ActionReducer<number[]> = () => [1, 2, 3];
20-
const featureBIndexReducer: ActionReducer<number> = () => 2;
24+
25+
// Trigger here is basically an action type used to trigger state update
26+
const createDummyReducer = <T>(def: T, trigger: string): ActionReducer<T> => (
27+
s = def,
28+
{ type, payload }: any
29+
) => (type === trigger ? payload : s);
30+
const rootFruitReducer = createDummyReducer('apple', 'fruit');
31+
const featureAReducer = createDummyReducer(5, 'a');
32+
const featureBListReducer = createDummyReducer([1, 2, 3], 'bList');
33+
const featureBIndexReducer = createDummyReducer(2, 'bIndex');
2134
const featureBReducerMap: ActionReducerMap<FeatureBState> = {
2235
list: featureBListReducer,
2336
index: featureBIndexReducer,
2437
};
2538

26-
@NgModule({
27-
imports: [StoreModule.forFeature('a', featureAReducer)],
28-
})
29-
class FeatureAModule {}
30-
31-
@NgModule({
32-
imports: [StoreModule.forFeature('b', featureBReducerMap)],
33-
})
34-
class FeatureBModule {}
35-
36-
@NgModule({
37-
imports: [
38-
StoreModule.forRoot<RootState>(reducersToken),
39-
FeatureAModule,
40-
FeatureBModule,
41-
],
42-
providers: [
43-
{
44-
provide: reducersToken,
45-
useValue: { fruit: rootFruitReducer },
46-
},
47-
],
48-
})
49-
class RootModule {}
50-
51-
beforeEach(() => {
52-
TestBed.configureTestingModule({
53-
imports: [RootModule],
39+
describe(`: Config`, () => {
40+
let featureAReducerFactory: any;
41+
let rootReducerFactory: any;
42+
43+
const featureAInitial = () => ({ a: 42 });
44+
const rootInitial = { fruit: 'orange' };
45+
46+
beforeEach(() => {
47+
featureAReducerFactory = createSpy(
48+
'featureAReducerFactory'
49+
).and.callFake((rm: any, initialState?: any) => {
50+
return (state: any, action: any) => 4;
51+
});
52+
rootReducerFactory = createSpy('rootReducerFactory').and.callFake(
53+
combineReducers
54+
);
55+
56+
@NgModule({
57+
imports: [
58+
StoreModule.forFeature(
59+
'a',
60+
{ a: featureAReducer },
61+
{
62+
initialState: featureAInitial,
63+
reducerFactory: featureAReducerFactory,
64+
}
65+
),
66+
],
67+
})
68+
class FeatureAModule {}
69+
70+
@NgModule({
71+
imports: [
72+
StoreModule.forRoot<RootState>(reducersToken, {
73+
initialState: rootInitial,
74+
reducerFactory: rootReducerFactory,
75+
}),
76+
FeatureAModule,
77+
],
78+
providers: [
79+
{
80+
provide: reducersToken,
81+
useValue: { fruit: rootFruitReducer },
82+
},
83+
],
84+
})
85+
class RootModule {}
86+
87+
TestBed.configureTestingModule({
88+
imports: [RootModule],
89+
});
90+
91+
store = TestBed.get(Store);
92+
});
93+
94+
it(`should accept configurations`, () => {
95+
expect(featureAReducerFactory).toHaveBeenCalledWith(
96+
{ a: featureAReducer },
97+
featureAInitial()
98+
);
99+
expect(rootReducerFactory).toHaveBeenCalledWith(
100+
{ fruit: rootFruitReducer },
101+
rootInitial
102+
);
54103
});
55104

56-
store = TestBed.get(Store);
105+
it(`should should use config.reducerFactory`, () => {
106+
store.dispatch({ type: 'fruit', payload: 'banana' });
107+
store.dispatch({ type: 'a', payload: 42 });
108+
109+
store.take(1).subscribe((s: any) => {
110+
expect(s).toEqual({
111+
fruit: 'banana',
112+
a: 4,
113+
});
114+
});
115+
});
57116
});
58117

59-
it('should nest the child module in the root store object', () => {
60-
store.take(1).subscribe((state: State) => {
61-
expect(state).toEqual({
62-
fruit: 'apple',
63-
a: 5,
64-
b: {
65-
list: [1, 2, 3],
66-
index: 2,
118+
describe(`: Nested`, () => {
119+
@NgModule({
120+
imports: [StoreModule.forFeature('a', featureAReducer)],
121+
})
122+
class FeatureAModule {}
123+
124+
@NgModule({
125+
imports: [StoreModule.forFeature('b', featureBReducerMap)],
126+
})
127+
class FeatureBModule {}
128+
129+
@NgModule({
130+
imports: [
131+
StoreModule.forRoot<RootState>(reducersToken),
132+
FeatureAModule,
133+
FeatureBModule,
134+
],
135+
providers: [
136+
{
137+
provide: reducersToken,
138+
useValue: { fruit: rootFruitReducer },
67139
},
140+
],
141+
})
142+
class RootModule {}
143+
144+
beforeEach(() => {
145+
TestBed.configureTestingModule({
146+
imports: [RootModule],
147+
});
148+
149+
store = TestBed.get(Store);
150+
});
151+
152+
it('should nest the child module in the root store object', () => {
153+
store.take(1).subscribe((state: State) => {
154+
expect(state).toEqual({
155+
fruit: 'apple',
156+
a: 5,
157+
b: {
158+
list: [1, 2, 3],
159+
index: 2,
160+
},
161+
});
68162
});
69163
});
70164
});

modules/store/spec/state.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Observable } from 'rxjs/Observable';
22
import { Subject } from 'rxjs/Subject';
33
import { ReflectiveInjector } from '@angular/core';
44
import { createInjector } from './helpers/injector';
5-
import { StoreModule, Store } from '../';
5+
import { StoreModule, Store, INIT } from '../';
66

77
describe('ngRx State', () => {
88
const initialState = 123;
@@ -22,7 +22,7 @@ describe('ngRx State', () => {
2222
injector.get(Store);
2323

2424
expect(reducer).toHaveBeenCalledWith(initialState, {
25-
type: '@ngrx/store/init',
25+
type: INIT,
2626
});
2727
});
2828
});

modules/store/spec/store.spec.ts

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
import 'rxjs/add/operator/take';
2-
import { Observable } from 'rxjs/Observable';
32
import { ReflectiveInjector } from '@angular/core';
43
import { hot } from 'jasmine-marbles';
54
import { createInjector } from './helpers/injector';
6-
import { Store, Action, combineReducers, StoreModule } from '../';
7-
import { ActionsSubject } from '../src/private_export';
5+
import { ActionsSubject, ReducerManager, Store, StoreModule } from '../';
86
import {
97
counterReducer,
108
INCREMENT,
119
DECREMENT,
1210
RESET,
1311
} from './fixtures/counter';
12+
import Spy = jasmine.Spy;
13+
import any = jasmine.any;
1414

1515
interface TestAppSchema {
1616
counter1: number;
1717
counter2: number;
1818
counter3: number;
19-
}
20-
21-
interface Todo {}
22-
23-
interface TodoAppSchema {
24-
visibilityFilter: string;
25-
todos: Todo[];
19+
counter4?: number;
2620
}
2721

2822
describe('ngRx Store', () => {
@@ -68,7 +62,7 @@ describe('ngRx Store', () => {
6862
});
6963
});
7064

71-
describe('basic store actions', function() {
65+
describe('basic store actions', () => {
7266
beforeEach(() => setup());
7367

7468
it('should provide an Observable Store', () => {
@@ -84,7 +78,7 @@ describe('ngRx Store', () => {
8478
e: { type: INCREMENT },
8579
};
8680

87-
it('should let you select state with a key name', function() {
81+
it('should let you select state with a key name', () => {
8882
const counterSteps = hot(actionSequence, actionValues);
8983

9084
counterSteps.subscribe(action => store.dispatch(action));
@@ -99,7 +93,7 @@ describe('ngRx Store', () => {
9993
);
10094
});
10195

102-
it('should let you select state with a selector function', function() {
96+
it('should let you select state with a selector function', () => {
10397
const counterSteps = hot(actionSequence, actionValues);
10498

10599
counterSteps.subscribe(action => store.dispatch(action));
@@ -114,13 +108,13 @@ describe('ngRx Store', () => {
114108
);
115109
});
116110

117-
it('should correctly lift itself', function() {
111+
it('should correctly lift itself', () => {
118112
const result = store.select('counter1');
119113

120114
expect(result instanceof Store).toBe(true);
121115
});
122116

123-
it('should increment and decrement counter1', function() {
117+
it('should increment and decrement counter1', () => {
124118
const counterSteps = hot(actionSequence, actionValues);
125119

126120
counterSteps.subscribe(action => store.dispatch(action));
@@ -133,7 +127,7 @@ describe('ngRx Store', () => {
133127
expect(counterState).toBeObservable(hot(stateSequence, counter1Values));
134128
});
135129

136-
it('should increment and decrement counter1 using the dispatcher', function() {
130+
it('should increment and decrement counter1 using the dispatcher', () => {
137131
const counterSteps = hot(actionSequence, actionValues);
138132

139133
counterSteps.subscribe(action => dispatcher.next(action));
@@ -146,7 +140,7 @@ describe('ngRx Store', () => {
146140
expect(counterState).toBeObservable(hot(stateSequence, counter1Values));
147141
});
148142

149-
it('should increment and decrement counter2 separately', function() {
143+
it('should increment and decrement counter2 separately', () => {
150144
const counterSteps = hot(actionSequence, actionValues);
151145

152146
counterSteps.subscribe(action => store.dispatch(action));
@@ -160,7 +154,7 @@ describe('ngRx Store', () => {
160154
expect(counter2State).toBeObservable(hot(stateSequence, counter2Values));
161155
});
162156

163-
it('should implement the observer interface forwarding actions and errors to the dispatcher', function() {
157+
it('should implement the observer interface forwarding actions and errors to the dispatcher', () => {
164158
spyOn(dispatcher, 'next');
165159
spyOn(dispatcher, 'error');
166160

@@ -171,7 +165,7 @@ describe('ngRx Store', () => {
171165
expect(dispatcher.error).toHaveBeenCalledWith(2);
172166
});
173167

174-
it('should not be completable', function() {
168+
it('should not be completable', () => {
175169
const storeSubscription = store.subscribe();
176170
const dispatcherSubscription = dispatcher.subscribe();
177171

@@ -192,4 +186,41 @@ describe('ngRx Store', () => {
192186
expect(dispatcherSubscription.closed).toBe(true);
193187
});
194188
});
189+
190+
describe(`add/remove reducers`, () => {
191+
let addReducerSpy: Spy;
192+
let removeReducerSpy: Spy;
193+
const key = 'counter4';
194+
195+
beforeEach(() => {
196+
setup();
197+
const reducerManager = injector.get(ReducerManager);
198+
addReducerSpy = spyOn(reducerManager, 'addReducer').and.callThrough();
199+
removeReducerSpy = spyOn(
200+
reducerManager,
201+
'removeReducer'
202+
).and.callThrough();
203+
});
204+
205+
it(`should delegate add/remove to ReducerManager`, () => {
206+
store.addReducer(key, counterReducer);
207+
expect(addReducerSpy).toHaveBeenCalledWith(key, counterReducer);
208+
209+
store.removeReducer(key);
210+
expect(removeReducerSpy).toHaveBeenCalledWith(key);
211+
});
212+
213+
it(`should work with added / removed reducers`, () => {
214+
store.addReducer(key, counterReducer);
215+
store.take(1).subscribe(val => {
216+
expect(val.counter4).toBe(0);
217+
});
218+
219+
store.removeReducer(key);
220+
store.dispatch({ type: INCREMENT });
221+
store.take(1).subscribe(val => {
222+
expect(val.counter4).toBeUndefined();
223+
});
224+
});
225+
});
195226
});

0 commit comments

Comments
 (0)