Skip to content

Commit

Permalink
[Test] Improve 8ade68e & update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
inamiy committed Dec 5, 2015
1 parent 8ade68e commit e9c1bf3
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 49 deletions.
77 changes: 74 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Any => 2, msg=Optional("Hello")

### Transition by Event

Use `<-!` operator to try transition by `Event` rather than specifying target `State` ([Test Case](https://github.com/ReactKit/SwiftState/blob/1be67826b3cc9187dfaac85c2e70613f3129fad6/SwiftStateTests/TryEventTests.swift#L32-L54)).
Use `<-!` operator to try transition by `Event` rather than specifying target `State`.

```swift
enum MyEvent: EventType {
Expand All @@ -79,7 +79,10 @@ let machine = StateMachine<MyState, MyEvent>(state: .State0) { machine in
.State1 => .State2,
])
}


// initial
XCTAssertEqual(machine.state, MyState.State0)

// tryEvent
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State1)
Expand All @@ -88,13 +91,81 @@ XCTAssertEqual(machine.state, MyState.State1)
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State2)

// tryEvent
// tryEvent (fails)
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any")
```

If there is no `Event`-based transition, use built-in `NoEvent` instead.

### State & Event enums with associated values

Above examples use _arrow-style routing_ which are easy to understand, but it lacks in ability to handle state & event enums with associated values. In such cases, use `machine.addRouteMapping()` and pass either of the following closure types (_closure-style routing_):

- `EventRouteMapping`: `(event: E?, fromState: S, userInfo: Any?) -> S?`
- `StateRouteMapping`: `(fromState: S, userInfo: Any?) -> [S]?`

For example:

```swift
enum StrState: StateType {
case Str(String) ...
}
enum StrEvent: EventType {
case Str(String) ...
}

let machine = Machine<StrState, StrEvent>(state: .Str("initial")) { machine in

// add EventRouteMapping
machine.addRouteMapping { event, fromState, userInfo -> StrState? in
// no route for no-event
guard let event = event else { return nil }

switch (event, fromState) {
case (.Str("gogogo"), .Str("initial")):
return .Str("Phase 1")
case (.Str("gogogo"), .Str("Phase 1")):
return .Str("Phase 2")
case (.Str("finish"), .Str("Phase 2")):
return .Str("end")
default:
return nil
}
}

}

// initial
XCTAssertEqual(machine.state, StrState.Str("initial"))

// tryEvent (fails)
machine <-! .Str("go?")
XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.")

// tryEvent
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"))

// tryEvent (fails)
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.")

// tryEvent
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"))

// tryEvent (fails)
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.")

// tryEvent
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("end"))
```

This behaves very similar to JavaScript's safe state-container [rackt/Redux](https://github.com/rackt/redux), where `EventRouteMapping` can be interpretted as `Redux.Reducer`.

For more examples, please see XCTest cases.


Expand Down
56 changes: 55 additions & 1 deletion Tests/BasicTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class BasicTests: _TestCase
])
}

// initial
XCTAssertEqual(machine.state, MyState.State0)

// tryEvent
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State1)
Expand All @@ -68,8 +71,59 @@ class BasicTests: _TestCase
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State2)

// tryEvent
// tryEvent (fails)
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any")
}

func testREADME_routeMapping()
{
let machine = Machine<StrState, StrEvent>(state: .Str("initial")) { machine in

// add EventRouteMapping
machine.addRouteMapping { event, fromState, userInfo -> StrState? in
// no route for no-event
guard let event = event else { return nil }

switch (event, fromState) {
case (.Str("gogogo"), .Str("initial")):
return .Str("Phase 1")
case (.Str("gogogo"), .Str("Phase 1")):
return .Str("Phase 2")
case (.Str("finish"), .Str("Phase 2")):
return .Str("end")
default:
return nil
}
}

}

// initial
XCTAssertEqual(machine.state, StrState.Str("initial"))

// tryEvent (fails)
machine <-! .Str("go?")
XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.")

// tryEvent
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"))

// tryEvent (fails)
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.")

// tryEvent
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"))

// tryEvent (fails)
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.")

// tryEvent
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("end"))
}
}
44 changes: 22 additions & 22 deletions Tests/MachineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,63 +306,63 @@ class MachineTests: _TestCase
{
var invokeCount = 0

let machine = Machine<MyState2, MyEvent2>(state: .State0("initial")) { machine in
let machine = Machine<StrState, StrEvent>(state: .Str("initial")) { machine in

// add EventRouteMapping
machine.addRouteMapping { event, fromState, userInfo -> MyState2? in
machine.addRouteMapping { event, fromState, userInfo -> StrState? in
// no route for no-event
guard let event = event else { return nil }

switch (event, fromState) {
case (.Event0("gogogo"), .State0("initial")):
return .State0("Phase 1")
case (.Event0("gogogo"), .State0("Phase 1")):
return .State0("Phase 2")
case (.Event0("finish"), .State0("Phase 2")):
return .State0("end")
case (.Str("gogogo"), .Str("initial")):
return .Str("Phase 1")
case (.Str("gogogo"), .Str("Phase 1")):
return .Str("Phase 2")
case (.Str("finish"), .Str("Phase 2")):
return .Str("end")
default:
return nil
}
}

machine.addHandler(event: .Event0("gogogo")) { context in
machine.addHandler(event: .Str("gogogo")) { context in
invokeCount++
return
}

}

// initial
XCTAssertEqual(machine.state, MyState2.State0("initial"))
XCTAssertEqual(machine.state, StrState.Str("initial"))

// tryEvent (fails)
machine <-! .Event0("go?")
XCTAssertEqual(machine.state, MyState2.State0("initial"), "No change.")
machine <-! .Str("go?")
XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.")
XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed")

// tryEvent
machine <-! .Event0("gogogo")
XCTAssertEqual(machine.state, MyState2.State0("Phase 1"))
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"))
XCTAssertEqual(invokeCount, 1)

// tryEvent (fails)
machine <-! .Event0("finish")
XCTAssertEqual(machine.state, MyState2.State0("Phase 1"), "No change.")
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.")
XCTAssertEqual(invokeCount, 1, "Handler should NOT be performed")

// tryEvent
machine <-! .Event0("gogogo")
XCTAssertEqual(machine.state, MyState2.State0("Phase 2"))
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"))
XCTAssertEqual(invokeCount, 2)

// tryEvent (fails)
machine <-! .Event0("gogogo")
XCTAssertEqual(machine.state, MyState2.State0("Phase 2"), "No change.")
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.")
XCTAssertEqual(invokeCount, 2, "Handler should NOT be performed")

// tryEvent
machine <-! .Event0("finish")
XCTAssertEqual(machine.state, MyState2.State0("end"))
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("end"))
XCTAssertEqual(invokeCount, 2, "gogogo-Handler should NOT be performed")

}
Expand Down
26 changes: 13 additions & 13 deletions Tests/MiscTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ class MiscTests: _TestCase
// StateType + associated value
func testREADME_associatedValue()
{
let machine = StateMachine<MyState2, MyEvent2>(state: .State0("0")) { machine in
let machine = StateMachine<StrState, StrEvent>(state: .Str("0")) { machine in

machine.addRoute(.State0("0") => .State0("1"))
machine.addRoute(.Any => .State0("2")) { context in print("Any => 2, msg=\(context.userInfo)") }
machine.addRoute(.State0("2") => .Any) { context in print("2 => Any, msg=\(context.userInfo)") }
machine.addRoute(.Str("0") => .Str("1"))
machine.addRoute(.Any => .Str("2")) { context in print("Any => 2, msg=\(context.userInfo)") }
machine.addRoute(.Str("2") => .Any) { context in print("2 => Any, msg=\(context.userInfo)") }

// add handler (handlerContext = (event, transition, order, userInfo))
machine.addHandler(.State0("0") => .State0("1")) { context in
machine.addHandler(.Str("0") => .Str("1")) { context in
print("0 => 1")
}

Expand All @@ -70,17 +70,17 @@ class MiscTests: _TestCase

// tryState 0 => 1 => 2 => 1 => 0

machine <- .State0("1")
XCTAssertEqual(machine.state, MyState2.State0("1"))
machine <- .Str("1")
XCTAssertEqual(machine.state, StrState.Str("1"))

machine <- (.State0("2"), "Hello")
XCTAssertEqual(machine.state, MyState2.State0("2"))
machine <- (.Str("2"), "Hello")
XCTAssertEqual(machine.state, StrState.Str("2"))

machine <- (.State0("1"), "Bye")
XCTAssertEqual(machine.state, MyState2.State0("1"))
machine <- (.Str("1"), "Bye")
XCTAssertEqual(machine.state, StrState.Str("1"))

machine <- .State0("0") // fail: no 1 => 0
XCTAssertEqual(machine.state, MyState2.State0("1"))
machine <- .Str("0") // fail: no 1 => 0
XCTAssertEqual(machine.state, StrState.Str("1"))

print("machine.state = \(machine.state)")
}
Expand Down
10 changes: 5 additions & 5 deletions Tests/MyEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ enum MyEvent: EventType
case Event0, Event1
}

enum MyEvent2: EventType
enum StrEvent: EventType
{
case Event0(String)
case Str(String)

var hashValue: Int
{
switch self {
case .Event0(let str): return str.hashValue
case .Str(let str): return str.hashValue
}
}
}

func == (lhs: MyEvent2, rhs: MyEvent2) -> Bool
func == (lhs: StrEvent, rhs: StrEvent) -> Bool
{
switch (lhs, rhs) {
case let (.Event0(str1), .Event0(str2)):
case let (.Str(str1), .Str(str2)):
return str1 == str2
// default:
// return false
Expand Down
10 changes: 5 additions & 5 deletions Tests/MyState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ enum MyState: StateType
case State0, State1, State2, State3
}

enum MyState2: StateType
enum StrState: StateType
{
case State0(String)
case Str(String)

var hashValue: Int
{
switch self {
case .State0(let str): return str.hashValue
case .Str(let str): return str.hashValue
}
}
}

func == (lhs: MyState2, rhs: MyState2) -> Bool
func == (lhs: StrState, rhs: StrState) -> Bool
{
switch (lhs, rhs) {
case let (.State0(str1), .State0(str2)):
case let (.Str(str1), .Str(str2)):
return str1 == str2
}
}

0 comments on commit e9c1bf3

Please sign in to comment.