@@ -4,122 +4,120 @@ import XCTest
4
4
5
5
// `@MainActor` introduces issues gathering tests on Linux
6
6
#if !os(Linux)
7
- @MainActor
8
- final class CompatibilityTests : XCTestCase {
9
- // Actions can be re-entrantly sent into the store if an action is sent that holds an object
10
- // which sends an action on deinit. In order to prevent a simultaneous access exception for this
11
- // case we need to use `withExtendedLifetime` on the buffered actions when clearing them out.
12
- func testCaseStudy_ActionReentranceFromClearedBufferCausingDeinitAction( ) {
13
- let cancelID = UUID ( )
14
-
15
- struct State : Equatable { }
16
- enum Action : Equatable {
17
- case start
18
- case kickOffAction
19
- case actionSender( OnDeinit )
20
- case stop
21
-
22
- var description : String {
23
- switch self {
24
- case . start:
25
- return " start "
26
- case . kickOffAction:
27
- return " kickOffAction "
28
- case . actionSender:
29
- return " actionSender "
30
- case . stop:
31
- return " stop "
32
- }
7
+ @MainActor
8
+ final class CompatibilityTests : XCTestCase {
9
+ // Actions can be re-entrantly sent into the store if an action is sent that holds an object
10
+ // which sends an action on deinit. In order to prevent a simultaneous access exception for this
11
+ // case we need to use `withExtendedLifetime` on the buffered actions when clearing them out.
12
+ func testCaseStudy_ActionReentranceFromClearedBufferCausingDeinitAction( ) {
13
+ let cancelID = UUID ( )
14
+
15
+ struct State : Equatable { }
16
+ enum Action : Equatable {
17
+ case start
18
+ case kickOffAction
19
+ case actionSender( OnDeinit )
20
+ case stop
21
+
22
+ var description : String {
23
+ switch self {
24
+ case . start:
25
+ return " start "
26
+ case . kickOffAction:
27
+ return " kickOffAction "
28
+ case . actionSender:
29
+ return " actionSender "
30
+ case . stop:
31
+ return " stop "
33
32
}
34
33
}
34
+ }
35
35
let ( signal, observer) = Signal < Action , Never > . pipe ( )
36
36
37
- var handledActions : [ String ] = [ ]
37
+ var handledActions : [ String ] = [ ]
38
38
39
- let reducer = AnyReducer < State , Action , Void > { state, action, env in
40
- handledActions. append ( action. description)
39
+ let reducer = Reduce < State , Action > { state, action in
40
+ handledActions. append ( action. description)
41
41
42
- switch action {
43
- case . start:
42
+ switch action {
43
+ case . start:
44
44
return signal. producer
45
- . eraseToEffect ( )
46
- . cancellable ( id: cancelID)
45
+ . eraseToEffect ( )
46
+ . cancellable ( id: cancelID)
47
47
48
- case . kickOffAction:
48
+ case . kickOffAction:
49
49
return EffectTask ( value: . actionSender( OnDeinit { observer. send ( value: . stop) } ) )
50
50
51
- case . actionSender:
52
- return . none
51
+ case . actionSender:
52
+ return . none
53
53
54
- case . stop:
55
- return . cancel( id: cancelID)
56
- }
54
+ case . stop:
55
+ return . cancel( id: cancelID)
57
56
}
58
-
59
- let store = Store (
60
- initialState: . init( ) ,
61
- reducer: reducer,
62
- environment: ( )
63
- )
64
-
65
- let viewStore = ViewStore ( store)
66
-
67
- viewStore. send ( . start)
68
- viewStore. send ( . kickOffAction)
69
-
70
- XCTAssertEqual (
71
- handledActions,
72
- [
73
- " start " ,
74
- " kickOffAction " ,
75
- " actionSender " ,
76
- " stop " ,
77
- ]
78
- )
79
57
}
80
58
81
- // Actions can be re-entrantly sent into the store while observing changes to the store's state.
82
- // In such cases we need to take special care that those re-entrant actions are handled _after_
83
- // the original action.
84
- //
85
- // In particular, this means that in the implementation of `Store.send` we need to flip
86
- // `isSending` to false _after_ the store's state mutation is made so that re-entrant actions
87
- // are buffered rather than immediately handled.
88
- func testCaseStudy_ActionReentranceFromStateObservation( ) {
89
- let store = Store < Int , Int > (
90
- initialState: 0 ,
91
- reducer: . init { state, action, _ in
92
- state = action
93
- return . none
94
- } ,
95
- environment: ( )
96
- )
97
-
98
- let viewStore = ViewStore ( store)
59
+ let store = Store (
60
+ initialState: . init( ) ,
61
+ reducer: reducer
62
+ )
63
+
64
+ let viewStore = ViewStore ( store, observe: { $0 } )
65
+
66
+ viewStore. send ( . start)
67
+ viewStore. send ( . kickOffAction)
68
+
69
+ XCTAssertEqual (
70
+ handledActions,
71
+ [
72
+ " start " ,
73
+ " kickOffAction " ,
74
+ " actionSender " ,
75
+ " stop " ,
76
+ ]
77
+ )
78
+ }
79
+
80
+ // Actions can be re-entrantly sent into the store while observing changes to the store's state.
81
+ // In such cases we need to take special care that those re-entrant actions are handled _after_
82
+ // the original action.
83
+ //
84
+ // In particular, this means that in the implementation of `Store.send` we need to flip
85
+ // `isSending` to false _after_ the store's state mutation is made so that re-entrant actions
86
+ // are buffered rather than immediately handled.
87
+ func testCaseStudy_ActionReentranceFromStateObservation( ) {
88
+ let store = Store < Int , Int > (
89
+ initialState: 0 ,
90
+ reducer: Reduce { state, action in
91
+ state = action
92
+ return . none
93
+ }
94
+ )
95
+
96
+ let viewStore = ViewStore ( store, observe: { $0 } )
99
97
100
98
viewStore. produced. producer
101
99
. startWithValues { value in
102
- if value == 1 {
103
- viewStore. send ( 0 )
104
- }
100
+ if value == 1 {
101
+ viewStore. send ( 0 )
105
102
}
103
+ }
106
104
107
- var stateChanges : [ Int ] = [ ]
105
+ var stateChanges : [ Int ] = [ ]
108
106
viewStore. produced. producer
109
107
. startWithValues { stateChanges. append ( $0) }
110
108
111
- XCTAssertEqual ( stateChanges, [ 0 ] )
112
- viewStore. send ( 1 )
113
- XCTAssertEqual ( stateChanges, [ 0 , 1 , 0 ] )
114
- }
109
+ XCTAssertEqual ( stateChanges, [ 0 ] )
110
+ viewStore. send ( 1 )
111
+ XCTAssertEqual ( stateChanges, [ 0 , 1 , 0 ] )
115
112
}
113
+ }
116
114
117
- private final class OnDeinit : Equatable {
118
- private let onDeinit : ( ) -> Void
119
- init ( onDeinit: @escaping ( ) -> Void ) {
120
- self . onDeinit = onDeinit
121
- }
122
- deinit { self . onDeinit ( ) }
123
- static func == ( lhs: OnDeinit , rhs: OnDeinit ) -> Bool { true }
115
+ private final class OnDeinit : Equatable {
116
+ private let onDeinit : ( ) -> Void
117
+ init ( onDeinit: @escaping ( ) -> Void ) {
118
+ self . onDeinit = onDeinit
124
119
}
120
+ deinit { self . onDeinit ( ) }
121
+ static func == ( lhs: OnDeinit , rhs: OnDeinit ) -> Bool { true }
122
+ }
125
123
#endif
0 commit comments