@@ -4,13 +4,20 @@ import {
4
4
createSlice ,
5
5
AnyAction ,
6
6
isAnyOf ,
7
+ PayloadAction ,
7
8
} from '@reduxjs/toolkit'
8
9
import {
9
10
createActionListenerMiddleware ,
10
11
addListenerAction ,
11
12
removeListenerAction ,
12
13
When ,
13
14
ActionListenerMiddlewareAPI ,
15
+ < << << << HEAD
16
+ = === ===
17
+ ActionListenerMiddleware ,
18
+ TypedAddListenerAction ,
19
+ TypedAddListener ,
20
+ > >>> >>> 12 a04c75 ( Add type tests )
14
21
} from '../index'
15
22
16
23
const middlewareApi = {
@@ -25,6 +32,44 @@ const middlewareApi = {
25
32
26
33
const noop = ( ) => { }
27
34
35
+ export declare type IsAny < T , True , False = never > = true | false extends (
36
+ T extends never ? true : false
37
+ )
38
+ ? True
39
+ : False
40
+
41
+ export declare type IsUnknown < T , True , False = never > = unknown extends T
42
+ ? IsAny < T , False , True >
43
+ : False
44
+
45
+ export function expectType < T > ( t : T ) : T {
46
+ return t
47
+ }
48
+
49
+ type Equals < T , U > = IsAny <
50
+ T ,
51
+ never ,
52
+ IsAny < U , never , [ T ] extends [ U ] ? ( [ U ] extends [ T ] ? any : never ) : never >
53
+ >
54
+ export function expectExactType < T > ( t : T ) {
55
+ return < U extends Equals < T , U > > ( u : U ) => { }
56
+ }
57
+
58
+ type EnsureUnknown < T extends any > = IsUnknown < T , any , never >
59
+ export function expectUnknown < T extends EnsureUnknown < T > > ( t : T ) {
60
+ return t
61
+ }
62
+
63
+ type EnsureAny < T extends any > = IsAny < T , any , never >
64
+ export function expectExactAny < T extends EnsureAny < T > > ( t : T ) {
65
+ return t
66
+ }
67
+
68
+ type IsNotAny < T > = IsAny < T , never , any >
69
+ export function expectNotAny < T extends IsNotAny < T > > ( t : T ) : T {
70
+ return t
71
+ }
72
+
28
73
describe ( 'createActionListenerMiddleware' , ( ) => {
29
74
let store = configureStore ( {
30
75
reducer : ( ) => 42 ,
@@ -42,16 +87,21 @@ describe('createActionListenerMiddleware', () => {
42
87
increment ( state ) {
43
88
state . value += 1
44
89
} ,
90
+ // Use the PayloadAction type to declare the contents of `action.payload`
91
+ incrementByAmount : ( state , action : PayloadAction < number > ) => {
92
+ state . value += action . payload
93
+ } ,
45
94
} ,
46
95
} )
47
- const { increment } = counterSlice . actions
96
+ const { increment, incrementByAmount } = counterSlice . actions
48
97
49
98
function delay ( ms : number ) {
50
99
return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) )
51
100
}
52
101
53
102
let reducer : jest . Mock
54
103
let middleware : ReturnType < typeof createActionListenerMiddleware >
104
+ // let middleware: ActionListenerMiddleware<CounterState> //: ReturnType<typeof createActionListenerMiddleware>
55
105
56
106
const testAction1 = createAction < string > ( 'testAction1' )
57
107
type TestAction1 = ReturnType < typeof testAction1 >
@@ -103,32 +153,6 @@ describe('createActionListenerMiddleware', () => {
103
153
expect ( resultAction ) . toBe ( originalAction )
104
154
} )
105
155
106
- test . skip ( 'Allows dispatching a thunk without TS errors' , ( ) => {
107
- const store = configureStore ( {
108
- reducer : counterSlice . reducer ,
109
- middleware : ( gDM ) => gDM ( ) . prepend ( middleware ) ,
110
- } )
111
- store . dispatch ( increment ( ) )
112
-
113
- let testState = 0
114
-
115
- middleware . addListener (
116
- ( action : any , state : any ) => {
117
- return increment . match ( action ) && state > 1
118
- } ,
119
- ( action , listenerApi ) => {
120
- // TODO Can't get the thunk dispatch types to carry through
121
- // listenerApi.dispatch((dispatch, getState) => {
122
- // testState = getState()
123
- // })
124
- }
125
- )
126
-
127
- store . dispatch ( increment ( ) )
128
-
129
- expect ( testState ) . toBe ( 2 )
130
- } )
131
-
132
156
test ( 'directly subscribing' , ( ) => {
133
157
const listener = jest . fn ( ( _ : TestAction1 ) => { } )
134
158
@@ -335,10 +359,14 @@ describe('createActionListenerMiddleware', () => {
335
359
336
360
test ( '"can unsubscribe via middleware api' , ( ) => {
337
361
const listener = jest . fn (
362
+ < < < << << HEAD
338
363
(
339
364
action : TestAction1 ,
340
365
api : ActionListenerMiddlewareAPI < any , any , any >
341
366
) = > {
367
+ = === = ==
368
+ ( action : TestAction1 , api : ActionListenerMiddlewareAPI < any , any > ) => {
369
+ > >>> > >> 12 a04c75 ( Add type tests )
342
370
if ( action . payload === 'b' ) {
343
371
api . unsubscribe ( )
344
372
}
@@ -486,38 +514,38 @@ describe('createActionListenerMiddleware', () => {
486
514
487
515
middleware . addListener ( ( ) => {
488
516
throw new Error ( 'Predicate Panic!' )
489
- } , firstListener ) ;
517
+ } , firstListener )
490
518
491
519
middleware . addListener ( matcher , secondListener )
492
520
493
521
store . dispatch ( testAction1 ( 'a' ) )
494
- expect ( firstListener ) . not . toHaveBeenCalled ( ) ;
522
+ expect ( firstListener ) . not . toHaveBeenCalled ( )
495
523
expect ( secondListener . mock . calls ) . toEqual ( [
496
524
[ testAction1 ( 'a' ) , middlewareApi ] ,
497
525
] )
498
526
} )
499
527
500
528
test ( 'Notifies listener errors to `onError`, if provided' , ( ) => {
501
- const onError = jest . fn ( ) ;
529
+ const onError = jest . fn ( )
502
530
middleware = createActionListenerMiddleware ( {
503
- onError
531
+ onError,
504
532
} )
505
533
reducer = jest . fn ( ( ) => ( { } ) )
506
534
store = configureStore ( {
507
535
reducer,
508
536
middleware : ( gDM ) => gDM ( ) . prepend ( middleware ) ,
509
537
} )
510
-
511
- const listenerError = new Error ( 'Boom!' ) ;
512
-
513
- const matcher = ( action : any ) => true
514
-
538
+
539
+ const listenerError = new Error ( 'Boom!' )
540
+
541
+ const matcher = ( action : any ) : action is any => true
542
+
515
543
middleware . addListener ( matcher , ( ) => {
516
- throw listenerError ;
517
- } ) ;
544
+ throw listenerError
545
+ } )
518
546
519
547
store . dispatch ( testAction1 ( 'a' ) )
520
- expect ( onError ) . toBeCalledWith ( listenerError ) ;
548
+ expect ( onError ) . toBeCalledWith ( listenerError )
521
549
} )
522
550
523
551
test ( 'condition method resolves promise when the predicate succeeds' , async ( ) => {
@@ -602,4 +630,189 @@ describe('createActionListenerMiddleware', () => {
602
630
603
631
expect ( finalCount ) . toBe ( 2 )
604
632
} )
633
+
634
+ describe ( 'Type tests' , ( ) => {
635
+ const middleware = createActionListenerMiddleware ( )
636
+ const store = configureStore ( {
637
+ reducer : counterSlice . reducer ,
638
+ middleware : ( gDM ) => gDM ( ) . prepend ( middleware ) ,
639
+ } )
640
+
641
+ test . skip ( 'State args default to unknown' , ( ) => {
642
+ middleware . addListener (
643
+ ( action , currentState , previousState ) : action is AnyAction => {
644
+ expectUnknown ( currentState )
645
+ expectUnknown ( previousState )
646
+ return true
647
+ } ,
648
+ ( action , listenerApi ) => { }
649
+ )
650
+
651
+ middleware . addListener ( increment . match , ( action , listenerApi ) => {
652
+ const listenerState = listenerApi . getState ( )
653
+ expectUnknown ( listenerState )
654
+ listenerApi . dispatch ( ( dispatch , getState ) => {
655
+ const thunkState = getState ( )
656
+ expectUnknown ( thunkState )
657
+ } )
658
+ } )
659
+
660
+ store . dispatch (
661
+ addListenerAction (
662
+ ( action , currentState , previousState ) : action is AnyAction => {
663
+ expectUnknown ( currentState )
664
+ expectUnknown ( previousState )
665
+ return true
666
+ } ,
667
+ ( action , listenerApi ) => {
668
+ const listenerState = listenerApi . getState ( )
669
+ expectUnknown ( listenerState )
670
+ listenerApi . dispatch ( ( dispatch , getState ) => {
671
+ const thunkState = getState ( )
672
+ expectUnknown ( thunkState )
673
+ } )
674
+ }
675
+ )
676
+ )
677
+
678
+ store . dispatch (
679
+ addListenerAction ( increment . match , ( action , listenerApi ) => {
680
+ const listenerState = listenerApi . getState ( )
681
+ expectUnknown ( listenerState )
682
+ // TODO Can't get the thunk dispatch types to carry through
683
+ listenerApi . dispatch ( ( dispatch , getState ) => {
684
+ const thunkState = getState ( )
685
+ expectUnknown ( thunkState )
686
+ } )
687
+ } )
688
+ )
689
+ } )
690
+
691
+ test . skip ( 'Action type is inferred from args' , ( ) => {
692
+ middleware . addListener ( 'abcd' , ( action , listenerApi ) => {
693
+ expectType < { type : 'abcd' } > ( action )
694
+ } )
695
+
696
+ middleware . addListener ( incrementByAmount , ( action , listenerApi ) => {
697
+ expectType < PayloadAction < number > > ( action )
698
+ } )
699
+
700
+ middleware . addListener ( incrementByAmount . match , ( action , listenerApi ) => {
701
+ expectType < PayloadAction < number > > ( action )
702
+ } )
703
+
704
+ store . dispatch (
705
+ addListenerAction ( 'abcd' , ( action , listenerApi ) => {
706
+ expectType < { type : 'abcd' } > ( action )
707
+ } )
708
+ )
709
+
710
+ store . dispatch (
711
+ addListenerAction ( incrementByAmount , ( action , listenerApi ) => {
712
+ expectType < PayloadAction < number > > ( action )
713
+ } )
714
+ )
715
+
716
+ store . dispatch (
717
+ addListenerAction ( incrementByAmount . match , ( action , listenerApi ) => {
718
+ expectType < PayloadAction < number > > ( action )
719
+ } )
720
+ )
721
+ } )
722
+
723
+ test . skip ( 'Can create a pre-typed middleware' , ( ) => {
724
+ const typedMiddleware = createActionListenerMiddleware < CounterState > ( )
725
+
726
+ typedMiddleware . addListener (
727
+ ( action , currentState , previousState ) : action is AnyAction => {
728
+ expectNotAny ( currentState )
729
+ expectNotAny ( previousState )
730
+ expectExactType < CounterState > ( currentState )
731
+ expectExactType < CounterState > ( previousState )
732
+ return true
733
+ } ,
734
+ ( action , listenerApi ) => { }
735
+ )
736
+
737
+ typedMiddleware . addListener ( incrementByAmount , ( action , listenerApi ) => {
738
+ const listenerState = listenerApi . getState ( )
739
+ expectExactType < CounterState > ( listenerState )
740
+ // TODO Can't get the thunk dispatch types to carry through
741
+ listenerApi . dispatch ( ( dispatch , getState ) => {
742
+ const thunkState = listenerApi . getState ( )
743
+ expectExactType < CounterState > ( thunkState )
744
+ } )
745
+ } )
746
+ } )
747
+
748
+ test . skip ( 'Can create pre-typed versions of addListener and addListenerAction' , ( ) => {
749
+ const typedAddListener =
750
+ middleware . addListener as TypedAddListener < CounterState >
751
+ const typedAddListenerAction =
752
+ addListenerAction as TypedAddListenerAction < CounterState >
753
+
754
+ typedAddListener (
755
+ ( action , currentState , previousState ) : action is AnyAction => {
756
+ expectNotAny ( currentState )
757
+ expectNotAny ( previousState )
758
+ expectExactType < CounterState > ( currentState )
759
+ expectExactType < CounterState > ( previousState )
760
+ return true
761
+ } ,
762
+ ( action , listenerApi ) => {
763
+ const listenerState = listenerApi . getState ( )
764
+ expectExactType < CounterState > ( listenerState )
765
+ // TODO Can't get the thunk dispatch types to carry through
766
+ listenerApi . dispatch ( ( dispatch , getState ) => {
767
+ const thunkState = listenerApi . getState ( )
768
+ expectExactType < CounterState > ( thunkState )
769
+ } )
770
+ }
771
+ )
772
+
773
+ typedAddListener ( incrementByAmount . match , ( action , listenerApi ) => {
774
+ const listenerState = listenerApi . getState ( )
775
+ expectExactType < CounterState > ( listenerState )
776
+ // TODO Can't get the thunk dispatch types to carry through
777
+ listenerApi . dispatch ( ( dispatch , getState ) => {
778
+ const thunkState = listenerApi . getState ( )
779
+ expectExactType < CounterState > ( thunkState )
780
+ } )
781
+ } )
782
+
783
+ store . dispatch (
784
+ typedAddListenerAction (
785
+ ( action , currentState , previousState ) : action is AnyAction => {
786
+ expectNotAny ( currentState )
787
+ expectNotAny ( previousState )
788
+ expectExactType < CounterState > ( currentState )
789
+ expectExactType < CounterState > ( previousState )
790
+ return true
791
+ } ,
792
+ ( action , listenerApi ) => {
793
+ const listenerState = listenerApi . getState ( )
794
+ expectExactType < CounterState > ( listenerState )
795
+ listenerApi . dispatch ( ( dispatch , getState ) => {
796
+ const thunkState = listenerApi . getState ( )
797
+ expectExactType < CounterState > ( thunkState )
798
+ } )
799
+ }
800
+ )
801
+ )
802
+
803
+ store . dispatch (
804
+ typedAddListenerAction (
805
+ incrementByAmount . match ,
806
+ ( action , listenerApi ) => {
807
+ const listenerState = listenerApi . getState ( )
808
+ expectExactType < CounterState > ( listenerState )
809
+ listenerApi . dispatch ( ( dispatch , getState ) => {
810
+ const thunkState = listenerApi . getState ( )
811
+ expectExactType < CounterState > ( thunkState )
812
+ } )
813
+ }
814
+ )
815
+ )
816
+ } )
817
+ } )
605
818
} )
0 commit comments