diff --git a/.gitignore b/.gitignore index 8179b60..c623c4f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ DerivedData *.xcuserstate Carthage/Build +.build +Packages/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..06a0e2d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Carthage/Checkouts/xcconfigs"] + path = Carthage/Checkouts/xcconfigs + url = https://github.com/mrackwitz/xcconfigs.git diff --git a/Cartfile.private b/Cartfile.private new file mode 100644 index 0000000..ad60b3a --- /dev/null +++ b/Cartfile.private @@ -0,0 +1 @@ +github "mrackwitz/xcconfigs" \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved new file mode 100644 index 0000000..16b22fd --- /dev/null +++ b/Cartfile.resolved @@ -0,0 +1 @@ +github "mrackwitz/xcconfigs" "3.0" diff --git a/Carthage/Checkouts/xcconfigs b/Carthage/Checkouts/xcconfigs new file mode 160000 index 0000000..6b2682f --- /dev/null +++ b/Carthage/Checkouts/xcconfigs @@ -0,0 +1 @@ +Subproject commit 6b2682f73720832f301fdea0ecb6538cf977c53f diff --git a/Configurations/Base.xcconfig b/Configurations/Base.xcconfig new file mode 100644 index 0000000..a8a1138 --- /dev/null +++ b/Configurations/Base.xcconfig @@ -0,0 +1,13 @@ +// +// Base.xcconfig +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-09. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer; +MACOSX_DEPLOYMENT_TARGET = 10.9; +IPHONEOS_DEPLOYMENT_TARGET = 8.0; +WATCHOS_DEPLOYMENT_TARGET = 2.0; +TVOS_DEPLOYMENT_TARGET = 9.0; \ No newline at end of file diff --git a/Configurations/Debug.xcconfig b/Configurations/Debug.xcconfig new file mode 100644 index 0000000..857b28b --- /dev/null +++ b/Configurations/Debug.xcconfig @@ -0,0 +1,11 @@ +// +// Debug.xcconfig +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-09. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +#include "Base.xcconfig" + +SWIFT_OPTIMIZATION_LEVEL = -Onone; \ No newline at end of file diff --git a/Configurations/Release.xcconfig b/Configurations/Release.xcconfig new file mode 100644 index 0000000..0deeadd --- /dev/null +++ b/Configurations/Release.xcconfig @@ -0,0 +1,11 @@ +// +// Release.xcconfig +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-09. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +#include "Base.xcconfig" + +SWIFT_OPTIMIZATION_LEVEL = -Owholemodule; \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..b86ac08 --- /dev/null +++ b/Package.swift @@ -0,0 +1,13 @@ +// +// Package.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import PackageDescription + +let package = Package( + name: "SwiftState" +) \ No newline at end of file diff --git a/README.md b/README.md index 6a2e989..e8dfc9c 100644 --- a/README.md +++ b/README.md @@ -11,63 +11,62 @@ Elegant state machine for Swift. ```swift enum MyState: StateType { case State0, State1, State2 - case AnyState // create case=Any - - init(nilLiteral: Void) { - self = AnyState - } } ``` ```swift -let machine = StateMachine(state: .State0) { machine in +// setup state machine +let machine = Machine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRoute(nil => .State2) { context in print("Any => 2, msg=\(context.userInfo)") } - machine.addRoute(.State2 => nil) { context in print("2 => Any, msg=\(context.userInfo)") } + machine.addRoute(.Any => .State2) { context in print("Any => 2, msg=\(context.userInfo)") } + machine.addRoute(.State2 => .Any) { context in print("2 => Any, msg=\(context.userInfo)") } - // add handler (handlerContext = (event, transition, order, userInfo)) + // add handler (`context = (event, fromState, toState, userInfo)`) machine.addHandler(.State0 => .State1) { context in print("0 => 1") } // add errorHandler - machine.addErrorHandler { (event, transition, order, userInfo) in + machine.addErrorHandler { event, fromState, toState, userInfo in print("[ERROR] \(transition.fromState) => \(transition.toState)") } } +// initial +XCTAssertTrue(machine.state == .State0) + // tryState 0 => 1 => 2 => 1 => 0 + machine <- .State1 +XCTAssertTrue(machine.state == .State1) + machine <- (.State2, "Hello") +XCTAssertTrue(machine.state == .State2) + machine <- (.State1, "Bye") +XCTAssertTrue(machine.state == .State1) + machine <- .State0 // fail: no 1 => 0 - -print("machine.state = \(machine.state)") +XCTAssertTrue(machine.state == .State1) ``` This will print: ```swift 0 => 1 -Any => 2, msg=Hello -2 => Any, msg=Bye -[ERROR] 1 => 0 -machine.state = 1 +Any => 2, msg=Optional("Hello") +2 => Any, msg=Optional("Bye") +[ERROR] State1 => State0 ``` ### Transition by Event -Use `<-!` operator to try transition by `Event` rather than specifying target `State` ([Test Case](https://github.com/ReactKit/SwiftState/blob/6858f8f49087c4b8b30bd980cfc81e8e74205718/SwiftStateTests/StateMachineEventTests.swift#L54-L76)). +Use `<-!` operator to try transition by `Event` rather than specifying target `State`. ```swift -enum MyEvent: StateEventType { +enum MyEvent: EventType { case Event0, Event1 - case AnyEvent // create case=Any - - init(nilLiteral: Void) { - self = AnyEvent - } } ``` @@ -75,12 +74,15 @@ enum MyEvent: StateEventType { let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoute(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) } - + +// initial +XCTAssertEqual(machine.state, MyState.State0) + // tryEvent machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) @@ -89,12 +91,82 @@ XCTAssertEqual(machine.state, MyState.State1) machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2) +// 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 either of the following functions to apply _closure-style routing_: + +- `machine.addRouteMapping(routeMapping)` + - `RouteMapping`: `(event: E?, fromState: S, userInfo: Any?) -> S?` +- `machine.addStateRouteMapping(stateRouteMapping)` + - `StateRouteMapping`: `(fromState: S, userInfo: Any?) -> [S]?` + +For example: + +```swift +enum StrState: StateType { + case Str(String) ... +} +enum StrEvent: EventType { + case Str(String) ... +} + +let machine = Machine(state: .Str("initial")) { machine in + + 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 -let success = machine <-! .Event0 -XCTAssertEqual(machine.state, MyState.State2) -XCTAssertFalse(success, "Event0 doesn't have 2 => Any") +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 `RouteMapping` can be interpretted as `Redux.Reducer`. + For more examples, please see XCTest cases. @@ -102,31 +174,38 @@ For more examples, please see XCTest cases. - Easy Swift syntax - Transition: `.State0 => .State1`, `[.State0, .State1] => .State2` - - Try transition: `machine <- .State1` - - Try transition + messaging: `machine <- (.State1, "GoGoGo")` + - Try state: `machine <- .State1` + - Try state + messaging: `machine <- (.State1, "GoGoGo")` - Try event: `machine <-! .Event1` - Highly flexible transition routing - - using Condition - - using AnyState (`nil` state) - - or both (blacklisting): `nil => nil` + condition -- Success/Error/Entry/Exit handlers with `order: UInt8` (no before/after handler stuff) -- Removable routes and handlers -- Chaining: `.State0 => .State1 => .State2` -- Event: `machine.addRouteEvent("WakeUp", transitions); machine <-! "WakeUp"` + - Using `Condition` + - Using `.Any` state + - Entry handling: `.Any => .SomeState` + - Exit handling: `.SomeState => .Any` + - Blacklisting: `.Any => .Any` + `Condition` + - Using `.Any` event + + - Route Mapping (closure-based routing): [#36](https://github.com/ReactKit/SwiftState/pull/36) +- Success/Error handlers with `order: UInt8` (more flexible than before/after handlers) +- Removable routes and handlers using `Disposable` +- Route Chaining: `.State0 => .State1 => .State2` - Hierarchical State Machine: [#10](https://github.com/ReactKit/SwiftState/pull/10) ## Terms -Term | Class | Description ---------- | ----------------------------- | ------------------------------------------ -State | `StateType` (protocol) | Mostly enum, describing each state e.g. `.State0`. -Event | `StateEventType` (protocol) | Name for route-group. Transition can be fired via `Event` instead of explicitly targeting next `State`. -Machine | `StateMachine` | State transition manager which can register `Route` and `Handler` separately for variety of transitions. -Transition | `StateTransition` | `From-` and `to-` states represented as `.State1 => .State2`. If `nil` is used for either state, it will be represented as `.AnyState`. -Route | `StateRoute` | `Transition` + `Condition`. -Condition | `Transition -> Bool` | Closure for validating transition. If condition returns `false`, transition will fail and associated handlers will not be invoked. -Handler | `HandlerContext -> Void` | Transition callback invoked after state has been changed. -Chain | `StateTransitionChain` | Group of continuous routes represented as `.State1 => .State2 => .State3` +Term | Type | Description +------------- | ----------------------------- | ------------------------------------------ +State | `StateType` (protocol) | Mostly enum, describing each state e.g. `.State0`. +Event | `EventType` (protocol) | Name for route-group. Transition can be fired via `Event` instead of explicitly targeting next `State`. +State Machine | `Machine` | State transition manager which can register `Route`/`RouteMapping` and `Handler` separately for variety of transitions. +Transition | `Transition` | `From-` and `to-` states represented as `.State1 => .State2`. Also, `.Any` can be used to represent _any state_. +Route | `Route` | `Transition` + `Condition`. +Condition | `Context -> Bool` | Closure for validating transition. If condition returns `false`, transition will fail and associated handlers will not be invoked. +Route Mapping | `(event: E?, fromState: S, userInfo: Any?) -> S?` | Another way of defining routes **using closure instead of transition arrows (`=>`)**. This is useful when state & event are enum with associated values. Return value (`S?`) means preferred-`toState`, where passing `nil` means no routes available. See [#36](https://github.com/ReactKit/SwiftState/pull/36) for more info. +State Route Mapping | `(fromState: S, userInfo: Any?) -> [S]?` | Another way of defining routes **using closure instead of transition arrows (`=>`)**. This is useful when state is enum with associated values. Return value (`[S]?`) means multiple `toState`s from single `fromState` (synonym for multiple routing e.g. `.State0 => [.State1, .State2]`). See [#36](https://github.com/ReactKit/SwiftState/pull/36) for more info. +Handler | `Context -> Void` | Transition callback invoked when state has been changed successfully. +Context | `(event: E?, fromState: S, toState: S, userInfo: Any?)` | Closure argument for `Condition` & `Handler`. +Chain | `TransitionChain` / `RouteChain` | Group of continuous routes represented as `.State1 => .State2 => .State3` ## Related Articles diff --git a/Screenshots/logo.png b/Screenshots/logo.png index 57be251..308596b 100644 Binary files a/Screenshots/logo.png and b/Screenshots/logo.png differ diff --git a/Sources/Disposable.swift b/Sources/Disposable.swift new file mode 100644 index 0000000..6bd3be7 --- /dev/null +++ b/Sources/Disposable.swift @@ -0,0 +1,45 @@ +// +// Disposable.swift +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2014-06-02. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +// +// NOTE: +// This file is a partial copy from ReactiveCocoa v4.0.0-alpha.4 (removing `Atomic` dependency), +// which has not been taken out as microframework yet. +// https://github.com/ReactiveCocoa/ReactiveCocoa/issues/2579 +// +// Note that `ActionDisposable` also works as `() -> ()` wrapper to help suppressing warning: +// "Expression resolved to unused function", when returned function was not used. +// + +/// Represents something that can be “disposed,” usually associated with freeing +/// resources or canceling work. +public protocol Disposable { + /// Whether this disposable has been disposed already. + var disposed: Bool { get } + + func dispose() +} + +/// A disposable that will run an action upon disposal. +public final class ActionDisposable: Disposable { + private var action: (() -> ())? + + public var disposed: Bool { + return action == nil + } + + /// Initializes the disposable to run the given action upon disposal. + public init(action: () -> ()) { + self.action = action + } + + public func dispose() { + self.action?() + self.action = nil + } +} \ No newline at end of file diff --git a/Sources/EventType.swift b/Sources/EventType.swift new file mode 100644 index 0000000..7b13c04 --- /dev/null +++ b/Sources/EventType.swift @@ -0,0 +1,88 @@ +// +// EventType.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/05. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +public protocol EventType: Hashable {} + +// MARK: Event + +/// `EventType` wrapper for handling `.Any` event. +public enum Event +{ + case Some(E) + case Any +} + +extension Event: Hashable +{ + public var hashValue: Int + { + switch self { + case .Some(let x): return x.hashValue + case .Any: return _hashValueForAny + } + } +} + +extension Event: RawRepresentable +{ + public init(rawValue: E?) + { + if let rawValue = rawValue { + self = .Some(rawValue) + } + else { + self = .Any + } + } + + public var rawValue: E? + { + switch self { + case .Some(let x): return x + default: return nil + } + } +} + +public func == (lhs: Event, rhs: Event) -> Bool +{ + switch (lhs, rhs) { + case let (.Some(x1), .Some(x2)) where x1 == x2: + return true + case (.Any, .Any): + return true + default: + return false + } +} + +public func == (lhs: Event, rhs: E) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +public func == (lhs: E, rhs: Event) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +// MARK: NoEvent + +/// Useful for creating StateMachine without events, i.e. `StateMachine`. +public enum NoEvent: EventType +{ + public var hashValue: Int + { + return 0 + } +} + +public func == (lhs: NoEvent, rhs: NoEvent) -> Bool +{ + return true +} diff --git a/SwiftState/Info.plist b/Sources/Info.plist similarity index 100% rename from SwiftState/Info.plist rename to Sources/Info.plist diff --git a/Sources/Machine.swift b/Sources/Machine.swift new file mode 100644 index 0000000..48d571c --- /dev/null +++ b/Sources/Machine.swift @@ -0,0 +1,579 @@ +// +// Machine.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +/// +/// State-machine which can `tryEvent()` (event-driven). +/// +/// This is a superclass (simpler version) of `StateMachine` that doesn't allow `tryState()` (direct state change). +/// +/// This class can be used as a safe state-container in similar way as [rackt/Redux](https://github.com/rackt/redux), +/// where `RouteMapping` can be interpretted as `Redux.Reducer`. +/// +public class Machine +{ + /// Closure argument for `Condition` & `Handler`. + public typealias Context = (event: E?, fromState: S, toState: S, userInfo: Any?) + + /// Closure for validating transition. + /// If condition returns `false`, transition will fail and associated handlers will not be invoked. + public typealias Condition = Context -> Bool + + /// Transition callback invoked when state has been changed successfully. + public typealias Handler = Context -> () + + /// Closure-based route, mainly for `tryEvent()` (and also works for subclass's `tryState()`). + /// - Returns: Preferred `toState`. + public typealias RouteMapping = (event: E?, fromState: S, userInfo: Any?) -> S? + + internal typealias _RouteDict = [Transition : [String : Condition?]] + + private lazy var _routes: [Event : _RouteDict] = [:] + private lazy var _routeMappings: [String : RouteMapping] = [:] + + /// `tryEvent()`-based handler collection. + private lazy var _handlers: [Event : [_HandlerInfo]] = [:] + + internal lazy var _errorHandlers: [_HandlerInfo] = [] + + internal var _state: S + + //-------------------------------------------------- + // MARK: - Init + //-------------------------------------------------- + + public init(state: S, initClosure: (Machine -> ())? = nil) + { + self._state = state + + initClosure?(self) + } + + public func configure(closure: Machine -> ()) + { + closure(self) + } + + public var state: S + { + return self._state + } + + //-------------------------------------------------- + // MARK: - hasRoute + //-------------------------------------------------- + + /// Check for added routes & routeMappings. + public func hasRoute(event event: E, transition: Transition, userInfo: Any? = nil) -> Bool + { + guard let fromState = transition.fromState.rawValue, + toState = transition.toState.rawValue else + { + assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") + return false + } + + return self.hasRoute(event: event, fromState: fromState, toState: toState, userInfo: userInfo) + } + + /// Check for added routes & routeMappings. + public func hasRoute(event event: E, fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + return self._hasRoute(event: event, fromState: fromState, toState: toState, userInfo: userInfo) + } + + internal func _hasRoute(event event: E?, fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + if self._hasRouteInDict(event: event, fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + + if self._hasRouteMappingInDict(event: event, fromState: fromState, toState: .Some(toState), userInfo: userInfo) != nil { + return true + } + + return false + } + + /// Check for `_routes`. + private func _hasRouteInDict(event event: E?, fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + let validTransitions = _validTransitions(fromState: fromState, toState: toState) + + for validTransition in validTransitions { + + var routeDicts: [_RouteDict] = [] + + if let event = event { + for (ev, routeDict) in self._routes { + if ev.rawValue == event || ev == .Any { + routeDicts += [routeDict] + } + } + } + else { + // + // NOTE: + // If `event` is `nil`, it means state-based-transition, + // and all registered event-based-routes will be examined. + // + routeDicts += self._routes.values.lazy + } + + for routeDict in routeDicts { + if let keyConditionDict = routeDict[validTransition] { + for (_, condition) in keyConditionDict { + if _canPassCondition(condition, forEvent: event, fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + } + } + } + } + + return false + } + + /// Check for `_routeMappings`. + private func _hasRouteMappingInDict(event event: E?, fromState: S, toState: S?, userInfo: Any? = nil) -> S? + { + for mapping in self._routeMappings.values { + if let preferredToState = mapping(event: event, fromState: fromState, userInfo: userInfo) + where preferredToState == toState || toState == nil + { + return preferredToState + } + } + + return nil + } + + //-------------------------------------------------- + // MARK: - tryEvent + //-------------------------------------------------- + + /// - Returns: Preferred-`toState`. + public func canTryEvent(event: E, userInfo: Any? = nil) -> S? + { + // check for `_routes` + for case let routeDict? in [self._routes[.Some(event)], self._routes[.Any]] { + for (transition, keyConditionDict) in routeDict { + if transition.fromState == .Some(self.state) || transition.fromState == .Any { + for (_, condition) in keyConditionDict { + // if toState is `.Any`, always treat as identity transition + let toState = transition.toState.rawValue ?? self.state + + if _canPassCondition(condition, forEvent: event, fromState: self.state, toState: toState, userInfo: userInfo) { + return toState + } + } + } + } + } + + // check for `_routeMappings` + if let toState = _hasRouteMappingInDict(event: event, fromState: self.state, toState: nil, userInfo: userInfo) { + return toState + } + + return nil + } + + public func tryEvent(event: E, userInfo: Any? = nil) -> Bool + { + let fromState = self.state + + if let toState = self.canTryEvent(event, userInfo: userInfo) { + + // collect valid handlers before updating state + let validHandlerInfos = self._validHandlerInfos(event: event, fromState: fromState, toState: toState) + + // update state + self._state = toState + + // perform validHandlers after updating state. + for handlerInfo in validHandlerInfos { + handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) + } + + return true + } + else { + for handlerInfo in self._errorHandlers { + let toState = self.state // NOTE: there's no `toState` for failure of event-based-transition + handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) + } + + return false + } + } + + private func _validHandlerInfos(event event: E, fromState: S, toState: S) -> [_HandlerInfo] + { + let validHandlerInfos = [ self._handlers[.Some(event)], self._handlers[.Any] ] + .filter { $0 != nil } + .map { $0! } + .flatten() + + return validHandlerInfos.sort { info1, info2 in + return info1.order < info2.order + } + } + + //-------------------------------------------------- + // MARK: - Route + //-------------------------------------------------- + + // MARK: addRoutes(event:) + + public func addRoutes(event event: E, transitions: [Transition], condition: Machine.Condition? = nil) -> Disposable + { + return self.addRoutes(event: .Some(event), transitions: transitions, condition: condition) + } + + public func addRoutes(event event: Event, transitions: [Transition], condition: Machine.Condition? = nil) -> Disposable + { + let routes = transitions.map { Route(transition: $0, condition: condition) } + return self.addRoutes(event: event, routes: routes) + } + + public func addRoutes(event event: E, routes: [Route]) -> Disposable + { + return self.addRoutes(event: .Some(event), routes: routes) + } + + public func addRoutes(event event: Event, routes: [Route]) -> Disposable + { + // NOTE: uses `map` with side-effects + let disposables = routes.map { self._addRoute(event: event, route: $0) } + + return ActionDisposable.init { + disposables.forEach { $0.dispose() } + } + } + + internal func _addRoute(event event: Event = .Any, route: Route) -> Disposable + { + let transition = route.transition + let condition = route.condition + + let key = _createUniqueString() + + if self._routes[event] == nil { + self._routes[event] = [:] + } + + var routeDict = self._routes[event]! + if routeDict[transition] == nil { + routeDict[transition] = [:] + } + + var keyConditionDict = routeDict[transition]! + keyConditionDict[key] = condition + routeDict[transition] = keyConditionDict + + self._routes[event] = routeDict + + let _routeID = _RouteID(event: event, transition: transition, key: key) + + return ActionDisposable.init { [weak self] in + self?._removeRoute(_routeID) + } + } + + // MARK: addRoutes(event:) + conditional handler + + public func addRoutes(event event: E, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> Disposable + { + return self.addRoutes(event: .Some(event), transitions: transitions, condition: condition, handler: handler) + } + + public func addRoutes(event event: Event, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> Disposable + { + let routes = transitions.map { Route(transition: $0, condition: condition) } + return self.addRoutes(event: event, routes: routes, handler: handler) + } + + public func addRoutes(event event: E, routes: [Route], handler: Handler) -> Disposable + { + return self.addRoutes(event: .Some(event), routes: routes, handler: handler) + } + + public func addRoutes(event event: Event, routes: [Route], handler: Handler) -> Disposable + { + let routeDisposable = self.addRoutes(event: event, routes: routes) + let handlerDisposable = self.addHandler(event: event, handler: handler) + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeRoute + + private func _removeRoute(_routeID: _RouteID) -> Bool + { + guard let event = _routeID.event else { return false } + + let transition = _routeID.transition + + if let routeDict_ = self._routes[event] { + var routeDict = routeDict_ + + if let keyConditionDict_ = routeDict[transition] { + var keyConditionDict = keyConditionDict_ + + keyConditionDict[_routeID.key] = nil + if keyConditionDict.count > 0 { + routeDict[transition] = keyConditionDict + } + else { + routeDict[transition] = nil + } + } + + if routeDict.count > 0 { + self._routes[event] = routeDict + } + else { + self._routes[event] = nil + } + + return true + } + + return false + } + + //-------------------------------------------------- + // MARK: - RouteMapping + //-------------------------------------------------- + + // MARK: addRouteMapping + + public func addRouteMapping(routeMapping: RouteMapping) -> Disposable + { + let key = _createUniqueString() + + self._routeMappings[key] = routeMapping + + let routeMappingID = _RouteMappingID(key: key) + + return ActionDisposable.init { [weak self] in + self?._removeRouteMapping(routeMappingID) + } + } + + // MARK: addRouteMapping + conditional handler + + public func addRouteMapping(routeMapping: RouteMapping, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + { + let routeDisposable = self.addRouteMapping(routeMapping) + + let handlerDisposable = self._addHandler(event: .Any, order: order) { context in + + guard let preferredToState = routeMapping(event: context.event, fromState: context.fromState, userInfo: context.userInfo) + where preferredToState == context.toState else + { + return + } + + handler(context) + } + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeRouteMapping + + private func _removeRouteMapping(routeMappingID: _RouteMappingID) -> Bool + { + if self._routeMappings[routeMappingID.key] != nil { + self._routeMappings[routeMappingID.key] = nil + return true + } + else { + return false + } + } + + //-------------------------------------------------- + // MARK: - Handler + //-------------------------------------------------- + + // MARK: addHandler(event:) + + public func addHandler(event event: E, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + { + return self.addHandler(event: .Some(event), order: order, handler: handler) + } + + public func addHandler(event event: Event, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + { + return self._addHandler(event: event, order: order) { context in + // skip if not event-based transition + guard let triggeredEvent = context.event else { + return + } + + if triggeredEvent == event.rawValue || event == .Any { + handler(context) + } + } + } + + private func _addHandler(event event: Event, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + if self._handlers[event] == nil { + self._handlers[event] = [] + } + + let key = _createUniqueString() + + var handlerInfos = self._handlers[event]! + let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) + _insertHandlerIntoArray(&handlerInfos, newHandlerInfo: newHandlerInfo) + + self._handlers[event] = handlerInfos + + let handlerID = _HandlerID(event: event, transition: .Any => .Any, key: key) // NOTE: use non-`nil` transition + + return ActionDisposable.init { [weak self] in + self?._removeHandler(handlerID) + } + } + + // MARK: addErrorHandler + + public func addErrorHandler(order order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + let key = _createUniqueString() + + let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) + _insertHandlerIntoArray(&self._errorHandlers, newHandlerInfo: newHandlerInfo) + + let handlerID = _HandlerID(event: nil, transition: nil, key: key) // NOTE: use `nil` transition + + return ActionDisposable.init { [weak self] in + self?._removeHandler(handlerID) + } + } + + // MARK: removeHandler + + private func _removeHandler(handlerID: _HandlerID) -> Bool + { + if let event = handlerID.event { + if let handlerInfos_ = self._handlers[event] { + var handlerInfos = handlerInfos_ + + if _removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { + self._handlers[event] = handlerInfos + return true + } + } + } + // `transition = nil` means errorHandler + else if handlerID.transition == nil { + if _removeHandlerFromArray(&self._errorHandlers, removingHandlerID: handlerID) { + return true + } + return false + } + + return false + } + +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +// MARK: `<-!` (tryEvent) + +infix operator <-! { associativity left } + +public func <-! (machine: Machine, event: E) -> Machine +{ + machine.tryEvent(event) + return machine +} + +public func <-! (machine: Machine, tuple: (E, Any?)) -> Machine +{ + machine.tryEvent(tuple.0, userInfo: tuple.1) + return machine +} + +//-------------------------------------------------- +// MARK: - HandlerOrder +//-------------------------------------------------- + +/// Precedence for registered handlers (higher number is called later). +public typealias HandlerOrder = UInt8 + +internal let _defaultOrder: HandlerOrder = 100 + +//-------------------------------------------------- +// MARK: - Internal +//-------------------------------------------------- + +// generate approx 126bit random string +internal func _createUniqueString() -> String +{ + var uniqueString: String = "" + for _ in 1...8 { + uniqueString += String(UnicodeScalar(_random(0xD800))) // 0xD800 = 55296 = 15.755bit + } + return uniqueString +} + +internal func _validTransitions(fromState fromState: S, toState: S) -> [Transition] +{ + return [ + fromState => toState, + fromState => .Any, + .Any => toState, + .Any => .Any + ] +} + +internal func _canPassCondition(condition: Machine.Condition?, forEvent event: E?, fromState: S, toState: S, userInfo: Any?) -> Bool +{ + return condition?((event, fromState, toState, userInfo)) ?? true +} + +internal func _insertHandlerIntoArray(inout handlerInfos: [_HandlerInfo], newHandlerInfo: _HandlerInfo) +{ + var index = handlerInfos.count + + for i in Array(0..(inout handlerInfos: [_HandlerInfo], removingHandlerID: _HandlerID) -> Bool +{ + for i in 0.. +{ + public let transition: Transition + public let condition: Machine.Condition? + + public init(transition: Transition, condition: Machine.Condition?) + { + self.transition = transition + self.condition = condition + } +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +/// e.g. [.State0, .State1] => .State, allowing [0 => 2, 1 => 2] +public func => (leftStates: [S], right: State) -> Route +{ + // NOTE: don't reuse ".Any => .Any + condition" for efficiency + return Route(transition: .Any => right, condition: { context -> Bool in + return leftStates.contains(context.fromState) + }) +} + +public func => (leftStates: [S], right: S) -> Route +{ + return leftStates => .Some(right) +} + +/// e.g. .State0 => [.State1, .State], allowing [0 => 1, 0 => 2] +public func => (left: State, rightStates: [S]) -> Route +{ + return Route(transition: left => .Any, condition: { context -> Bool in + return rightStates.contains(context.toState) + }) +} + +public func => (left: S, rightStates: [S]) -> Route +{ + return .Some(left) => rightStates +} + +/// e.g. [.State0, .State1] => [.State, .State3], allowing [0 => 2, 0 => 3, 1 => 2, 1 => 3] +public func => (leftStates: [S], rightStates: [S]) -> Route +{ + return Route(transition: .Any => .Any, condition: { context -> Bool in + return leftStates.contains(context.fromState) && rightStates.contains(context.toState) + }) +} \ No newline at end of file diff --git a/Sources/RouteChain.swift b/Sources/RouteChain.swift new file mode 100644 index 0000000..f2b6d57 --- /dev/null +++ b/Sources/RouteChain.swift @@ -0,0 +1,27 @@ +// +// RouteChain.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/04. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +/// Group of continuous `Route`s. +public struct RouteChain +{ + public private(set) var routes: [Route] + + public init(routes: [Route]) + { + self.routes = routes + } + + public init(transitionChain: TransitionChain, condition: Machine.Condition? = nil) + { + var routes: [Route] = [] + for transition in transitionChain.transitions { + routes += [Route(transition: transition, condition: condition)] + } + self.init(routes: routes) + } +} \ No newline at end of file diff --git a/Sources/StateMachine.swift b/Sources/StateMachine.swift new file mode 100644 index 0000000..217b8de --- /dev/null +++ b/Sources/StateMachine.swift @@ -0,0 +1,518 @@ +// +// StateMachine.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +/// +/// State-machine which can `tryState()` (state-driven) as well as `tryEvent()` (event-driven). +/// +/// - Note: +/// Use `NoEvent` type to ignore event-handlings whenever necessary. +/// +public final class StateMachine: Machine +{ + /// Closure-based routes for `tryState()`. + /// - Returns: Multiple `toState`s from single `fromState`, similar to `.State0 => [.State1, .State2]` + public typealias StateRouteMapping = (fromState: S, userInfo: Any?) -> [S]? + + private lazy var _routes: _RouteDict = [:] + private lazy var _routeMappings: [String : StateRouteMapping] = [:] // NOTE: `StateRouteMapping`, not `RouteMapping` + + /// `tryState()`-based handler collection. + private lazy var _handlers: [Transition : [_HandlerInfo]] = [:] + + //-------------------------------------------------- + // MARK: - Init + //-------------------------------------------------- + + public override init(state: S, initClosure: (StateMachine -> ())? = nil) + { + super.init(state: state, initClosure: { machine in + initClosure?(machine as! StateMachine) + return + }) + } + + public override func configure(closure: StateMachine -> ()) + { + closure(self) + } + + //-------------------------------------------------- + // MARK: - hasRoute + //-------------------------------------------------- + + /// Check for added routes & routeMappings. + /// - Note: This method also checks for event-based-routes. + public func hasRoute(transition: Transition, userInfo: Any? = nil) -> Bool + { + guard let fromState = transition.fromState.rawValue, + toState = transition.toState.rawValue else + { + assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") + return false + } + + return self.hasRoute(fromState: fromState, toState: toState, userInfo: userInfo) + } + + /// Check for added routes & routeMappings. + /// - Note: This method also checks for event-based-routes. + public func hasRoute(fromState fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + if self._hasRouteInDict(fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + + if self._hasRouteMappingInDict(fromState: fromState, toState: toState, userInfo: userInfo) != nil { + return true + } + + // look for all event-based-routes + return super._hasRoute(event: nil, fromState: fromState, toState: toState, userInfo: userInfo) + } + + /// Check for `_routes`. + private func _hasRouteInDict(fromState fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + let validTransitions = _validTransitions(fromState: fromState, toState: toState) + + for validTransition in validTransitions { + + // check for `_routes + if let keyConditionDict = self._routes[validTransition] { + for (_, condition) in keyConditionDict { + if _canPassCondition(condition, forEvent: nil, fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + } + } + } + + return false + } + + /// Check for `_routeMappings`. + private func _hasRouteMappingInDict(fromState fromState: S, toState: S, userInfo: Any? = nil) -> S? + { + for mapping in self._routeMappings.values { + if let preferredToStates = mapping(fromState: fromState, userInfo: userInfo) { + return preferredToStates.contains(toState) ? toState : nil + } + } + + return nil + } + + //-------------------------------------------------- + // MARK: - tryState + //-------------------------------------------------- + + /// - Note: This method also checks for event-based-routes. + public func canTryState(toState: S, userInfo: Any? = nil) -> Bool + { + return self.hasRoute(fromState: self.state, toState: toState, userInfo: userInfo) + } + + /// - Note: This method also tries state-change for event-based-routes. + public func tryState(toState: S, userInfo: Any? = nil) -> Bool + { + let fromState = self.state + + if self.canTryState(toState, userInfo: userInfo) { + + // collect valid handlers before updating state + let validHandlerInfos = self._validHandlerInfos(fromState: fromState, toState: toState) + + // update state + self._state = toState + + // + // Perform validHandlers after updating state. + // + // NOTE: + // Instead of using before/after handlers as seen in many other StateMachine libraries, + // SwiftState uses `order` value to perform handlers in 'fine-grained' order, + // only after state has been updated. (Any problem?) + // + for handlerInfo in validHandlerInfos { + handlerInfo.handler(Context(event: nil, fromState: fromState, toState: toState, userInfo: userInfo)) + } + + return true + + } + else { + for handlerInfo in self._errorHandlers { + handlerInfo.handler(Context(event: nil, fromState: fromState, toState: toState, userInfo: userInfo)) + } + } + + return false + } + + private func _validHandlerInfos(fromState fromState: S, toState: S) -> [_HandlerInfo] + { + var validHandlerInfos: [_HandlerInfo] = [] + + let validTransitions = _validTransitions(fromState: fromState, toState: toState) + + for validTransition in validTransitions { + if let handlerInfos = self._handlers[validTransition] { + for handlerInfo in handlerInfos { + validHandlerInfos += [handlerInfo] + } + } + } + + validHandlerInfos.sortInPlace { info1, info2 in + return info1.order < info2.order + } + + return validHandlerInfos + } + + //-------------------------------------------------- + // MARK: - Route + //-------------------------------------------------- + + // MARK: addRoute (no-event) + + public func addRoute(transition: Transition, condition: Condition? = nil) -> Disposable + { + let route = Route(transition: transition, condition: condition) + return self.addRoute(route) + } + + public func addRoute(route: Route) -> Disposable + { + let transition = route.transition + let condition = route.condition + + if self._routes[transition] == nil { + self._routes[transition] = [:] + } + + let key = _createUniqueString() + + var keyConditionDict = self._routes[transition]! + keyConditionDict[key] = condition + self._routes[transition] = keyConditionDict + + let _routeID = _RouteID(event: Optional>.None, transition: transition, key: key) + + return ActionDisposable.init { [weak self] in + self?._removeRoute(_routeID) + } + } + + // MARK: addRoute (no-event) + conditional handler + + public func addRoute(transition: Transition, condition: Condition? = nil, handler: Handler) -> Disposable + { + let route = Route(transition: transition, condition: condition) + return self.addRoute(route, handler: handler) + } + + public func addRoute(route: Route, handler: Handler) -> Disposable + { + let transition = route.transition + let condition = route.condition + + let routeDisposable = self.addRoute(transition, condition: condition) + + let handlerDisposable = self.addHandler(transition) { context in + if _canPassCondition(condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + handler(context) + } + } + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeRoute + + private func _removeRoute(_routeID: _RouteID) -> Bool + { + guard _routeID.event == nil else { + return false + } + + let transition = _routeID.transition + + guard let keyConditionDict_ = self._routes[transition] else { + return false + } + var keyConditionDict = keyConditionDict_ + + let removed = keyConditionDict.removeValueForKey(_routeID.key) != nil + + if keyConditionDict.count > 0 { + self._routes[transition] = keyConditionDict + } + else { + self._routes[transition] = nil + } + + return removed + } + + //-------------------------------------------------- + // MARK: - Handler + //-------------------------------------------------- + + // MARK: addHandler (no-event) + + public func addHandler(transition: Transition, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + if self._handlers[transition] == nil { + self._handlers[transition] = [] + } + + let key = _createUniqueString() + + var handlerInfos = self._handlers[transition]! + let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) + _insertHandlerIntoArray(&handlerInfos, newHandlerInfo: newHandlerInfo) + + self._handlers[transition] = handlerInfos + + let handlerID = _HandlerID(event: nil, transition: transition, key: key) + + return ActionDisposable.init { [weak self] in + self?._removeHandler(handlerID) + } + } + + // MARK: removeHandler + + private func _removeHandler(handlerID: _HandlerID) -> Bool + { + if let transition = handlerID.transition { + if let handlerInfos_ = self._handlers[transition] { + var handlerInfos = handlerInfos_ + + if _removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { + self._handlers[transition] = handlerInfos + return true + } + } + } + + return false + } + + //-------------------------------------------------- + // MARK: - RouteChain + //-------------------------------------------------- + // + // NOTE: + // `RouteChain` allows to register `handler` which will be invoked + // only when state-based-transitions in `RouteChain` succeeded continuously. + // + + // MARK: addRouteChain + conditional handler + + public func addRouteChain(chain: TransitionChain, condition: Condition? = nil, handler: Handler) -> Disposable + { + let routeChain = RouteChain(transitionChain: chain, condition: condition) + return self.addRouteChain(routeChain, handler: handler) + } + + public func addRouteChain(chain: RouteChain, handler: Handler) -> Disposable + { + let routeDisposables = chain.routes.map { self.addRoute($0) } + let handlerDisposable = self.addChainHandler(chain, handler: handler) + + return ActionDisposable.init { + routeDisposables.forEach { $0.dispose() } + handlerDisposable.dispose() + } + } + + // MARK: addChainHandler + + public func addChainHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self.addChainHandler(RouteChain(transitionChain: chain), order: order, handler: handler) + } + + public func addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self._addChainHandler(chain, order: order, handler: handler, isError: false) + } + + // MARK: addChainErrorHandler + + public func addChainErrorHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self.addChainErrorHandler(RouteChain(transitionChain: chain), order: order, handler: handler) + } + + public func addChainErrorHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self._addChainHandler(chain, order: order, handler: handler, isError: true) + } + + private func _addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler, isError: Bool) -> Disposable + { + var handlerDisposables: [Disposable] = [] + + var shouldStop = true + var shouldIncrementChainingCount = true + var chainingCount = 0 + var allCount = 0 + + // reset count on 1st route + let firstRoute = chain.routes.first! + var handlerDisposable = self.addHandler(firstRoute.transition) { context in + if _canPassCondition(firstRoute.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + if shouldStop { + shouldStop = false + chainingCount = 0 + allCount = 0 + } + } + } + handlerDisposables += [handlerDisposable] + + // increment chainingCount on every route + for route in chain.routes { + + handlerDisposable = self.addHandler(route.transition) { context in + // skip duplicated transition handlers e.g. chain = 0 => 1 => 0 => 1 & transiting 0 => 1 + if !shouldIncrementChainingCount { return } + + if _canPassCondition(route.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + if !shouldStop { + chainingCount++ + + shouldIncrementChainingCount = false + } + } + } + handlerDisposables += [handlerDisposable] + } + + // increment allCount (+ invoke chainErrorHandler) on any routes + handlerDisposable = self.addHandler(.Any => .Any, order: 150) { context in + + shouldIncrementChainingCount = true + + if !shouldStop { + allCount++ + } + + if chainingCount < allCount { + shouldStop = true + if isError { + handler(context) + } + } + } + handlerDisposables += [handlerDisposable] + + // invoke chainHandler on last route + let lastRoute = chain.routes.last! + handlerDisposable = self.addHandler(lastRoute.transition, order: 200) { context in + if _canPassCondition(lastRoute.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + if chainingCount == allCount && chainingCount == chain.routes.count && chainingCount == chain.routes.count { + shouldStop = true + + if !isError { + handler(context) + } + } + } + } + handlerDisposables += [handlerDisposable] + + return ActionDisposable.init { + handlerDisposables.forEach { $0.dispose() } + } + } + + //-------------------------------------------------- + // MARK: - StateRouteMapping + //-------------------------------------------------- + + // MARK: addStateRouteMapping + + public func addStateRouteMapping(routeMapping: StateRouteMapping) -> Disposable + { + let key = _createUniqueString() + + self._routeMappings[key] = routeMapping + + let routeMappingID = _RouteMappingID(key: key) + + return ActionDisposable.init { [weak self] in + self?._removeStateRouteMapping(routeMappingID) + } + } + + // MARK: addStateRouteMapping + conditional handler + + public func addStateRouteMapping(routeMapping: StateRouteMapping, handler: Handler) -> Disposable + { + let routeDisposable = self.addStateRouteMapping(routeMapping) + + let handlerDisposable = self.addHandler(.Any => .Any) { context in + + guard context.event == nil else { return } + + guard let preferredToStates = routeMapping(fromState: context.fromState, userInfo: context.userInfo) + where preferredToStates.contains(context.toState) else + { + return + } + + handler(context) + } + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeStateRouteMapping + + private func _removeStateRouteMapping(routeMappingID: _RouteMappingID) -> Bool + { + if self._routeMappings[routeMappingID.key] != nil { + self._routeMappings[routeMappingID.key] = nil + return true + } + else { + return false + } + } + +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +// MARK: `<-` (tryState) + +infix operator <- { associativity left } + +public func <- (machine: StateMachine, state: S) -> StateMachine +{ + machine.tryState(state) + return machine +} + +public func <- (machine: StateMachine, tuple: (S, Any?)) -> StateMachine +{ + machine.tryState(tuple.0, userInfo: tuple.1) + return machine +} \ No newline at end of file diff --git a/Sources/StateType.swift b/Sources/StateType.swift new file mode 100644 index 0000000..663bcb9 --- /dev/null +++ b/Sources/StateType.swift @@ -0,0 +1,69 @@ +// +// StateType.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +public protocol StateType: Hashable {} + +// MARK: State + +/// `StateType` wrapper for handling `.Any` state. +public enum State +{ + case Some(S) + case Any +} + +extension State: Hashable +{ + public var hashValue: Int + { + switch self { + case .Some(let x): return x.hashValue + case .Any: return _hashValueForAny + } + } +} + +extension State: RawRepresentable +{ + public init(rawValue: S?) + { + if let rawValue = rawValue { + self = .Some(rawValue) + } + else { + self = .Any + } + } + + public var rawValue: S? + { + switch self { + case .Some(let x): return x + default: return nil + } + } +} + +public func == (lhs: State, rhs: State) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +public func == (lhs: State, rhs: S) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +public func == (lhs: S, rhs: State) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +// MARK: Private + +internal let _hashValueForAny = Int.min/2 diff --git a/SwiftState/SwiftState.h b/Sources/SwiftState.h similarity index 100% rename from SwiftState/SwiftState.h rename to Sources/SwiftState.h diff --git a/Sources/Transition.swift b/Sources/Transition.swift new file mode 100644 index 0000000..e37aabc --- /dev/null +++ b/Sources/Transition.swift @@ -0,0 +1,88 @@ +// +// Transition.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +/// +/// "From-" and "to-" states represented as `.State1 => .State2`. +/// Also, `.Any` can be used to represent _any state_. +/// +public struct Transition: Hashable +{ + public let fromState: State + public let toState: State + + public init(fromState: State, toState: State) + { + self.fromState = fromState + self.toState = toState + } + + public init(fromState: S, toState: State) + { + self.init(fromState: .Some(fromState), toState: toState) + } + + public init(fromState: State, toState: S) + { + self.init(fromState: fromState, toState: .Some(toState)) + } + + public init(fromState: S, toState: S) + { + self.init(fromState: .Some(fromState), toState: .Some(toState)) + } + + public var hashValue: Int + { + return self.fromState.hashValue &+ self.toState.hashValue.byteSwapped + } +} + +// for Transition Equatable +public func == (left: Transition, right: Transition) -> Bool +{ + return left.hashValue == right.hashValue +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +infix operator => { associativity left } + +/// e.g. .State0 => .State1 +public func => (left: State, right: State) -> Transition +{ + return Transition(fromState: left, toState: right) +} + +public func => (left: State, right: S) -> Transition +{ + return left => .Some(right) +} + +public func => (left: S, right: State) -> Transition +{ + return .Some(left) => right +} + +public func => (left: S, right: S) -> Transition +{ + return .Some(left) => .Some(right) +} + +//-------------------------------------------------- +// MARK: - Printable +//-------------------------------------------------- + +extension Transition: CustomStringConvertible +{ + public var description: String + { + return "\(self.fromState) => \(self.toState) (\(self.hashValue))" + } +} \ No newline at end of file diff --git a/Sources/TransitionChain.swift b/Sources/TransitionChain.swift new file mode 100644 index 0000000..29306f0 --- /dev/null +++ b/Sources/TransitionChain.swift @@ -0,0 +1,80 @@ +// +// TransitionChain.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/04. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +/// Group of continuous `Transition`s represented as `.State1 => .State2 => .State3`. +public struct TransitionChain +{ + public private(set) var states: [State] + + public init(states: [State]) + { + self.states = states + } + + public init(transition: Transition) + { + self.init(states: [transition.fromState, transition.toState]) + } + + public var transitions: [Transition] + { + var transitions: [Transition] = [] + + for i in 0.. states[i+1]] + } + + return transitions + } +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +// e.g. (.State0 => .State1) => .State +public func => (left: Transition, right: State) -> TransitionChain +{ + return TransitionChain(states: [left.fromState, left.toState]) => right +} + +public func => (left: Transition, right: S) -> TransitionChain +{ + return left => .Some(right) +} + +public func => (left: TransitionChain, right: State) -> TransitionChain +{ + return TransitionChain(states: left.states + [right]) +} + +public func => (left: TransitionChain, right: S) -> TransitionChain +{ + return left => .Some(right) +} + +// e.g. .State0 => (.State1 => .State) +public func => (left: State, right:Transition) -> TransitionChain +{ + return left => TransitionChain(states: [right.fromState, right.toState]) +} + +public func => (left: S, right:Transition) -> TransitionChain +{ + return .Some(left) => right +} + +public func => (left: State, right: TransitionChain) -> TransitionChain +{ + return TransitionChain(states: [left] + right.states) +} + +public func => (left: S, right: TransitionChain) -> TransitionChain +{ + return .Some(left) => right +} diff --git a/Sources/_HandlerID.swift b/Sources/_HandlerID.swift new file mode 100644 index 0000000..8f8daf6 --- /dev/null +++ b/Sources/_HandlerID.swift @@ -0,0 +1,24 @@ +// +// _HandlerID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +internal final class _HandlerID +{ + internal let event: Event? + + /// - Note: `nil` is used for error-handlerID + internal let transition: Transition? + + internal let key: String + + internal init(event: Event?, transition: Transition?, key: String) + { + self.event = event + self.transition = transition + self.key = key + } +} \ No newline at end of file diff --git a/Sources/_HandlerInfo.swift b/Sources/_HandlerInfo.swift new file mode 100644 index 0000000..a8b8ae1 --- /dev/null +++ b/Sources/_HandlerInfo.swift @@ -0,0 +1,21 @@ +// +// _HandlerInfo.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +internal final class _HandlerInfo +{ + internal let order: HandlerOrder + internal let key: String + internal let handler: Machine.Handler + + internal init(order: HandlerOrder, key: String, handler: Machine.Handler) + { + self.order = order + self.key = key + self.handler = handler + } +} \ No newline at end of file diff --git a/Sources/_Random.swift b/Sources/_Random.swift new file mode 100644 index 0000000..cf38838 --- /dev/null +++ b/Sources/_Random.swift @@ -0,0 +1,22 @@ +// +// _Random.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) +import Darwin +#else +import Glibc +#endif + +internal func _random(upperBound: Int) -> Int +{ + #if os(OSX) || os(iOS) + return Int(arc4random_uniform(UInt32(upperBound))) + #else + return Int(random() % upperBound) + #endif +} diff --git a/Sources/_RouteID.swift b/Sources/_RouteID.swift new file mode 100644 index 0000000..e3de663 --- /dev/null +++ b/Sources/_RouteID.swift @@ -0,0 +1,21 @@ +// +// _RouteID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +internal final class _RouteID +{ + internal let event: Event? + internal let transition: Transition + internal let key: String + + internal init(event: Event?, transition: Transition, key: String) + { + self.event = event + self.transition = transition + self.key = key + } +} \ No newline at end of file diff --git a/Sources/_RouteMappingID.swift b/Sources/_RouteMappingID.swift new file mode 100644 index 0000000..7fae7ce --- /dev/null +++ b/Sources/_RouteMappingID.swift @@ -0,0 +1,17 @@ +// +// _RouteMappingID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +internal final class _RouteMappingID +{ + internal let key: String + + internal init(key: String) + { + self.key = key + } +} \ No newline at end of file diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index de301b0..6b476b0 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -8,55 +8,39 @@ /* Begin PBXBuildFile section */ 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; - 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; + 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; + 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; + 1F4336AD1C17113F00E7C1AC /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AC1C17113F00E7C1AC /* StateTests.swift */; }; + 1F4336B01C17115700E7C1AC /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AF1C17115700E7C1AC /* EventTests.swift */; }; + 1F532C541C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; + 1F532C571C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; + 1F532C611C12D7BD00D3813A /* MiscTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C601C12D7BD00D3813A /* MiscTests.swift */; }; + 1F532C641C12EA8000D3813A /* _Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C631C12EA8000D3813A /* _Random.swift */; }; + 1F70FB661BF0F46000E5AC8C /* _RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */; }; + 1F70FB6C1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */; }; + 1F70FB6F1BF0F59600E5AC8C /* _HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */; }; + 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */; }; 1FA620061996601000460108 /* SwiftState.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FA620051996601000460108 /* SwiftState.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1FA620201996606300460108 /* StateEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* StateEventType.swift */; }; - 1FA620211996606300460108 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* StateMachine.swift */; }; - 1FA620221996606300460108 /* StateRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* StateRoute.swift */; }; - 1FA620231996606300460108 /* StateRouteChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201C1996606300460108 /* StateRouteChain.swift */; }; - 1FA620241996606300460108 /* StateTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* StateTransition.swift */; }; - 1FA620251996606300460108 /* StateTransitionChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201E1996606300460108 /* StateTransitionChain.swift */; }; + 1FA620201996606300460108 /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* EventType.swift */; }; + 1FA620221996606300460108 /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* Route.swift */; }; + 1FA620231996606300460108 /* RouteChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201C1996606300460108 /* RouteChain.swift */; }; + 1FA620241996606300460108 /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* Transition.swift */; }; + 1FA620251996606300460108 /* TransitionChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201E1996606300460108 /* TransitionChain.swift */; }; 1FA620261996606300460108 /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201F1996606300460108 /* StateType.swift */; }; 1FA62030199660CA00460108 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62027199660CA00460108 /* _TestCase.swift */; }; 1FA62031199660CA00460108 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62028199660CA00460108 /* BasicTests.swift */; }; 1FA62032199660CA00460108 /* MyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62029199660CA00460108 /* MyState.swift */; }; - 1FA62033199660CA00460108 /* StateMachineChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* StateMachineChainTests.swift */; }; + 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; 1FA62034199660CA00460108 /* StateMachineEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */; }; 1FA62035199660CA00460108 /* StateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* StateMachineTests.swift */; }; - 1FA62036199660CA00460108 /* StateRouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* StateRouteTests.swift */; }; - 1FA62037199660CA00460108 /* StateTransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* StateTransitionChainTests.swift */; }; - 1FA62038199660CA00460108 /* StateTransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* StateTransitionTests.swift */; }; + 1FA62036199660CA00460108 /* RouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* RouteTests.swift */; }; + 1FA62037199660CA00460108 /* TransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* TransitionChainTests.swift */; }; + 1FA62038199660CA00460108 /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* TransitionTests.swift */; }; 1FB1EC8A199E515B00ABD937 /* MyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB1EC89199E515B00ABD937 /* MyEvent.swift */; }; - 1FB1EC8F199E60F800ABD937 /* String+SwiftState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB1EC8E199E60F800ABD937 /* String+SwiftState.swift */; }; - 1FB4B39C1AAB3B190072E65D /* BugFixTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */; }; - 1FB4B39D1AAB3B190072E65D /* BugFixTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */; }; - 1FD01B6019EC2B5C00DA1C91 /* HierarchicalStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD01B5F19EC2B5C00DA1C91 /* HierarchicalStateMachine.swift */; }; - 1FD01B6119EC2B5C00DA1C91 /* HierarchicalStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD01B5F19EC2B5C00DA1C91 /* HierarchicalStateMachine.swift */; }; - 1FD01B6319EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD01B6219EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift */; }; - 1FD01B6419EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD01B6219EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift */; }; 1FF692041996625900E3CE40 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FA620001996601000460108 /* SwiftState.framework */; }; - 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62027199660CA00460108 /* _TestCase.swift */; }; - 4822F0A919D008E700F5F572 /* MyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62029199660CA00460108 /* MyState.swift */; }; - 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB1EC89199E515B00ABD937 /* MyEvent.swift */; }; - 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62028199660CA00460108 /* BasicTests.swift */; }; - 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; - 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* StateMachineTests.swift */; }; - 4822F0AE19D008EB00F5F572 /* StateMachineChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* StateMachineChainTests.swift */; }; - 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */; }; - 4822F0B019D008EB00F5F572 /* StateTransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* StateTransitionTests.swift */; }; - 4822F0B119D008EB00F5F572 /* StateTransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* StateTransitionChainTests.swift */; }; - 4822F0B219D008EB00F5F572 /* StateRouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* StateRouteTests.swift */; }; - 48797D5D19B42CBE0085D80F /* SwiftState.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FA620051996601000460108 /* SwiftState.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48797D5E19B42CCE0085D80F /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* StateMachine.swift */; }; - 48797D5F19B42CCE0085D80F /* StateTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* StateTransition.swift */; }; - 48797D6019B42CCE0085D80F /* StateTransitionChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201E1996606300460108 /* StateTransitionChain.swift */; }; - 48797D6119B42CCE0085D80F /* StateRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* StateRoute.swift */; }; - 48797D6219B42CCE0085D80F /* StateRouteChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201C1996606300460108 /* StateRouteChain.swift */; }; - 48797D6319B42CD40085D80F /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201F1996606300460108 /* StateType.swift */; }; - 48797D6419B42CD40085D80F /* StateEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* StateEventType.swift */; }; - 48797D6519B42CD40085D80F /* String+SwiftState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB1EC8E199E60F800ABD937 /* String+SwiftState.swift */; }; - C662B6F51B861CC400479524 /* RasmusTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C662B6F41B861CC400479524 /* RasmusTest.swift */; }; - C662B6F61B861CC400479524 /* RasmusTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C662B6F41B861CC400479524 /* RasmusTest.swift */; }; + 4836FF5A1C0EFD700038B7D2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4836FF591C0EFD700038B7D2 /* StateMachine.swift */; }; + 483F35571C0EB192007C70D7 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483F35561C0EB192007C70D7 /* Machine.swift */; }; + 4876510F1C0ECBEB005961AC /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4876510E1C0ECBEB005961AC /* MachineTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -71,35 +55,47 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; + 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HierarchicalMachineTests.swift; sourceTree = ""; }; + 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteMappingTests.swift; sourceTree = ""; }; + 1F4336AC1C17113F00E7C1AC /* StateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateTests.swift; sourceTree = ""; }; + 1F4336AF1C17115700E7C1AC /* EventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventTests.swift; sourceTree = ""; }; + 1F532C531C12B5BC00D3813A /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Disposable.swift; sourceTree = ""; }; + 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _HandlerInfo.swift; sourceTree = ""; }; + 1F532C601C12D7BD00D3813A /* MiscTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MiscTests.swift; sourceTree = ""; }; + 1F532C631C12EA8000D3813A /* _Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Random.swift; sourceTree = ""; }; + 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _RouteID.swift; sourceTree = ""; }; + 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _RouteMappingID.swift; sourceTree = ""; }; + 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _HandlerID.swift; sourceTree = ""; }; + 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+TestExt.swift"; sourceTree = ""; }; 1FA620001996601000460108 /* SwiftState.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftState.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 1FA620041996601000460108 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1FA620051996601000460108 /* SwiftState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftState.h; sourceTree = ""; }; - 1FA6200B1996601000460108 /* SwiftState-OSXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftState-OSXTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1FA6200B1996601000460108 /* SwiftStateTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftStateTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1FA6200E1996601000460108 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 1FA620191996606200460108 /* StateEventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateEventType.swift; sourceTree = ""; }; - 1FA6201A1996606300460108 /* StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachine.swift; sourceTree = ""; }; - 1FA6201B1996606300460108 /* StateRoute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateRoute.swift; sourceTree = ""; }; - 1FA6201C1996606300460108 /* StateRouteChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateRouteChain.swift; sourceTree = ""; }; - 1FA6201D1996606300460108 /* StateTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateTransition.swift; sourceTree = ""; }; - 1FA6201E1996606300460108 /* StateTransitionChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateTransitionChain.swift; sourceTree = ""; }; + 1FA620191996606200460108 /* EventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventType.swift; sourceTree = ""; }; + 1FA6201B1996606300460108 /* Route.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = ""; }; + 1FA6201C1996606300460108 /* RouteChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChain.swift; sourceTree = ""; }; + 1FA6201D1996606300460108 /* Transition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = ""; }; + 1FA6201E1996606300460108 /* TransitionChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionChain.swift; sourceTree = ""; }; 1FA6201F1996606300460108 /* StateType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateType.swift; sourceTree = ""; }; 1FA62027199660CA00460108 /* _TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _TestCase.swift; sourceTree = ""; }; 1FA62028199660CA00460108 /* BasicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTests.swift; sourceTree = ""; }; 1FA62029199660CA00460108 /* MyState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyState.swift; sourceTree = ""; }; - 1FA6202A199660CA00460108 /* StateMachineChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachineChainTests.swift; sourceTree = ""; }; + 1FA6202A199660CA00460108 /* RouteChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainTests.swift; sourceTree = ""; }; 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachineEventTests.swift; sourceTree = ""; }; 1FA6202C199660CA00460108 /* StateMachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachineTests.swift; sourceTree = ""; }; - 1FA6202D199660CA00460108 /* StateRouteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateRouteTests.swift; sourceTree = ""; }; - 1FA6202E199660CA00460108 /* StateTransitionChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateTransitionChainTests.swift; sourceTree = ""; }; - 1FA6202F199660CA00460108 /* StateTransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateTransitionTests.swift; sourceTree = ""; }; + 1FA6202D199660CA00460108 /* RouteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteTests.swift; sourceTree = ""; }; + 1FA6202E199660CA00460108 /* TransitionChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionChainTests.swift; sourceTree = ""; }; + 1FA6202F199660CA00460108 /* TransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionTests.swift; sourceTree = ""; }; 1FB1EC89199E515B00ABD937 /* MyEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyEvent.swift; sourceTree = ""; }; - 1FB1EC8E199E60F800ABD937 /* String+SwiftState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SwiftState.swift"; sourceTree = ""; }; - 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BugFixTests.swift; sourceTree = ""; }; - 1FD01B5F19EC2B5C00DA1C91 /* HierarchicalStateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HierarchicalStateMachine.swift; sourceTree = ""; }; - 1FD01B6219EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HierarchicalStateMachineTests.swift; sourceTree = ""; }; - 4822F0A619D0085E00F5F572 /* SwiftStateTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftStateTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 4872D5AC19B4211900F326B5 /* SwiftState.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftState.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C662B6F41B861CC400479524 /* RasmusTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RasmusTest.swift; sourceTree = ""; }; + 1FD79F961C1736D600CE7060 /* UniversalFramework_Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = UniversalFramework_Base.xcconfig; sourceTree = ""; }; + 1FD79F971C1736D600CE7060 /* UniversalFramework_Framework.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = UniversalFramework_Framework.xcconfig; sourceTree = ""; }; + 1FD79F981C1736D600CE7060 /* UniversalFramework_Test.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = UniversalFramework_Test.xcconfig; sourceTree = ""; }; + 1FD79FA41C1740D900CE7060 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; + 1FD79FA51C17412600CE7060 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 1FD79FA61C17412600CE7060 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 4836FF591C0EFD700038B7D2 /* StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachine.swift; sourceTree = ""; }; + 483F35561C0EB192007C70D7 /* Machine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Machine.swift; sourceTree = ""; }; + 4876510E1C0ECBEB005961AC /* MachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -118,29 +114,38 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4822F0A119D0085E00F5F572 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */, +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1F70FB741BF0FAB900E5AC8C /* Internals */ = { + isa = PBXGroup; + children = ( + 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */, + 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */, + 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */, + 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */, + 1F532C631C12EA8000D3813A /* _Random.swift */, ); - runOnlyForDeploymentPostprocessing = 0; + name = Internals; + sourceTree = ""; }; - 4872D5A819B4211900F326B5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( + 1F70FB751BF0FAE800E5AC8C /* Routes */ = { + isa = PBXGroup; + children = ( + 1FA6201D1996606300460108 /* Transition.swift */, + 1FA6201E1996606300460108 /* TransitionChain.swift */, + 1FA6201B1996606300460108 /* Route.swift */, + 1FA6201C1996606300460108 /* RouteChain.swift */, ); - runOnlyForDeploymentPostprocessing = 0; + name = Routes; + sourceTree = ""; }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ 1FA61FF61996601000460108 = { isa = PBXGroup; children = ( - 1FA620021996601000460108 /* SwiftState */, - 1FA6200C1996601000460108 /* SwiftStateTests */, + 1FD79F9E1C173C6C00CE7060 /* Configurations */, + 1FA620021996601000460108 /* Sources */, + 1FA6200C1996601000460108 /* Tests */, 1FA620011996601000460108 /* Products */, ); sourceTree = ""; @@ -149,56 +154,47 @@ isa = PBXGroup; children = ( 1FA620001996601000460108 /* SwiftState.framework */, - 1FA6200B1996601000460108 /* SwiftState-OSXTests.xctest */, - 4872D5AC19B4211900F326B5 /* SwiftState.framework */, - 4822F0A619D0085E00F5F572 /* SwiftStateTests-iOS.xctest */, + 1FA6200B1996601000460108 /* SwiftStateTests.xctest */, ); name = Products; sourceTree = ""; }; - 1FA620021996601000460108 /* SwiftState */ = { + 1FA620021996601000460108 /* Sources */ = { isa = PBXGroup; children = ( 1FA620051996601000460108 /* SwiftState.h */, - 1FB1EC88199E4ABC00ABD937 /* Type */, - 1FA6201A1996606300460108 /* StateMachine.swift */, - 1FA6201D1996606300460108 /* StateTransition.swift */, - 1FA6201E1996606300460108 /* StateTransitionChain.swift */, - 1FA6201B1996606300460108 /* StateRoute.swift */, - 1FA6201C1996606300460108 /* StateRouteChain.swift */, - 1FD01B5F19EC2B5C00DA1C91 /* HierarchicalStateMachine.swift */, - 1FA620031996601000460108 /* Supporting Files */, - ); - path = SwiftState; + 483F35561C0EB192007C70D7 /* Machine.swift */, + 4836FF591C0EFD700038B7D2 /* StateMachine.swift */, + 1F532C531C12B5BC00D3813A /* Disposable.swift */, + 1FB1EC88199E4ABC00ABD937 /* Protocols */, + 1F70FB751BF0FAE800E5AC8C /* Routes */, + 1F70FB741BF0FAB900E5AC8C /* Internals */, + ); + path = Sources; sourceTree = ""; }; - 1FA620031996601000460108 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 1FA620041996601000460108 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 1FA6200C1996601000460108 /* SwiftStateTests */ = { + 1FA6200C1996601000460108 /* Tests */ = { isa = PBXGroup; children = ( 1FA62027199660CA00460108 /* _TestCase.swift */, + 1FB1EC8D199E609900ABD937 /* State & Event */, 1FA62028199660CA00460108 /* BasicTests.swift */, - 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */, - 1FD01B6219EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift */, + 1F532C601C12D7BD00D3813A /* MiscTests.swift */, 1F198C5B19972320001C3700 /* QiitaTests.swift */, - C662B6F41B861CC400479524 /* RasmusTest.swift */, - 1FA6202A199660CA00460108 /* StateMachineChainTests.swift */, - 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */, + 4876510E1C0ECBEB005961AC /* MachineTests.swift */, 1FA6202C199660CA00460108 /* StateMachineTests.swift */, - 1FA6202D199660CA00460108 /* StateRouteTests.swift */, - 1FA6202E199660CA00460108 /* StateTransitionChainTests.swift */, - 1FA6202F199660CA00460108 /* StateTransitionTests.swift */, + 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */, + 1F4336AC1C17113F00E7C1AC /* StateTests.swift */, + 1F4336AF1C17115700E7C1AC /* EventTests.swift */, + 1FA6202F199660CA00460108 /* TransitionTests.swift */, + 1FA6202E199660CA00460108 /* TransitionChainTests.swift */, + 1FA6202D199660CA00460108 /* RouteTests.swift */, + 1FA6202A199660CA00460108 /* RouteChainTests.swift */, + 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */, + 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */, 1FA6200D1996601000460108 /* Supporting Files */, - 1FB1EC8D199E609900ABD937 /* Type */, ); - path = SwiftStateTests; + path = Tests; sourceTree = ""; }; 1FA6200D1996601000460108 /* Supporting Files */ = { @@ -209,23 +205,54 @@ name = "Supporting Files"; sourceTree = ""; }; - 1FB1EC88199E4ABC00ABD937 /* Type */ = { + 1FB1EC88199E4ABC00ABD937 /* Protocols */ = { isa = PBXGroup; children = ( 1FA6201F1996606300460108 /* StateType.swift */, - 1FA620191996606200460108 /* StateEventType.swift */, - 1FB1EC8E199E60F800ABD937 /* String+SwiftState.swift */, + 1FA620191996606200460108 /* EventType.swift */, ); - name = Type; + name = Protocols; sourceTree = ""; }; - 1FB1EC8D199E609900ABD937 /* Type */ = { + 1FB1EC8D199E609900ABD937 /* State & Event */ = { isa = PBXGroup; children = ( 1FA62029199660CA00460108 /* MyState.swift */, 1FB1EC89199E515B00ABD937 /* MyEvent.swift */, + 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */, + ); + name = "State & Event"; + sourceTree = ""; + }; + 1FD79F931C1736D600CE7060 /* mrackwitz/xcconfigs (for target) */ = { + isa = PBXGroup; + children = ( + 1FD79F961C1736D600CE7060 /* UniversalFramework_Base.xcconfig */, + 1FD79F971C1736D600CE7060 /* UniversalFramework_Framework.xcconfig */, + 1FD79F981C1736D600CE7060 /* UniversalFramework_Test.xcconfig */, + ); + name = "mrackwitz/xcconfigs (for target)"; + path = Carthage/Checkouts/xcconfigs; + sourceTree = ""; + }; + 1FD79F9E1C173C6C00CE7060 /* Configurations */ = { + isa = PBXGroup; + children = ( + 1FD79FA01C17409300CE7060 /* project xcconfigs */, + 1FD79F931C1736D600CE7060 /* mrackwitz/xcconfigs (for target) */, ); - name = Type; + name = Configurations; + sourceTree = ""; + }; + 1FD79FA01C17409300CE7060 /* project xcconfigs */ = { + isa = PBXGroup; + children = ( + 1FD79FA41C1740D900CE7060 /* Base.xcconfig */, + 1FD79FA51C17412600CE7060 /* Debug.xcconfig */, + 1FD79FA61C17412600CE7060 /* Release.xcconfig */, + ); + name = "project xcconfigs"; + path = Configurations; sourceTree = ""; }; /* End PBXGroup section */ @@ -239,20 +266,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4872D5A919B4211900F326B5 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 48797D5D19B42CBE0085D80F /* SwiftState.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - 1FA61FFF1996601000460108 /* SwiftState-OSX */ = { + 1FA61FFF1996601000460108 /* SwiftState */ = { isa = PBXNativeTarget; - buildConfigurationList = 1FA620131996601000460108 /* Build configuration list for PBXNativeTarget "SwiftState-OSX" */; + buildConfigurationList = 1FA620131996601000460108 /* Build configuration list for PBXNativeTarget "SwiftState" */; buildPhases = ( 1FA61FFB1996601000460108 /* Sources */, 1FA61FFC1996601000460108 /* Frameworks */, @@ -263,14 +282,14 @@ ); dependencies = ( ); - name = "SwiftState-OSX"; + name = SwiftState; productName = SwiftState; productReference = 1FA620001996601000460108 /* SwiftState.framework */; productType = "com.apple.product-type.framework"; }; - 1FA6200A1996601000460108 /* SwiftState-OSXTests */ = { + 1FA6200A1996601000460108 /* SwiftStateTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 1FA620161996601000460108 /* Build configuration list for PBXNativeTarget "SwiftState-OSXTests" */; + buildConfigurationList = 1FA620161996601000460108 /* Build configuration list for PBXNativeTarget "SwiftStateTests" */; buildPhases = ( 1FA620071996601000460108 /* Sources */, 1FA620081996601000460108 /* Frameworks */, @@ -281,46 +300,11 @@ dependencies = ( 1FF69206199662A000E3CE40 /* PBXTargetDependency */, ); - name = "SwiftState-OSXTests"; + name = SwiftStateTests; productName = SwiftStateTests; - productReference = 1FA6200B1996601000460108 /* SwiftState-OSXTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 4822F09E19D0085E00F5F572 /* SwiftState-iOSTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4822F0A319D0085E00F5F572 /* Build configuration list for PBXNativeTarget "SwiftState-iOSTests" */; - buildPhases = ( - 4822F09F19D0085E00F5F572 /* Sources */, - 4822F0A119D0085E00F5F572 /* Frameworks */, - 4822F0A219D0085E00F5F572 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "SwiftState-iOSTests"; - productName = "SwiftState-iOS2Tests"; - productReference = 4822F0A619D0085E00F5F572 /* SwiftStateTests-iOS.xctest */; + productReference = 1FA6200B1996601000460108 /* SwiftStateTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 4872D5AB19B4211900F326B5 /* SwiftState-iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4872D5C019B4211900F326B5 /* Build configuration list for PBXNativeTarget "SwiftState-iOS" */; - buildPhases = ( - 4872D5A719B4211900F326B5 /* Sources */, - 4872D5A819B4211900F326B5 /* Frameworks */, - 4872D5A919B4211900F326B5 /* Headers */, - 4872D5AA19B4211900F326B5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "SwiftState-iOS"; - productName = "SwiftState-iOS"; - productReference = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; - productType = "com.apple.product-type.framework"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -337,9 +321,6 @@ 1FA6200A1996601000460108 = { CreatedOnToolsVersion = 6.0; }; - 4872D5AB19B4211900F326B5 = { - CreatedOnToolsVersion = 6.0; - }; }; }; buildConfigurationList = 1FA61FFA1996601000460108 /* Build configuration list for PBXProject "SwiftState" */; @@ -354,10 +335,8 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 1FA61FFF1996601000460108 /* SwiftState-OSX */, - 1FA6200A1996601000460108 /* SwiftState-OSXTests */, - 4872D5AB19B4211900F326B5 /* SwiftState-iOS */, - 4822F09E19D0085E00F5F572 /* SwiftState-iOSTests */, + 1FA61FFF1996601000460108 /* SwiftState */, + 1FA6200A1996601000460108 /* SwiftStateTests */, ); }; /* End PBXProject section */ @@ -377,20 +356,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4822F0A219D0085E00F5F572 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4872D5AA19B4211900F326B5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -398,15 +363,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1FA620251996606300460108 /* StateTransitionChain.swift in Sources */, - 1FA620241996606300460108 /* StateTransition.swift in Sources */, - 1FB1EC8F199E60F800ABD937 /* String+SwiftState.swift in Sources */, - 1FA620201996606300460108 /* StateEventType.swift in Sources */, - 1FA620221996606300460108 /* StateRoute.swift in Sources */, - 1FA620211996606300460108 /* StateMachine.swift in Sources */, - 1FD01B6019EC2B5C00DA1C91 /* HierarchicalStateMachine.swift in Sources */, + 1F532C641C12EA8000D3813A /* _Random.swift in Sources */, + 1FA620251996606300460108 /* TransitionChain.swift in Sources */, + 1F532C541C12B5BC00D3813A /* Disposable.swift in Sources */, + 1FA620241996606300460108 /* Transition.swift in Sources */, + 1F70FB661BF0F46000E5AC8C /* _RouteID.swift in Sources */, + 1FA620201996606300460108 /* EventType.swift in Sources */, + 1F532C571C12BBBB00D3813A /* _HandlerInfo.swift in Sources */, + 4836FF5A1C0EFD700038B7D2 /* StateMachine.swift in Sources */, + 483F35571C0EB192007C70D7 /* Machine.swift in Sources */, + 1F70FB6C1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */, + 1FA620221996606300460108 /* Route.swift in Sources */, + 1F70FB6F1BF0F59600E5AC8C /* _HandlerID.swift in Sources */, 1FA620261996606300460108 /* StateType.swift in Sources */, - 1FA620231996606300460108 /* StateRouteChain.swift in Sources */, + 1FA620231996606300460108 /* RouteChain.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -416,64 +386,31 @@ files = ( 1FB1EC8A199E515B00ABD937 /* MyEvent.swift in Sources */, 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */, - 1FD01B6319EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift in Sources */, - 1FA62038199660CA00460108 /* StateTransitionTests.swift in Sources */, - 1FA62033199660CA00460108 /* StateMachineChainTests.swift in Sources */, + 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */, + 4876510F1C0ECBEB005961AC /* MachineTests.swift in Sources */, + 1FA62038199660CA00460108 /* TransitionTests.swift in Sources */, + 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */, 1FA62030199660CA00460108 /* _TestCase.swift in Sources */, - 1FA62036199660CA00460108 /* StateRouteTests.swift in Sources */, + 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */, + 1F4336AD1C17113F00E7C1AC /* StateTests.swift in Sources */, + 1FA62036199660CA00460108 /* RouteTests.swift in Sources */, + 1F532C611C12D7BD00D3813A /* MiscTests.swift in Sources */, 1FA62031199660CA00460108 /* BasicTests.swift in Sources */, 1FA62034199660CA00460108 /* StateMachineEventTests.swift in Sources */, - 1FB4B39C1AAB3B190072E65D /* BugFixTests.swift in Sources */, 1FA62035199660CA00460108 /* StateMachineTests.swift in Sources */, - 1FA62037199660CA00460108 /* StateTransitionChainTests.swift in Sources */, - C662B6F51B861CC400479524 /* RasmusTest.swift in Sources */, + 1FA62037199660CA00460108 /* TransitionChainTests.swift in Sources */, + 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */, + 1F4336B01C17115700E7C1AC /* EventTests.swift in Sources */, 1FA62032199660CA00460108 /* MyState.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 4822F09F19D0085E00F5F572 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */, - 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */, - 1FD01B6419EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift in Sources */, - 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */, - 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, - 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, - 4822F0AE19D008EB00F5F572 /* StateMachineChainTests.swift in Sources */, - 4822F0B019D008EB00F5F572 /* StateTransitionTests.swift in Sources */, - 4822F0A919D008E700F5F572 /* MyState.swift in Sources */, - 1FB4B39D1AAB3B190072E65D /* BugFixTests.swift in Sources */, - 4822F0B219D008EB00F5F572 /* StateRouteTests.swift in Sources */, - 4822F0B119D008EB00F5F572 /* StateTransitionChainTests.swift in Sources */, - C662B6F61B861CC400479524 /* RasmusTest.swift in Sources */, - 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4872D5A719B4211900F326B5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48797D6219B42CCE0085D80F /* StateRouteChain.swift in Sources */, - 48797D6019B42CCE0085D80F /* StateTransitionChain.swift in Sources */, - 48797D6519B42CD40085D80F /* String+SwiftState.swift in Sources */, - 48797D5F19B42CCE0085D80F /* StateTransition.swift in Sources */, - 48797D6319B42CD40085D80F /* StateType.swift in Sources */, - 48797D5E19B42CCE0085D80F /* StateMachine.swift in Sources */, - 1FD01B6119EC2B5C00DA1C91 /* HierarchicalStateMachine.swift in Sources */, - 48797D6119B42CCE0085D80F /* StateRoute.swift in Sources */, - 48797D6419B42CD40085D80F /* StateEventType.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 1FF69206199662A000E3CE40 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 1FA61FFF1996601000460108 /* SwiftState-OSX */; + target = 1FA61FFF1996601000460108 /* SwiftState */; targetProxy = 1FF69205199662A000E3CE40 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -481,6 +418,7 @@ /* Begin XCBuildConfiguration section */ 1FA620111996601000460108 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79FA51C17412600CE7060 /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -515,11 +453,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -528,6 +463,7 @@ }; 1FA620121996601000460108 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79FA61C17412600CE7060 /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -556,10 +492,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -567,6 +500,7 @@ }; 1FA620141996601000460108 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F971C1736D600CE7060 /* UniversalFramework_Framework.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; @@ -575,20 +509,17 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SwiftState/Info.plist; + INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; - SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 1FA620151996601000460108 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F971C1736D600CE7060 /* UniversalFramework_Framework.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; @@ -597,19 +528,17 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SwiftState/Info.plist; + INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; - SDKROOT = macosx; SKIP_INSTALL = YES; }; name = Release; }; 1FA620171996601000460108 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F981C1736D600CE7060 /* UniversalFramework_Test.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -620,109 +549,24 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + INFOPLIST_FILE = Tests/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; }; name = Debug; }; 1FA620181996601000460108 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F981C1736D600CE7060 /* UniversalFramework_Test.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + INFOPLIST_FILE = Tests/Info.plist; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - }; - name = Release; - }; - 4822F0A419D0085E00F5F572 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "SwiftStateTests-iOS"; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 4822F0A519D0085E00F5F572 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "SwiftStateTests-iOS"; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 4872D5BC19B4211900F326B5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = "$(SRCROOT)/SwiftState/Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(PROJECT_NAME)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 4872D5BD19B4211900F326B5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/SwiftState/Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; - PRODUCT_NAME = "$(PROJECT_NAME)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; }; name = Release; }; @@ -738,7 +582,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 1FA620131996601000460108 /* Build configuration list for PBXNativeTarget "SwiftState-OSX" */ = { + 1FA620131996601000460108 /* Build configuration list for PBXNativeTarget "SwiftState" */ = { isa = XCConfigurationList; buildConfigurations = ( 1FA620141996601000460108 /* Debug */, @@ -747,7 +591,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 1FA620161996601000460108 /* Build configuration list for PBXNativeTarget "SwiftState-OSXTests" */ = { + 1FA620161996601000460108 /* Build configuration list for PBXNativeTarget "SwiftStateTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 1FA620171996601000460108 /* Debug */, @@ -756,24 +600,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 4822F0A319D0085E00F5F572 /* Build configuration list for PBXNativeTarget "SwiftState-iOSTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4822F0A419D0085E00F5F572 /* Debug */, - 4822F0A519D0085E00F5F572 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4872D5C019B4211900F326B5 /* Build configuration list for PBXNativeTarget "SwiftState-iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4872D5BC19B4211900F326B5 /* Debug */, - 4872D5BD19B4211900F326B5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 1FA61FF71996601000460108 /* Project object */; diff --git a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme deleted file mode 100644 index fab0dc5..0000000 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState.xcscheme similarity index 90% rename from SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme rename to SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState.xcscheme index 1231e10..87e399e 100644 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme +++ b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState.xcscheme @@ -16,25 +16,26 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "1FA61FFF1996601000460108" BuildableName = "SwiftState.framework" - BlueprintName = "SwiftState-OSX" + BlueprintName = "SwiftState" ReferencedContainer = "container:SwiftState.xcodeproj"> + codeCoverageEnabled = "YES"> @@ -44,7 +45,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "1FA61FFF1996601000460108" BuildableName = "SwiftState.framework" - BlueprintName = "SwiftState-OSX" + BlueprintName = "SwiftState" ReferencedContainer = "container:SwiftState.xcodeproj"> @@ -52,11 +53,11 @@ @@ -74,17 +75,17 @@ diff --git a/SwiftState/HierarchicalStateMachine.swift b/SwiftState/HierarchicalStateMachine.swift deleted file mode 100644 index 3b95fb3..0000000 --- a/SwiftState/HierarchicalStateMachine.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// HierarchicalStateMachine.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/10/13. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import Foundation - -public typealias HSM = HierarchicalStateMachine - -// -// NOTE: -// When subclassing StateMachine, -// don't directly set String as a replacement of StateType (generic type) -// due to Xcode6.1-GM2 generics bug(?) causing EXC_I386_GPFLT when overriding method e.g. `hasRoute()`. -// -// Ideally, class `HierarchicalStateMachine` should be declared as following: -// -// `public class HierarchicalStateMachine: StateMachine` -// -// To avoid above issue, we use `typealias HSM` instead. -// - -/// nestable StateMachine with StateType as String -public class HierarchicalStateMachine: StateMachine, CustomStringConvertible -{ - private var _submachines = [String : HSM]() - - public let name: String - - /// init with submachines - public init(name: String, submachines: [HSM]? = nil, state: State, initClosure: (StateMachine -> Void)? = nil) - { - self.name = name - - if let submachines = submachines { - for submachine in submachines { - self._submachines[submachine.name] = submachine - } - } - - super.init(state: state, initClosure: initClosure) - } - - public var description: String - { - return self.name - } - - /// - /// Converts dot-chained state sent from mainMachine into (submachine, substate) tuple. - /// e.g. - /// - /// - state="MainState1" will return (nil, "MainState1") - /// - state="SubMachine1.State1" will return (submachine1, "State1") - /// - state="" (nil) will return (nil, nil) - /// - private func _submachineTupleForState(state: State) -> (HSM?, HSM.State) - { - assert(state is HSM.State, "HSM state must be String.") - - let components = (state as! HSM.State).characters.split { $0 == "." }.map { String($0) } - - switch components.count { - case 2: - let submachineName = components[0] - let substate = components[1] - return (self._submachines[submachineName], substate) - - case 1: - let state = components[0] - return (nil, state) - - default: - // NOTE: reaches here when state="" (empty) as AnyState - return (nil, nil) - } - } - - public override var state: State - { - // NOTE: returning `substate` is not reliable (as a spec), so replace it with actual `submachine.state` instead - let (submachine, _) = self._submachineTupleForState(self._state) - - if let submachine = submachine { - self._state = "\(submachine.name).\(submachine.state)" as! State - } - - return self._state - } - - public override func hasRoute(transition: Transition, forEvent event: Event = nil) -> Bool - { - let (fromSubmachine, fromSubstate) = self._submachineTupleForState(transition.fromState) - let (toSubmachine, toSubstate) = self._submachineTupleForState(transition.toState) - - // check submachine-internal routes - if fromSubmachine != nil && toSubmachine != nil && fromSubmachine === toSubmachine { - return fromSubmachine!.hasRoute(fromSubstate => toSubstate, forEvent: nil) - } - - return super.hasRoute(transition, forEvent: event) - } - - internal override func _addRoute(var route: Route, forEvent event: Event = nil) -> RouteID - { - let originalCondition = route.condition - - let condition: Condition = { transition -> Bool in - - let (fromSubmachine, _) = self._submachineTupleForState(route.transition.fromState) - let (toSubmachine, toSubstate) = self._submachineTupleForState(route.transition.toState) - - // - // For external-route, don't let mainMachine switch to submachine.state=toSubstate - // when current submachine.state is not toSubstate. - // - // e.g. ignore `"MainState0" => "Sub1.State1"` transition when - // `mainMachine.state="MainState0"` but `submachine.state="State2"` (not "State1") - // - if toSubmachine != nil && toSubmachine!.state != toSubstate && fromSubmachine !== toSubmachine { - return false - } - - return originalCondition?(transition: transition) ?? true - } - - route = Route(transition: route.transition, condition: condition) - - return super._addRoute(route, forEvent: event) - } - - // TODO: apply mainMachine's events to submachines - internal override func _tryState(state: State, userInfo: Any? = nil, forEvent event: Event) -> Bool - { - assert(state is HSM.State, "HSM state must be String.") - - let fromState = self.state - let toState = state - - let (fromSubmachine, _) = self._submachineTupleForState(fromState) - let (toSubmachine, toSubstate) = self._submachineTupleForState(toState) - - // try changing submachine-internal state - if fromSubmachine != nil && toSubmachine != nil && fromSubmachine === toSubmachine { - - if toSubmachine!.canTryState(toSubstate, forEvent: event as! HSM.Event) { - - // - // NOTE: - // Change mainMachine's state first to invoke its handlers - // before changing toSubmachine's state because mainMachine.state relies on it. - // - super._tryState(toState, userInfo: userInfo, forEvent: event) - - toSubmachine! <- (toSubstate as HSM.State) - - return true - } - - } - - return super._tryState(toState, userInfo: userInfo, forEvent: event) - } -} \ No newline at end of file diff --git a/SwiftState/StateEventType.swift b/SwiftState/StateEventType.swift deleted file mode 100644 index 804c72d..0000000 --- a/SwiftState/StateEventType.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// StateEventType.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/05. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -public protocol StateEventType: Hashable, NilLiteralConvertible -{ - -} \ No newline at end of file diff --git a/SwiftState/StateMachine.swift b/SwiftState/StateMachine.swift deleted file mode 100644 index e3d3849..0000000 --- a/SwiftState/StateMachine.swift +++ /dev/null @@ -1,898 +0,0 @@ -// -// StateMachine.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import Darwin - -// NOTE: nested type inside generic StateMachine class is not allowed in Swift 1.1 -// NOTE: 'public struct' didn't work since Xcode6-beta6 -public class StateMachineRouteID -{ - private typealias Transition = StateTransition - private typealias RouteKey = StateMachine.RouteKey - - private let transition: Transition? - private let routeKey: RouteKey? - private let event: E? - - private let bundledRouteIDs: [StateMachineRouteID]? - - private init(transition: Transition?, routeKey: RouteKey?, event: E?) - { - self.transition = transition - self.routeKey = routeKey - self.event = event - - self.bundledRouteIDs = nil - } - - private init(bundledRouteIDs: [StateMachineRouteID]?) - { - self.bundledRouteIDs = bundledRouteIDs - - self.transition = nil - self.routeKey = nil - self.event = nil - } -} - -public class StateMachineHandlerID -{ - private typealias Transition = StateTransition - private typealias HandlerKey = StateMachine.HandlerKey - - private let transition: Transition? // NOTE: nil is used for error-handlerID - private let handlerKey: HandlerKey? - - private let bundledHandlerIDs: [StateMachineHandlerID]? - - private init(transition: Transition?, handlerKey: HandlerKey?) - { - self.transition = transition - self.handlerKey = handlerKey - - self.bundledHandlerIDs = nil - } - - private init(bundledHandlerIDs: [StateMachineHandlerID]?) - { - self.bundledHandlerIDs = bundledHandlerIDs - - self.transition = nil - self.handlerKey = nil - } -} - -internal class _StateMachineHandlerInfo -{ - private typealias HandlerOrder = StateMachine.HandlerOrder - private typealias HandlerKey = StateMachine.HandlerKey - private typealias Handler = StateMachine.Handler - - private let order: HandlerOrder - private let handlerKey: HandlerKey - private let handler: Handler - - private init(order: HandlerOrder, handlerKey: HandlerKey, handler: Handler) - { - self.order = order - self.handlerKey = handlerKey - self.handler = handler - } -} - -public class StateMachine -{ - public typealias State = S - public typealias Event = E - - public typealias Transition = StateTransition - public typealias TransitionChain = StateTransitionChain - - public typealias Route = StateRoute - public typealias RouteID = StateMachineRouteID - public typealias RouteChain = StateRouteChain - - public typealias Condition = Route.Condition - - public typealias HandlerOrder = UInt8 - public typealias Handler = (HandlerContext -> Void) - public typealias HandlerContext = (event: Event, transition: Transition, order: HandlerOrder, userInfo: Any?) - public typealias HandlerID = StateMachineHandlerID - - private typealias RouteKey = String - private typealias HandlerKey = String - private typealias HandlerInfo = _StateMachineHandlerInfo - - private typealias TransitionRouteDictionary = [Transition : [RouteKey : Condition?]] - - private var _routes: [Event : TransitionRouteDictionary] = [:] - private var _handlers: [Transition : [HandlerInfo]] = [:] - private var _errorHandlers: [HandlerInfo] = [] - - internal var _state: State - - private class var _defaultOrder: HandlerOrder { return 100 } - - //-------------------------------------------------- - // MARK: - Utility - //-------------------------------------------------- - - // generate approx 126bit random string - private class func _createUniqueString() -> String - { - var uniqueString: String = "" - for _ in 1...8 { - uniqueString += String(UnicodeScalar(arc4random_uniform(0xD800))) // 0xD800 = 55296 = 15.755bit - } - return uniqueString - } - - //-------------------------------------------------- - // MARK: - Init - //-------------------------------------------------- - - public init(state: State, initClosure: (StateMachine -> Void)? = nil) - { - self._state = state - - initClosure?(self) - } - - public func configure(closure: StateMachine -> Void) - { - closure(self) - } - - //-------------------------------------------------- - // MARK: - State/Event/Transition - //-------------------------------------------------- - - public var state: State - { - return self._state - } - - public func hasRoute(transition: Transition, forEvent event: Event = nil) -> Bool - { - let validTransitions = self._validTransitionsForTransition(transition) - - for validTransition in validTransitions { - - var transitionDicts: [TransitionRouteDictionary] = [] - - if event == nil as Event { - transitionDicts += self._routes.values.lazy - } - else { - for (ev, transitionDict) in self._routes { - if ev == event || ev == nil as Event { - transitionDicts += [transitionDict] - } - } - } - - for transitionDict in transitionDicts { - if let routeKeyDict = transitionDict[validTransition] { - for (_, condition) in routeKeyDict { - if self._canPassCondition(condition, transition: transition) { - return true - } - } - } - } - } - - return false - } - - private func _canPassCondition(condition: Condition?, transition: Transition) -> Bool - { - return condition?(transition: transition) ?? true - } - - public func canTryState(state: State, forEvent event: Event = nil) -> Bool - { - let fromState = self.state - let toState = state - - return self.hasRoute(fromState => toState, forEvent: event) - } - - public func tryState(state: State, userInfo: Any? = nil) -> Bool - { - return self._tryState(state, userInfo: userInfo, forEvent: nil) - } - - internal func _tryState(state: State, userInfo: Any? = nil, forEvent event: Event) -> Bool - { - var didTransit = false - - let fromState = self.state - let toState = state - let transition = fromState => toState - - if self.canTryState(state, forEvent: event) { - - // collect valid handlers before updating state - let validHandlerInfos = self._validHandlerInfosForTransition(transition) - - // update state - self._state = toState - - // - // Perform validHandlers after updating state. - // - // NOTE: - // Instead of using before/after handlers as seen in many other StateMachine libraries, - // SwiftState uses `order` value to perform handlers in 'fine-grained' order, - // only after state has been updated. (Any problem?) - // - for handlerInfo in validHandlerInfos { - let order = handlerInfo.order - let handler = handlerInfo.handler - - handler(HandlerContext(event: event, transition: transition, order: order, userInfo: userInfo)) - } - - didTransit = true - } - else { - for handlerInfo in self._errorHandlers { - let order = handlerInfo.order - let handler = handlerInfo.handler - - handler(HandlerContext(event: event, transition: transition, order: order, userInfo: userInfo)) - } - } - - return didTransit - } - - private func _validHandlerInfosForTransition(transition: Transition) -> [HandlerInfo] - { - var validHandlerInfos: [HandlerInfo] = [] - - let validTransitions = self._validTransitionsForTransition(transition) - - for validTransition in validTransitions { - if let handlerInfos = self._handlers[validTransition] { - for handlerInfo in handlerInfos { - validHandlerInfos += [handlerInfo] - } - } - } - - validHandlerInfos.sortInPlace { info1, info2 in - return info1.order < info2.order - } - - return validHandlerInfos - } - - private func _validTransitionsForTransition(transition: Transition) -> [Transition] - { - var transitions: [Transition] = [] - - // anywhere - transitions += [nil => nil] - - // exit - if transition.fromState != nil as State { - transitions += [transition.fromState => nil] - } - - // entry - if transition.toState != nil as State { - transitions += [nil => transition.toState] - } - - // specific - if (transition.fromState != nil as State) && (transition.toState != nil as State) { - transitions += [transition] - } - - return transitions - } - - public func canTryEvent(event: Event) -> State? - { - var validEvents: [Event] = [] - if event == nil as Event { - validEvents += self._routes.keys.lazy - } - else { - validEvents += [event] - } - - for validEvent in validEvents { - if let transitionDict = self._routes[validEvent] { - for (transition, routeKeyDict) in transitionDict { - if transition.fromState == self.state || transition.fromState == nil { - for (_, condition) in routeKeyDict { - // Pass current state as from-state to avoid passing nil by mistake when - // transition has multiple from-states that will be evaluated in its condition closure - if self._canPassCondition(condition, transition: self.state => transition.toState) { - return transition.toState - } - } - } - } - } - } - - return nil - } - - public func tryEvent(event: Event, userInfo: Any? = nil) -> Bool - { - if var toState = self.canTryEvent(event) { - - // current state should not be changed if `toState == nil` - if toState == nil { - toState = self.state - } - - self._tryState(toState, userInfo: userInfo, forEvent: event) - return true - } - - return false - } - - //-------------------------------------------------- - // MARK: - Route - //-------------------------------------------------- - - // MARK: addRoute - - public func addRoute(transition: Transition, condition: Condition? = nil) -> RouteID - { - let route = Route(transition: transition, condition: condition) - return self.addRoute(route) - } - - public func addRoute(transition: Transition, @autoclosure(escaping) condition: () -> Bool) -> RouteID - { - return self.addRoute(transition, condition: { t in condition() }) - } - - public func addRoute(route: Route) -> RouteID - { - return self._addRoute(route) - } - - internal func _addRoute(route: Route, forEvent event: Event = nil) -> RouteID - { - let transition = route.transition - let condition = route.condition - - if self._routes[event] == nil { - self._routes[event] = [:] - } - - var transitionDict = self._routes[event]! - if transitionDict[transition] == nil { - transitionDict[transition] = [:] - } - - let routeKey = self.dynamicType._createUniqueString() - - var routeKeyDict = transitionDict[transition]! - routeKeyDict[routeKey] = condition - transitionDict[transition] = routeKeyDict - - self._routes[event] = transitionDict - - let routeID = RouteID(transition: transition, routeKey: routeKey, event: event) - - return routeID - } - - // MARK: addRoute + conditional handler - - public func addRoute(transition: Transition, handler: Handler) -> (RouteID, HandlerID) - { - return self.addRoute(transition, condition: nil, handler: handler) - } - - public func addRoute(transition: Transition, condition: Condition?, handler: Handler) -> (RouteID, HandlerID) - { - let route = Route(transition: transition, condition: condition) - return self.addRoute(route, handler: handler) - } - - public func addRoute(transition: Transition, @autoclosure(escaping) condition: () -> Bool, handler: Handler) -> (RouteID, HandlerID) - { - return self.addRoute(transition, condition: { t in condition() }, handler: handler) - } - - public func addRoute(route: Route, handler: Handler) -> (RouteID, HandlerID) - { - let transition = route.transition - let condition = route.condition - - let routeID = self.addRoute(transition, condition: condition) - - let handlerID = self.addHandler(transition) { [weak self] context in - if let self_ = self { - if self_._canPassCondition(condition, transition: context.transition) { - handler(context) - } - } - } - - return (routeID, handlerID) - } - - // MARK: removeRoute - - public func removeRoute(routeID: RouteID) -> Bool - { - if let routeKey = routeID.routeKey { - let event = routeID.event! - let transition = routeID.transition! - - if var transitionDict = self._routes[event] { - if var routeKeyDict = transitionDict[transition] { - routeKeyDict[routeKey] = nil - if routeKeyDict.count > 0 { - transitionDict[transition] = routeKeyDict - } - else { - transitionDict[transition] = nil - } - } - - if transitionDict.count > 0 { - self._routes[event] = transitionDict - } - else { - self._routes[event] = nil - } - - return true - } - } - else { - var success = false - for bundledRouteID in routeID.bundledRouteIDs! { - success = self.removeRoute(bundledRouteID) || success - } - - return success - } - - return false - } - - public func removeAllRoutes() -> Bool - { - let removingCount = self._routes.count - - self._routes = [:] - - return removingCount > 0 - } - - //-------------------------------------------------- - // MARK: - Handler - //-------------------------------------------------- - - public func addHandler(transition: Transition, handler: Handler) -> HandlerID - { - return self.addHandler(transition, order: self.dynamicType._defaultOrder, handler: handler) - } - - public func addHandler(transition: Transition, order: HandlerOrder, handler: Handler) -> HandlerID - { - if self._handlers[transition] == nil { - self._handlers[transition] = [] - } - - let handlerKey = self.dynamicType._createUniqueString() - - var handlerInfos = self._handlers[transition]! - let newHandlerInfo = HandlerInfo(order: order, handlerKey: handlerKey, handler: handler) - self._insertHandlerIntoArray(&handlerInfos, newHandlerInfo: newHandlerInfo) - - self._handlers[transition] = handlerInfos - - let handlerID = HandlerID(transition: transition, handlerKey: handlerKey) - - return handlerID - } - - private func _insertHandlerIntoArray(inout handlerInfos: [HandlerInfo], newHandlerInfo: HandlerInfo) - { - var index = handlerInfos.count - - for i in Array(0.. HandlerID - { - return self.addHandler(nil => state, order: self.dynamicType._defaultOrder, handler: handler) - } - - public func addEntryHandler(state: State, order: HandlerOrder, handler: Handler) -> HandlerID - { - return self.addHandler(nil => state, handler: handler) - } - - // MARK: addExitHandler - - public func addExitHandler(state: State, handler: Handler) -> HandlerID - { - return self.addHandler(state => nil, order: self.dynamicType._defaultOrder, handler: handler) - } - - public func addExitHandler(state: State, order: HandlerOrder, handler: Handler) -> HandlerID - { - return self.addHandler(state => nil, handler: handler) - } - - // MARK: removeHandler - - public func removeHandler(handlerID: HandlerID) -> Bool - { - if handlerID.handlerKey != nil { - if let transition = handlerID.transition { - if var handlerInfos = self._handlers[transition] { - - if self._removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { - self._handlers[transition] = handlerInfos - return true - } - } - } - // `transition = nil` means errorHandler - else { - if self._removeHandlerFromArray(&self._errorHandlers, removingHandlerID: handlerID) { - return true - } - return false - } - } - else { - var success = false - for bundledHandlerID in handlerID.bundledHandlerIDs! { - success = self.removeHandler(bundledHandlerID) || success - } - - return success - } - - return false - } - - private func _removeHandlerFromArray(inout handlerInfos: [HandlerInfo], removingHandlerID: HandlerID) -> Bool - { - for i in 0.. Bool - { - let removingCount = self._handlers.count + self._errorHandlers.count - - self._handlers = [:] - self._errorHandlers = [] - - return removingCount > 0 - } - - // MARK: addErrorHandler - - public func addErrorHandler(handler: Handler) -> HandlerID - { - return self.addErrorHandler(order: self.dynamicType._defaultOrder, handler: handler) - } - - public func addErrorHandler(order order: HandlerOrder, handler: Handler) -> HandlerID - { - let handlerKey = self.dynamicType._createUniqueString() - - let newHandlerInfo = HandlerInfo(order: order, handlerKey: handlerKey, handler: handler) - self._insertHandlerIntoArray(&self._errorHandlers, newHandlerInfo: newHandlerInfo) - - let handlerID = HandlerID(transition: nil, handlerKey: handlerKey) - - return handlerID - } - - //-------------------------------------------------- - // MARK: - RouteChain - //-------------------------------------------------- - // NOTE: handler is required for addRouteChain - - // MARK: addRouteChain + conditional handler - - public func addRouteChain(chain: TransitionChain, handler: Handler) -> (RouteID, HandlerID) - { - return self.addRouteChain(chain, condition: nil, handler: handler) - } - - public func addRouteChain(chain: TransitionChain, condition: Condition?, handler: Handler) -> (RouteID, HandlerID) - { - let routeChain = RouteChain(transitionChain: chain, condition: condition) - return self.addRouteChain(routeChain, handler: handler) - } - - public func addRouteChain(chain: TransitionChain, @autoclosure(escaping) condition: () -> Bool, handler: Handler) -> (RouteID, HandlerID) - { - return self.addRouteChain(chain, condition: { t in condition() }, handler: handler) - } - - public func addRouteChain(chain: RouteChain, handler: Handler) -> (RouteID, HandlerID) - { - var routeIDs: [RouteID] = [] - - for route in chain.routes { - let routeID = self.addRoute(route) - routeIDs += [routeID] - } - - let handlerID = self.addChainHandler(chain, handler: handler) - - let bundledRouteID = RouteID(bundledRouteIDs: routeIDs) - - return (bundledRouteID, handlerID) - } - - // MARK: addChainHandler - - public func addChainHandler(chain: TransitionChain, handler: Handler) -> HandlerID - { - return self.addChainHandler(chain.toRouteChain(), handler: handler) - } - - public func addChainHandler(chain: TransitionChain, order: HandlerOrder, handler: Handler) -> HandlerID - { - return self.addChainHandler(chain.toRouteChain(), order: order, handler: handler) - } - - public func addChainHandler(chain: RouteChain, handler: Handler) -> HandlerID - { - return self.addChainHandler(chain, order: self.dynamicType._defaultOrder, handler: handler) - } - - public func addChainHandler(chain: RouteChain, order: HandlerOrder, handler: Handler) -> HandlerID - { - return self._addChainHandler(chain, order: order, handler: handler, isError: false) - } - - // MARK: addChainErrorHandler - - public func addChainErrorHandler(chain: TransitionChain, handler: Handler) -> HandlerID - { - return self.addChainErrorHandler(chain.toRouteChain(), handler: handler) - } - - public func addChainErrorHandler(chain: TransitionChain, order: HandlerOrder, handler: Handler) -> HandlerID - { - return self.addChainErrorHandler(chain.toRouteChain(), order: order, handler: handler) - } - - public func addChainErrorHandler(chain: RouteChain, handler: Handler) -> HandlerID - { - return self.addChainErrorHandler(chain, order: self.dynamicType._defaultOrder, handler: handler) - } - - public func addChainErrorHandler(chain: RouteChain, order: HandlerOrder, handler: Handler) -> HandlerID - { - return self._addChainHandler(chain, order: order, handler: handler, isError: true) - } - - private func _addChainHandler(chain: RouteChain, order: HandlerOrder, handler: Handler, isError: Bool) -> HandlerID - { - var handlerIDs: [HandlerID] = [] - - var shouldStop = true - var shouldIncrementChainingCount = true - var chainingCount = 0 - var allCount = 0 - - // reset count on 1st route - let firstRoute = chain.routes.first! - var handlerID = self.addHandler(firstRoute.transition) { [weak self] context in - if let self_ = self { - if self_._canPassCondition(firstRoute.condition, transition: context.transition) { - if shouldStop { - shouldStop = false - chainingCount = 0 - allCount = 0 - } - } - } - } - handlerIDs += [handlerID] - - // increment chainingCount on every route - for route in chain.routes { - - handlerID = self.addHandler(route.transition) { [weak self] context in - - if let self_ = self { - - // skip duplicated transition handlers e.g. chain = 0 => 1 => 0 => 1 & transiting 0 => 1 - if !shouldIncrementChainingCount { return } - - if self_._canPassCondition(route.condition, transition: context.transition) { - if !shouldStop { - chainingCount++ - - shouldIncrementChainingCount = false - } - } - } - } - handlerIDs += [handlerID] - } - - // increment allCount (+ invoke chainErrorHandler) on any routes - handlerID = self.addHandler(nil => nil, order: 150) { context in - - shouldIncrementChainingCount = true - - if !shouldStop { - allCount++ - } - - if chainingCount < allCount { - shouldStop = true - if isError { - handler(context) - } - } - } - handlerIDs += [handlerID] - - // invoke chainHandler on last route - let lastRoute = chain.routes.last! - handlerID = self.addHandler(lastRoute.transition, order: 200) { [weak self] context in - if let self_ = self { - if self_._canPassCondition(lastRoute.condition, transition: context.transition) { - if chainingCount == allCount && chainingCount == chain.routes.count && chainingCount == chain.routes.count { - shouldStop = true - - if !isError { - handler(context) - } - } - } - } - } - handlerIDs += [handlerID] - - let bundledHandlerID = HandlerID(bundledHandlerIDs: handlerIDs) - - return bundledHandlerID - } - - //-------------------------------------------------- - // MARK: - RouteEvent - //-------------------------------------------------- - - public func addRouteEvent(event: Event, transitions: [Transition], condition: Condition? = nil) -> [RouteID] - { - var routes: [Route] = [] - for transition in transitions { - let route = Route(transition: transition, condition: condition) - routes += [route] - } - - return self.addRouteEvent(event, routes: routes) - } - - public func addRouteEvent(event: Event, transitions: [Transition], @autoclosure(escaping) condition: () -> Bool) -> [RouteID] - { - return self.addRouteEvent(event, transitions: transitions, condition: { t in condition() }) - } - - public func addRouteEvent(event: Event, routes: [Route]) -> [RouteID] - { - var routeIDs: [RouteID] = [] - for route in routes { - let routeID = self._addRoute(route, forEvent: event) - routeIDs += [routeID] - } - - return routeIDs - } - - // MARK: addRouteEvent + conditional handler - - public func addRouteEvent(event: Event, transitions: [Transition], handler: Handler) -> ([RouteID], HandlerID) - { - return self.addRouteEvent(event, transitions: transitions, condition: nil, handler: handler) - } - - public func addRouteEvent(event: Event, transitions: [Transition], condition: Condition?, handler: Handler) -> ([RouteID], HandlerID) - { - let routeIDs = self.addRouteEvent(event, transitions: transitions, condition: condition) - - let handlerID = self.addEventHandler(event, order: self.dynamicType._defaultOrder, handler: handler) - - return (routeIDs, handlerID) - } - - public func addRouteEvent(event: Event, transitions: [Transition], @autoclosure(escaping) condition: () -> Bool, handler: Handler) -> ([RouteID], HandlerID) - { - return self.addRouteEvent(event, transitions: transitions, condition: { t in condition() }, handler: handler) - } - - public func addRouteEvent(event: Event, routes: [Route], handler: Handler) -> ([RouteID], HandlerID) - { - let routeIDs = self.addRouteEvent(event, routes: routes) - - let handlerID = self.addEventHandler(event, order: self.dynamicType._defaultOrder, handler: handler) - - return (routeIDs, handlerID) - } - - // MARK: addEventHandler - - public func addEventHandler(event: Event, handler: Handler) -> HandlerID - { - return self.addEventHandler(event, order: self.dynamicType._defaultOrder, handler: handler) - } - - public func addEventHandler(event: Event, order: HandlerOrder, handler: Handler) -> HandlerID - { - let handlerID = self.addHandler(nil => nil, order: order) { context in - if context.event == event || event == nil { - handler(context) - } - } - - return handlerID - } -} - -//-------------------------------------------------- -// MARK: - Custom Operators -//-------------------------------------------------- - -infix operator <- { associativity right } - -public func <- (machine: StateMachine, state: S) -> Bool -{ - return machine.tryState(state) -} - -public func <- (machine: StateMachine, tuple: (S, Any?)) -> Bool -{ - return machine.tryState(tuple.0, userInfo: tuple.1) -} - -infix operator <-! { associativity right } - -public func <-! (machine: StateMachine, event: E) -> Bool -{ - return machine.tryEvent(event) -} - -public func <-! (machine: StateMachine, tuple: (E, Any?)) -> Bool -{ - return machine.tryEvent(tuple.0, userInfo: tuple.1) -} diff --git a/SwiftState/StateRoute.swift b/SwiftState/StateRoute.swift deleted file mode 100644 index f793524..0000000 --- a/SwiftState/StateRoute.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// StateRoute.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/04. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -public struct StateRoute -{ - public typealias Condition = ((transition: Transition) -> Bool) - - public typealias State = S - public typealias Transition = StateTransition - - public let transition: Transition - public let condition: Condition? - - public init(transition: Transition, condition: Condition?) - { - self.transition = transition - self.condition = condition - } - - public init(transition: Transition, @autoclosure(escaping) condition: () -> Bool) - { - self.init(transition: transition, condition: { t in condition() }) - } - - public func toTransition() -> Transition - { - return self.transition - } - - public func toRouteChain() -> StateRouteChain - { - var routes: [StateRoute] = [] - routes += [self] - return StateRouteChain(routes: routes) - } -} - -//-------------------------------------------------- -// MARK: - Custom Operators -//-------------------------------------------------- - -/// e.g. [.State0, .State1] => .State2, allowing [0 => 2, 1 => 2] -public func => (leftStates: [S], right: S) -> StateRoute -{ - // NOTE: don't reuse "nil => nil + condition" for efficiency - return StateRoute(transition: nil => right, condition: { transition -> Bool in - return leftStates.contains(transition.fromState) - }) -} - -/// e.g. .State0 => [.State1, .State2], allowing [0 => 1, 0 => 2] -public func => (left: S, rightStates: [S]) -> StateRoute -{ - return StateRoute(transition: left => nil, condition: { transition -> Bool in - return rightStates.contains(transition.toState) - }) -} - -/// e.g. [.State0, .State1] => [.State2, .State3], allowing [0 => 2, 0 => 3, 1 => 2, 1 => 3] -public func => (leftStates: [S], rightStates: [S]) -> StateRoute -{ - return StateRoute(transition: nil => nil, condition: { transition -> Bool in - return leftStates.contains(transition.fromState) && rightStates.contains(transition.toState) - }) -} \ No newline at end of file diff --git a/SwiftState/StateRouteChain.swift b/SwiftState/StateRouteChain.swift deleted file mode 100644 index 6c28dcc..0000000 --- a/SwiftState/StateRouteChain.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// StateRouteChain.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/04. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -public struct StateRouteChain -{ - public typealias State = S - public typealias Transition = StateTransition - public typealias TransitionChain = StateTransitionChain - public typealias Route = StateRoute - public typealias Condition = Route.Condition - - internal var routes: [Route] - - public init(routes: [Route]) - { - self.routes = routes - } - - public init(transitionChain: TransitionChain, condition: Condition?) - { - var routes: [Route] = [] - for transition in transitionChain.transitions { - routes += [Route(transition: transition, condition: condition)] - } - self.routes = routes - } - - public var numberOfRoutes: Int - { - return self.routes.count - } - - mutating public func prepend(state: State, condition: Condition?) - { - let firstFromState = self.routes.first!.transition.fromState - let newRoute = Route(transition: state => firstFromState, condition: condition) - - self.routes.insert(newRoute, atIndex: 0) - } - - mutating internal func append(state: State, condition: Condition?) - { - let lastToState = self.routes.last!.transition.toState - let newRoute = Route(transition: lastToState => state, condition: condition) - - self.routes += [newRoute] - } - - public func toTransitionChain() -> TransitionChain - { - let transitions = self.routes.map { route in route.transition } - return StateTransitionChain(transitions: transitions) - } - - public func toRoutes() -> [Route] - { - return self.routes - } -} \ No newline at end of file diff --git a/SwiftState/StateTransition.swift b/SwiftState/StateTransition.swift deleted file mode 100644 index b037b01..0000000 --- a/SwiftState/StateTransition.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// StateTransition.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -public struct StateTransition: Hashable -{ - public typealias State = S - - public let fromState: State - public let toState: State - - public init(fromState: State, toState: State) - { - self.fromState = fromState - self.toState = toState - } - - public var hashValue: Int - { - return self.fromState.hashValue &+ self.toState.hashValue.byteSwapped - } - - public func toTransitionChain() -> StateTransitionChain - { - return StateTransitionChain(transition: self) - } - - public func toRoute() -> StateRoute - { - return StateRoute(transition: self, condition: nil) - } -} - -// for StateTransition Equatable -public func == (left: StateTransition, right: StateTransition) -> Bool -{ - return left.hashValue == right.hashValue -} - -//-------------------------------------------------- -// MARK: - Custom Operators -//-------------------------------------------------- - -infix operator => { associativity left } - -/// e.g. .State0 => .State1 -// NOTE: argument types (S) don't need to be optional because it automatically converts nil to Any via NilLiteralConvertible -public func => (left: S, right: S) -> StateTransition -{ - return StateTransition(fromState: left, toState: right) -} - -//-------------------------------------------------- -// MARK: - Printable -//-------------------------------------------------- - -extension StateTransition: CustomStringConvertible -{ - public var description: String - { - return "\(self.fromState) => \(self.toState) (\(self.hashValue))" - } -} \ No newline at end of file diff --git a/SwiftState/StateTransitionChain.swift b/SwiftState/StateTransitionChain.swift deleted file mode 100644 index bf8cf26..0000000 --- a/SwiftState/StateTransitionChain.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// StateTransitionChain.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/04. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -public struct StateTransitionChain -{ - public typealias State = S - public typealias Transition = StateTransition - - public var states: [State] - - public init(transition: Transition) - { - self.init(transitions: [transition]) - } - - public init(transitions: [Transition]) - { - assert(transitions.count > 0, "StateTransitionChain must be initialized with at least 1 transition.") - - var states: [State] = [] - for i in 0.. states[i+1]] - } - - return transitions - } - - public var firstState: State - { - return self.states.first! - } - - public var lastState: State - { - return self.states.last! - } - - public var numberOfTransitions: Int - { - return self.states.count-1 - } - - mutating public func prepend(state: State) - { - self.states.insert(state, atIndex: 0) - } - - mutating public func append(state: State) - { - self.states += [state] - } - - public func toRouteChain() -> StateRouteChain - { - return StateRouteChain(transitionChain: self, condition: nil) - } - - public func toTransitions() -> [Transition] - { - return self.transitions - } -} - -//-------------------------------------------------- -// MARK: - Custom Operators -//-------------------------------------------------- - -// e.g. (.State0 => .State1) => .State2 -public func => (left: StateTransition, right: S) -> StateTransitionChain -{ - return left.toTransitionChain() => right -} -public func => (var left: StateTransitionChain, right: S) -> StateTransitionChain -{ - left.append(right) - return left -} - -// e.g. .State0 => (.State1 => .State2) -public func => (left: S, right:StateTransition) -> StateTransitionChain -{ - return left => right.toTransitionChain() -} -public func => (left: S, var right: StateTransitionChain) -> StateTransitionChain -{ - right.prepend(left) - return right -} diff --git a/SwiftState/StateType.swift b/SwiftState/StateType.swift deleted file mode 100644 index 4c62823..0000000 --- a/SwiftState/StateType.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// StateType.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -public protocol StateType: Hashable, NilLiteralConvertible -{ - -} \ No newline at end of file diff --git a/SwiftState/String+SwiftState.swift b/SwiftState/String+SwiftState.swift deleted file mode 100644 index a152492..0000000 --- a/SwiftState/String+SwiftState.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// String+SwiftState.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/16. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -// for usage of String as StateType and/or StateEventType -extension String: StateType, StateEventType -{ - public init(nilLiteral: Void) - { - self = "" - } -} \ No newline at end of file diff --git a/SwiftStateTests/BasicTests.swift b/SwiftStateTests/BasicTests.swift deleted file mode 100644 index 8979851..0000000 --- a/SwiftStateTests/BasicTests.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// BasicTests.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/08. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class BasicTests: _TestCase -{ - func testREADME() - { - let machine = StateMachine(state: .State0) { machine in - - machine.addRoute(.State0 => .State1) - machine.addRoute(nil => .State2) { context in print("Any => 2, msg=\(context.userInfo)") } - machine.addRoute(.State2 => nil) { context in print("2 => Any, msg=\(context.userInfo)") } - - // add handler (handlerContext = (event, transition, order, userInfo)) - machine.addHandler(.State0 => .State1) { context in - print("0 => 1") - } - - // add errorHandler - machine.addErrorHandler { (event, transition, order, userInfo) in - print("[ERROR] \(transition.fromState) => \(transition.toState)") - } - } - - // tryState 0 => 1 => 2 => 1 => 0 - machine <- .State1 - machine <- (.State2, "Hello") - machine <- (.State1, "Bye") - machine <- .State0 // fail: no 1 => 0 - - print("machine.state = \(machine.state)") - } - - func testExample() - { - let machine = StateMachine(state: .State0) { - - // add 0 => 1 - $0.addRoute(.State0 => .State1) { context in - print("[Transition 0=>1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") - } - // add 0 => 1 once more - $0.addRoute(.State0 => .State1) { context in - print("[Transition 0=>1b] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") - } - // add 2 => Any - $0.addRoute(.State2 => nil) { context in - print("[Transition exit 2] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue) (Any)") - } - // add Any => 2 - $0.addRoute(nil => .State2) { context in - print("[Transition Entry 2] \(context.transition.fromState.rawValue) (Any) => \(context.transition.toState.rawValue)") - } - // add 1 => 0 (no handler) - $0.addRoute(.State1 => .State0) - - } - - // 0 => 1 - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) - - // 1 => 0 - XCTAssertTrue(machine.hasRoute(.State1 => .State0)) - - // 2 => Any - XCTAssertTrue(machine.hasRoute(.State2 => .State0)) - XCTAssertTrue(machine.hasRoute(.State2 => .State1)) - XCTAssertTrue(machine.hasRoute(.State2 => .State2)) - XCTAssertTrue(machine.hasRoute(.State2 => .State3)) - - // Any => 2 - XCTAssertTrue(machine.hasRoute(.State0 => .State2)) - XCTAssertTrue(machine.hasRoute(.State1 => .State2)) - XCTAssertTrue(machine.hasRoute(.State3 => .State2)) - - // others - XCTAssertFalse(machine.hasRoute(.State0 => .State0)) - XCTAssertFalse(machine.hasRoute(.State0 => .State3)) - XCTAssertFalse(machine.hasRoute(.State1 => .State1)) - XCTAssertFalse(machine.hasRoute(.State1 => .State3)) - XCTAssertFalse(machine.hasRoute(.State3 => .State0)) - XCTAssertFalse(machine.hasRoute(.State3 => .State1)) - XCTAssertFalse(machine.hasRoute(.State3 => .State3)) - - machine.configure { - - // error - $0.addErrorHandler { context in - print("[ERROR 1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") - } - - // entry - $0.addEntryHandler(.State0) { context in - print("[Entry 0] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") // NOTE: this should not be called - } - $0.addEntryHandler(.State1) { context in - print("[Entry 1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") - } - $0.addEntryHandler(.State2) { context in - print("[Entry 2] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") - } - $0.addEntryHandler(.State2) { context in - print("[Entry 2b] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") - } - - // exit - $0.addExitHandler(.State0) { context in - print("[Exit 0] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") - } - $0.addExitHandler(.State1) { context in - print("[Exit 1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") - } - $0.addExitHandler(.State2) { context in - print("[Exit 2] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") - } - $0.addExitHandler(.State2) { context in - print("[Exit 2b] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") - } - } - - // tryState 0 => 1 => 2 => 1 => 0 => 3 - XCTAssertTrue(machine <- .State1) - XCTAssertTrue(machine <- (.State2, "State2 activate")) - XCTAssertTrue(machine <- (.State1, "State2 deactivate")) - XCTAssertTrue(machine <- .State0) - XCTAssertFalse(machine <- .State3) - - XCTAssertEqual(machine.state, MyState.State0) - } -} \ No newline at end of file diff --git a/SwiftStateTests/BugFixTests.swift b/SwiftStateTests/BugFixTests.swift deleted file mode 100644 index 40544a8..0000000 --- a/SwiftStateTests/BugFixTests.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// BugFixTests.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2015/03/07. -// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class BugFixTests: _TestCase -{ - /// `hasRoute` test for "AnyEvent" & "SomeEvent" routes - func testHasRoute_anyEvent_someEvent() - { - let machine = StateMachine(state: .State0) - - machine.addRoute(.State0 => .State1); - machine.addRouteEvent(.Event0, transitions: [.State1 => .State2]) - - let hasRoute = machine.hasRoute(.State1 => .State2, forEvent: .Event0) - XCTAssertTrue(hasRoute) - } -} \ No newline at end of file diff --git a/SwiftStateTests/HierarchicalStateMachineTests.swift b/SwiftStateTests/HierarchicalStateMachineTests.swift deleted file mode 100644 index 3ff352e..0000000 --- a/SwiftStateTests/HierarchicalStateMachineTests.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// HierarchicalStateMachineTests.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/10/13. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class HierarchicalStateMachineTests: _TestCase -{ - var mainMachine: HSM? - var sub1Machine: HSM? - var sub2Machine: HSM? - - // - // set up hierarchical state machines as following: - // - // - mainMachine - // - sub1Machine - // - State1 (substate) - // - State2 (substate) - // - sub2Machine - // - State1 (substate) - // - State2 (substate) - // - MainState0 (state) - // - override func setUp() - { - super.setUp() - - let sub1Machine = HSM(name: "Sub1", state: "State1") - let sub2Machine = HSM(name: "Sub2", state: "State1") - - // [sub1] add 1-1 => 1-2 - sub1Machine.addRoute("State1" => "State2") - - // [sub2] add 2-1 => 2-2 - sub2Machine.addRoute("State1" => "State2") - - // create mainMachine with configuring submachines - // NOTE: accessing submachine's state will be of form: "\(submachine.name).\(substate)" - let mainMachine = HSM(name: "Main", submachines:[sub1Machine, sub2Machine], state: "Sub1.State1") - - // [main] add '1-2 => 2-1' & '2-2 => 0' & '0 => 1-1' (switching submachine) - // NOTE: MainState0 does not belong to any submachine's state - mainMachine.addRoute("Sub1.State2" => "Sub2.State1") - mainMachine.addRoute("Sub2.State2" => "MainState0") - mainMachine.addRoute("MainState0" => "Sub1.State1") - - // add logging handlers - sub1Machine.addHandler(nil => nil) { print("[Sub1] \($0.transition)") } - sub1Machine.addErrorHandler { print("[ERROR][Sub1] \($0.transition)") } - sub2Machine.addHandler(nil => nil) { print("[Sub2] \($0.transition)") } - sub2Machine.addErrorHandler { print("[ERROR][Sub2] \($0.transition)") } - mainMachine.addHandler(nil => nil) { print("[Main] \($0.transition)") } - mainMachine.addErrorHandler { print("[ERROR][Main] \($0.transition)") } - - self.mainMachine = mainMachine - self.sub1Machine = sub1Machine - self.sub2Machine = sub2Machine - } - - func testHasRoute_submachine_internal() - { - let mainMachine = self.mainMachine! - - // NOTE: mainMachine can check submachine's internal routes - - // sub1 internal routes - XCTAssertFalse(mainMachine.hasRoute("Sub1.State1" => "Sub1.State1")) - XCTAssertTrue(mainMachine.hasRoute("Sub1.State1" => "Sub1.State2")) // 1-1 => 1-2 - XCTAssertFalse(mainMachine.hasRoute("Sub1.State2" => "Sub1.State1")) - XCTAssertFalse(mainMachine.hasRoute("Sub1.State2" => "Sub1.State2")) - - // sub2 internal routes - XCTAssertFalse(mainMachine.hasRoute("Sub2.State1" => "Sub2.State1")) - XCTAssertTrue(mainMachine.hasRoute("Sub2.State1" => "Sub2.State2")) // 2-1 => 2-2 - XCTAssertFalse(mainMachine.hasRoute("Sub2.State2" => "Sub2.State1")) - XCTAssertFalse(mainMachine.hasRoute("Sub2.State2" => "Sub2.State2")) - } - - func testHasRoute_submachine_switching() - { - let mainMachine = self.mainMachine! - - // NOTE: mainMachine can check switchable submachines - // (external routes between submachines = Sub1, Sub2, or nil) - - XCTAssertFalse(mainMachine.hasRoute("Sub1.State1" => "Sub2.State1")) - XCTAssertFalse(mainMachine.hasRoute("Sub1.State1" => "Sub2.State2")) - XCTAssertFalse(mainMachine.hasRoute("Sub1.State1" => "MainState0")) - XCTAssertTrue(mainMachine.hasRoute("Sub1.State2" => "Sub2.State1")) // 1-2 => 2-1 - XCTAssertFalse(mainMachine.hasRoute("Sub1.State2" => "Sub2.State2")) - XCTAssertFalse(mainMachine.hasRoute("Sub1.State2" => "MainState0")) - - XCTAssertFalse(mainMachine.hasRoute("Sub2.State1" => "Sub1.State1")) - XCTAssertFalse(mainMachine.hasRoute("Sub2.State1" => "Sub1.State2")) - XCTAssertFalse(mainMachine.hasRoute("Sub2.State1" => "MainState0")) - XCTAssertFalse(mainMachine.hasRoute("Sub2.State2" => "Sub1.State1")) - XCTAssertFalse(mainMachine.hasRoute("Sub2.State2" => "Sub1.State2")) - XCTAssertTrue(mainMachine.hasRoute("Sub2.State2" => "MainState0")) // 2-2 => 0 - - XCTAssertTrue(mainMachine.hasRoute("MainState0" => "Sub1.State1")) // 0 => 1-1 - XCTAssertFalse(mainMachine.hasRoute("MainState0" => "Sub1.State2")) - XCTAssertFalse(mainMachine.hasRoute("MainState0" => "Sub2.State1")) - XCTAssertFalse(mainMachine.hasRoute("MainState0" => "Sub2.State2")) - } - - func testTryState() - { - let mainMachine = self.mainMachine! - let sub1Machine = self.sub1Machine! - - XCTAssertEqual(mainMachine.state, "Sub1.State1") - - // 1-1 => 1-2 (sub1 internal transition) - mainMachine <- "Sub1.State2" - XCTAssertEqual(mainMachine.state, "Sub1.State2") - - // dummy - mainMachine <- "DUMMY" - XCTAssertEqual(mainMachine.state, "Sub1.State2", "mainMachine.state should not be updated because there is no 1-2 => DUMMY route.") - - // 1-2 => 2-1 (sub1 => sub2 external transition) - mainMachine <- "Sub2.State1" - XCTAssertEqual(mainMachine.state, "Sub2.State1") - - // dummy - mainMachine <- "MainState0" - XCTAssertEqual(mainMachine.state, "Sub2.State1", "mainMachine.state should not be updated because there is no 2-1 => 0 route.") - - // 2-1 => 2-2 (sub1 internal transition) - mainMachine <- "Sub2.State2" - XCTAssertEqual(mainMachine.state, "Sub2.State2") - - // 2-2 => 0 (sub2 => main external transition) - mainMachine <- "MainState0" - XCTAssertEqual(mainMachine.state, "MainState0") - - // 0 => 1-1 (fail) - mainMachine <- "Sub1.State1" - XCTAssertEqual(mainMachine.state, "MainState0", "mainMachine.state should not be updated because current sub1Machine.state is State2, not State1.") - XCTAssertEqual(sub1Machine.state, "State2") - - // let's add resetting route for sub1Machine & reset to Sub1.State1 - sub1Machine.addRoute(nil => "State1") - sub1Machine <- "State1" - XCTAssertEqual(mainMachine.state, "MainState0") - XCTAssertEqual(sub1Machine.state, "State1") - - // 0 => 1-1 (retry, succeed) - mainMachine <- "Sub1.State1" - XCTAssertEqual(mainMachine.state, "Sub1.State1") - XCTAssertEqual(sub1Machine.state, "State1") - } - - func testAddHandler() - { - let mainMachine = self.mainMachine! - - var didPass = false - - // NOTE: this handler is added to mainMachine and doesn't make submachines dirty - mainMachine.addHandler("Sub1.State1" => "Sub1.State2") { context in - print("[Main] 1-1 => 1-2 (specific)") - didPass = true - } - - XCTAssertEqual(mainMachine.state, "Sub1.State1") - XCTAssertFalse(didPass) - - mainMachine <- "Sub1.State2" - XCTAssertEqual(mainMachine.state, "Sub1.State2") - XCTAssertTrue(didPass) - } -} diff --git a/SwiftStateTests/MyEvent.swift b/SwiftStateTests/MyEvent.swift deleted file mode 100644 index 9dcd7ae..0000000 --- a/SwiftStateTests/MyEvent.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// MyEvent.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState - -enum MyEvent: StateEventType -{ - case Event0, Event1 - case AnyEvent // IMPORTANT: create case=Any & use it in convertFromNilLiteral() - - init(nilLiteral: Void) - { - self = AnyEvent - } -} \ No newline at end of file diff --git a/SwiftStateTests/MyState.swift b/SwiftStateTests/MyState.swift deleted file mode 100644 index 92139ce..0000000 --- a/SwiftStateTests/MyState.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// MyState.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState - -enum MyState: Int, StateType, CustomStringConvertible -{ - case State0, State1, State2, State3 - case AnyState // IMPORTANT: create case=Any & use it in convertFromNilLiteral() - - // - // NOTE: enum + associated value is our future, but it won't conform to Equatable so easily - // http://stackoverflow.com/questions/24339807/how-to-test-equality-of-swift-enums-with-associated-values - // - //case MyState(Int) - - init(nilLiteral: Void) - { - self = AnyState - } - - var description: String - { - return "\(self.rawValue)" - } -} \ No newline at end of file diff --git a/SwiftStateTests/RasmusTest.swift b/SwiftStateTests/RasmusTest.swift deleted file mode 100644 index 2eb3355..0000000 --- a/SwiftStateTests/RasmusTest.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// RasmusTest.swift -// SwiftState -// -// Created by Rasmus Taulborg Hummelmose on 20/08/15. -// Copyright © 2015 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -enum RasmusTestState: StateType { - case Any - case State1, State2, State3, State4 - init(nilLiteral: Void) { - self = Any - } -} - -enum RasmusTestEvent: StateEventType { - case Any - case State2FromState1 - case State4FromState2OrState3 - init(nilLiteral: Void) { - self = Any - } -} - -class RasmusTest: _TestCase { - func testStateRoute() { - let stateMachine = StateMachine(state: .State1) - stateMachine.addRouteEvent(.State2FromState1, transitions: [ .State1 => .State2 ]) - stateMachine.addRouteEvent(.State4FromState2OrState3, routes: [ [ .State2, .State3 ] => .State4 ]) - XCTAssertEqual(stateMachine.state, RasmusTestState.State1) - stateMachine <-! .State2FromState1 - XCTAssertEqual(stateMachine.state, RasmusTestState.State2) - stateMachine <-! .State4FromState2OrState3 - XCTAssertEqual(stateMachine.state, RasmusTestState.State4) - } -} diff --git a/SwiftStateTests/StateMachineEventTests.swift b/SwiftStateTests/StateMachineEventTests.swift deleted file mode 100644 index ee42479..0000000 --- a/SwiftStateTests/StateMachineEventTests.swift +++ /dev/null @@ -1,304 +0,0 @@ -// -// StateMachineEventTests.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/05. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class StateMachineEventTests: _TestCase -{ - func testCanTryEvent() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 & 1 => 2 - // (NOTE: this is not chaining e.g. 0 => 1 => 2) - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - XCTAssertTrue(machine.canTryEvent(.Event0) != nil) - XCTAssertTrue(machine.canTryEvent(.AnyEvent) != nil) - } - - func testTryState() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 & 1 => 2 - // (NOTE: this is not chaining e.g. 0 => 1 => 2) - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - // tryState 0 => 1 - machine <- .State1 - XCTAssertEqual(machine.state, MyState.State1) - - // tryState 1 => 2 - machine <- .State2 - XCTAssertEqual(machine.state, MyState.State2) - - // tryState 2 => 3 - let success = machine <- .State3 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "2 => 3 is not registered.") - } - - func testTryEvent() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State1) - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - - // tryEvent - let success = machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event0 doesn't have 2 => Any") - } - - /// https://github.com/ReactKit/SwiftState/issues/20 - func testTryEvent_issue20() - { - let machine = StateMachine(state: MyState.State2) { machine in - machine.addRouteEvent(.Event0, transitions: [.AnyState => .State0]) - } - - XCTAssertTrue(machine <-! .Event0) - XCTAssertEqual(machine.state, MyState.State0) - } - - /// https://github.com/ReactKit/SwiftState/issues/28 - func testTryEvent_issue28() - { - var eventCount = 0 - let machine = StateMachine(state: .State0) { machine in - machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Event0, transitions: [nil => nil]) { _ in - eventCount++ - } - } - - XCTAssertEqual(eventCount, 0) - - machine <-! .Event0 - XCTAssertEqual(eventCount, 1) - XCTAssertEqual(machine.state, MyState.State0, "State should NOT be changed") - - machine <- .State1 - XCTAssertEqual(machine.state, MyState.State1, "State should be changed") - - machine <-! .Event0 - XCTAssertEqual(eventCount, 2) - XCTAssertEqual(machine.state, MyState.State1, "State should NOT be changed") - } - - func testTryEvent_string() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 => 2 - machine.addRouteEvent("Run", transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - // tryEvent - machine <-! "Run" - XCTAssertEqual(machine.state, MyState.State1) - - // tryEvent - machine <-! "Run" - XCTAssertEqual(machine.state, MyState.State2) - - // tryEvent - let success = machine <-! "Run" - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event=Run doesn't have 2 => Any") - } - - func testAddRouteEvent_multiple() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - // add 2 => 1 => 0 - machine.addRouteEvent(.Event1, transitions: [ - .State2 => .State1, - .State1 => .State0, - ]) - - var success: Bool - - // tryEvent - success = machine <-! .Event1 - XCTAssertEqual(machine.state, MyState.State0) - XCTAssertFalse(success, "Event1 doesn't have 0 => Any.") - - // tryEvent - success = machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State1) - XCTAssertTrue(success) - - // tryEvent - success = machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertTrue(success) - - // tryEvent - success = machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event0 doesn't have 2 => Any.") - - // tryEvent - success = machine <-! .Event1 - XCTAssertEqual(machine.state, MyState.State1) - XCTAssertTrue(success) - - // tryEvent - success = machine <-! .Event1 - XCTAssertEqual(machine.state, MyState.State0) - XCTAssertTrue(success) - } - - func testAddRouteEvent_handler() - { - let machine = StateMachine(state: .State0) - - var invokeCount = 0 - - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ], handler: { context in - invokeCount++ - return - }) - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State1) - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - - XCTAssertEqual(invokeCount, 2) - } - - func testAddEventHandler() - { - let machine = StateMachine(state: .State0) - - var invokeCount = 0 - - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - machine.addEventHandler(.Event0) { context in - invokeCount++ - return - } - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State1) - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - - XCTAssertEqual(invokeCount, 2) - } - - func testRemoveRouteEvent() - { - let machine = StateMachine(state: .State0) - - var invokeCount = 0 - - // add 0 => 1 => 2 - let routeIDs = machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - machine.addEventHandler(.Event0) { context in - invokeCount++ - return - } - - // removeRoute - for routeID in routeIDs { - machine.removeRoute(routeID) - } - - // tryEvent - var success = machine <-! .Event0 - XCTAssertFalse(success, "RouteEvent should be removed.") - - // tryEvent - success = machine <-! .Event0 - XCTAssertFalse(success, "RouteEvent should be removed.") - - XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") - } - - func testRemoveEventHandler() - { - let machine = StateMachine(state: .State0) - - var invokeCount = 0 - - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) - - let handlerID = machine.addEventHandler(.Event0) { context in - invokeCount++ - return - } - - // removeHandler - machine.removeHandler(handlerID) - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State1, "0 => 1 should be succesful") - - // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2, "1 => 2 should be succesful") - - XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") - } -} \ No newline at end of file diff --git a/SwiftStateTests/StateMachineTests.swift b/SwiftStateTests/StateMachineTests.swift deleted file mode 100644 index 7ecf416..0000000 --- a/SwiftStateTests/StateMachineTests.swift +++ /dev/null @@ -1,534 +0,0 @@ -// -// StateMachineTests.swift -// StateMachineTests -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class StateMachineTests: _TestCase -{ - func testInit() - { - let machine = StateMachine(state: .State0) - - XCTAssertEqual(machine.state, MyState.State0) - } - - //-------------------------------------------------- - // MARK: - addRoute - //-------------------------------------------------- - - // add state1 => state2 - func testAddRoute() - { - let machine = StateMachine(state: .State0) - - machine.addRoute(.State0 => .State1) - - XCTAssertFalse(machine.hasRoute(.State0 => .State0)) - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true - XCTAssertFalse(machine.hasRoute(.State0 => .State2)) - XCTAssertFalse(machine.hasRoute(.State1 => .State0)) - XCTAssertFalse(machine.hasRoute(.State1 => .State1)) - XCTAssertFalse(machine.hasRoute(.State1 => .State2)) - } - - // add nil => state - func testAddRoute_fromAnyState() - { - let machine = StateMachine(state: .State0) - - machine.addRoute(nil => .State1) // Any => State1 - - XCTAssertFalse(machine.hasRoute(.State0 => .State0)) - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true - XCTAssertFalse(machine.hasRoute(.State0 => .State2)) - XCTAssertFalse(machine.hasRoute(.State1 => .State0)) - XCTAssertTrue(machine.hasRoute(.State1 => .State1)) // true - XCTAssertFalse(machine.hasRoute(.State1 => .State2)) - } - - // add state => nil - func testAddRoute_toAnyState() - { - let machine = StateMachine(state: .State0) - - machine.addRoute(.State1 => nil) // State1 => Any - - XCTAssertFalse(machine.hasRoute(.State0 => .State0)) - XCTAssertFalse(machine.hasRoute(.State0 => .State1)) - XCTAssertFalse(machine.hasRoute(.State0 => .State2)) - XCTAssertTrue(machine.hasRoute(.State1 => .State0)) // true - XCTAssertTrue(machine.hasRoute(.State1 => .State1)) // true - XCTAssertTrue(machine.hasRoute(.State1 => .State2)) // true - } - - // add nil => nil - func testAddRoute_bothAnyState() - { - let machine = StateMachine(state: .State0) - - machine.addRoute(nil => nil) // Any => Any - - XCTAssertTrue(machine.hasRoute(.State0 => .State0)) // true - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true - XCTAssertTrue(machine.hasRoute(.State0 => .State2)) // true - XCTAssertTrue(machine.hasRoute(.State1 => .State0)) // true - XCTAssertTrue(machine.hasRoute(.State1 => .State1)) // true - XCTAssertTrue(machine.hasRoute(.State1 => .State2)) // true - } - - // add state0 => state0 - func testAddRoute_sameState() - { - let machine = StateMachine(state: .State0) - - machine.addRoute(.State0 => .State0) - - XCTAssertTrue(machine.hasRoute(.State0 => .State0)) - - // tryState 0 => 0 - XCTAssertTrue(machine <- .State0) - } - - // add route + condition - func testAddRoute_condition() - { - let machine = StateMachine(state: .State0) - - var flag = false - - // add 0 => 1 - machine.addRoute(.State0 => .State1, condition: flag) - - XCTAssertFalse(machine.hasRoute(.State0 => .State1)) - - flag = true - - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) - } - - // add route + condition + blacklist - func testAddRoute_condition_blacklist() - { - let machine = StateMachine(state: .State0) - - // add 0 => Any, except 0 => 2 - machine.addRoute(.State0 => nil, condition: { transition in - return transition.toState != .State2 - }) - - XCTAssertTrue(machine.hasRoute(.State0 => .State0)) - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) - XCTAssertFalse(machine.hasRoute(.State0 => .State2)) - XCTAssertTrue(machine.hasRoute(.State0 => .State3)) - } - - // add route + handler - func testAddRoute_handler() - { - let machine = StateMachine(state: .State0) - - var returnedTransition: StateTransition? - - machine.addRoute(.State0 => .State1) { context in - returnedTransition = context.transition - } - - XCTAssertTrue(returnedTransition == nil, "Transition has not started yet.") - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) - } - - // add route + conditional handler - func testAddRoute_conditionalHandler() - { - let machine = StateMachine(state: .State0) - - var flag = false - var returnedTransition: StateTransition? - - // add 0 => 1 without condition to guarantee 0 => 1 transition - machine.addRoute(.State0 => .State1) - - // add 0 => 1 with condition + conditionalHandler - machine.addRoute(.State0 => .State1, condition: flag) { context in - returnedTransition = context.transition - } - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertEqual(machine.state, MyState.State1 , "0 => 1 transition should be performed.") - XCTAssertTrue(returnedTransition == nil, "Conditional handler should NOT be performed because flag=false.") - - // add 1 => 0 for resetting state - machine.addRoute(.State1 => .State0) - - // tryState 1 => 0 - machine <- .State0 - - flag = true - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) - } - - // MARK: addRoute using array - - func testAddRoute_array_left() - { - let machine = StateMachine(state: .State0) - - // add 0 => 2 or 1 => 2 - machine.addRoute([.State0, .State1] => .State2) - - XCTAssertFalse(machine.hasRoute(.State0 => .State0)) - XCTAssertFalse(machine.hasRoute(.State0 => .State1)) - XCTAssertTrue(machine.hasRoute(.State0 => .State2)) - XCTAssertFalse(machine.hasRoute(.State1 => .State0)) - XCTAssertFalse(machine.hasRoute(.State1 => .State1)) - XCTAssertTrue(machine.hasRoute(.State1 => .State2)) - } - - func testAddRoute_array_right() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 or 0 => 2 - machine.addRoute(.State0 => [.State1, .State2]) - - XCTAssertFalse(machine.hasRoute(.State0 => .State0)) - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) - XCTAssertTrue(machine.hasRoute(.State0 => .State2)) - XCTAssertFalse(machine.hasRoute(.State1 => .State0)) - XCTAssertFalse(machine.hasRoute(.State1 => .State1)) - XCTAssertFalse(machine.hasRoute(.State1 => .State2)) - } - - func testAddRoute_array_both() - { - let machine = StateMachine(state: .State0) - - // add 0 => 2 or 0 => 3 or 1 => 2 or 1 => 3 - machine.addRoute([MyState.State0, MyState.State1] => [MyState.State2, MyState.State3]) - - XCTAssertFalse(machine.hasRoute(.State0 => .State0)) - XCTAssertFalse(machine.hasRoute(.State0 => .State1)) - XCTAssertTrue(machine.hasRoute(.State0 => .State2)) - XCTAssertTrue(machine.hasRoute(.State0 => .State3)) - XCTAssertFalse(machine.hasRoute(.State1 => .State0)) - XCTAssertFalse(machine.hasRoute(.State1 => .State1)) - XCTAssertTrue(machine.hasRoute(.State1 => .State2)) - XCTAssertTrue(machine.hasRoute(.State1 => .State3)) - XCTAssertFalse(machine.hasRoute(.State2 => .State0)) - XCTAssertFalse(machine.hasRoute(.State2 => .State1)) - XCTAssertFalse(machine.hasRoute(.State2 => .State2)) - XCTAssertFalse(machine.hasRoute(.State2 => .State3)) - XCTAssertFalse(machine.hasRoute(.State3 => .State0)) - XCTAssertFalse(machine.hasRoute(.State3 => .State1)) - XCTAssertFalse(machine.hasRoute(.State3 => .State2)) - XCTAssertFalse(machine.hasRoute(.State3 => .State3)) - } - - //-------------------------------------------------- - // MARK: - removeRoute - //-------------------------------------------------- - - func testRemoveRoute() - { - let machine = StateMachine(state: .State0) - - let routeID = machine.addRoute(.State0 => .State1) - - XCTAssertTrue(machine.hasRoute(.State0 => .State1)) - - var success: Bool - success = machine.removeRoute(routeID) - - XCTAssertTrue(success) - XCTAssertFalse(machine.hasRoute(.State0 => .State1)) - - // fails removing already unregistered route - success = machine.removeRoute(routeID) - - XCTAssertFalse(success) - } - - //-------------------------------------------------- - // MARK: - tryState a.k.a <- - //-------------------------------------------------- - - // machine <- state - func testTryState() - { - let machine = StateMachine(state: .State0) - - // tryState 0 => 1, without registering any transitions - machine <- .State1 - - XCTAssertEqual(machine.state, MyState.State0, "0 => 1 should fail because transition is not added yet.") - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - // tryState 0 => 1, returning flag - let success = machine <- .State1 - - XCTAssertTrue(success) - XCTAssertEqual(machine.state, MyState.State1) - } - - func testTryState_string() - { - let machine = StateMachine(state: "0") - - // tryState 0 => 1, without registering any transitions - machine <- "1" - - XCTAssertEqual(machine.state, "0", "0 => 1 should fail because transition is not added yet.") - - // add 0 => 1 - machine.addRoute("0" => "1") - - // tryState 0 => 1, returning flag - let success = machine <- "1" - - XCTAssertTrue(success) - XCTAssertEqual(machine.state, "1") - } - - //-------------------------------------------------- - // MARK: - addHandler - //-------------------------------------------------- - - func testAddHandler() - { - let machine = StateMachine(state: .State0) - - var returnedTransition: StateTransition? - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - machine.addHandler(.State0 => .State1) { context in -// returnedTransition = context.transition - } - - machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition - } - - // not tried yet - XCTAssertTrue(returnedTransition == nil, "Transition has not started yet.") - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) - } - - func testAddHandler_order() - { - let machine = StateMachine(state: .State0) - - var returnedTransition: StateTransition? - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - // order = 100 (default) - machine.addHandler(.State0 => .State1) { context in -// XCTAssertEqual(context.order, 100) // TODO: Xcode6.1-GM bug - XCTAssertTrue(context.order == 100) - XCTAssertTrue(returnedTransition != nil, "returnedTransition should already be set.") - - returnedTransition = context.transition - } - - // order = 99 - machine.addHandler(.State0 => .State1, order: 99) { context in -// XCTAssertEqual(context.order, 99) // TODO: Xcode6.1-GM bug - XCTAssertTrue(context.order == 99) - XCTAssertTrue(returnedTransition == nil, "returnedTransition should NOT be set at this point.") - - returnedTransition = context.transition // set returnedTransition for first time - } - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) - } - - - func testAddHandler_multiple() - { - let machine = StateMachine(state: .State0) - - var returnedTransition: StateTransition? - var returnedTransition2: StateTransition? - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition - } - - // add 0 => 1 once more - machine.addRoute(.State0 => .State1) - - machine.addHandler(.State0 => .State1) { context in - returnedTransition2 = context.transition - } - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertTrue(returnedTransition != nil) - XCTAssertTrue(returnedTransition2 != nil) - } - - func testAddHandler_overload() - { - let machine = StateMachine(state: .State0) - - var returnedTransition: StateTransition? - - machine.addRoute(.State0 => .State1) - - machine.addHandler(.State0 => .State1) { context in - // empty - } - - machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition - } - - XCTAssertTrue(returnedTransition == nil) - - machine <- .State1 - - XCTAssertTrue(returnedTransition != nil) - } - - //-------------------------------------------------- - // MARK: - removeHandler - //-------------------------------------------------- - - func testRemoveHandler() - { - let machine = StateMachine(state: .State0) - - var returnedTransition: StateTransition? - var returnedTransition2: StateTransition? - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - let handlerID = machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition - XCTFail("Should never reach here") - } - - // add 0 => 1 once more - machine.addRoute(.State0 => .State1) - - machine.addHandler(.State0 => .State1) { context in - returnedTransition2 = context.transition - } - - machine.removeHandler(handlerID) - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertTrue(returnedTransition == nil, "Handler should be removed and never performed.") - XCTAssertTrue(returnedTransition2 != nil) - } - - func testRemoveHandler_unregistered() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - let handlerID = machine.addHandler(.State0 => .State1) { context in - // empty - } - - // remove handler - XCTAssertTrue(machine.removeHandler(handlerID)) - - // remove already unregistered handler - XCTAssertFalse(machine.removeHandler(handlerID), "removeHandler should fail because handler is already removed.") - } - - func testRemoveErrorHandler() - { - let machine = StateMachine(state: .State0) - - var returnedTransition: StateTransition? - var returnedTransition2: StateTransition? - - // add 2 => 1 - machine.addRoute(.State2 => .State1) - - let handlerID = machine.addErrorHandler { context in - returnedTransition = context.transition - XCTFail("Should never reach here") - } - - // add 2 => 1 once more - machine.addRoute(.State2 => .State1) - - machine.addErrorHandler { context in - returnedTransition2 = context.transition - } - - machine.removeHandler(handlerID) - - // tryState 0 => 1 - machine <- .State1 - - XCTAssertTrue(returnedTransition == nil, "Handler should be removed and never performed.") - XCTAssertTrue(returnedTransition2 != nil) - } - - func testRemoveErrorHandler_unregistered() - { - let machine = StateMachine(state: .State0) - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - let handlerID = machine.addErrorHandler { context in - // empty - } - - // remove handler - XCTAssertTrue(machine.removeHandler(handlerID)) - - // remove already unregistered handler - XCTAssertFalse(machine.removeHandler(handlerID), "removeHandler should fail because handler is already removed.") - } -} diff --git a/SwiftStateTests/StateRouteTests.swift b/SwiftStateTests/StateRouteTests.swift deleted file mode 100644 index 3dfa2bc..0000000 --- a/SwiftStateTests/StateRouteTests.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// StateRouteTests.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/04. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class StateRouteTests: _TestCase -{ - func testInit() - { - let route = StateRoute(transition: .State0 => .State1, condition: nil) - XCTAssertEqual(route.transition.fromState, MyState.State0) - XCTAssertEqual(route.transition.toState, MyState.State1) - XCTAssertTrue(route.condition == nil) - - let route2 = StateRoute(transition: .State1 => .State2, condition: false) - XCTAssertEqual(route2.transition.fromState, MyState.State1) - XCTAssertEqual(route2.transition.toState, MyState.State2) - XCTAssertTrue(route2.condition != nil) - - let route3 = StateRoute(transition: .State2 => .State3, condition: { transition in false }) - XCTAssertEqual(route3.transition.fromState, MyState.State2) - XCTAssertEqual(route3.transition.toState, MyState.State3) - XCTAssertTrue(route3.condition != nil) - } -} \ No newline at end of file diff --git a/SwiftStateTests/StateTransitionChainTests.swift b/SwiftStateTests/StateTransitionChainTests.swift deleted file mode 100644 index cc8e812..0000000 --- a/SwiftStateTests/StateTransitionChainTests.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// StateTransitionChainTests.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/04. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class StateTransitionChainTests: _TestCase -{ - func testInit() - { - // 0 => 1 => 2 - var chain = MyState.State0 => .State1 => .State2 - - XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State2) - - // (1 => 2) => 3 - chain = MyState.State1 => .State2 => .State3 - - XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State1) - XCTAssertEqual(chain.lastState, MyState.State3) - - // 2 => (3 => 0) - chain = MyState.State2 => (.State3 => .State0) - - XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State2) - XCTAssertEqual(chain.lastState, MyState.State0) - } - - func testAppend() - { - // 0 => 1 - let transition = MyState.State0 => .State1 - var chain = transition.toTransitionChain() - - XCTAssertEqual(chain.numberOfTransitions, 1) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State1) - - // 0 => 1 => 2 - chain = chain => .State2 // same as append - XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State2) - - // 0 => 1 => 2 => 3 - chain.append(.State3) - XCTAssertEqual(chain.numberOfTransitions, 3) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State3) - } - - func testPrepend() - { - // 0 => 1 - let transition = MyState.State0 => .State1 - var chain = transition.toTransitionChain() - - XCTAssertEqual(chain.numberOfTransitions, 1) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State1) - - // 2 => 0 => 1 - chain = .State2 => chain // same as prepend - XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State2) - XCTAssertEqual(chain.lastState, MyState.State1) - - // 3 => 2 => 0 => 1 - chain.prepend(.State3) - XCTAssertEqual(chain.numberOfTransitions, 3) - XCTAssertEqual(chain.firstState, MyState.State3) - XCTAssertEqual(chain.lastState, MyState.State1) - } -} \ No newline at end of file diff --git a/SwiftStateTests/StateTransitionTests.swift b/SwiftStateTests/StateTransitionTests.swift deleted file mode 100644 index 705a67f..0000000 --- a/SwiftStateTests/StateTransitionTests.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// StateTransitionTests.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import SwiftState -import XCTest - -class StateTransitionTests: _TestCase -{ - func testInit() - { - let transition = StateTransition(fromState: .State0, toState: .State1) - XCTAssertEqual(transition.fromState, MyState.State0) - XCTAssertEqual(transition.toState, MyState.State1) - - // shorthand - let transition2 = MyState.State1 => .State0 - XCTAssertEqual(transition2.fromState, MyState.State1) - XCTAssertEqual(transition2.toState, MyState.State0) - } - - func testNil() - { - // nil => state - let transition = nil => MyState.State0 - XCTAssertEqual(transition.fromState, nil as MyState) - XCTAssertEqual(transition.toState, MyState.State0) - - // state => nil - let transition2 = MyState.State0 => nil - XCTAssertEqual(transition2.fromState, MyState.State0) - XCTAssertEqual(transition2.toState, nil as MyState) - - // nil => nil - let transition3: StateTransition = nil => nil - XCTAssertEqual(transition3.fromState, nil as MyState) - XCTAssertEqual(transition3.toState, nil as MyState) - } -} \ No newline at end of file diff --git a/Tests/BasicTests.swift b/Tests/BasicTests.swift new file mode 100644 index 0000000..1363a71 --- /dev/null +++ b/Tests/BasicTests.swift @@ -0,0 +1,128 @@ +// +// BasicTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/08. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class BasicTests: _TestCase +{ + func testREADME() + { + // setup state machine + let machine = StateMachine(state: .State0) { machine in + + machine.addRoute(.State0 => .State1) + machine.addRoute(.Any => .State2) { context in print("Any => 2, msg=\(context.userInfo)") } + machine.addRoute(.State2 => .Any) { context in print("2 => Any, msg=\(context.userInfo)") } + + // add handler (`context = (event, fromState, toState, userInfo)`) + machine.addHandler(.State0 => .State1) { context in + print("0 => 1") + } + + // add errorHandler + machine.addErrorHandler { event, fromState, toState, userInfo in + print("[ERROR] \(fromState) => \(toState)") + } + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // tryState 0 => 1 => 2 => 1 => 0 + + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + + machine <- (.State2, "Hello") + XCTAssertEqual(machine.state, MyState.State2) + + machine <- (.State1, "Bye") + XCTAssertEqual(machine.state, MyState.State1) + + machine <- .State0 // fail: no 1 => 0 + XCTAssertEqual(machine.state, MyState.State1) + } + + func testREADME_tryEvent() + { + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent (fails) + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") + } + + func testREADME_routeMapping() + { + let machine = Machine(state: .Str("initial")) { machine in + + 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")) + } +} \ No newline at end of file diff --git a/Tests/EventTests.swift b/Tests/EventTests.swift new file mode 100644 index 0000000..9571932 --- /dev/null +++ b/Tests/EventTests.swift @@ -0,0 +1,39 @@ +// +// EventTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-08. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class EventTests: _TestCase +{ + func testInit_event() + { + let event = Event(rawValue: .Event0) + XCTAssertTrue(event == .Event0) + XCTAssertTrue(.Event0 == event) + } + + func testInit_nil() + { + let event = Event(rawValue: nil) + XCTAssertTrue(event == .Any) + XCTAssertTrue(.Any == event) + } + + func testRawValue_event() + { + let event = Event.Some(.Event0) + XCTAssertTrue(event.rawValue == .Event0) + } + + func testRawValue_any() + { + let event = Event.Any + XCTAssertTrue(event.rawValue == nil) + } +} \ No newline at end of file diff --git a/Tests/HierarchicalMachineTests.swift b/Tests/HierarchicalMachineTests.swift new file mode 100644 index 0000000..103bd7d --- /dev/null +++ b/Tests/HierarchicalMachineTests.swift @@ -0,0 +1,285 @@ +// +// HierarchicalMachineTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-29. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +private enum _MainState: StateType +{ + case MainState0 + case SubMachine1(_SubState) + case SubMachine2(_SubState) + + var hashValue: Int + { + switch self { + case .MainState0: + return "MainState0".hashValue + case let .SubMachine1(state): + return "SubMachine1-\(state)".hashValue + case let .SubMachine2(state): + return "SubMachine2-\(state)".hashValue + } + } +} + +private enum _SubState: StateType +{ + case SubState0, SubState1, SubState2 +} + +private func ==(lhs: _MainState, rhs: _MainState) -> Bool +{ + switch (lhs, rhs) { + case (.MainState0, .MainState0): + return true + case let (.SubMachine1(state1), .SubMachine1(state2)): + return state1 == state2 + case let (.SubMachine2(state1), .SubMachine2(state2)): + return state1 == state2 + default: + return false + } +} + +class HierarchicalMachineTests: _TestCase +{ + /// + /// Hierarchical state machine. + /// + /// - mainMachine + /// - MainState0 (initial) + /// - subMachine1 + /// - SubState0 + /// - SubState1 + /// - subMachine2 + /// - SubState0 + /// - SubState1 + /// + /// - Warning: + /// This is a naive implementation and easily lose consistency when `subMachine.state` is changed directly, e.g. `subMachine1 <- .SubState1`. + /// + private var mainMachine: StateMachine<_MainState, NoEvent>? + + private var subMachine1: StateMachine<_SubState, NoEvent>? + private var subMachine2: StateMachine<_SubState, NoEvent>? + + override func setUp() + { + super.setUp() + + let subMachine1 = StateMachine<_SubState, NoEvent>(state: .SubState0) { subMachine1 in + // add Sub1-0 => Sub1-1 + subMachine1.addRoute(.SubState0 => .SubState1) + + subMachine1.addHandler(.Any => .Any) { print("[Sub1] \($0.fromState) => \($0.toState)") } + subMachine1.addErrorHandler { print("[ERROR][Sub1] \($0.fromState) => \($0.toState)") } + } + + let subMachine2 = StateMachine<_SubState, NoEvent>(state: .SubState0) { subMachine2 in + // add Sub2-0 => Sub2-1 + subMachine2.addRoute(.SubState0 => .SubState1) + + subMachine2.addHandler(.Any => .Any) { print("[Sub2] \($0.fromState) => \($0.toState)") } + subMachine2.addErrorHandler { print("[ERROR][Sub2] \($0.fromState) => \($0.toState)") } + } + + let mainMachine = StateMachine<_MainState, NoEvent>(state: .MainState0) { mainMachine in + + // add routes & handle for same-subMachine internal transitions + mainMachine.addRoute(.Any => .Any, condition: { _, fromState, toState, userInfo in + + switch (fromState, toState) { + case let (.SubMachine1(state1), .SubMachine1(state2)): + return subMachine1.hasRoute(fromState: state1, toState: state2) + case let (.SubMachine2(state1), .SubMachine2(state2)): + return subMachine2.hasRoute(fromState: state1, toState: state2) + default: + return false + } + + }, handler: { _, fromState, toState, userInfo in + switch (fromState, toState) { + case let (.SubMachine1, .SubMachine1(state2)): + subMachine1 <- state2 + case let (.SubMachine2, .SubMachine2(state2)): + subMachine2 <- state2 + default: + break + } + }) + + // add routes for mainMachine-state transitions (submachine switching) + mainMachine.addRouteMapping { event, fromState, userInfo -> _MainState? in + + // NOTE: use external submachine's states only for evaluating `toState`, but not for `fromState` + switch fromState { + // "Main0" => "Sub1-0" + case .MainState0 where subMachine1.state == .SubState0: + return .SubMachine1(.SubState0) + + // "Sub1-1" => "Sub2-0" + case let .SubMachine1(state) where state == .SubState1 && subMachine2.state == .SubState0: + return .SubMachine2(.SubState0) + + // "Sub2-1" => "Main0" + case let .SubMachine2(state) where state == .SubState1: + return .MainState0 + + default: + return nil + } + + } + + mainMachine.addHandler(.Any => .Any) { print("[Main] \($0.fromState) => \($0.toState)") } + mainMachine.addErrorHandler { print("[ERROR][Main] \($0.fromState) => \($0.toState)") } + } + + self.mainMachine = mainMachine + self.subMachine1 = subMachine1 + self.subMachine2 = subMachine2 + } + + /// `mainMachine.hasRoute()` test to check submachine's internal routes + func testHasRoute_submachine_internal() + { + let mainMachine = self.mainMachine! + let subMachine1 = self.subMachine1! + let subMachine2 = self.subMachine2! + + // initial + XCTAssertTrue(mainMachine.state == .MainState0) + XCTAssertTrue(subMachine1.state == .SubState0) + XCTAssertTrue(subMachine2.state == .SubState0) + + // subMachine1 internal routes + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState0) => .SubMachine1(.SubState0))) + XCTAssertTrue(mainMachine.hasRoute(.SubMachine1(.SubState0) => .SubMachine1(.SubState1))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState1) => .SubMachine1(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState1) => .SubMachine1(.SubState1))) + + // subMachine2 internal routes + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState0) => .SubMachine2(.SubState0))) + XCTAssertTrue(mainMachine.hasRoute(.SubMachine2(.SubState0) => .SubMachine2(.SubState1))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState1) => .SubMachine2(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState1) => .SubMachine2(.SubState1))) + } + + /// `mainMachine.hasRoute()` test to check switchable submachines + func testHasRoute_submachine_switching() + { + let mainMachine = self.mainMachine! + let subMachine1 = self.subMachine1! + let subMachine2 = self.subMachine2! + + // NOTE: mainMachine can check switchable submachines + // (external routes between submachines = SubState1, SubState2, or nil) + + // initial + XCTAssertTrue(mainMachine.state == .MainState0) + XCTAssertTrue(subMachine1.state == .SubState0) + XCTAssertTrue(subMachine2.state == .SubState0) + + // from Main0 + XCTAssertTrue(mainMachine.hasRoute(.MainState0 => .SubMachine1(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.MainState0 => .SubMachine1(.SubState1))) + XCTAssertFalse(mainMachine.hasRoute(.MainState0 => .SubMachine2(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.MainState0 => .SubMachine2(.SubState1))) + + // from Sub1-0 + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState0) => .SubMachine2(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState0) => .SubMachine2(.SubState1))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState0) => .MainState0)) + + // from Sub1-1 + XCTAssertTrue(mainMachine.hasRoute(.SubMachine1(.SubState1) => .SubMachine2(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState1) => .SubMachine2(.SubState1))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine1(.SubState1) => .MainState0)) + + // from Sub2-0 + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState0) => .SubMachine1(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState0) => .SubMachine1(.SubState1))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState0) => .MainState0)) + + // from Sub2-1 + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState1) => .SubMachine1(.SubState0))) + XCTAssertFalse(mainMachine.hasRoute(.SubMachine2(.SubState1) => .SubMachine1(.SubState1))) + XCTAssertTrue(mainMachine.hasRoute(.SubMachine2(.SubState1) => .MainState0)) + + } + + func testTryState() + { + let mainMachine = self.mainMachine! + + // initial + XCTAssertTrue(mainMachine.state == .MainState0) + + // Main0 => Sub1-0 + mainMachine <- .SubMachine1(.SubState0) + XCTAssertTrue(mainMachine.state == .SubMachine1(.SubState0)) + + // Sub1-0 => Sub1-1 (Sub1 internal transition) + mainMachine <- .SubMachine1(.SubState1) + XCTAssertTrue(mainMachine.state == .SubMachine1(.SubState1)) + + // Sub1-1 => Sub1-2 (Sub1 internal transition, but fails) + mainMachine <- .SubMachine1(.SubState2) + XCTAssertTrue(mainMachine.state == .SubMachine1(.SubState1), "No change.") + + // Sub1-1 => Sub2-2 (fails) + mainMachine <- .SubMachine2(.SubState2) + XCTAssertTrue(mainMachine.state == .SubMachine1(.SubState1), "No change.") + + // Sub1-1 => Sub2-0 + mainMachine <- .SubMachine2(.SubState0) + XCTAssertTrue(mainMachine.state == .SubMachine2(.SubState0)) + + // Sub2-0 => Main0 (fails) + mainMachine <- .MainState0 + XCTAssertTrue(mainMachine.state == .SubMachine2(.SubState0), "No change.") + + // Sub2-0 => Sub2-1 + mainMachine <- .SubMachine2(.SubState1) + XCTAssertTrue(mainMachine.state == .SubMachine2(.SubState1)) + + // Sub2-1 => Main + mainMachine <- .MainState0 + XCTAssertTrue(mainMachine.state == .MainState0) + + } + + func testAddHandler() + { + let mainMachine = self.mainMachine! + + var didPass = false + + // NOTE: this handler is added to mainMachine and doesn't make submachines dirty + mainMachine.addHandler(.MainState0 => .SubMachine1(.SubState0)) { context in + print("[Main] 1-1 => 1-2 (specific)") + didPass = true + } + + // initial + XCTAssertTrue(mainMachine.state == .MainState0) + XCTAssertFalse(didPass) + + // Main0 => Sub1-1 (fails) + mainMachine <- .SubMachine1(.SubState1) + XCTAssertTrue(mainMachine.state == .MainState0, "No change.") + XCTAssertFalse(didPass) + + // Main0 => Sub1-0 + mainMachine <- .SubMachine1(.SubState0) + XCTAssertTrue(mainMachine.state == .SubMachine1(.SubState0)) + XCTAssertTrue(didPass) + } + +} diff --git a/SwiftStateTests/Info.plist b/Tests/Info.plist similarity index 100% rename from SwiftStateTests/Info.plist rename to Tests/Info.plist diff --git a/Tests/MachineTests.swift b/Tests/MachineTests.swift new file mode 100644 index 0000000..fda2995 --- /dev/null +++ b/Tests/MachineTests.swift @@ -0,0 +1,540 @@ +// +// MachineTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/05. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class MachineTests: _TestCase +{ + func testConfigure() + { + let machine = Machine(state: .State0) + + machine.configure { + $0.addRoutes(event: .Event0, transitions: [ .State0 => .State1 ]) + } + + XCTAssertTrue(machine.canTryEvent(.Event0) != nil) + } + + //-------------------------------------------------- + // MARK: - tryEvent a.k.a `<-!` + //-------------------------------------------------- + + func testCanTryEvent() + { + let machine = Machine(state: .State0) + + // add 0 => 1 & 1 => 2 + // (NOTE: this is not chaining e.g. 0 => 1 => 2) + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + XCTAssertTrue(machine.canTryEvent(.Event0) != nil) + } + + func testTryEvent() + { + let machine = Machine(state: .State0) { machine in + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") + } + + func testTryEvent_userInfo() + { + var userInfo: Any? = nil + + let machine = Machine(state: .State0) { machine in + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ], handler: { context in + userInfo = context.userInfo + }) + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + XCTAssertNil(userInfo) + + // tryEvent + machine <-! (.Event0, "gogogo") + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertTrue(userInfo as! String == "gogogo") + + // tryEvent + machine <-! (.Event0, "done") + XCTAssertEqual(machine.state, MyState.State2) + XCTAssertTrue(userInfo as! String == "done") + } + + func testTryEvent_twice() + { + let machine = Machine(state: .State0) { machine in + // add 0 => 1 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + ]) + // add 0 => 1 + machine.addRoutes(event: .Event1, transitions: [ + .State1 => .State2, + ]) + } + + // tryEvent (twice) + machine <-! .Event0 <-! .Event1 + XCTAssertEqual(machine.state, MyState.State2) + } + + func testTryEvent_string() + { + let machine = Machine(state: .State0) + + // add 0 => 1 => 2 + machine.addRoutes(event: "Run", transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State2, "Event=Run doesn't have 2 => Any") + } + + // https://github.com/ReactKit/SwiftState/issues/20 + func testTryEvent_issue20() + { + let machine = Machine(state: MyState.State2) { machine in + machine.addRoutes(event: .Event0, transitions: [.Any => .State0]) + } + + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0) + } + + // Fix for transitioning of routes w/ multiple from-states + // https://github.com/ReactKit/SwiftState/pull/32 + func testTryEvent_issue32() + { + let machine = Machine(state: .State0) { machine in + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1 ]) + machine.addRoutes(event: .Event1, routes: [ [ .State1, .State2 ] => .State3 ]) + } + + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State3) + } + + //-------------------------------------------------- + // MARK: - add/removeRoute + //-------------------------------------------------- + + func testAddRoute_multiple() + { + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + // add 2 => 1 => 0 + machine.addRoutes(event: .Event1, transitions: [ + .State2 => .State1, + .State1 => .State0, + ]) + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0, "Event1 doesn't have 0 => Any.") + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any.") + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0) + } + + func testAddRoute_handler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ], handler: { context in + invokeCount++ + return + }) + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + XCTAssertEqual(invokeCount, 2) + } + + func testRemoveRoute() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + let routeDisposable = machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + // removeRoute + routeDisposable.dispose() + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0, "Route should be removed.") + + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + } + + func testRemoveRoute_handler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + let routeDisposable = machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ], handler: { context in + invokeCount++ + return + }) + + // removeRoute + routeDisposable.dispose() + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0, "Route should be removed.") + + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + } + + //-------------------------------------------------- + // MARK: - add/removeHandler + //-------------------------------------------------- + + func testAddHandler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + XCTAssertEqual(invokeCount, 2) + } + + func testAddErrorHandler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1 ]) + machine.addErrorHandler { event, fromState, toState, userInfo in + invokeCount++ + } + } + + XCTAssertEqual(invokeCount, 0) + + // tryEvent (fails) + machine <-! .Event1 + + XCTAssertEqual(invokeCount, 1, "Error handler should be called.") + + } + + func testRemoveHandler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + let handlerDisposable = machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + // remove handler + handlerDisposable.dispose() + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1, "0 => 1 should be succesful") + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "1 => 2 should be succesful") + + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + } + + //-------------------------------------------------- + // MARK: - RouteMapping + //-------------------------------------------------- + + func testAddRouteMapping() + { + var invokeCount = 0 + + let machine = Machine(state: .Str("initial")) { machine in + + 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 + } + } + + machine.addHandler(event: .Str("gogogo")) { context in + invokeCount++ + return + } + + } + + // initial + XCTAssertEqual(machine.state, StrState.Str("initial")) + + // tryEvent (fails) + machine <-! .Str("go?") + XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.") + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + + // tryEvent + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 1")) + XCTAssertEqual(invokeCount, 1) + + // tryEvent (fails) + machine <-! .Str("finish") + XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.") + XCTAssertEqual(invokeCount, 1, "Handler should NOT be performed") + + // tryEvent + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 2")) + XCTAssertEqual(invokeCount, 2) + + // tryEvent (fails) + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.") + XCTAssertEqual(invokeCount, 2, "Handler should NOT be performed") + + // tryEvent + machine <-! .Str("finish") + XCTAssertEqual(machine.state, StrState.Str("end")) + XCTAssertEqual(invokeCount, 2, "gogogo-Handler should NOT be performed") + + } + + func testAddRouteMapping_handler() + { + var invokeCount1 = 0 + var invokeCount2 = 0 + var disposables = [Disposable]() + + let machine = Machine(state: .Str("initial")) { machine in + + let d = 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") + default: + return nil + } + }, handler: { context in + invokeCount1++ + }) + + disposables += [d] + + let d2 = machine.addRouteMapping({ event, fromState, userInfo -> StrState? in + // no route for no-event + guard let event = event else { return nil } + + switch (event, fromState) { + case (.Str("finish"), .Str("Phase 1")): + return .Str("end") + default: + return nil + } + }, handler: { context in + invokeCount2++ + }) + + disposables += [d2] + + } + + // initial + XCTAssertEqual(machine.state, StrState.Str("initial")) + + // tryEvent (fails) + machine <-! .Str("go?") + XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.") + XCTAssertEqual(invokeCount1, 0) + XCTAssertEqual(invokeCount2, 0) + + // tryEvent + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 1")) + XCTAssertEqual(invokeCount1, 1) + XCTAssertEqual(invokeCount2, 0) + + // tryEvent (fails) + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.") + XCTAssertEqual(invokeCount1, 1) + XCTAssertEqual(invokeCount2, 0) + + // tryEvent + machine <-! .Str("finish") + XCTAssertEqual(machine.state, StrState.Str("end")) + XCTAssertEqual(invokeCount1, 1) + XCTAssertEqual(invokeCount2, 1) + + // hasRoute (before dispose) + XCTAssertEqual(machine.hasRoute(event: .Str("gogogo"), transition: .Str("initial") => .Str("Phase 1")), true) + XCTAssertEqual(machine.hasRoute(event: .Str("finish"), transition: .Str("Phase 1") => .Str("end")), true) + + disposables.forEach { $0.dispose() } + + // hasRoute (after dispose) + XCTAssertEqual(machine.hasRoute(event: .Str("gogogo"), transition: .Str("initial") => .Str("Phase 1")), false, "Routes & handlers should be disposed.") + XCTAssertEqual(machine.hasRoute(event: .Str("finish"), transition: .Str("Phase 1") => .Str("end")), false, "Routes & handlers should be disposed.") + + } + +} \ No newline at end of file diff --git a/Tests/MiscTests.swift b/Tests/MiscTests.swift new file mode 100644 index 0000000..d46782a --- /dev/null +++ b/Tests/MiscTests.swift @@ -0,0 +1,194 @@ +// +// MiscTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +/// Unarranged tests. +class MiscTests: _TestCase +{ + func testREADME_string() + { + let machine = StateMachine(state: ".State0") { machine in + + machine.addRoute(".State0" => ".State1") + machine.addRoute(.Any => ".State2") { context in print("Any => 2, msg=\(context.userInfo)") } + machine.addRoute(".State2" => .Any) { context in print("2 => Any, msg=\(context.userInfo)") } + + // add handler (handlerContext = (event, transition, order, userInfo)) + machine.addHandler(".State0" => ".State1") { context in + print("0 => 1") + } + + // add errorHandler + machine.addErrorHandler { event, fromState, toState, userInfo in + print("[ERROR] \(fromState) => \(toState)") + } + } + + // tryState 0 => 1 => 2 => 1 => 0 + + machine <- ".State1" + XCTAssertEqual(machine.state, ".State1") + + machine <- (".State2", "Hello") + XCTAssertEqual(machine.state, ".State2") + + machine <- (".State1", "Bye") + XCTAssertEqual(machine.state, ".State1") + + machine <- ".State0" // fail: no 1 => 0 + XCTAssertEqual(machine.state, ".State1") + + print("machine.state = \(machine.state)") + } + + // StateType + associated value + func testREADME_associatedValue() + { + let machine = StateMachine(state: .Str("0")) { machine in + + 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(.Str("0") => .Str("1")) { context in + print("0 => 1") + } + + // add errorHandler + machine.addErrorHandler { event, fromState, toState, userInfo in + print("[ERROR] \(fromState) => \(toState)") + } + } + + // tryState 0 => 1 => 2 => 1 => 0 + + machine <- .Str("1") + XCTAssertEqual(machine.state, StrState.Str("1")) + + machine <- (.Str("2"), "Hello") + XCTAssertEqual(machine.state, StrState.Str("2")) + + machine <- (.Str("1"), "Bye") + XCTAssertEqual(machine.state, StrState.Str("1")) + + machine <- .Str("0") // fail: no 1 => 0 + XCTAssertEqual(machine.state, StrState.Str("1")) + + print("machine.state = \(machine.state)") + } + + func testExample() + { + let machine = StateMachine(state: .State0) { + + // add 0 => 1 + $0.addRoute(.State0 => .State1) { context in + print("[Transition 0=>1] \(context.fromState) => \(context.toState)") + } + // add 0 => 1 once more + $0.addRoute(.State0 => .State1) { context in + print("[Transition 0=>1b] \(context.fromState) => \(context.toState)") + } + // add 2 => Any + $0.addRoute(.State2 => .Any) { context in + print("[Transition exit 2] \(context.fromState) => \(context.toState) (Any)") + } + // add Any => 2 + $0.addRoute(.Any => .State2) { context in + print("[Transition Entry 2] \(context.fromState) (Any) => \(context.toState)") + } + // add 1 => 0 (no handler) + $0.addRoute(.State1 => .State0) + + } + + // 0 => 1 + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + + // 1 => 0 + XCTAssertTrue(machine.hasRoute(.State1 => .State0)) + + // 2 => Any + XCTAssertTrue(machine.hasRoute(.State2 => .State0)) + XCTAssertTrue(machine.hasRoute(.State2 => .State1)) + XCTAssertTrue(machine.hasRoute(.State2 => .State2)) + XCTAssertTrue(machine.hasRoute(.State2 => .State3)) + + // Any => 2 + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State1 => .State2)) + XCTAssertTrue(machine.hasRoute(.State3 => .State2)) + + // others + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertFalse(machine.hasRoute(.State0 => .State3)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State3)) + XCTAssertFalse(machine.hasRoute(.State3 => .State0)) + XCTAssertFalse(machine.hasRoute(.State3 => .State1)) + XCTAssertFalse(machine.hasRoute(.State3 => .State3)) + + machine.configure { + + // add error handlers + $0.addErrorHandler { context in + print("[ERROR 1] \(context.fromState) => \(context.toState)") + } + + // add entry handlers + $0.addHandler(.Any => .State0) { context in + print("[Entry 0] \(context.fromState) => \(context.toState)") // NOTE: this should not be called + } + $0.addHandler(.Any => .State1) { context in + print("[Entry 1] \(context.fromState) => \(context.toState)") + } + $0.addHandler(.Any => .State2) { context in + print("[Entry 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") + } + $0.addHandler(.Any => .State2) { context in + print("[Entry 2b] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") + } + + // add exit handlers + $0.addHandler(.State0 => .Any) { context in + print("[Exit 0] \(context.fromState) => \(context.toState)") + } + $0.addHandler(.State1 => .Any) { context in + print("[Exit 1] \(context.fromState) => \(context.toState)") + } + $0.addHandler(.State2 => .Any) { context in + print("[Exit 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") + } + $0.addHandler(.State2 => .Any) { context in + print("[Exit 2b] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") + } + } + + XCTAssertEqual(machine.state, MyState.State0) + + // tryState 0 => 1 => 2 => 1 => 0 => 3 + + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + + machine <- (.State2, "State2 activate") + XCTAssertEqual(machine.state, MyState.State2) + + machine <- (.State1, "State2 deactivate") + XCTAssertEqual(machine.state, MyState.State1) + + machine <- .State0 + XCTAssertEqual(machine.state, MyState.State0) + + machine <- .State3 + XCTAssertEqual(machine.state, MyState.State0, "No 0 => 3.") + } +} \ No newline at end of file diff --git a/Tests/MyEvent.swift b/Tests/MyEvent.swift new file mode 100644 index 0000000..6c7d7b3 --- /dev/null +++ b/Tests/MyEvent.swift @@ -0,0 +1,36 @@ +// +// MyEvent.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState + +enum MyEvent: EventType +{ + case Event0, Event1 +} + +enum StrEvent: EventType +{ + case Str(String) + + var hashValue: Int + { + switch self { + case .Str(let str): return str.hashValue + } + } +} + +func == (lhs: StrEvent, rhs: StrEvent) -> Bool +{ + switch (lhs, rhs) { + case let (.Str(str1), .Str(str2)): + return str1 == str2 +// default: +// return false + } +} \ No newline at end of file diff --git a/Tests/MyState.swift b/Tests/MyState.swift new file mode 100644 index 0000000..ab4f214 --- /dev/null +++ b/Tests/MyState.swift @@ -0,0 +1,34 @@ +// +// MyState.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState + +enum MyState: StateType +{ + case State0, State1, State2, State3 +} + +enum StrState: StateType +{ + case Str(String) + + var hashValue: Int + { + switch self { + case .Str(let str): return str.hashValue + } + } +} + +func == (lhs: StrState, rhs: StrState) -> Bool +{ + switch (lhs, rhs) { + case let (.Str(str1), .Str(str2)): + return str1 == str2 + } +} \ No newline at end of file diff --git a/SwiftStateTests/QiitaTests.swift b/Tests/QiitaTests.swift similarity index 83% rename from SwiftStateTests/QiitaTests.swift rename to Tests/QiitaTests.swift index f5852ba..ee594bb 100644 --- a/SwiftStateTests/QiitaTests.swift +++ b/Tests/QiitaTests.swift @@ -13,16 +13,10 @@ import XCTest // Swiftで有限オートマトン(ステートマシン)を作る - Qiita // http://qiita.com/inamiy/items/cd218144c90926f9a134 -enum InputKey: StateType, NilLiteralConvertible +enum InputKey: StateType { case None case Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9 - case Any - - init(nilLiteral: Void) - { - self = Any - } } class QiitaTests: _TestCase @@ -31,10 +25,10 @@ class QiitaTests: _TestCase { var success = false - let machine = StateMachine(state: .None) { machine in + let machine = StateMachine(state: .None) { machine in // connect all states - machine.addRoute(nil => nil) + machine.addRoute(.Any => .Any) // success = true only when transitionChain 2 => 3 => 5 => 7 is fulfilled machine.addRouteChain(.Key2 => .Key3 => .Key5 => .Key7) { context in diff --git a/SwiftStateTests/StateMachineChainTests.swift b/Tests/RouteChainTests.swift similarity index 55% rename from SwiftStateTests/StateMachineChainTests.swift rename to Tests/RouteChainTests.swift index a7669a4..e258fea 100644 --- a/SwiftStateTests/StateMachineChainTests.swift +++ b/Tests/RouteChainTests.swift @@ -1,5 +1,5 @@ // -// StateMachineChainTests.swift +// MachineChainTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/04. @@ -9,18 +9,20 @@ import SwiftState import XCTest -class StateMachineChainTests: _TestCase +class MachineChainTests: _TestCase { func testAddRouteChain() { - let machine = StateMachine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRouteChain(.State0 => .State1 => .State2) { context in + invokeCount++ + return + } + } // tryState 0 => 1 => 2 @@ -44,15 +46,17 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_condition() { - let machine = StateMachine(state: .State0) - var flag = false var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2, condition: flag) { context in - invokeCount++ - return + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRouteChain(.State0 => .State1 => .State2, condition: { _ in flag }) { context in + invokeCount++ + return + } + } // tryState 0 => 1 => 2 @@ -78,11 +82,13 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_failBySkipping() { - let machine = StateMachine(state: .State0) + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRouteChain(.State0 => .State1 => .State2) { context in + XCTFail("Handler should NOT be performed because 0 => 2 is skipping 1.") + } - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2) { context in - XCTFail("Handler should NOT be performed because 0 => 2 is skipping 1.") } // tryState 0 => 2 directly (skipping 1) @@ -91,13 +97,15 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_failByHangingAround() { - let machine = StateMachine(state: .State0) + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRouteChain(.State0 => .State1 => .State2) { context in + XCTFail("Handler should NOT be performed because 0 => 1 => 3 => 2 is hanging around 3.") + } + machine.addRoute(.State1 => .State3) // add 1 => 3 route for hanging around - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2) { context in - XCTFail("Handler should NOT be performed because 0 => 1 => 3 => 2 is hanging around 3.") } - machine.addRoute(.State1 => .State3) // add 1 => 3 route for hanging around // tryState 0 => 1 => 3 => 2 (hanging around 3) machine <- .State1 @@ -107,16 +115,18 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_succeedByFailingHangingAround() { - let machine = StateMachine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRouteChain(.State0 => .State1 => .State2) { context in + invokeCount++ + return + } + // machine.addRoute(.State1 => .State3) // comment-out: 1 => 3 is not possible + } - // machine.addRoute(.State1 => .State3) // comment-out: 1 => 3 is not possible // tryState 0 => 1 => 3 => 2 (cannot hang around 3) machine <- .State1 @@ -128,14 +138,16 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_goBackHomeAWhile() { - let machine = StateMachine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 => 0 (back home) => 2 - machine.addRouteChain(.State0 => .State1 => .State2 => .State0 => .State2) { context in - invokeCount++ - return + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 => 0 (back home) => 2 + machine.addRouteChain(.State0 => .State1 => .State2 => .State0 => .State2) { context in + invokeCount++ + return + } + } // tryState 0 => 1 => 2 => 0 => 2 @@ -150,16 +162,18 @@ class StateMachineChainTests: _TestCase // https://github.com/inamiy/SwiftState/issues/2 func testAddRouteChain_goBackHomeAWhile2() { - let machine = StateMachine(state: .State0) - var invokeCount = 0 - machine.addRoute(nil => nil) // connect all states + let machine = StateMachine(state: .State0) { machine in - // add 0 => 1 => 2 => 0 (back home) => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2 => .State0 => .State1 => .State2) { context in - invokeCount++ - return + machine.addRoute(.Any => .Any) // connect all states + + // add 0 => 1 => 2 => 0 (back home) => 1 => 2 + machine.addRouteChain(.State0 => .State1 => .State2 => .State0 => .State1 => .State2) { context in + invokeCount++ + return + } + } // tryState 0 => 1 => 2 => 0 => 1 => 0 => 2 @@ -187,69 +201,52 @@ class StateMachineChainTests: _TestCase func testRemoveRouteChain() { - let machine = StateMachine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - let (routeID, _) = machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + let chainDisposable = machine.addRouteChain(.State0 => .State1 => .State2) { context in + invokeCount++ + return + } + + // remove chain + chainDisposable.dispose() + } - // removeRoute - machine.removeRoute(routeID) - - // tryState 0 => 1 - let success = machine <- .State1 - - XCTAssertFalse(success, "RouteChain should be removed.") - XCTAssertEqual(invokeCount, 0, "ChainHandler should NOT be performed.") - } - - func testRemoveChainHandler() - { - let machine = StateMachine(state: .State0) - - var invokeCount = 0 - - // add 0 => 1 => 2 - let (_, handlerID) = machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return - } - - // removeHandler - XCTAssertTrue(machine.removeHandler(handlerID)) - // tryState 0 => 1 => 2 + machine <- .State1 - let success = machine <- .State2 + XCTAssertEqual(invokeCount, 0, "ChainHandler should NOT be performed.") - XCTAssertTrue(success, "0 => 1 => 2 should be successful.") + machine <- .State2 XCTAssertEqual(invokeCount, 0, "ChainHandler should NOT be performed.") } func testAddChainErrorHandler() { - let machine = StateMachine(state: .State0) - var errorCount = 0 - let transitionChain = MyState.State0 => .State1 => .State2 + let machine = StateMachine(state: .State0) { machine in - machine.addRoute(nil => nil) // connect all states + let transitionChain = MyState.State0 => .State1 => .State2 + + machine.addRoute(.Any => .Any) // connect all states + + // add 0 => 1 => 2 + machine.addRouteChain(transitionChain) { context in + XCTFail("0 => 1 => 2 should not be succeeded.") + return + } - // add 0 => 1 => 2 - machine.addRouteChain(transitionChain) { context in - XCTFail("0 => 1 => 2 should not be succeeded.") - return - } + // add 0 => 1 => 2 chainErrorHandler + machine.addChainErrorHandler(transitionChain) { context in + errorCount++ + return + } - // add 0 => 1 => 2 chainErrorHandler - machine.addChainErrorHandler(transitionChain) { context in - errorCount++ - return } // tryState 0 (starting state) => 1 => 0 @@ -265,23 +262,25 @@ class StateMachineChainTests: _TestCase func testRemoveChainErrorHandler() { - let machine = StateMachine(state: .State0) - var errorCount = 0 - let transitionChain = MyState.State0 => .State1 => .State2 - - machine.addRoute(nil => nil) // connect all states + let machine = StateMachine(state: .State0) { machine in + + let transitionChain = MyState.State0 => .State1 => .State2 + + machine.addRoute(.Any => .Any) // connect all states + + // add 0 => 1 => 2 chainErrorHandler + let chainErrorHandlerDisposable = machine.addChainErrorHandler(transitionChain) { context in + errorCount++ + return + } + + // remove chainErrorHandler + chainErrorHandlerDisposable.dispose() - // add 0 => 1 => 2 chainErrorHandler - let handlerID = machine.addChainErrorHandler(transitionChain) { context in - errorCount++ - return } - // remove chainErrorHandler - machine.removeHandler(handlerID) - // tryState 0 (starting state) => 1 => 0 machine <- .State1 machine <- .State0 @@ -291,4 +290,5 @@ class StateMachineChainTests: _TestCase machine <- .State2 XCTAssertEqual(errorCount, 0, "Chain error, but chainErrorHandler should NOT be performed.") } + } \ No newline at end of file diff --git a/Tests/RouteMappingTests.swift b/Tests/RouteMappingTests.swift new file mode 100644 index 0000000..93b21bf --- /dev/null +++ b/Tests/RouteMappingTests.swift @@ -0,0 +1,189 @@ +// +// RouteMappingTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-02. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +// +// RouteMapping for StateType & EventType using associated values +// +// https://github.com/ReactKit/SwiftState/issues/34 +// https://github.com/ReactKit/SwiftState/pull/36 +// + +private enum _State: StateType, Hashable +{ + case Pending + case Loading(Int) + + var hashValue: Int + { + switch self { + case .Pending: + return "Pending".hashValue + case let .Loading(x): + return "Loading\(x)".hashValue + } + } +} + +private func ==(lhs: _State, rhs: _State) -> Bool +{ + switch (lhs, rhs) { + case (.Pending, .Pending): + return true + case let (.Loading(x1), .Loading(x2)): + return x1 == x2 + default: + return false + } +} + +private enum _Event: EventType, Hashable +{ + case CancelAction + case LoadAction(Int) + + var hashValue: Int + { + switch self { + case .CancelAction: + return "CancelAction".hashValue + case let .LoadAction(x): + return "LoadAction\(x)".hashValue + } + } +} + +private func ==(lhs: _Event, rhs: _Event) -> Bool +{ + switch (lhs, rhs) { + case (.CancelAction, .CancelAction): + return true + case let (.LoadAction(x1), .LoadAction(x2)): + return x1 == x2 + default: + return false + } +} + +class RouteMappingTests: _TestCase +{ + /// Test for state & event with associated values + func testAddRouteMapping() + { + var count = 0 + + let machine = StateMachine<_State, _Event>(state: .Pending) { machine in + + machine.addRouteMapping { event, fromState, userInfo in + // no routes for no event + guard let event = event else { + return nil + } + + switch event { + case .CancelAction: + // can transit to `.Pending` if current state is not the same + return fromState == .Pending ? nil : .Pending + case .LoadAction(let actionId): + // can transit to `.Loading(actionId)` if current state is not the same + return fromState == .Loading(actionId) ? nil : .Loading(actionId) + } + } + + // increment `count` when any events i.e. `.CancelAction` and `.LoadAction(x)` succeed. + machine.addHandler(event: .Any) { event, transition, order, userInfo in + count++ + } + + } + + // initial + XCTAssertEqual(machine.state, _State.Pending) + XCTAssertEqual(count, 0) + + // CancelAction (to .Pending state, same as before) + machine <-! .CancelAction + XCTAssertEqual(machine.state, _State.Pending) + XCTAssertEqual(count, 0, "`tryEvent()` failed, and `count` should not be incremented.") + + // LoadAction(1) (to .Loading(1) state) + machine <-! .LoadAction(1) + XCTAssertEqual(machine.state, _State.Loading(1)) + XCTAssertEqual(count, 1) + + // LoadAction(1) (same as before) + machine <-! .LoadAction(1) + XCTAssertEqual(machine.state, _State.Loading(1)) + XCTAssertEqual(count, 1, "`tryEvent()` failed, and `count` should not be incremented.") + + machine <-! .LoadAction(2) + XCTAssertEqual(machine.state, _State.Loading(2)) + XCTAssertEqual(count, 2) + + machine <-! .CancelAction + XCTAssertEqual(machine.state, _State.Pending) + XCTAssertEqual(count, 3) + } + + /// Test for state with associated values + func testAddStateRouteMapping() + { + var count = 0 + + let machine = StateMachine<_State, _Event>(state: .Pending) { machine in + + // Add following routes: + // - `.Pending => .Loading(1)` + // - `.Loading(x) => .Loading(x+10)` + // - `.Loading(x) => .Loading(x+100)` + machine.addStateRouteMapping { fromState, userInfo in + switch fromState { + case .Pending: + return [.Loading(1)] + case .Loading(let actionId): + return [.Loading(actionId+10), .Loading(actionId+100)] + } + } + + // increment `count` when any events i.e. `.CancelAction` and `.LoadAction(x)` succeed. + machine.addHandler(.Any => .Any) { event, transition, order, userInfo in + count++ + } + + } + + // initial + XCTAssertEqual(machine.state, _State.Pending) + XCTAssertEqual(count, 0) + + // .Loading(999) (fails) + machine <- .Loading(999) + XCTAssertEqual(machine.state, _State.Pending) + XCTAssertEqual(count, 0, "`tryState()` failed, and `count` should not be incremented.") + + // .Loading(1) + machine <- .Loading(1) + XCTAssertEqual(machine.state, _State.Loading(1)) + XCTAssertEqual(count, 1) + + // .Loading(999) (fails) + machine <- .Loading(999) + XCTAssertEqual(machine.state, _State.Loading(1)) + XCTAssertEqual(count, 1, "`tryState()` failed, and `count` should not be incremented.") + + machine <- .Loading(11) + XCTAssertEqual(machine.state, _State.Loading(11)) + XCTAssertEqual(count, 2) + + machine <- .Loading(111) + XCTAssertEqual(machine.state, _State.Loading(111)) + XCTAssertEqual(count, 3) + } +} diff --git a/Tests/RouteTests.swift b/Tests/RouteTests.swift new file mode 100644 index 0000000..23712b2 --- /dev/null +++ b/Tests/RouteTests.swift @@ -0,0 +1,31 @@ +// +// RouteTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/04. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class RouteTests: _TestCase +{ + func testInit() + { + let route = Route(transition: .State0 => .State1, condition: nil) + XCTAssertEqual(route.transition.fromState.rawValue, MyState.State0) + XCTAssertEqual(route.transition.toState.rawValue, MyState.State1) + XCTAssertTrue(route.condition == nil) + + let route2 = Route(transition: .State1 => .State2, condition: { _ in false }) + XCTAssertEqual(route2.transition.fromState.rawValue, MyState.State1) + XCTAssertEqual(route2.transition.toState.rawValue, MyState.State2) + XCTAssertTrue(route2.condition != nil) + + let route3 = Route(transition: .State2 => .State3, condition: { context in false }) + XCTAssertEqual(route3.transition.fromState.rawValue, MyState.State2) + XCTAssertEqual(route3.transition.toState.rawValue, MyState.State3) + XCTAssertTrue(route3.condition != nil) + } +} \ No newline at end of file diff --git a/Tests/StateMachineEventTests.swift b/Tests/StateMachineEventTests.swift new file mode 100644 index 0000000..9f85b98 --- /dev/null +++ b/Tests/StateMachineEventTests.swift @@ -0,0 +1,377 @@ +// +// StateMachineEventTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/05. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class StateMachineEventTests: _TestCase +{ + func testCanTryEvent() + { + let machine = StateMachine(state: .State0) + + // add 0 => 1 & 1 => 2 + // (NOTE: this is not chaining e.g. 0 => 1 => 2) + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + XCTAssertTrue(machine.canTryEvent(.Event0) != nil) + } + + //-------------------------------------------------- + // MARK: - tryEvent a.k.a `<-!` + //-------------------------------------------------- + + func testTryEvent() + { + let machine = StateMachine(state: .State0) + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") + } + + func testTryEvent_string() + { + let machine = StateMachine(state: .State0) + + // add 0 => 1 => 2 + machine.addRoutes(event: "Run", transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State2, "Event=Run doesn't have 2 => Any") + } + + // https://github.com/ReactKit/SwiftState/issues/20 + func testTryEvent_issue20() + { + let machine = StateMachine(state: MyState.State2) { machine in + machine.addRoutes(event: .Event0, transitions: [.Any => .State0]) + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0) + } + + // https://github.com/ReactKit/SwiftState/issues/28 + func testTryEvent_issue28() + { + var eventCount = 0 + + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) + machine.addRoutes(event: .Event0, transitions: [.Any => .Any]) { _ in + eventCount++ + } + } + + XCTAssertEqual(eventCount, 0) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(eventCount, 1) + XCTAssertEqual(machine.state, MyState.State0, "State should NOT be changed") + + // tryEvent + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1, "State should be changed") + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(eventCount, 2) + XCTAssertEqual(machine.state, MyState.State1, "State should NOT be changed") + } + + // Fix for transitioning of routes w/ multiple from-states + // https://github.com/ReactKit/SwiftState/pull/32 + func testTryEvent_issue32() + { + let machine = StateMachine(state: .State0) { machine in + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1 ]) + machine.addRoutes(event: .Event1, routes: [ [ .State1, .State2 ] => .State3 ]) + } + + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State3) + } + + // MARK: hasRoute + event + + func testHasRoute_anyEvent() + { + ({ + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) + machine.addRoutes(event: .Any, transitions: [.State0 => .State1]) + } + + let hasRoute = machine.hasRoute(event: .Event0, transition: .State0 => .State1) + XCTAssertTrue(hasRoute) + })() + + ({ + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) + machine.addRoutes(event: .Any, transitions: [.State2 => .State3]) + } + + let hasRoute = machine.hasRoute(event: .Event0, transition: .State0 => .State1) + XCTAssertFalse(hasRoute) + })() + } + + // Fix hasRoute() bug when there are routes for no-event & with-event. + // https://github.com/ReactKit/SwiftState/pull/19 + func testHasRoute_issue19() + { + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) // no-event + machine.addRoutes(event: .Event0, transitions: [.State1 => .State2]) // with-event + } + + let hasRoute = machine.hasRoute(event: .Event0, transition: .State1 => .State2) + XCTAssertTrue(hasRoute) + } + + //-------------------------------------------------- + // MARK: - add/removeRoute + //-------------------------------------------------- + + func testAddRoute_tryState() + { + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 & 1 => 2 + // (NOTE: this is not chaining e.g. 0 => 1 => 2) + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + } + + // tryState 0 => 1 + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + + // tryState 1 => 2 + machine <- .State2 + XCTAssertEqual(machine.state, MyState.State2) + + // tryState 2 => 3 + machine <- .State3 + XCTAssertEqual(machine.state, MyState.State2, "2 => 3 is not registered.") + } + + func testAddRoute_multiple() + { + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + // add 2 => 1 => 0 + machine.addRoutes(event: .Event1, transitions: [ + .State2 => .State1, + .State1 => .State0, + ]) + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0, "Event1 doesn't have 0 => Any.") + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any.") + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0) + } + + func testAddRoute_handler() + { + var invokeCount = 0 + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ], handler: { context in + invokeCount++ + return + }) + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + XCTAssertEqual(invokeCount, 2) + } + + func testRemoveRoute() + { + var invokeCount = 0 + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + let routeDisposable = machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + // removeRoute + routeDisposable.dispose() + + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0, "Route should be removed.") + + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + } + + //-------------------------------------------------- + // MARK: - add/removeHandler + //-------------------------------------------------- + + func testAddHandler() + { + var invokeCount = 0 + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + XCTAssertEqual(invokeCount, 2) + } + + func testRemoveHandler() + { + var invokeCount = 0 + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + let handlerDisposable = machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + // remove handler + handlerDisposable.dispose() + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1, "0 => 1 should be succesful") + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "1 => 2 should be succesful") + + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + } +} \ No newline at end of file diff --git a/Tests/StateMachineTests.swift b/Tests/StateMachineTests.swift new file mode 100644 index 0000000..dfd90ff --- /dev/null +++ b/Tests/StateMachineTests.swift @@ -0,0 +1,780 @@ +// +// StateMachineTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class StateMachineTests: _TestCase +{ + func testInit() + { + let machine = StateMachine(state: .State0) + + XCTAssertEqual(machine.state, MyState.State0) + } + + //-------------------------------------------------- + // MARK: - tryState a.k.a `<-` + //-------------------------------------------------- + + // machine <- state + func testTryState() + { + let machine = StateMachine(state: .State0) + + // tryState 0 => 1, without registering any transitions + machine <- .State1 + + XCTAssertEqual(machine.state, MyState.State0, "0 => 1 should fail because transition is not added yet.") + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + // tryState 0 => 1 + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + } + + func testTryState_string() + { + let machine = StateMachine(state: "0") + + // tryState 0 => 1, without registering any transitions + machine <- "1" + + XCTAssertEqual(machine.state, "0", "0 => 1 should fail because transition is not added yet.") + + // add 0 => 1 + machine.addRoute("0" => "1") + + // tryState 0 => 1 + machine <- "1" + XCTAssertEqual(machine.state, "1") + } + + //-------------------------------------------------- + // MARK: - addRoute + //-------------------------------------------------- + + // add state1 => state2 + func testAddRoute() + { + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true + XCTAssertFalse(machine.hasRoute(.State0 => .State2)) + XCTAssertFalse(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + } + + // add .Any => state + func testAddRoute_fromAnyState() + { + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.Any => .State1) // Any => State1 + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true + XCTAssertFalse(machine.hasRoute(.State0 => .State2)) + XCTAssertFalse(machine.hasRoute(.State1 => .State0)) + XCTAssertTrue(machine.hasRoute(.State1 => .State1)) // true + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + } + + // add state => .Any + func testAddRoute_toAnyState() + { + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.State1 => .Any) // State1 => Any + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + XCTAssertFalse(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State1 => .State0)) // true + XCTAssertTrue(machine.hasRoute(.State1 => .State1)) // true + XCTAssertTrue(machine.hasRoute(.State1 => .State2)) // true + } + + // add .Any => .Any + func testAddRoute_bothAnyState() + { + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.Any => .Any) // Any => Any + } + + XCTAssertTrue(machine.hasRoute(.State0 => .State0)) // true + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) // true + XCTAssertTrue(machine.hasRoute(.State1 => .State0)) // true + XCTAssertTrue(machine.hasRoute(.State1 => .State1)) // true + XCTAssertTrue(machine.hasRoute(.State1 => .State2)) // true + } + + // add state0 => state0 + func testAddRoute_sameState() + { + let machine = StateMachine(state: .State0) { machine in + machine.addRoute(.State0 => .State0) + } + + XCTAssertTrue(machine.hasRoute(.State0 => .State0)) + } + + // add route + condition + func testAddRoute_condition() + { + var flag = false + + let machine = StateMachine(state: .State0) { machine in + // add 0 => 1 + machine.addRoute(.State0 => .State1, condition: { _ in flag }) + + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + + flag = true + + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + } + + // add route + condition + blacklist + func testAddRoute_condition_blacklist() + { + let machine = StateMachine(state: .State0) { machine in + // add 0 => Any, except 0 => 2 + machine.addRoute(.State0 => .Any, condition: { context in + return context.toState != .State2 + }) + } + + XCTAssertTrue(machine.hasRoute(.State0 => .State0)) + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + XCTAssertFalse(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State0 => .State3)) + } + + // add route + handler + func testAddRoute_handler() + { + var invokedCount = 0 + + let machine = StateMachine(state: .State0) { machine in + + machine.addRoute(.State0 => .State1) { context in + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ + } + + } + + XCTAssertEqual(invokedCount, 0, "Transition has not started yet.") + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertEqual(invokedCount, 1) + } + + // add route + conditional handler + func testAddRoute_conditionalHandler() + { + var invokedCount = 0 + var flag = false + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 without condition to guarantee 0 => 1 transition + machine.addRoute(.State0 => .State1) + + // add 0 => 1 with condition + conditionalHandler + machine.addRoute(.State0 => .State1, condition: { _ in flag }) { context in + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ + } + + // add 1 => 0 for resetting state + machine.addRoute(.State1 => .State0) + + } + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertEqual(invokedCount, 0, "Conditional handler should NOT be performed because flag=false.") + + // tryState 1 => 0 (resetting to 0) + machine <- .State0 + + XCTAssertEqual(machine.state, MyState.State0) + + flag = true + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertEqual(invokedCount, 1) + + } + + // MARK: addRoute using array + + func testAddRoute_array_left() + { + let machine = StateMachine(state: .State0) { machine in + // add 0 => 2 or 1 => 2 + machine.addRoute([.State0, .State1] => .State2) + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) + XCTAssertFalse(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertTrue(machine.hasRoute(.State1 => .State2)) + } + + func testAddRoute_array_right() + { + let machine = StateMachine(state: .State0) { machine in + // add 0 => 1 or 0 => 2 + machine.addRoute(.State0 => [.State1, .State2]) + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) + XCTAssertFalse(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + } + + func testAddRoute_array_both() + { + let machine = StateMachine(state: .State0) { machine in + // add 0 => 2 or 0 => 3 or 1 => 2 or 1 => 3 + machine.addRoute([MyState.State0, MyState.State1] => [MyState.State2, MyState.State3]) + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State0 => .State3)) + XCTAssertFalse(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertTrue(machine.hasRoute(.State1 => .State2)) + XCTAssertTrue(machine.hasRoute(.State1 => .State3)) + XCTAssertFalse(machine.hasRoute(.State2 => .State0)) + XCTAssertFalse(machine.hasRoute(.State2 => .State1)) + XCTAssertFalse(machine.hasRoute(.State2 => .State2)) + XCTAssertFalse(machine.hasRoute(.State2 => .State3)) + XCTAssertFalse(machine.hasRoute(.State3 => .State0)) + XCTAssertFalse(machine.hasRoute(.State3 => .State1)) + XCTAssertFalse(machine.hasRoute(.State3 => .State2)) + XCTAssertFalse(machine.hasRoute(.State3 => .State3)) + } + + //-------------------------------------------------- + // MARK: - removeRoute + //-------------------------------------------------- + + func testRemoveRoute() + { + let machine = StateMachine(state: .State0) + + let routeDisposable = machine.addRoute(.State0 => .State1) + + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + + // remove route + routeDisposable.dispose() + + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + } + + func testRemoveRoute_handler() + { + let machine = StateMachine(state: .State0) + + let routeDisposable = machine.addRoute(.State0 => .State1, handler: { _ in }) + + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + + // remove route + routeDisposable.dispose() + + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + } + + //-------------------------------------------------- + // MARK: - addHandler + //-------------------------------------------------- + + func testAddHandler() + { + var invokedCount = 0 + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + machine.addHandler(.State0 => .State1) { context in + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ + } + + } + + // not tried yet + XCTAssertEqual(invokedCount, 0, "Transition has not started yet.") + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertEqual(invokedCount, 1) + } + + func testAddHandler_order() + { + var invokedCount = 0 + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + // order = 100 (default) + machine.addHandler(.State0 => .State1) { context in + XCTAssertEqual(invokedCount, 1) + + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ + } + + // order = 99 + machine.addHandler(.State0 => .State1, order: 99) { context in + XCTAssertEqual(invokedCount, 0) + + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ + } + + } + + XCTAssertEqual(invokedCount, 0) + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertEqual(invokedCount, 2) + } + + + func testAddHandler_multiple() + { + var passed1 = false + var passed2 = false + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + machine.addHandler(.State0 => .State1) { context in + passed1 = true + } + + // add 0 => 1 once more + machine.addRoute(.State0 => .State1) + + machine.addHandler(.State0 => .State1) { context in + passed2 = true + } + + } + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertTrue(passed1) + XCTAssertTrue(passed2) + } + + func testAddHandler_overload() + { + var passed = false + + let machine = StateMachine(state: .State0) { machine in + + machine.addRoute(.State0 => .State1) + + machine.addHandler(.State0 => .State1) { context in + // empty + } + + machine.addHandler(.State0 => .State1) { context in + passed = true + } + + } + + XCTAssertFalse(passed) + + machine <- .State1 + + XCTAssertTrue(passed) + } + + //-------------------------------------------------- + // MARK: - removeHandler + //-------------------------------------------------- + + func testRemoveHandler() + { + var passed = false + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + let handlerDisposable = machine.addHandler(.State0 => .State1) { context in + XCTFail("Should never reach here") + } + + // add 0 => 1 once more + machine.addRoute(.State0 => .State1) + + machine.addHandler(.State0 => .State1) { context in + passed = true + } + + // remove handler + handlerDisposable.dispose() + + } + + XCTAssertFalse(passed) + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertTrue(passed) + } + + func testRemoveHandler_unregistered() + { + let machine = StateMachine(state: .State0) + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + let handlerDisposable = machine.addHandler(.State0 => .State1) { context in + // empty + } + + XCTAssertFalse(handlerDisposable.disposed) + + // remove handler + handlerDisposable.dispose() + + // remove already unregistered handler + XCTAssertTrue(handlerDisposable.disposed, "removeHandler should fail because handler is already removed.") + } + + func testRemoveErrorHandler() + { + var passed = false + + let machine = StateMachine(state: .State0) { machine in + + // add 2 => 1 + machine.addRoute(.State2 => .State1) + + let handlerDisposable = machine.addErrorHandler { context in + XCTFail("Should never reach here") + } + + // add 2 => 1 once more + machine.addRoute(.State2 => .State1) + + machine.addErrorHandler { context in + passed = true + } + + // remove handler + handlerDisposable.dispose() + + } + + // tryState 0 => 1 + machine <- .State1 + + XCTAssertTrue(passed) + } + + func testRemoveErrorHandler_unregistered() + { + let machine = StateMachine(state: .State0) + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + let handlerDisposable = machine.addErrorHandler { context in + // empty + } + + XCTAssertFalse(handlerDisposable.disposed) + + // remove handler + handlerDisposable.dispose() + + // remove already unregistered handler + XCTAssertTrue(handlerDisposable.disposed, "removeHandler should fail because handler is already removed.") + } + + //-------------------------------------------------- + // MARK: - addRouteChain + //-------------------------------------------------- + + func testAddRouteChain() + { + var success = false + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 => 3 + machine.addRouteChain(.State0 => .State1 => .State2 => .State3) { context in + success = true + } + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // 0 => 1 + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertFalse(success, "RouteChain is not completed yet.") + + // 1 => 2 + machine <- .State2 + XCTAssertEqual(machine.state, MyState.State2) + XCTAssertFalse(success, "RouteChain is not completed yet.") + + // 2 => 3 + machine <- .State3 + XCTAssertEqual(machine.state, MyState.State3) + XCTAssertTrue(success) + } + + func testAddChainHandler() + { + var success = false + + let machine = StateMachine(state: .State0) { machine in + + // add all routes + machine.addRoute(.Any => .Any) + + // add 0 => 1 => 2 => 3 + machine.addChainHandler(.State0 => .State1 => .State2 => .State3) { context in + success = true + } + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // 0 => 1 + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertFalse(success, "RouteChain is not completed yet.") + + // 1 => 2 + machine <- .State2 + XCTAssertEqual(machine.state, MyState.State2) + XCTAssertFalse(success, "RouteChain is not completed yet.") + + // 2 => 2 (fails & resets chaining count) + machine <- .State2 + XCTAssertEqual(machine.state, MyState.State2, "State should not change.") + XCTAssertFalse(success, "RouteChain failed and reset count.") + + // 2 => 3 (chaining is failed) + machine <- .State3 + XCTAssertEqual(machine.state, MyState.State3) + XCTAssertFalse(success, "RouteChain is already failed.") + + // go back to 0 & run 0 => 1 => 2 => 3 + machine <- .State0 <- .State1 <- .State2 <- .State3 + XCTAssertEqual(machine.state, MyState.State3) + XCTAssertTrue(success, "RouteChain is resetted & should succeed its chaining.") + } + + //-------------------------------------------------- + // MARK: - Event/StateRouteMapping + //-------------------------------------------------- + + func testAddStateRouteMapping() + { + var routeMappingDisposable: Disposable? + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 & 0 => 2 + routeMappingDisposable = machine.addStateRouteMapping { fromState, userInfo -> [MyState]? in + if fromState == .State0 { + return [.State1, .State2] + } + else { + return nil + } + } + + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) + XCTAssertFalse(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + XCTAssertFalse(machine.hasRoute(.State2 => .State0)) + XCTAssertFalse(machine.hasRoute(.State2 => .State1)) + XCTAssertFalse(machine.hasRoute(.State2 => .State2)) + + // remove routeMapping + routeMappingDisposable?.dispose() + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + XCTAssertFalse(machine.hasRoute(.State0 => .State2)) + XCTAssertFalse(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + XCTAssertFalse(machine.hasRoute(.State2 => .State0)) + XCTAssertFalse(machine.hasRoute(.State2 => .State1)) + XCTAssertFalse(machine.hasRoute(.State2 => .State2)) + } + + func testAddStateRouteMapping_handler() + { + var invokedCount = 0 + var routeMappingDisposable: Disposable? + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 & 0 => 2 + routeMappingDisposable = machine.addStateRouteMapping({ fromState, userInfo -> [MyState]? in + + if fromState == .State0 { + return [.State1, .State2] + } + else { + return nil + } + + }, handler: { context in + invokedCount++ + }) + + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + XCTAssertEqual(invokedCount, 0) + + // 0 => 1 + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertEqual(invokedCount, 1) + + // remove routeMapping + routeMappingDisposable?.dispose() + + // 1 => 2 (fails) + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertEqual(invokedCount, 1) + + } + + /// Test `Event/StateRouteMapping`s. + func testAddBothRouteMappings() + { + var routeMappingDisposable: Disposable? + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 & 0 => 2 + routeMappingDisposable = machine.addStateRouteMapping { fromState, userInfo -> [MyState]? in + if fromState == .State0 { + return [.State1, .State2] + } + else { + return nil + } + } + + // add 1 => 0 (can also use `RouteMapping` closure for single-`toState`) + machine.addRouteMapping { event, fromState, userInfo -> MyState? in + guard event == nil else { return nil } + + if fromState == .State1 { + return .State0 + } + else { + return nil + } + } + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + XCTAssertFalse(machine.hasRoute(.State2 => .State0)) + XCTAssertFalse(machine.hasRoute(.State2 => .State1)) + XCTAssertFalse(machine.hasRoute(.State2 => .State2)) + + // remove routeMapping + routeMappingDisposable?.dispose() + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + XCTAssertFalse(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + XCTAssertFalse(machine.hasRoute(.State2 => .State0)) + XCTAssertFalse(machine.hasRoute(.State2 => .State1)) + XCTAssertFalse(machine.hasRoute(.State2 => .State2)) + } +} diff --git a/Tests/StateTests.swift b/Tests/StateTests.swift new file mode 100644 index 0000000..ca2a525 --- /dev/null +++ b/Tests/StateTests.swift @@ -0,0 +1,39 @@ +// +// StateTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-08. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class StateTests: _TestCase +{ + func testInit_state() + { + let state = State(rawValue: .State0) + XCTAssertTrue(state == .State0) + XCTAssertTrue(.State0 == state) + } + + func testInit_nil() + { + let state = State(rawValue: nil) + XCTAssertTrue(state == .Any) + XCTAssertTrue(.Any == state) + } + + func testRawValue_state() + { + let state = State.Some(.State0) + XCTAssertTrue(state.rawValue == .State0) + } + + func testRawValue_any() + { + let state = State.Any + XCTAssertTrue(state.rawValue == nil) + } +} \ No newline at end of file diff --git a/Tests/String+TestExt.swift b/Tests/String+TestExt.swift new file mode 100644 index 0000000..098070d --- /dev/null +++ b/Tests/String+TestExt.swift @@ -0,0 +1,13 @@ +// +// String+TestExt.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import SwiftState + +extension String: StateType {} + +extension String: EventType {} \ No newline at end of file diff --git a/Tests/TransitionChainTests.swift b/Tests/TransitionChainTests.swift new file mode 100644 index 0000000..ac98a20 --- /dev/null +++ b/Tests/TransitionChainTests.swift @@ -0,0 +1,92 @@ +// +// TransitionChainTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/04. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class TransitionChainTests: _TestCase +{ + func testInit() + { + // 0 => 1 => 2 + var chain = MyState.State0 => .State1 => .State2 + + XCTAssertEqual(chain.states.count, 3) + XCTAssertTrue(chain.states[0] == .State0) + XCTAssertTrue(chain.states[1] == .State1) + XCTAssertTrue(chain.states[2] == .State2) + + // (1 => 2) => 3 + chain = MyState.State1 => .State2 => .State3 + + XCTAssertEqual(chain.states.count, 3) + XCTAssertTrue(chain.states[0] == .State1) + XCTAssertTrue(chain.states[1] == .State2) + XCTAssertTrue(chain.states[2] == .State3) + + // 2 => (3 => 0) + chain = MyState.State2 => (.State3 => .State0) + + XCTAssertEqual(chain.states.count, 3) + XCTAssertTrue(chain.states[0] == .State2) + XCTAssertTrue(chain.states[1] == .State3) + XCTAssertTrue(chain.states[2] == .State0) + } + + func testAppend() + { + // 0 => 1 + let transition = MyState.State0 => .State1 + var chain = TransitionChain(transition: transition) + + XCTAssertEqual(chain.states.count, 2) + XCTAssertTrue(chain.states[0] == .State0) + XCTAssertTrue(chain.states[1] == .State1) + + // 0 => 1 => 2 + chain = chain => .State2 + XCTAssertEqual(chain.states.count, 3) + XCTAssertTrue(chain.states[0] == .State0) + XCTAssertTrue(chain.states[1] == .State1) + XCTAssertTrue(chain.states[2] == .State2) + + // 0 => 1 => 2 => 3 + chain = chain => .State3 + XCTAssertEqual(chain.states.count, 4) + XCTAssertTrue(chain.states[0] == .State0) + XCTAssertTrue(chain.states[1] == .State1) + XCTAssertTrue(chain.states[2] == .State2) + XCTAssertTrue(chain.states[3] == .State3) + } + + func testPrepend() + { + // 0 => 1 + let transition = MyState.State0 => .State1 + var chain = TransitionChain(transition: transition) + + XCTAssertEqual(chain.states.count, 2) + XCTAssertTrue(chain.states[0] == .State0) + XCTAssertTrue(chain.states[1] == .State1) + + // 2 => 0 => 1 + chain = .State2 => chain // same as prepend + XCTAssertEqual(chain.states.count, 3) + XCTAssertTrue(chain.states[0] == .State2) + XCTAssertTrue(chain.states[1] == .State0) + XCTAssertTrue(chain.states[2] == .State1) + + // 3 => 2 => 0 => 1 + chain = .State3 => chain + XCTAssertEqual(chain.states.count, 4) + XCTAssertTrue(chain.states[0] == .State3) + XCTAssertTrue(chain.states[1] == .State2) + XCTAssertTrue(chain.states[2] == .State0) + XCTAssertTrue(chain.states[3] == .State1) + } +} \ No newline at end of file diff --git a/Tests/TransitionTests.swift b/Tests/TransitionTests.swift new file mode 100644 index 0000000..f3347f0 --- /dev/null +++ b/Tests/TransitionTests.swift @@ -0,0 +1,67 @@ +// +// TransitionTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class TransitionTests: _TestCase +{ + func testInit() + { + let transition = Transition(fromState: .State0, toState: .State1) + XCTAssertEqual(transition.fromState.rawValue, MyState.State0) + XCTAssertEqual(transition.toState.rawValue, MyState.State1) + + // shorthand + let transition2 = MyState.State1 => .State0 + XCTAssertEqual(transition2.fromState.rawValue, MyState.State1) + XCTAssertEqual(transition2.toState.rawValue, MyState.State0) + } + + func testInit_fromAny() + { + let transition = Transition(fromState: .Any, toState: .State1) + XCTAssertNil(transition.fromState.rawValue) + XCTAssertEqual(transition.toState.rawValue, MyState.State1) + + // shorthand + let transition2 = .Any => MyState.State0 + XCTAssertNil(transition2.fromState.rawValue) + XCTAssertEqual(transition2.toState.rawValue, MyState.State0) + } + + func testInit_toAny() + { + let transition = Transition(fromState: .State0, toState: .Any) + XCTAssertEqual(transition.fromState.rawValue, MyState.State0) + XCTAssertNil(transition.toState.rawValue) + + // shorthand + let transition2 = MyState.State1 => .Any + XCTAssertEqual(transition2.fromState.rawValue, MyState.State1) + XCTAssertNil(transition2.toState.rawValue) + } + + func testNil() + { + // .Any => state + let transition = .Any => MyState.State0 + XCTAssertTrue(transition.fromState == .Any) + XCTAssertTrue(transition.toState == .State0) + + // state => .Any + let transition2 = MyState.State0 => .Any + XCTAssertTrue(transition2.fromState == .State0) + XCTAssertTrue(transition2.toState == .Any) + + // .Any => .Any + let transition3: Transition = .Any => .Any + XCTAssertTrue(transition3.fromState == .Any) + XCTAssertTrue(transition3.toState == .Any) + } +} \ No newline at end of file diff --git a/SwiftStateTests/_TestCase.swift b/Tests/_TestCase.swift similarity index 100% rename from SwiftStateTests/_TestCase.swift rename to Tests/_TestCase.swift diff --git a/circle.yml b/circle.yml index a165778..f3a3870 100644 --- a/circle.yml +++ b/circle.yml @@ -1,12 +1,17 @@ machine: xcode: version: "7.0" - + +checkout: + post: + - git submodule sync + - git submodule update --init + test: override: - set -o pipefail && xcodebuild - -scheme "SwiftState-OSX" + -scheme "SwiftState" clean build test | tee $CIRCLE_ARTIFACTS/xcode_raw-OSX.log | xcpretty --color --report junit --output $CIRCLE_TEST_REPORTS/xcode/results-OSX.xml @@ -18,7 +23,7 @@ test: PROVISIONING_PROFILE= -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=9.0,name=iPhone 6' - -scheme "SwiftState-iOS" + -scheme "SwiftState" clean build test | tee $CIRCLE_ARTIFACTS/xcode_raw-iOS.log | xcpretty --color --report junit --output $CIRCLE_TEST_REPORTS/xcode/results-iOS.xml \ No newline at end of file