@@ -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,13 @@ 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+ }
3374}
3475
35- public struct ReceiveStep < ActionType, StateType> {
76+ public struct Receive < ActionType, StateType> : StepProtocol {
3677 public init ( isExpectedAction: @escaping ( ActionType ) -> Bool ,
3778 file: StaticString = #file,
3879 line: UInt = #line,
@@ -59,33 +100,60 @@ public struct ReceiveStep<ActionType, StateType> {
59100 let file : StaticString
60101 let line : UInt
61102 let stateChange : ( inout StateType ) -> Void
62-
63103 let isExpectedAction : ( ActionType ) -> Bool
104+
105+ public var asStep : Step < ActionType , StateType > {
106+ . receive( isExpectedAction: isExpectedAction, file: file, line: line, stateChange: stateChange)
107+ }
108+ }
109+
110+ public struct SideEffectResult < ActionType, StateType> : StepProtocol {
111+ public init ( do perform: @escaping ( ) -> Void ) {
112+ self . perform = perform
113+ }
114+ let perform : ( ) -> Void
115+
116+ public var asStep : Step < ActionType , StateType > {
117+ . sideEffectResult( do: perform)
118+ }
64119}
65120
66- public enum Step < ActionType, StateType> {
67- case send( SendStep < ActionType , StateType > )
68- case receive( ReceiveStep < ActionType , StateType > )
121+ public enum Step < ActionType, StateType> : StepProtocol {
122+ case send(
123+ action: @autoclosure ( ) -> ActionType ,
124+ file: StaticString = #file,
125+ line: UInt = #line,
126+ stateChange: ( inout StateType ) -> Void = { _ in }
127+ )
128+ case receive(
129+ isExpectedAction: ( ActionType ) -> Bool ,
130+ file: StaticString = #file,
131+ line: UInt = #line,
132+ stateChange: ( inout StateType ) -> Void = { _ in }
133+ )
69134 case sideEffectResult(
70135 do: ( ) -> Void
71136 )
137+
138+ public var asStep : Step < ActionType , StateType > {
139+ self
140+ }
72141}
73142
74143extension XCTestCase {
75144 public func assert< M: Middleware > (
76145 initialValue: M . StateType ,
77146 reducer: Reducer < M . InputActionType , M . StateType > ,
78147 middleware: M ,
79- steps: Step < M . InputActionType , M . StateType > ... ,
80- otherSteps: [ Step < M . InputActionType , M . StateType > ] = [ ] ,
148+ @StepBuilder < M . InputActionType , M . StateType > steps: ( ) -> [ Step < M . InputActionType , M . StateType > ] ,
81149 file: StaticString = #file,
82150 line: UInt = #line
83151 ) where M. InputActionType == M . OutputActionType , M. StateType: Equatable {
84152 assert (
85153 initialValue: initialValue,
86154 reducer: reducer,
87155 middleware: middleware,
88- steps: steps + otherSteps ,
156+ steps: steps,
89157 stateEquating: == ,
90158 file: file,
91159 line: line
@@ -96,28 +164,7 @@ extension XCTestCase {
96164 initialValue: M . StateType ,
97165 reducer: Reducer < M . InputActionType , M . StateType > ,
98166 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 > ] ,
167+ @StepBuilder < M . InputActionType , M . StateType > steps: ( ) -> [ Step < M . InputActionType , M . StateType > ] ,
121168 stateEquating: ( M . StateType , M . StateType ) -> Bool ,
122169 file: StaticString = #file,
123170 line: UInt = #line
@@ -132,21 +179,17 @@ extension XCTestCase {
132179 }
133180 middleware. receiveContext ( getState: { state } , output: anyActionHandler)
134181
135- steps. forEach { outerStep in
182+ steps ( ) . forEach { outerStep in
136183 var expected = state
137184
138185 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-
186+ case let . send( action, file, line, stateChange) : //action, file, line, stateChange):
144187 if !middlewareResponses. isEmpty {
145188 XCTFail ( " Action sent before handling \( middlewareResponses. count) pending effect(s) " , file: file, line: line)
146189 }
147190
148191 var afterReducer : AfterReducer = . doNothing( )
149- let action = step . action ( )
192+ let action = action ( )
150193 middleware. handle (
151194 action: action,
152195 from: . init( file: " \( file) " , function: " " , line: line, info: nil ) ,
@@ -157,11 +200,7 @@ extension XCTestCase {
157200
158201 stateChange ( & expected)
159202 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-
203+ case let . receive( action, file, line, stateChange) : //action, file, line, stateChange):
165204 if middlewareResponses. isEmpty {
166205 _ = XCTWaiter . wait ( for: [ gotAction] , timeout: 0.2 )
167206 }
@@ -170,7 +209,7 @@ extension XCTestCase {
170209 break
171210 }
172211 let first = middlewareResponses. removeFirst ( )
173- XCTAssertTrue ( step . isExpectedAction ( first) , file: file, line: line)
212+ XCTAssertTrue ( action ( first) , file: file, line: line)
174213
175214 var afterReducer : AfterReducer = . doNothing( )
176215 middleware. handle (
@@ -208,9 +247,7 @@ extension XCTestCase {
208247 var stateString : String = " " , expectedString : String = " "
209248 dump ( statusQuo, to: & stateString, name: nil , indent: 2 )
210249 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) "
250+ return " Expected state after step \( step) different from current state \n \( expectedString) \n \( stateString) "
214251 } ( ) ,
215252 file: file,
216253 line: line
0 commit comments