Skip to content

Commit 73ef859

Browse files
Merge pull request #199 from ngrx/master
Convert Unsupported Operations
2 parents a030b03 + 24711ca commit 73ef859

File tree

86 files changed

+3915
-1580
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+3915
-1580
lines changed

.circleci/config.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ jobs:
9797
- checkout
9898
- restore_cache:
9999
key: *cache_key
100-
- run: yarn run example:test --watch=false
100+
- run: yarn run example:test --watch=false --runInBand
101101

102102
example-e2e-tests:
103103
<<: *run_in_browser
@@ -207,9 +207,9 @@ workflows:
207207
- example-tests:
208208
requires:
209209
- install
210-
- example-e2e-tests:
211-
requires:
212-
- install
210+
# - example-e2e-tests:
211+
# requires:
212+
# - install
213213
- docs-tests:
214214
requires:
215215
- install
@@ -226,7 +226,7 @@ workflows:
226226
requires:
227227
- docs
228228
- example-tests
229-
- example-e2e-tests
229+
# - example-e2e-tests
230230
- docs-tests
231231
- test
232232
filters:

modules/effects/spec/actions.spec.ts

Lines changed: 197 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Injector } from '@angular/core';
22
import {
33
Action,
4-
StoreModule,
4+
props,
55
ScannedActionsSubject,
66
ActionsSubject,
7+
createAction,
78
} from '@ngrx/store';
89
import { Actions, ofType } from '../';
910
import { map, toArray, switchMap } from 'rxjs/operators';
@@ -25,16 +26,12 @@ describe('Actions', function() {
2526
type: 'SUBTRACT';
2627
}
2728

28-
function reducer(state: number = 0, action: Action) {
29-
switch (action.type) {
30-
case ADD:
31-
return state + 1;
32-
case SUBTRACT:
33-
return state - 1;
34-
default:
35-
return state;
36-
}
37-
}
29+
const square = createAction('SQUARE');
30+
const multiply = createAction('MULTYPLY', props<{ by: number }>());
31+
const divide = createAction('DIVIDE', props<{ by: number }>());
32+
33+
// Class-based Action types
34+
const actions = [ADD, ADD, SUBTRACT, ADD, SUBTRACT];
3835

3936
beforeEach(function() {
4037
const injector = Injector.create([
@@ -69,12 +66,12 @@ describe('Actions', function() {
6966
});
7067

7168
actions.forEach(action => dispatcher.next(action));
69+
dispatcher.complete();
7270
});
7371

74-
const actions = [ADD, ADD, SUBTRACT, ADD, SUBTRACT];
75-
const expected = actions.filter(type => type === ADD);
72+
it('should filter out actions', () => {
73+
const expected = actions.filter(type => type === ADD);
7674

77-
it('should let you filter out actions', function() {
7875
actions$
7976
.pipe(
8077
ofType(ADD),
@@ -83,15 +80,17 @@ describe('Actions', function() {
8380
)
8481
.subscribe({
8582
next(actual) {
86-
expect(actual).toEqual(expected as any[]);
83+
expect(actual).toEqual(expected);
8784
},
8885
});
8986

9087
actions.forEach(action => dispatcher.next({ type: action }));
9188
dispatcher.complete();
9289
});
9390

94-
it('should let you filter out actions and ofType can take an explicit type argument', function() {
91+
it('should filter out actions and ofType can take an explicit type argument', () => {
92+
const expected = actions.filter(type => type === ADD);
93+
9594
actions$
9695
.pipe(
9796
ofType<AddAction>(ADD),
@@ -100,11 +99,192 @@ describe('Actions', function() {
10099
)
101100
.subscribe({
102101
next(actual) {
103-
expect(actual).toEqual(expected as any[]);
102+
expect(actual).toEqual(expected);
103+
},
104+
});
105+
106+
actions.forEach(action => dispatcher.next({ type: action }));
107+
dispatcher.complete();
108+
});
109+
110+
it('should let you filter out multiple action types with explicit type argument', () => {
111+
const expected = actions.filter(type => type === ADD || type === SUBTRACT);
112+
113+
actions$
114+
.pipe(
115+
ofType<AddAction | SubtractAction>(ADD, SUBTRACT),
116+
map(update => update.type),
117+
toArray()
118+
)
119+
.subscribe({
120+
next(actual) {
121+
expect(actual).toEqual(expected);
122+
},
123+
});
124+
125+
actions.forEach(action => dispatcher.next({ type: action }));
126+
dispatcher.complete();
127+
});
128+
129+
it('should filter out actions by action creator', () => {
130+
actions$
131+
.pipe(
132+
ofType(square),
133+
map(update => update.type),
134+
toArray()
135+
)
136+
.subscribe({
137+
next(actual) {
138+
expect(actual).toEqual(['SQUARE']);
139+
},
140+
});
141+
142+
[...actions, square.type].forEach(action =>
143+
dispatcher.next({ type: action })
144+
);
145+
dispatcher.complete();
146+
});
147+
148+
it('should infer the type for the action when it is filter by action creator with property', () => {
149+
const MULTYPLY_BY = 5;
150+
151+
actions$
152+
.pipe(
153+
ofType(multiply),
154+
map(update => update.by),
155+
toArray()
156+
)
157+
.subscribe({
158+
next(actual) {
159+
expect(actual).toEqual([MULTYPLY_BY]);
104160
},
105161
});
106162

163+
// Unrelated Actions
107164
actions.forEach(action => dispatcher.next({ type: action }));
165+
// Action under test
166+
dispatcher.next(multiply({ by: MULTYPLY_BY }));
167+
dispatcher.complete();
168+
});
169+
170+
it('should infer the type for the action when it is filter by action creator', () => {
171+
// Types are not provided for generic Actions
172+
const untypedActions$: Actions = actions$;
173+
const MULTYPLY_BY = 5;
174+
175+
untypedActions$
176+
.pipe(
177+
ofType(multiply),
178+
// Type is infered, even though untypedActions$ is Actions<Action>
179+
map(update => update.by),
180+
toArray()
181+
)
182+
.subscribe({
183+
next(actual) {
184+
expect(actual).toEqual([MULTYPLY_BY]);
185+
},
186+
});
187+
188+
// Unrelated Actions
189+
actions.forEach(action => dispatcher.next({ type: action }));
190+
// Action under test
191+
dispatcher.next(multiply({ by: MULTYPLY_BY }));
192+
dispatcher.complete();
193+
});
194+
195+
it('should filter out multiple actions by action creator', () => {
196+
const DIVIDE_BY = 3;
197+
const MULTYPLY_BY = 5;
198+
const expected = [DIVIDE_BY, MULTYPLY_BY];
199+
200+
actions$
201+
.pipe(
202+
ofType(divide, multiply),
203+
// Both have 'by' property
204+
map(update => update.by),
205+
toArray()
206+
)
207+
.subscribe({
208+
next(actual) {
209+
expect(actual).toEqual(expected);
210+
},
211+
});
212+
213+
// Unrelated Actions
214+
actions.forEach(action => dispatcher.next({ type: action }));
215+
// Actions under test, in specific order
216+
dispatcher.next(divide({ by: DIVIDE_BY }));
217+
dispatcher.next(divide({ by: MULTYPLY_BY }));
218+
dispatcher.complete();
219+
});
220+
221+
it('should filter out actions by action creator and type string', () => {
222+
const expected = [...actions.filter(type => type === ADD), square.type];
223+
224+
actions$
225+
.pipe(
226+
ofType(ADD, square),
227+
map(update => update.type),
228+
toArray()
229+
)
230+
.subscribe({
231+
next(actual) {
232+
expect(actual).toEqual(expected);
233+
},
234+
});
235+
236+
[...actions, square.type].forEach(action =>
237+
dispatcher.next({ type: action })
238+
);
239+
240+
dispatcher.complete();
241+
});
242+
243+
it('should filter out actions by action creator and type string, with explicit type argument', () => {
244+
const expected = [...actions.filter(type => type === ADD), square.type];
245+
246+
actions$
247+
.pipe(
248+
// Provided type overrides any inference from arguments
249+
ofType<AddAction | ReturnType<typeof square>>(ADD, square),
250+
map(update => update.type),
251+
toArray()
252+
)
253+
.subscribe({
254+
next(actual) {
255+
expect(actual).toEqual(expected);
256+
},
257+
});
258+
259+
[...actions, square.type].forEach(action =>
260+
dispatcher.next({ type: action })
261+
);
262+
263+
dispatcher.complete();
264+
});
265+
266+
it('should filter out up to 5 actions with type inference', () => {
267+
// Mixing all of them, up to 5
268+
const expected = [divide.type, ADD, square.type, SUBTRACT, multiply.type];
269+
270+
actions$
271+
.pipe(
272+
ofType(divide, ADD, square, SUBTRACT, multiply),
273+
map(update => update.type),
274+
toArray()
275+
)
276+
.subscribe({
277+
next(actual) {
278+
expect(actual).toEqual(expected);
279+
},
280+
});
281+
282+
// Actions under test, in specific order
283+
dispatcher.next(divide({ by: 1 }));
284+
dispatcher.next({ type: ADD });
285+
dispatcher.next(square());
286+
dispatcher.next({ type: SUBTRACT });
287+
dispatcher.next(multiply({ by: 2 }));
108288
dispatcher.complete();
109289
});
110290
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { of } from 'rxjs';
2+
import { createEffect, getCreateEffectMetadata } from '../src/effect_creator';
3+
4+
describe('createEffect()', () => {
5+
it('should flag the variable with a meta tag', () => {
6+
const effect = createEffect(() => of({ type: 'a' }));
7+
8+
expect(effect.hasOwnProperty('__@ngrx/effects_create__')).toBe(true);
9+
});
10+
11+
it('should dispatch by default', () => {
12+
const effect: any = createEffect(() => of({ type: 'a' }));
13+
14+
expect(effect['__@ngrx/effects_create__']).toEqual({ dispatch: true });
15+
});
16+
17+
it('should be possible to explicitly create a dispatching effect', () => {
18+
const effect: any = createEffect(() => of({ type: 'a' }), {
19+
dispatch: true,
20+
});
21+
22+
expect(effect['__@ngrx/effects_create__']).toEqual({ dispatch: true });
23+
});
24+
25+
it('should be possible to create a non-dispatching effect', () => {
26+
const effect: any = createEffect(() => of({ type: 'a' }), {
27+
dispatch: false,
28+
});
29+
30+
expect(effect['__@ngrx/effects_create__']).toEqual({ dispatch: false });
31+
});
32+
33+
describe('getCreateEffectMetadata', () => {
34+
it('should get the effects metadata for a class instance', () => {
35+
class Fixture {
36+
a = createEffect(() => of({ type: 'a' }));
37+
b = createEffect(() => of({ type: 'b' }), { dispatch: true });
38+
c = createEffect(() => of({ type: 'c' }), { dispatch: false });
39+
}
40+
41+
const mock = new Fixture();
42+
43+
expect(getCreateEffectMetadata(mock)).toEqual([
44+
{ propertyName: 'a', dispatch: true },
45+
{ propertyName: 'b', dispatch: true },
46+
{ propertyName: 'c', dispatch: false },
47+
]);
48+
});
49+
50+
it('should return an empty array if the effect has not been created with createEffect()', () => {
51+
const fakeCreateEffect: any = () => {};
52+
class Fixture {
53+
a = fakeCreateEffect(() => of({ type: 'A' }));
54+
}
55+
56+
const mock = new Fixture();
57+
58+
expect(getCreateEffectMetadata(mock)).toEqual([]);
59+
});
60+
});
61+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Effect, getEffectDecoratorMetadata } from '../src/effect_decorator';
2+
3+
describe('@Effect()', () => {
4+
describe('getEffectDecoratorMetadata', () => {
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 })
10+
c: any;
11+
}
12+
13+
const mock = new Fixture();
14+
15+
expect(getEffectDecoratorMetadata(mock)).toEqual([
16+
{ propertyName: 'a', dispatch: true },
17+
{ propertyName: 'b', dispatch: true },
18+
{ propertyName: 'c', dispatch: false },
19+
]);
20+
});
21+
22+
it('should return an empty array if the class has not been decorated', () => {
23+
class Fixture {
24+
a: any;
25+
}
26+
27+
const mock = new Fixture();
28+
29+
expect(getEffectDecoratorMetadata(mock)).toEqual([]);
30+
});
31+
});
32+
});

0 commit comments

Comments
 (0)