@@ -9,6 +9,10 @@ describe('ComponentStore types', () => {
9
9
import { of, EMPTY, Observable } from 'rxjs';
10
10
import { concatMap } from 'rxjs/operators';
11
11
12
+ interface Obj {
13
+ prop: string;
14
+ }
15
+
12
16
const number$: Observable<number> = of(5);
13
17
const string$: Observable<string> = of('string');
14
18
@@ -20,76 +24,88 @@ describe('ComponentStore types', () => {
20
24
21
25
describe ( 'infers Subscription' , ( ) => {
22
26
it ( 'when argument type is specified and a variable with corresponding type is passed' , ( ) => {
23
- expectSnippet (
24
- `const eff = componentStore.effect((e: Observable<string>) => number$)('string');`
25
- ) . toInfer ( 'eff ' , 'Subscription' ) ;
27
+ const effectTest = `const sub = componentStore.effect((e: Observable<string>) => number$)('string');` ;
28
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
29
+ expectSnippet ( effectTest ) . toInfer ( 'sub ' , 'Subscription' ) ;
26
30
} ) ;
27
31
28
32
it (
29
33
'when argument type is specified, returns EMPTY and ' +
30
34
'a variable with corresponding type is passed' ,
31
35
( ) => {
32
- expectSnippet (
33
- `const eff = componentStore.effect((e: Observable<string>) => EMPTY)('string');`
34
- ) . toInfer ( 'eff ' , 'Subscription' ) ;
36
+ const effectTest = `const sub = componentStore.effect((e: Observable<string>) => EMPTY)('string');` ;
37
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
38
+ expectSnippet ( effectTest ) . toInfer ( 'sub ' , 'Subscription' ) ;
35
39
}
36
40
) ;
37
41
38
42
it ( 'when argument type is specified and an Observable with corresponding type is passed' , ( ) => {
39
- expectSnippet (
40
- `const eff = componentStore.effect((e: Observable<string>) => EMPTY)(string$);`
41
- ) . toInfer ( 'eff ' , 'Subscription' ) ;
43
+ const effectTest = `const sub = componentStore.effect((e: Observable<string>) => EMPTY)(string$);` ;
44
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
45
+ expectSnippet ( effectTest ) . toInfer ( 'sub ' , 'Subscription' ) ;
42
46
} ) ;
43
47
44
48
it ( 'when argument type is specified as Observable<unknown> and any type is passed' , ( ) => {
45
- expectSnippet (
46
- `const eff = componentStore.effect((e: Observable<unknown>) => EMPTY)(5);`
47
- ) . toInfer ( 'eff ' , 'Subscription' ) ;
49
+ const effectTest = `const sub = componentStore.effect((e: Observable<unknown>) => EMPTY)(5);` ;
50
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
51
+ expectSnippet ( effectTest ) . toInfer ( 'sub ' , 'Subscription' ) ;
48
52
} ) ;
49
53
50
54
it ( 'when generic type is specified and a variable with corresponding type is passed' , ( ) => {
51
- expectSnippet (
52
- `const eff = componentStore.effect<string>((e) => number$)('string');`
53
- ) . toInfer ( 'eff ' , 'Subscription' ) ;
55
+ const effectTest = `const sub = componentStore.effect<string>((e) => number$)('string');` ;
56
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
57
+ expectSnippet ( effectTest ) . toInfer ( 'sub ' , 'Subscription' ) ;
54
58
} ) ;
55
59
56
60
it ( 'when generic type is specified as unknown and a variable with any type is passed' , ( ) => {
57
- expectSnippet (
58
- `const eff = componentStore.effect<unknown>((e) => number$)('string');`
59
- ) . toInfer ( 'eff ' , 'Subscription' ) ;
61
+ const effectTest = `const sub = componentStore.effect<unknown>((e) => number$)('string');` ;
62
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
63
+ expectSnippet ( effectTest ) . toInfer ( 'sub ' , 'Subscription' ) ;
60
64
} ) ;
61
65
62
66
it ( 'when generic type is specified as unknown and origin can still be piped' , ( ) => {
63
- expectSnippet (
64
- `const eff = componentStore.effect<unknown>((e) => e.pipe(concatMap(() => of())))('string');`
65
- ) . toInfer ( 'eff ' , 'Subscription' ) ;
67
+ const effectTest = `const sub = componentStore.effect<unknown>((e) => e.pipe(concatMap(() => of())))('string');` ;
68
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
69
+ expectSnippet ( effectTest ) . toInfer ( 'sub ' , 'Subscription' ) ;
66
70
} ) ;
67
71
68
72
it ( 'when generic type is specified as unknown and origin can still be piped' , ( ) => {
69
73
expectSnippet (
70
- `const eff = componentStore.effect<unknown>((e) => e.pipe(concatMap(() => of())))('string');`
71
- ) . toInfer ( 'eff' , 'Subscription' ) ;
74
+ `const sub = componentStore.effect<unknown>((e) => e.pipe(concatMap(() => of())))('string');`
75
+ ) . toInfer ( 'sub' , 'Subscription' ) ;
76
+ } ) ;
77
+
78
+ it ( 'when argument type is an interface and a variable with corresponding type is passed' , ( ) => {
79
+ const effectTest = `const sub = componentStore.effect((e: Observable<Obj>) => number$)({prop: 'string'});` ;
80
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
81
+ expectSnippet ( effectTest ) . toInfer ( 'sub' , 'Subscription' ) ;
82
+ } ) ;
83
+
84
+ it ( 'when argument type is a partial interface and a variable with corresponding type is passed' , ( ) => {
85
+ const effectTest = `const sub = componentStore.effect((e: Observable<Partial<Obj>>) => number$)({prop: 'string'});` ;
86
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
87
+ expectSnippet ( effectTest ) . toInfer ( 'sub' , 'Subscription' ) ;
72
88
} ) ;
73
89
} ) ;
74
90
75
91
describe ( 'infers void' , ( ) => {
76
92
it ( 'when argument type is specified as Observable<void> and nothing is passed' , ( ) => {
77
- expectSnippet (
78
- `const eff = componentStore.effect((e: Observable<void>) => string$) ();`
79
- ) . toInfer ( 'eff ' , 'void' ) ;
93
+ const effectTest = `const v = componentStore.effect((e: Observable<void>) => string$)();` ;
94
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
95
+ expectSnippet ( effectTest ) . toInfer ( 'v ' , 'void' ) ;
80
96
} ) ;
81
97
82
98
it ( 'when type is not specified and origin can still be piped' , ( ) => {
83
- expectSnippet (
84
- // treated as Observable<void> 👇
85
- `const eff = componentStore.effect((e) => e.pipe(concatMap(() => of())))();`
86
- ) . toInfer ( 'eff ' , 'void' ) ;
99
+ // treated as Observable<void> 👇
100
+ const effectTest = `const v = componentStore.effect((e) => e.pipe(concatMap(() => of())))();` ;
101
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
102
+ expectSnippet ( effectTest ) . toInfer ( 'v ' , 'void' ) ;
87
103
} ) ;
88
104
89
105
it ( 'when generic type is specified as void and origin can still be piped' , ( ) => {
90
- expectSnippet (
91
- `const eff = componentStore.effect<void>((e) => e.pipe(concatMap(() => number$)))();`
92
- ) . toInfer ( 'eff ' , 'void' ) ;
106
+ const effectTest = `const v = componentStore.effect<void>((e) => e.pipe(concatMap(() => number$)))();` ;
107
+ expectSnippet ( effectTest ) . toSucceed ( ) ;
108
+ expectSnippet ( effectTest ) . toInfer ( 'v ' , 'void' ) ;
93
109
} ) ;
94
110
} ) ;
95
111
@@ -144,7 +160,7 @@ describe('ComponentStore types', () => {
144
160
145
161
it ( 'when type is not specified and anything is passed' , ( ) => {
146
162
expectSnippet (
147
- `const eff = componentStore.effect((e) => EMPTY)('string');`
163
+ `const sub = componentStore.effect((e) => EMPTY)('string');`
148
164
) . toFail ( / E x p e c t e d 0 a r g u m e n t s , b u t g o t 1 / ) ;
149
165
} ) ;
150
166
@@ -155,4 +171,128 @@ describe('ComponentStore types', () => {
155
171
} ) ;
156
172
} ) ;
157
173
} ) ;
174
+
175
+ describe ( 'updater' , ( ) => {
176
+ const expectSnippet = expecter (
177
+ ( code ) => `
178
+ import { ComponentStore } from '@ngrx/component-store';
179
+ import { of, EMPTY, Observable } from 'rxjs';
180
+ import { concatMap } from 'rxjs/operators';
181
+
182
+ export enum LoadingState {
183
+ INIT = 'INIT',
184
+ LOADING = 'LOADING',
185
+ LOADED = 'LOADED',
186
+ ERROR = 'ERROR',
187
+ }
188
+
189
+ interface Obj {
190
+ prop: string;
191
+ }
192
+
193
+ const number$: Observable<number> = of(5);
194
+ const string$: Observable<string> = of('string');
195
+
196
+ const componentStore = new ComponentStore({ prop: 'init', prop2: 'yeah!'});
197
+ ${ code }
198
+ ` ,
199
+ compilerOptions ( )
200
+ ) ;
201
+
202
+ describe ( 'infers Subscription' , ( ) => {
203
+ it ( 'when argument type is specified and a variable with corresponding type is passed' , ( ) => {
204
+ const updaterTest = `const sub = componentStore.updater((state, v: string) => ({...state}))('string');` ;
205
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
206
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
207
+ } ) ;
208
+
209
+ it ( 'when argument type is specified and an Observable with corresponding type is passed' , ( ) => {
210
+ const updaterTest = `const sub = componentStore.updater((state, v: string) => ({...state}))(string$);` ;
211
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
212
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
213
+ } ) ;
214
+
215
+ it ( 'when argument type is an interface and a variable with corresponding type is passed' , ( ) => {
216
+ const updaterTest = `const sub = componentStore.updater((state, v: Obj) => ({...state}))({prop: 'obj'});` ;
217
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
218
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
219
+ } ) ;
220
+
221
+ it ( 'when argument type is an partial interface and a variable with corresponding type is passed' , ( ) => {
222
+ const updaterTest = `const sub = componentStore.updater((state, v: Partial<Obj>) => ({...state}))({prop: 'obj'});` ;
223
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
224
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
225
+ } ) ;
226
+
227
+ it ( 'when argument type is an enum and a variable with corresponding type is passed' , ( ) => {
228
+ const updaterTest = `const sub = componentStore.updater((state, v: LoadingState) => ({...state}))(LoadingState.LOADED);` ;
229
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
230
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
231
+ } ) ;
232
+
233
+ it ( 'when argument type is a union and a variable with corresponding type is passed' , ( ) => {
234
+ const updaterTest = `const sub = componentStore.updater((state, v: string|number) => ({...state}))(5);` ;
235
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
236
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
237
+ } ) ;
238
+
239
+ it ( 'when argument type is an intersection and a variable with corresponding type is passed' , ( ) => {
240
+ const updaterTest = `const sub = componentStore.updater((state, v: {p: string} & {p2: number}) => ({...state}))({p: 's', p2: 3});` ;
241
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
242
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
243
+ } ) ;
244
+
245
+ it ( 'when argument type is unknown and any variable is passed' , ( ) => {
246
+ const updaterTest = `const sub = componentStore.updater((state, v: unknown) => ({...state}))({anything: 'works'});` ;
247
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
248
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
249
+ } ) ;
250
+
251
+ it ( 'when generic type is specified and any variable is passed' , ( ) => {
252
+ const updaterTest = `const sub = componentStore.updater<string>((state, v) => ({...state}))('works');` ;
253
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
254
+ expectSnippet ( updaterTest ) . toInfer ( 'sub' , 'Subscription' ) ;
255
+ } ) ;
256
+
257
+ it ( 'when type is not specified and nothing is passed' , ( ) => {
258
+ const updaterTest = `const v = componentStore.updater((state) => ({...state}))();` ;
259
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
260
+ expectSnippet ( updaterTest ) . toInfer ( 'v' , 'void' ) ;
261
+ } ) ;
262
+
263
+ it ( 'when type void is specified and nothing is passed' , ( ) => {
264
+ const updaterTest = `const v = componentStore.updater<void>((state) => ({...state}))();` ;
265
+ expectSnippet ( updaterTest ) . toSucceed ( ) ;
266
+ expectSnippet ( updaterTest ) . toInfer ( 'v' , 'void' ) ;
267
+ } ) ;
268
+ } ) ;
269
+
270
+ describe ( 'catches improper usage' , ( ) => {
271
+ it ( 'when type is specified and argument is not passed' , ( ) => {
272
+ expectSnippet (
273
+ `const sub = componentStore.updater((state, v: string) => ({...state}))();`
274
+ ) . toFail ( / E x p e c t e d 1 a r g u m e n t s , b u t g o t 0 / ) ;
275
+ } ) ;
276
+
277
+ it ( 'when argument type is unknown and nothing is passed' , ( ) => {
278
+ expectSnippet (
279
+ `const sub = componentStore.updater((state, v: unknown) => ({...state}))();`
280
+ ) . toFail ( / E x p e c t e d 1 a r g u m e n t s , b u t g o t 0 / ) ;
281
+ } ) ;
282
+
283
+ it ( 'when no argument is expected but one is passed' , ( ) => {
284
+ expectSnippet (
285
+ `const sub = componentStore.updater((state) => ({...state}))('string');`
286
+ ) . toFail ( / E x p e c t e d 0 a r g u m e n t s , b u t g o t 1 / ) ;
287
+ } ) ;
288
+
289
+ it ( 'when type is specified and Observable argument of incorrect type is passed' , ( ) => {
290
+ expectSnippet (
291
+ `const sub = componentStore.updater((state, v: string) => ({...state}))(number$);`
292
+ ) . toFail (
293
+ / A r g u m e n t o f t y p e ' O b s e r v a b l e < n u m b e r > ' i s n o t a s s i g n a b l e t o p a r a m e t e r o f t y p e ' s t r i n g \| O b s e r v a b l e < s t r i n g > ' /
294
+ ) ;
295
+ } ) ;
296
+ } ) ;
297
+ } ) ;
158
298
} ) ;
0 commit comments