@@ -13,7 +13,44 @@ import Foundation
1313@testable import SwiftRex
1414import XCTest
1515
16- public struct SendStep < ActionType, StateType> {
16+ @resultBuilder public struct StepBuilder < Action, State> {
17+ public static func buildBlock( _ steps: Step < Action , State > ... ) -> [ Step < Action , State > ] {
18+ steps
19+ }
20+
21+ public static func buildEither( first component: [ Step < Action , State > ] ) -> [ Step < Action , State > ] {
22+ component
23+ }
24+
25+ public static func buildEither( second component: [ Step < Action , State > ] ) -> [ Step < Action , State > ] {
26+ component
27+ }
28+
29+ public static func buildLimitedAvailability( _ component: [ Step < Action , State > ] ) -> [ Step < Action , State > ] {
30+ component
31+ }
32+
33+ public static func buildOptional( _ component: [ Step < Action , State > ] ? ) -> [ Step < Action , State > ] {
34+ component ?? [ ]
35+ }
36+
37+ public static func buildArray( _ components: [ [ Step < Action , State > ] ] ) -> [ Step < Action , State > ] {
38+ components. flatMap { $0 }
39+ }
40+
41+ public static func buildExpression< S: StepProtocol > ( _ expression: S ) -> Step < Action , State > where S. ActionType == Action , S. StateType == State {
42+ expression. asStep
43+ }
44+ }
45+
46+ public protocol StepProtocol {
47+ associatedtype ActionType
48+ associatedtype StateType
49+
50+ var asStep : Step < ActionType , StateType > { get }
51+ }
52+
53+ public struct Send < ActionType, StateType> : StepProtocol {
1754 public init (
1855 action: @autoclosure @escaping ( ) -> ActionType ,
1956 file: StaticString = #file,
@@ -30,9 +67,20 @@ public struct SendStep<ActionType, StateType> {
3067 let file : StaticString
3168 let line : UInt
3269 let stateChange : ( inout StateType ) -> Void
70+
71+ public var asStep : Step < ActionType , StateType > {
72+ . send( action: action ( ) , file: file, line: line, stateChange: stateChange)
73+ }
74+
75+ public func expectStateToHaveChanged( _ expectedMutation: @escaping ( inout StateType ) -> Void = { _ in } ) -> Send {
76+ . init( action: action ( ) , file: file, line: line, stateChange: { state in
77+ self . stateChange ( & state)
78+ expectedMutation ( & state)
79+ } )
80+ }
3381}
3482
35- public struct ReceiveStep < ActionType, StateType> {
83+ public struct Receive < ActionType, StateType> : StepProtocol {
3684 public init ( isExpectedAction: @escaping ( ActionType ) -> Bool ,
3785 file: StaticString = #file,
3886 line: UInt = #line,
@@ -59,33 +107,67 @@ public struct ReceiveStep<ActionType, StateType> {
59107 let file : StaticString
60108 let line : UInt
61109 let stateChange : ( inout StateType ) -> Void
62-
63110 let isExpectedAction : ( ActionType ) -> Bool
111+
112+ public var asStep : Step < ActionType , StateType > {
113+ . receive( isExpectedAction: isExpectedAction, file: file, line: line, stateChange: stateChange)
114+ }
115+
116+ public func expectStateToHaveChanged( _ expectedMutation: @escaping ( inout StateType ) -> Void = { _ in } ) -> Receive {
117+ . init( isExpectedAction: isExpectedAction, file: file, line: line, stateChange: { state in
118+ self . stateChange ( & state)
119+ expectedMutation ( & state)
120+ } )
121+ }
64122}
65123
66- public enum Step < ActionType, StateType> {
67- case send( SendStep < ActionType , StateType > )
68- case receive( ReceiveStep < ActionType , StateType > )
124+ public struct SideEffectResult < ActionType, StateType> : StepProtocol {
125+ public init ( do perform: @escaping ( ) -> Void ) {
126+ self . perform = perform
127+ }
128+ let perform : ( ) -> Void
129+
130+ public var asStep : Step < ActionType , StateType > {
131+ . sideEffectResult( do: perform)
132+ }
133+ }
134+
135+ public enum Step < ActionType, StateType> : StepProtocol {
136+ case send(
137+ action: @autoclosure ( ) -> ActionType ,
138+ file: StaticString = #file,
139+ line: UInt = #line,
140+ stateChange: ( inout StateType ) -> Void = { _ in }
141+ )
142+ case receive(
143+ isExpectedAction: ( ActionType ) -> Bool ,
144+ file: StaticString = #file,
145+ line: UInt = #line,
146+ stateChange: ( inout StateType ) -> Void = { _ in }
147+ )
69148 case sideEffectResult(
70149 do: ( ) -> Void
71150 )
151+
152+ public var asStep : Step < ActionType , StateType > {
153+ self
154+ }
72155}
73156
74157extension XCTestCase {
75158 public func assert< M: Middleware > (
76159 initialValue: M . StateType ,
77160 reducer: Reducer < M . InputActionType , M . StateType > ,
78161 middleware: M ,
79- steps: Step < M . InputActionType , M . StateType > ... ,
80- otherSteps: [ Step < M . InputActionType , M . StateType > ] = [ ] ,
162+ @StepBuilder < M . InputActionType , M . StateType > steps: ( ) -> [ Step < M . InputActionType , M . StateType > ] ,
81163 file: StaticString = #file,
82164 line: UInt = #line
83165 ) where M. InputActionType == M . OutputActionType , M. StateType: Equatable {
84166 assert (
85167 initialValue: initialValue,
86168 reducer: reducer,
87169 middleware: middleware,
88- steps: steps + otherSteps ,
170+ steps: steps,
89171 stateEquating: == ,
90172 file: file,
91173 line: line
@@ -96,28 +178,7 @@ extension XCTestCase {
96178 initialValue: M . StateType ,
97179 reducer: Reducer < M . InputActionType , M . StateType > ,
98180 middleware: M ,
99- steps: Step < M . InputActionType , M . StateType > ... ,
100- otherSteps: [ Step < M . InputActionType , M . StateType > ] = [ ] ,
101- stateEquating: ( M . StateType , M . StateType ) -> Bool ,
102- file: StaticString = #file,
103- line: UInt = #line
104- ) where M. InputActionType == M . OutputActionType {
105- assert (
106- initialValue: initialValue,
107- reducer: reducer,
108- middleware: middleware,
109- steps: steps + otherSteps,
110- stateEquating: stateEquating,
111- file: file,
112- line: line
113- )
114- }
115-
116- public func assert< M: Middleware > (
117- initialValue: M . StateType ,
118- reducer: Reducer < M . InputActionType , M . StateType > ,
119- middleware: M ,
120- steps: [ Step < M . InputActionType , M . StateType > ] ,
181+ @StepBuilder < M . InputActionType , M . StateType > steps: ( ) -> [ Step < M . InputActionType , M . StateType > ] ,
121182 stateEquating: ( M . StateType , M . StateType ) -> Bool ,
122183 file: StaticString = #file,
123184 line: UInt = #line
@@ -132,21 +193,17 @@ extension XCTestCase {
132193 }
133194 middleware. receiveContext ( getState: { state } , output: anyActionHandler)
134195
135- steps. forEach { outerStep in
196+ steps ( ) . forEach { outerStep in
136197 var expected = state
137198
138199 switch outerStep {
139- case let . send( step) : //action, file, line, stateChange):
140- let file = step. file
141- let line = step. line
142- let stateChange = step. stateChange
143-
200+ case let . send( action, file, line, stateChange) : //action, file, line, stateChange):
144201 if !middlewareResponses. isEmpty {
145202 XCTFail ( " Action sent before handling \( middlewareResponses. count) pending effect(s) " , file: file, line: line)
146203 }
147204
148205 var afterReducer : AfterReducer = . doNothing( )
149- let action = step . action ( )
206+ let action = action ( )
150207 middleware. handle (
151208 action: action,
152209 from: . init( file: " \( file) " , function: " " , line: line, info: nil ) ,
@@ -157,11 +214,7 @@ extension XCTestCase {
157214
158215 stateChange ( & expected)
159216 ensureStateMutation ( equating: stateEquating, statusQuo: state, expected: expected, step: outerStep)
160- case let . receive( step) : //action, file, line, stateChange):
161- let file = step. file
162- let line = step. line
163- let stateChange = step. stateChange
164-
217+ case let . receive( action, file, line, stateChange) : //action, file, line, stateChange):
165218 if middlewareResponses. isEmpty {
166219 _ = XCTWaiter . wait ( for: [ gotAction] , timeout: 0.2 )
167220 }
@@ -170,7 +223,7 @@ extension XCTestCase {
170223 break
171224 }
172225 let first = middlewareResponses. removeFirst ( )
173- XCTAssertTrue ( step . isExpectedAction ( first) , file: file, line: line)
226+ XCTAssertTrue ( action ( first) , file: file, line: line)
174227
175228 var afterReducer : AfterReducer = . doNothing( )
176229 middleware. handle (
@@ -208,9 +261,7 @@ extension XCTestCase {
208261 var stateString : String = " " , expectedString : String = " "
209262 dump ( statusQuo, to: & stateString, name: nil , indent: 2 )
210263 dump ( expected, to: & expectedString, name: nil , indent: 2 )
211- let difference = diff ( old: expectedString, new: stateString) ?? " "
212-
213- return " Expected state after step \( step) different from current state \n \( difference) "
264+ return " Expected state after step \( step) different from current state \n \( expectedString) \n \( stateString) "
214265 } ( ) ,
215266 file: file,
216267 line: line
0 commit comments