@@ -5,7 +5,10 @@ import XCTest
5
5
6
6
@MainActor
7
7
final class CompatibilityTests : XCTestCase {
8
- func testCaseStudy_ReentrantActionsFromBuffer( ) {
8
+ // Actions can be re-entrantly sent into the store if an action is sent that holds an object
9
+ // which sends an action on deinit. In order to prevent a simultaneous access exception for this
10
+ // case we need to use `withExtendedLifetime` on the buffered actions when clearing them out.
11
+ func testCaseStudy_ActionReentranceFromClearedBufferCausingDeinitAction( ) {
9
12
let cancelID = UUID ( )
10
13
11
14
struct State : Equatable { }
@@ -75,55 +78,40 @@ final class CompatibilityTests: XCTestCase {
75
78
)
76
79
}
77
80
78
- func testCaseStudy_ReentrantActionsFromPublisher( ) {
79
- struct State : Equatable {
80
- var city : String
81
- var country : String
82
- }
83
-
84
- enum Action : Equatable {
85
- case updateCity( String )
86
- case updateCountry( String )
87
- }
88
-
89
- let reducer = Reducer < State , Action , Void > { state, action, _ in
90
- switch action {
91
- case let . updateCity( city) :
92
- state. city = city
93
- return . none
94
- case let . updateCountry( country) :
95
- state. country = country
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
96
93
return . none
97
- }
98
- }
99
-
100
- let store = Store (
101
- initialState: State ( city: " New York " , country: " USA " ) ,
102
- reducer: reducer,
94
+ } ,
103
95
environment: ( )
104
96
)
97
+
105
98
let viewStore = ViewStore ( store)
106
99
107
100
var cancellables : Set < AnyCancellable > = [ ]
108
-
109
- viewStore. publisher. city
110
- . sink { city in
111
- if city == " London " {
112
- viewStore. send ( . updateCountry( " UK " ) )
113
- }
101
+ viewStore. publisher
102
+ . sink { value in
103
+ if value == 1 { viewStore. send ( 0 ) }
114
104
}
115
105
. store ( in: & cancellables)
116
106
117
- var countryUpdates = [ String] ( )
118
- viewStore. publisher. country
119
- . sink { country in
120
- countryUpdates. append ( country)
121
- }
107
+ var stateChanges : [ Int ] = [ ]
108
+ viewStore. publisher
109
+ . sink { stateChanges. append ( $0) }
122
110
. store ( in: & cancellables)
123
111
124
- viewStore . send ( . updateCity ( " London " ) )
125
-
126
- XCTAssertEqual ( countryUpdates , [ " USA " , " UK " ] )
112
+ XCTAssertEqual ( stateChanges , [ 0 ] )
113
+ viewStore . send ( 1 )
114
+ XCTAssertEqual ( stateChanges , [ 0 , 1 , 0 ] )
127
115
}
128
116
}
129
117
0 commit comments