Skip to content

Commit 015107f

Browse files
MikeRyanDevbrandonroberts
authored andcommitted
refactor(Effects): Simplified AP, added better error reporting and effects stream control
BREAKING CHANGES: Effects API for registering effects has been updated to allow for multiple classes to be provided. BEFORE: ```ts @NgModule({ imports: [ EffectsModule.run(SourceA), EffectsModule.run(SourceB) ] }) export class AppModule { } ``` AFTER: ```ts @NgModule({ imports: [ EffectsModule.forRoot([ SourceA, SourceB, SourceC, ]) ] }) export class AppModule { } @NgModule({ imports: [ EffectsModule.forFeature([ FeatureSourceA, FeatureSourceB, FeatureSourceC, ]) ] }) export class SomeFeatureModule { } ```
1 parent e21d688 commit 015107f

37 files changed

+673
-572
lines changed

modules/effects/spec/actions.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
44
import 'rxjs/add/observable/of';
55
import { ReflectiveInjector } from '@angular/core';
66
import { Action, StoreModule, ScannedActionsSubject, ActionsSubject } from '@ngrx/store';
7-
import { Actions } from '../src/actions';
7+
import { Actions } from '../';
88

99

1010
describe('Actions', function() {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import 'rxjs/add/operator/concat';
2+
import 'rxjs/add/operator/catch';
3+
import { cold } from 'jasmine-marbles';
4+
import { Observable } from 'rxjs/Observable';
5+
import { of } from 'rxjs/observable/of';
6+
import { _throw } from 'rxjs/observable/throw';
7+
import { never } from 'rxjs/observable/never';
8+
import { empty } from 'rxjs/observable/empty';
9+
import { TestBed } from '@angular/core/testing';
10+
import { ErrorReporter } from '../src/error_reporter';
11+
import { CONSOLE } from '../src/tokens';
12+
import { Effect, EffectSources } from '../';
13+
14+
describe('EffectSources', () => {
15+
let mockErrorReporter: ErrorReporter;
16+
let effectSources: EffectSources;
17+
18+
beforeEach(() => {
19+
TestBed.configureTestingModule({
20+
providers: [
21+
EffectSources,
22+
ErrorReporter,
23+
{
24+
provide: CONSOLE,
25+
useValue: console,
26+
},
27+
],
28+
});
29+
30+
mockErrorReporter = TestBed.get(ErrorReporter);
31+
effectSources = TestBed.get(EffectSources);
32+
33+
spyOn(mockErrorReporter, 'report');
34+
});
35+
36+
it('should have an "addEffects" method to push new source instances', () => {
37+
const effectSource = {};
38+
spyOn(effectSources, 'next');
39+
40+
effectSources.addEffects(effectSource);
41+
42+
expect(effectSources.next).toHaveBeenCalledWith(effectSource);
43+
});
44+
45+
describe('toActions() Operator', () => {
46+
const a = { type: 'From Source A' };
47+
const b = { type: 'From Source B' };
48+
const c = { type: 'From Source C that completes' };
49+
const d = { not: 'a valid action' };
50+
const error = new Error('An Error');
51+
52+
class SourceA {
53+
@Effect() a$ = alwaysOf(a);
54+
}
55+
56+
class SourceB {
57+
@Effect() b$ = alwaysOf(b);
58+
}
59+
60+
class SourceC {
61+
@Effect() c$ = of(c);
62+
}
63+
64+
class SourceD {
65+
@Effect() d$ = alwaysOf(d);
66+
}
67+
68+
class SourceE {
69+
@Effect() e$ = _throw(error);
70+
}
71+
72+
it('should resolve effects from instances', () => {
73+
const sources$ = cold('--a--', { a: new SourceA() });
74+
const expected = cold('--a--', { a });
75+
76+
const output = toActions(sources$);
77+
78+
expect(output).toBeObservable(expected);
79+
});
80+
81+
it('should ignore duplicate sources', () => {
82+
const sources$ = cold('--a--b--c--', {
83+
a: new SourceA(),
84+
b: new SourceA(),
85+
c: new SourceA(),
86+
});
87+
const expected = cold('--a--------', { a });
88+
89+
const output = toActions(sources$);
90+
91+
expect(output).toBeObservable(expected);
92+
});
93+
94+
it('should report an error if an effect dispatches an invalid action', () => {
95+
const sources$ = of(new SourceD());
96+
97+
toActions(sources$).subscribe();
98+
99+
expect(mockErrorReporter.report).toHaveBeenCalled();
100+
});
101+
102+
function toActions(source: any): Observable<any> {
103+
source['errorReporter'] = mockErrorReporter;
104+
return effectSources.toActions.call(source);
105+
}
106+
});
107+
108+
function alwaysOf<T>(value: T) {
109+
return of(value).concat(never<T>());
110+
}
111+
});

modules/effects/spec/effects-subscription.spec.ts

Lines changed: 0 additions & 67 deletions
This file was deleted.

modules/effects/spec/effects.spec.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { EffectSources } from '../src/effect_sources';
3+
import { FEATURE_EFFECTS } from '../src/tokens';
4+
import { EffectsFeatureModule } from '../src/effects_feature_module';
5+
6+
describe('Effects Feature Module', () => {
7+
const sourceA = 'sourceA';
8+
const sourceB = 'sourceB';
9+
const sourceC = 'sourceC';
10+
const effectSourceGroups = [[sourceA], [sourceB], [sourceC]];
11+
let mockEffectSources: { addEffects: jasmine.Spy };
12+
13+
beforeEach(() => {
14+
TestBed.configureTestingModule({
15+
providers: [
16+
{
17+
provide: EffectSources,
18+
useValue: {
19+
addEffects: jasmine.createSpy('addEffects'),
20+
},
21+
},
22+
{
23+
provide: FEATURE_EFFECTS,
24+
useValue: effectSourceGroups,
25+
},
26+
EffectsFeatureModule,
27+
],
28+
});
29+
30+
mockEffectSources = TestBed.get(mockEffectSources);
31+
});
32+
33+
it('should add all effects when instantiated', () => {
34+
TestBed.get(EffectsFeatureModule);
35+
36+
expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceA);
37+
expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceB);
38+
expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceC);
39+
});
40+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Effect, getSourceMetadata, getSourceForInstance } from '../src/effects_metadata';
2+
3+
describe('Effect Metadata', () => {
4+
describe('getSourceMetadata', () => {
5+
it('should get the effects metadata for a class instance', () => {
6+
class Fixture {
7+
@Effect() a: any;
8+
@Effect() b: any;
9+
@Effect({ dispatch: false }) c: any;
10+
}
11+
12+
const mock = new Fixture();
13+
14+
expect(getSourceMetadata(mock)).toEqual([
15+
{ propertyName: 'a', dispatch: true },
16+
{ propertyName: 'b', dispatch: true },
17+
{ propertyName: 'c', dispatch: false }
18+
]);
19+
});
20+
21+
it('should return an empty array if the class has not been decorated', () => {
22+
class Fixture {
23+
a: any;
24+
b: any;
25+
c: any;
26+
}
27+
28+
const mock = new Fixture();
29+
30+
expect(getSourceMetadata(mock)).toEqual([]);
31+
});
32+
});
33+
34+
describe('getSourceProto', () => {
35+
it('should get the prototype for an instance of a source', () => {
36+
class Fixture { }
37+
const instance = new Fixture();
38+
39+
const proto = getSourceForInstance(instance);
40+
41+
expect(proto).toBe(Fixture.prototype);
42+
});
43+
});
44+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'rxjs/add/observable/of';
2+
import { Observable } from 'rxjs/Observable';
3+
import { Effect, mergeEffects } from '../';
4+
5+
6+
describe('mergeEffects', () => {
7+
8+
});

modules/effects/spec/index.spec.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

modules/effects/spec/metadata.spec.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)