@@ -4,7 +4,10 @@ import XCTest
4
4
5
5
@MainActor
6
6
final class CompatibilityTests : XCTestCase {
7
- func testCaseStudy_ReentrantActionsFromBuffer( ) {
7
+ // Actions can be re-entrantly sent into the store if an action is sent that holds an object
8
+ // which sends an action on deinit. In order to prevent a simultaneous access exception for this
9
+ // case we need to use `withExtendedLifetime` on the buffered actions when clearing them out.
10
+ func testCaseStudy_ActionReentranceFromClearedBufferCausingDeinitAction( ) {
8
11
let cancelID = UUID ( )
9
12
10
13
struct State : Equatable { }
@@ -73,51 +76,37 @@ final class CompatibilityTests: XCTestCase {
73
76
)
74
77
}
75
78
76
- func testCaseStudy_ReentrantActionsFromProducer( ) {
77
- struct State : Equatable {
78
- var city : String
79
- var country : String
80
- }
81
-
82
- enum Action : Equatable {
83
- case updateCity( String )
84
- case updateCountry( String )
85
- }
86
-
87
- let reducer = Reducer < State , Action , Void > { state, action, _ in
88
- switch action {
89
- case let . updateCity( city) :
90
- state. city = city
79
+ // Actions can be re-entrantly sent into the store while observing changes to the store's state.
80
+ // In such cases we need to take special care that those re-entrant actions are handled _after_
81
+ // the original action.
82
+ //
83
+ // In particular, this means that in the implementation of `Store.send` we need to flip
84
+ // `isSending` to false _after_ the store's state mutation is made so that re-entrant actions
85
+ // are buffered rather than immediately handled.
86
+ func testCaseStudy_ActionReentranceFromStateObservation( ) {
87
+ let store = Store < Int , Int > (
88
+ initialState: 0 ,
89
+ reducer: . init { state, action, _ in
90
+ state = action
91
91
return . none
92
- case let . updateCountry( country) :
93
- state. country = country
94
- return . none
95
- }
96
- }
97
-
98
- let store = Store (
99
- initialState: State ( city: " New York " , country: " USA " ) ,
100
- reducer: reducer,
92
+ } ,
101
93
environment: ( )
102
94
)
103
- let viewStore = ViewStore ( store)
104
95
105
- viewStore. produced. city
106
- . startWithValues { city in
107
- if city == " London " {
108
- viewStore. send ( . updateCountry( " UK " ) )
109
- }
110
- }
96
+ let viewStore = ViewStore ( store)
111
97
112
- var countryUpdates = [ String] ( )
113
- viewStore. produced. country
114
- . startWithValues { country in
115
- countryUpdates. append ( country)
98
+ viewStore. produced. producer
99
+ . startWithValues { value in
100
+ if value == 1 { viewStore. send ( 0 ) }
116
101
}
117
102
118
- viewStore. send ( . updateCity( " London " ) )
103
+ var stateChanges : [ Int ] = [ ]
104
+ viewStore. produced. producer
105
+ . startWithValues { stateChanges. append ( $0) }
119
106
120
- XCTAssertEqual ( countryUpdates, [ " USA " , " UK " ] )
107
+ XCTAssertEqual ( stateChanges, [ 0 ] )
108
+ viewStore. send ( 1 )
109
+ XCTAssertEqual ( stateChanges, [ 0 , 1 , 0 ] )
121
110
}
122
111
}
123
112
0 commit comments