Skip to content

Commit

Permalink
refactor(Effects): Simplified AP, added better error reporting and ef…
Browse files Browse the repository at this point in the history
…fects 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 { }
```
  • Loading branch information
MikeRyanDev authored and brandonroberts committed Jun 8, 2017
1 parent e21d688 commit 015107f
Show file tree
Hide file tree
Showing 37 changed files with 673 additions and 572 deletions.
2 changes: 1 addition & 1 deletion modules/effects/spec/actions.spec.ts
Expand Up @@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import { ReflectiveInjector } from '@angular/core';
import { Action, StoreModule, ScannedActionsSubject, ActionsSubject } from '@ngrx/store';
import { Actions } from '../src/actions';
import { Actions } from '../';


describe('Actions', function() {
Expand Down
111 changes: 111 additions & 0 deletions modules/effects/spec/effect_sources.spec.ts
@@ -0,0 +1,111 @@
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/catch';
import { cold } from 'jasmine-marbles';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { _throw } from 'rxjs/observable/throw';
import { never } from 'rxjs/observable/never';
import { empty } from 'rxjs/observable/empty';
import { TestBed } from '@angular/core/testing';
import { ErrorReporter } from '../src/error_reporter';
import { CONSOLE } from '../src/tokens';
import { Effect, EffectSources } from '../';

describe('EffectSources', () => {
let mockErrorReporter: ErrorReporter;
let effectSources: EffectSources;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
EffectSources,
ErrorReporter,
{
provide: CONSOLE,
useValue: console,
},
],
});

mockErrorReporter = TestBed.get(ErrorReporter);
effectSources = TestBed.get(EffectSources);

spyOn(mockErrorReporter, 'report');
});

it('should have an "addEffects" method to push new source instances', () => {
const effectSource = {};
spyOn(effectSources, 'next');

effectSources.addEffects(effectSource);

expect(effectSources.next).toHaveBeenCalledWith(effectSource);
});

describe('toActions() Operator', () => {
const a = { type: 'From Source A' };
const b = { type: 'From Source B' };
const c = { type: 'From Source C that completes' };
const d = { not: 'a valid action' };
const error = new Error('An Error');

class SourceA {
@Effect() a$ = alwaysOf(a);
}

class SourceB {
@Effect() b$ = alwaysOf(b);
}

class SourceC {
@Effect() c$ = of(c);
}

class SourceD {
@Effect() d$ = alwaysOf(d);
}

class SourceE {
@Effect() e$ = _throw(error);
}

it('should resolve effects from instances', () => {
const sources$ = cold('--a--', { a: new SourceA() });
const expected = cold('--a--', { a });

const output = toActions(sources$);

expect(output).toBeObservable(expected);
});

it('should ignore duplicate sources', () => {
const sources$ = cold('--a--b--c--', {
a: new SourceA(),
b: new SourceA(),
c: new SourceA(),
});
const expected = cold('--a--------', { a });

const output = toActions(sources$);

expect(output).toBeObservable(expected);
});

it('should report an error if an effect dispatches an invalid action', () => {
const sources$ = of(new SourceD());

toActions(sources$).subscribe();

expect(mockErrorReporter.report).toHaveBeenCalled();
});

function toActions(source: any): Observable<any> {
source['errorReporter'] = mockErrorReporter;
return effectSources.toActions.call(source);
}
});

function alwaysOf<T>(value: T) {
return of(value).concat(never<T>());
}
});
67 changes: 0 additions & 67 deletions modules/effects/spec/effects-subscription.spec.ts

This file was deleted.

26 changes: 0 additions & 26 deletions modules/effects/spec/effects.spec.ts

This file was deleted.

40 changes: 40 additions & 0 deletions modules/effects/spec/effects_feature_module.ts
@@ -0,0 +1,40 @@
import { TestBed } from '@angular/core/testing';
import { EffectSources } from '../src/effect_sources';
import { FEATURE_EFFECTS } from '../src/tokens';
import { EffectsFeatureModule } from '../src/effects_feature_module';

describe('Effects Feature Module', () => {
const sourceA = 'sourceA';
const sourceB = 'sourceB';
const sourceC = 'sourceC';
const effectSourceGroups = [[sourceA], [sourceB], [sourceC]];
let mockEffectSources: { addEffects: jasmine.Spy };

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: EffectSources,
useValue: {
addEffects: jasmine.createSpy('addEffects'),
},
},
{
provide: FEATURE_EFFECTS,
useValue: effectSourceGroups,
},
EffectsFeatureModule,
],
});

mockEffectSources = TestBed.get(mockEffectSources);
});

it('should add all effects when instantiated', () => {
TestBed.get(EffectsFeatureModule);

expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceA);
expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceB);
expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceC);
});
});
44 changes: 44 additions & 0 deletions modules/effects/spec/effects_metadata.spec.ts
@@ -0,0 +1,44 @@
import { Effect, getSourceMetadata, getSourceForInstance } from '../src/effects_metadata';

describe('Effect Metadata', () => {
describe('getSourceMetadata', () => {
it('should get the effects metadata for a class instance', () => {
class Fixture {
@Effect() a: any;
@Effect() b: any;
@Effect({ dispatch: false }) c: any;
}

const mock = new Fixture();

expect(getSourceMetadata(mock)).toEqual([
{ propertyName: 'a', dispatch: true },
{ propertyName: 'b', dispatch: true },
{ propertyName: 'c', dispatch: false }
]);
});

it('should return an empty array if the class has not been decorated', () => {
class Fixture {
a: any;
b: any;
c: any;
}

const mock = new Fixture();

expect(getSourceMetadata(mock)).toEqual([]);
});
});

describe('getSourceProto', () => {
it('should get the prototype for an instance of a source', () => {
class Fixture { }
const instance = new Fixture();

const proto = getSourceForInstance(instance);

expect(proto).toBe(Fixture.prototype);
});
});
});
8 changes: 8 additions & 0 deletions modules/effects/spec/effects_resolver.spec.ts
@@ -0,0 +1,8 @@
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Observable';
import { Effect, mergeEffects } from '../';


describe('mergeEffects', () => {

});
3 changes: 0 additions & 3 deletions modules/effects/spec/index.spec.ts

This file was deleted.

31 changes: 0 additions & 31 deletions modules/effects/spec/metadata.spec.ts

This file was deleted.

0 comments on commit 015107f

Please sign in to comment.