From cc6bc8f77473f58705e3c0f936346af4f2f10054 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Mon, 2 Nov 2015 04:36:40 +0900 Subject: [PATCH 01/27] [Test] Add FrogcjnTest (event + associated value). --- SwiftState.xcodeproj/project.pbxproj | 6 ++ SwiftStateTests/FrogcjnTest.swift | 143 +++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 SwiftStateTests/FrogcjnTest.swift diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index de301b0..de787e0 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* 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 */; }; + 1F27771E1BE68D1D00C57CC9 /* FrogcjnTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */; }; + 1F27771F1BE68D1E00C57CC9 /* FrogcjnTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.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 */; }; @@ -71,6 +73,7 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; + 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrogcjnTest.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 = ""; }; @@ -189,6 +192,7 @@ 1FD01B6219EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift */, 1F198C5B19972320001C3700 /* QiitaTests.swift */, C662B6F41B861CC400479524 /* RasmusTest.swift */, + 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */, 1FA6202A199660CA00460108 /* StateMachineChainTests.swift */, 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */, 1FA6202C199660CA00460108 /* StateMachineTests.swift */, @@ -427,6 +431,7 @@ 1FA62035199660CA00460108 /* StateMachineTests.swift in Sources */, 1FA62037199660CA00460108 /* StateTransitionChainTests.swift in Sources */, C662B6F51B861CC400479524 /* RasmusTest.swift in Sources */, + 1F27771E1BE68D1D00C57CC9 /* FrogcjnTest.swift in Sources */, 1FA62032199660CA00460108 /* MyState.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -448,6 +453,7 @@ 4822F0B219D008EB00F5F572 /* StateRouteTests.swift in Sources */, 4822F0B119D008EB00F5F572 /* StateTransitionChainTests.swift in Sources */, C662B6F61B861CC400479524 /* RasmusTest.swift in Sources */, + 1F27771F1BE68D1E00C57CC9 /* FrogcjnTest.swift in Sources */, 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SwiftStateTests/FrogcjnTest.swift b/SwiftStateTests/FrogcjnTest.swift new file mode 100644 index 0000000..611072f --- /dev/null +++ b/SwiftStateTests/FrogcjnTest.swift @@ -0,0 +1,143 @@ +// +// FrogcjnTest.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-02. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +// https://github.com/ReactKit/SwiftState/issues/34 + +private enum _State: StateType, Hashable +{ + case Pending + case Loading(Int) + case AnyState + + init(nilLiteral: ()) + { + self = AnyState + } + + var hashValue: Int + { + switch self { + case .Pending: + return "Pending".hashValue + case let .Loading(x): + return "Loading\(x)".hashValue + case .AnyState: + return "AnyState".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 + case (.AnyState, .AnyState): + return true + default: + return false + } +} + +private enum _Event: StateEventType, Hashable +{ + case CancelAction + case LoadAction(Int) + case AnyEvent + + init(nilLiteral: ()) + { + self = AnyEvent + } + + var hashValue: Int + { + switch self { + case .CancelAction: + return "CancelAction".hashValue + case let .LoadAction(x): + return "LoadAction\(x)".hashValue + case .AnyEvent: + return "AnyEvent".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 + case (.AnyEvent, .AnyEvent): + return true + default: + return false + } +} + +class FrogcjnTest: _TestCase +{ + func testEventWithAssociatedValue() + { + var count = 0 + + let machine = StateMachine<_State, _Event>(state: .Pending) { machine in + + machine.addRouteEvent(.CancelAction, transitions: [ .AnyState => .Pending ], condition: { $0.fromState != .Pending }) + + // + // If you have **finite** number of `LoadActionId`s (let's say 1 to 100), + // you can `addRouteEvent()` in finite number of times. + // (In this case, `LoadActionId` should have enum type instead) + // + for actionId in 1...100 { + machine.addRouteEvent(.LoadAction(actionId), transitions: [ .AnyState => .Loading(actionId) ], condition: { $0.fromState != .Loading(actionId) }) + } + + // increment `count` when any events i.e. `.CancelAction` and `.LoadAction(x)` succeed. + machine.addEventHandler(.AnyEvent) { event, transition, order, userInfo in + count++ + } + } + + // initial + XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(count, 0) + + // CancelAction (to .Pending state, same as before) + machine <-! .CancelAction + XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(count, 0, "`tryEvent()` failed, and `count` should not be incremented.") + + // LoadAction(1) (to .Loading(1) state) + machine <-! .LoadAction(1) + XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(count, 1) + + // LoadAction(1) (same as before) + machine <-! .LoadAction(1) + print(machine.state) + XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(count, 1, "`tryEvent()` failed, and `count` should not be incremented.") + + machine <-! .LoadAction(2) + XCTAssertTrue(machine.state == .Loading(2)) + XCTAssertEqual(count, 2) + + machine <-! .CancelAction + XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(count, 3) + } +} From ef07416fce4933966cb7e7be61d6d5ad8a0df44a Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sat, 14 Nov 2015 21:38:43 +0900 Subject: [PATCH 02/27] Refactor code for better typing, naming, and routeMapping support. --- SwiftState.xcodeproj/project.pbxproj | 148 +-- SwiftState/EventType.swift | 103 ++ SwiftState/HandlerID.swift | 31 + SwiftState/HierarchicalStateMachine.swift | 167 ---- SwiftState/Machine.swift | 859 +++++++++++++++++ SwiftState/Route.swift | 70 ++ SwiftState/RouteChain.swift | 58 ++ SwiftState/RouteChainID.swift | 17 + SwiftState/RouteID.swift | 21 + SwiftState/RouteMappingID.swift | 17 + SwiftState/StateEventType.swift | 12 - SwiftState/StateMachine.swift | 898 ------------------ SwiftState/StateRoute.swift | 70 -- SwiftState/StateRouteChain.swift | 64 -- SwiftState/StateTransition.swift | 67 -- SwiftState/StateTransitionChain.swift | 106 --- SwiftState/StateType.swift | 39 +- SwiftState/String+SwiftState.swift | 16 - SwiftState/Transition.swift | 94 ++ SwiftState/TransitionChain.swift | 135 +++ SwiftStateTests/BasicTests.swift | 129 ++- SwiftStateTests/BugFixTests.swift | 7 +- SwiftStateTests/FrogcjnTest.swift | 87 +- .../HierarchicalStateMachineTests.swift | 179 ---- SwiftStateTests/MyEvent.swift | 24 +- SwiftStateTests/MyState.swift | 31 +- SwiftStateTests/QiitaTests.swift | 12 +- SwiftStateTests/RasmusTest.swift | 24 +- SwiftStateTests/StateMachineChainTests.swift | 48 +- SwiftStateTests/StateMachineEventTests.swift | 31 +- SwiftStateTests/StateMachineTests.swift | 179 ++-- SwiftStateTests/StateRouteTests.swift | 22 +- .../StateTransitionChainTests.swift | 40 +- SwiftStateTests/StateTransitionTests.swift | 38 +- SwiftStateTests/String+TestExt.swift | 13 + 35 files changed, 1946 insertions(+), 1910 deletions(-) create mode 100644 SwiftState/EventType.swift create mode 100644 SwiftState/HandlerID.swift delete mode 100644 SwiftState/HierarchicalStateMachine.swift create mode 100644 SwiftState/Machine.swift create mode 100644 SwiftState/Route.swift create mode 100644 SwiftState/RouteChain.swift create mode 100644 SwiftState/RouteChainID.swift create mode 100644 SwiftState/RouteID.swift create mode 100644 SwiftState/RouteMappingID.swift delete mode 100644 SwiftState/StateEventType.swift delete mode 100644 SwiftState/StateMachine.swift delete mode 100644 SwiftState/StateRoute.swift delete mode 100644 SwiftState/StateRouteChain.swift delete mode 100644 SwiftState/StateTransition.swift delete mode 100644 SwiftState/StateTransitionChain.swift delete mode 100644 SwiftState/String+SwiftState.swift create mode 100644 SwiftState/Transition.swift create mode 100644 SwiftState/TransitionChain.swift delete mode 100644 SwiftStateTests/HierarchicalStateMachineTests.swift create mode 100644 SwiftStateTests/String+TestExt.swift diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index de787e0..924d653 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -11,13 +11,23 @@ 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; 1F27771E1BE68D1D00C57CC9 /* FrogcjnTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */; }; 1F27771F1BE68D1E00C57CC9 /* FrogcjnTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */; }; + 1F70FB661BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; + 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; + 1F70FB691BF0F46E00E5AC8C /* RouteChainID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */; }; + 1F70FB6A1BF0F46E00E5AC8C /* RouteChainID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */; }; + 1F70FB6C1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */; }; + 1F70FB6D1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */; }; + 1F70FB6F1BF0F59600E5AC8C /* HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */; }; + 1F70FB701BF0F59600E5AC8C /* HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */; }; + 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */; }; + 1F70FB7A1BF0FB7100E5AC8C /* 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 */; }; + 1FA620211996606300460108 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* Machine.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 */; }; @@ -29,13 +39,8 @@ 1FA62037199660CA00460108 /* StateTransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* StateTransitionChainTests.swift */; }; 1FA62038199660CA00460108 /* StateTransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* StateTransitionTests.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 */; }; @@ -49,14 +54,13 @@ 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 */; }; + 48797D5E19B42CCE0085D80F /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* Machine.swift */; }; + 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* Transition.swift */; }; + 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201E1996606300460108 /* TransitionChain.swift */; }; + 48797D6119B42CCE0085D80F /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* Route.swift */; }; + 48797D6219B42CCE0085D80F /* RouteChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201C1996606300460108 /* RouteChain.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 */; }; + 48797D6419B42CD40085D80F /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* EventType.swift */; }; C662B6F51B861CC400479524 /* RasmusTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C662B6F41B861CC400479524 /* RasmusTest.swift */; }; C662B6F61B861CC400479524 /* RasmusTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C662B6F41B861CC400479524 /* RasmusTest.swift */; }; /* End PBXBuildFile section */ @@ -74,17 +78,22 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrogcjnTest.swift; sourceTree = ""; }; + 1F70FB651BF0F46000E5AC8C /* RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteID.swift; sourceTree = ""; }; + 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainID.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; }; 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 = ""; }; + 1FA6201A1996606300460108 /* Machine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Machine.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 = ""; }; @@ -96,10 +105,7 @@ 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 = ""; }; 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 = ""; }; @@ -139,6 +145,28 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1F70FB741BF0FAB900E5AC8C /* Identifiers */ = { + isa = PBXGroup; + children = ( + 1F70FB651BF0F46000E5AC8C /* RouteID.swift */, + 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */, + 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */, + 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */, + ); + name = Identifiers; + sourceTree = ""; + }; + 1F70FB751BF0FAE800E5AC8C /* Routes */ = { + isa = PBXGroup; + children = ( + 1FA6201D1996606300460108 /* Transition.swift */, + 1FA6201E1996606300460108 /* TransitionChain.swift */, + 1FA6201B1996606300460108 /* Route.swift */, + 1FA6201C1996606300460108 /* RouteChain.swift */, + ); + name = Routes; + sourceTree = ""; + }; 1FA61FF61996601000460108 = { isa = PBXGroup; children = ( @@ -163,13 +191,10 @@ 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 */, + 1FA6201A1996606300460108 /* Machine.swift */, + 1FB1EC88199E4ABC00ABD937 /* Protocols */, + 1F70FB751BF0FAE800E5AC8C /* Routes */, + 1F70FB741BF0FAB900E5AC8C /* Identifiers */, 1FA620031996601000460108 /* Supporting Files */, ); path = SwiftState; @@ -187,9 +212,9 @@ isa = PBXGroup; children = ( 1FA62027199660CA00460108 /* _TestCase.swift */, + 1FB1EC8D199E609900ABD937 /* State & Event */, 1FA62028199660CA00460108 /* BasicTests.swift */, 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */, - 1FD01B6219EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift */, 1F198C5B19972320001C3700 /* QiitaTests.swift */, C662B6F41B861CC400479524 /* RasmusTest.swift */, 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */, @@ -200,7 +225,6 @@ 1FA6202E199660CA00460108 /* StateTransitionChainTests.swift */, 1FA6202F199660CA00460108 /* StateTransitionTests.swift */, 1FA6200D1996601000460108 /* Supporting Files */, - 1FB1EC8D199E609900ABD937 /* Type */, ); path = SwiftStateTests; sourceTree = ""; @@ -213,23 +237,23 @@ 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 = Type; + name = "State & Event"; sourceTree = ""; }; /* End PBXGroup section */ @@ -402,15 +426,17 @@ 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 */, + 1F70FB691BF0F46E00E5AC8C /* RouteChainID.swift in Sources */, + 1FA620251996606300460108 /* TransitionChain.swift in Sources */, + 1FA620241996606300460108 /* Transition.swift in Sources */, + 1F70FB661BF0F46000E5AC8C /* RouteID.swift in Sources */, + 1FA620201996606300460108 /* EventType.swift in Sources */, + 1F70FB6C1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */, + 1FA620221996606300460108 /* Route.swift in Sources */, + 1F70FB6F1BF0F59600E5AC8C /* HandlerID.swift in Sources */, + 1FA620211996606300460108 /* Machine.swift in Sources */, 1FA620261996606300460108 /* StateType.swift in Sources */, - 1FA620231996606300460108 /* StateRouteChain.swift in Sources */, + 1FA620231996606300460108 /* RouteChain.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -420,7 +446,7 @@ files = ( 1FB1EC8A199E515B00ABD937 /* MyEvent.swift in Sources */, 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */, - 1FD01B6319EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift in Sources */, + 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */, 1FA62038199660CA00460108 /* StateTransitionTests.swift in Sources */, 1FA62033199660CA00460108 /* StateMachineChainTests.swift in Sources */, 1FA62030199660CA00460108 /* _TestCase.swift in Sources */, @@ -442,7 +468,7 @@ files = ( 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */, 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */, - 1FD01B6419EC2B6700DA1C91 /* HierarchicalStateMachineTests.swift in Sources */, + 1F70FB7A1BF0FB7100E5AC8C /* String+TestExt.swift in Sources */, 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */, 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, @@ -462,15 +488,17 @@ 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 */, + 1F70FB6A1BF0F46E00E5AC8C /* RouteChainID.swift in Sources */, + 48797D6219B42CCE0085D80F /* RouteChain.swift in Sources */, + 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */, + 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */, + 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */, + 1F70FB6D1BF0F47700E5AC8C /* RouteMappingID.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 */, + 1F70FB701BF0F59600E5AC8C /* HandlerID.swift in Sources */, + 48797D5E19B42CCE0085D80F /* Machine.swift in Sources */, + 48797D6119B42CCE0085D80F /* Route.swift in Sources */, + 48797D6419B42CD40085D80F /* EventType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftState/EventType.swift b/SwiftState/EventType.swift new file mode 100644 index 0000000..9edb650 --- /dev/null +++ b/SwiftState/EventType.swift @@ -0,0 +1,103 @@ +// +// 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 (internal) + +internal enum _Event: Hashable +{ + case Some(E) + case Any // represents any `Some(E)` events but not `.None` + case None // default internal value for `addRoute()` without event + + internal var hashValue: Int + { + switch self { + case .Some(let x): return x.hashValue + case .Any: return -4611686018427387904 + case .None: return -4611686018427387905 + } + } + + internal var value: E? + { + switch self { + case .Some(let x): return x + default: return nil + } + } +} + +internal func == (lhs: _Event, rhs: _Event) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +internal func == (lhs: _Event, rhs: E) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +internal func == (lhs: E, rhs: _Event) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +// MARK: Event (public) + +/// `EventType` wrapper for handling`.Any` event. +public enum Event: Equatable +{ + case Some(E) + case Any + + public var value: E? + { + switch self { + case .Some(let x): return x + default: return nil + } + } + + internal func _toInternal() -> _Event + { + switch self { + case .Some(let x): return .Some(x) + case .Any: return .Any + } + } +} + +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 + } +} + +// MARK: NoEvent + +/// Useful for creating StateMachine without events, i.e. `Machine`. +public enum NoEvent: EventType +{ + public var hashValue: Int + { + return 0 + } +} + +public func == (lhs: NoEvent, rhs: NoEvent) -> Bool +{ + return true +} \ No newline at end of file diff --git a/SwiftState/HandlerID.swift b/SwiftState/HandlerID.swift new file mode 100644 index 0000000..20904e2 --- /dev/null +++ b/SwiftState/HandlerID.swift @@ -0,0 +1,31 @@ +// +// HandlerID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +public class HandlerID +{ + /// - Note: `nil` is used for error-handlerID + internal let transition: Transition? + + internal let key: String + + internal init(transition: Transition?, key: String) + { + self.transition = transition + self.key = key + } +} + +public class ChainHandlerID +{ + internal let bundledHandlerIDs: [HandlerID] + + internal init(bundledHandlerIDs: [HandlerID]) + { + self.bundledHandlerIDs = bundledHandlerIDs + } +} \ No newline at end of file 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/Machine.swift b/SwiftState/Machine.swift new file mode 100644 index 0000000..eae6168 --- /dev/null +++ b/SwiftState/Machine.swift @@ -0,0 +1,859 @@ +// +// Machine.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import Darwin + +public typealias HandlerOrder = UInt8 +private let _defaultOrder: HandlerOrder = 100 + +public class Machine +{ + // NOTE: `event = nil` is equivalent to `_Event.None`, which happens when non-event-based transition e.g. `tryState()` occurs. + public typealias Context = (event: E?, fromState: S, toState: S, userInfo: Any?) + + public typealias Condition = (Context -> Bool) + + public typealias Handler = Context -> () + + public typealias Mapping = (event: E?, fromState: S, userInfo: Any?) -> S? + + private var _routes: [_Event : [Transition : [String : Condition?]]] = [:] + + private var _routeMappings: [String : Mapping] = [:] + + private var _handlers: [Transition : [_HandlerInfo]] = [:] + private 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) + } + + //-------------------------------------------------- + // MARK: - State/Event/Transition + //-------------------------------------------------- + + public var state: S + { + return self._state + } + + public func hasRoute(transition: Transition, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool + { + guard let fromState = transition.fromState.value, + toState = transition.toState.value else + { + assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") + return false + } + + return self.hasRoute(fromState: fromState, toState: toState, forEvent: event, userInfo: userInfo) + } + + public func hasRoute(fromState fromState: S, toState: S, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool + { + if _hasRoute(fromState: fromState, toState: toState, forEvent: event, userInfo: userInfo) { + return true + } + + if _hasRouteMapping(fromState: fromState, toState: .Some(toState), forEvent: event, userInfo: userInfo) != nil { + return true + } + + return false + } + + public func hasRoute(fromState fromState: S, toState: S, forEvent event: E, userInfo: Any? = nil) -> Bool + { + return self.hasRoute(fromState: fromState, toState: toState, forEvent: .Some(event), userInfo: userInfo) + } + + /// Check for `_routes`. + private func _hasRoute(fromState fromState: S, toState: S, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool + { + let validTransitions = _validTransitions(fromState: fromState, toState: toState) + + for validTransition in validTransitions { + + var transitionDicts: [[Transition : [String : Condition?]]] = [] + + if let event = event { + for (ev, transitionDict) in self._routes { + if ev.value == event || ev == .Any { // NOTE: no .Default + transitionDicts += [transitionDict] + } + } + } + else { + transitionDicts += self._routes.values.lazy + } + + // check for `_routes + for transitionDict in transitionDicts { + if let keyConditionDict = transitionDict[validTransition] { + for (_, condition) in keyConditionDict { + if _canPassCondition(condition, forEvent: event, fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + } + } + } + } + + return false + } + + /// + /// Check for `_routeMappings`. + /// + /// - Returns: Preferred `mapped-toState` in case of `toState = .Any`. + /// + private func _hasRouteMapping(fromState fromState: S, toState: State, forEvent event: E? = nil, userInfo: Any? = nil) -> S? + { + for mapping in self._routeMappings.values { + if let mappedToState = mapping(event: event, fromState: fromState, userInfo: userInfo) + where mappedToState == toState.value || toState == .Any + { + return mappedToState + } + } + + return nil + } + + public func canTryState(toState: S, forEvent event: E? = nil) -> Bool + { + let fromState = self.state + + return self.hasRoute(fromState: fromState, toState: toState, forEvent: event) + } + + public func canTryState(toState: S, forEvent event: E) -> Bool + { + return self.canTryState(toState, forEvent: .Some(event)) + } + + public func tryState(toState: S, userInfo: Any? = nil) -> Bool + { + return self._tryState(toState, userInfo: userInfo, forEvent: nil) + } + + internal func _tryState(toState: S, userInfo: Any? = nil, forEvent event: E?) -> Bool + { + var didTransit = false + + let fromState = self.state + + if self.canTryState(toState, forEvent: event) { + + // collect valid handlers before updating state + let validHandlerInfos = self._validHandlerInfosForTransition(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 Machine 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: event, fromState: fromState, toState: toState, userInfo: userInfo)) + } + + didTransit = true + } + else { + for handlerInfo in self._errorHandlers { + handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) + } + } + + return didTransit + } + + private func _validHandlerInfosForTransition(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 + } + + public func canTryEvent(event: E, userInfo: Any? = nil) -> S? + { + if let transitionDict = self._routes[_Event.Some(event)] { + for (transition, keyConditionDict) in transitionDict { + if transition.fromState == .Some(self.state) || transition.fromState == .Any { + for (_, condition) in keyConditionDict { + // if toState is `.Any`, it means identity transition + let toState = transition.toState.value ?? self.state + + if _canPassCondition(condition, forEvent: .Some(event), fromState: self.state, toState: toState, userInfo: userInfo) { + return toState + } + } + } + } + } + + if let toState = _hasRouteMapping(fromState: self.state, toState: .Any, forEvent: event, userInfo: userInfo) { + return toState + } + + return nil + } + + public func tryEvent(event: E, userInfo: Any? = nil) -> Bool + { + if let toState = self.canTryEvent(event, userInfo: userInfo) { + self._tryState(toState, userInfo: userInfo, forEvent: .Some(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(route: Route) -> RouteID + { + return self._addRoute(route) + } + + internal func _addRoute(route: Route, forEvent event: _Event = .None) -> 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 key = _createUniqueString() + + var keyConditionDict = transitionDict[transition]! + keyConditionDict[key] = condition + transitionDict[transition] = keyConditionDict + + self._routes[event] = transitionDict + + let routeID = RouteID(event: event, transition: transition, key: key) + + 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(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) { context in + if _canPassCondition(condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + handler(context) + } + } + + return (routeID, handlerID) + } + + // MARK: removeRoute + + public func removeRoute(routeID: RouteID) -> Bool + { + let event = routeID.event + let transition = routeID.transition + + if var transitionDict = self._routes[event] { + if var keyConditionDict = transitionDict[transition] { + keyConditionDict[routeID.key] = nil + if keyConditionDict.count > 0 { + transitionDict[transition] = keyConditionDict + } + else { + transitionDict[transition] = nil + } + } + + if transitionDict.count > 0 { + self._routes[event] = transitionDict + } + else { + self._routes[event] = nil + } + + return true + } + + return false + } + + //-------------------------------------------------- + // MARK: - Handler + //-------------------------------------------------- + + public func addHandler(transition: Transition, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + 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(transition: transition, key: key) + + return handlerID + } + + // MARK: addEntryHandler + + public func addEntryHandler(state: State, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + return self.addHandler(.Any => state, handler: handler) + } + + public func addEntryHandler(state: S, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + return self.addHandler(.Any => .Some(state), handler: handler) + } + + // MARK: addExitHandler + + public func addExitHandler(state: State, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + return self.addHandler(state => .Any, handler: handler) + } + + public func addExitHandler(state: S, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + return self.addHandler(.Some(state) => .Any, handler: handler) + } + + // MARK: removeHandler + + public func removeHandler(handlerID: HandlerID) -> Bool + { + if let transition = handlerID.transition { + if var handlerInfos = self._handlers[transition] { + + if _removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { + self._handlers[transition] = handlerInfos + return true + } + } + } + // `transition = nil` means errorHandler + else { + if _removeHandlerFromArray(&self._errorHandlers, removingHandlerID: handlerID) { + return true + } + return false + } + + return false + } + + // MARK: addErrorHandler + + public func addErrorHandler(order order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + let key = _createUniqueString() + + let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) + _insertHandlerIntoArray(&self._errorHandlers, newHandlerInfo: newHandlerInfo) + + let handlerID = HandlerID(transition: nil, key: key) + + return handlerID + } + + //-------------------------------------------------- + // MARK: - RouteChain + //-------------------------------------------------- + // NOTE: handler is required for addRouteChain + + // MARK: addRouteChain + conditional handler + + public func addRouteChain(chain: TransitionChain, handler: Handler) -> (RouteChainID, ChainHandlerID) + { + return self.addRouteChain(chain, condition: nil, handler: handler) + } + + public func addRouteChain(chain: TransitionChain, condition: Condition?, handler: Handler) -> (RouteChainID, ChainHandlerID) + { + let routeChain = RouteChain(transitionChain: chain, condition: condition) + return self.addRouteChain(routeChain, handler: handler) + } + + public func addRouteChain(chain: RouteChain, handler: Handler) -> (RouteChainID, ChainHandlerID) + { + var routeIDs: [RouteID] = [] + + for route in chain.routes { + let routeID = self.addRoute(route) + routeIDs += [routeID] + } + + let chainHandlerID = self.addChainHandler(chain, handler: handler) + + let routeChainID = RouteChainID(bundledRouteIDs: routeIDs) + + return (routeChainID, chainHandlerID) + } + + // MARK: addChainHandler + + public func addChainHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID + { + return self.addChainHandler(chain.toRouteChain(), order: order, handler: handler) + } + + public func addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID + { + return self._addChainHandler(chain, order: order, handler: handler, isError: false) + } + + // MARK: addChainErrorHandler + + public func addChainErrorHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID + { + return self.addChainErrorHandler(chain.toRouteChain(), order: order, handler: handler) + } + + public func addChainErrorHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID + { + return self._addChainHandler(chain, order: order, handler: handler, isError: true) + } + + private func _addChainHandler(chain: RouteChain, order: HandlerOrder, handler: Handler, isError: Bool) -> ChainHandlerID + { + 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) { 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 + } + } + } + handlerIDs += [handlerID] + + // increment chainingCount on every route + for route in chain.routes { + + handlerID = 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 + } + } + } + handlerIDs += [handlerID] + } + + // increment allCount (+ invoke chainErrorHandler) on any routes + handlerID = self.addHandler(.Any => .Any, 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) { 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) + } + } + } + } + handlerIDs += [handlerID] + + let chainHandlerID = ChainHandlerID(bundledHandlerIDs: handlerIDs) + + return chainHandlerID + } + + // MARK: removeRouteChain + + public func removeRouteChain(routeChainID: RouteChainID) -> Bool + { + var success = false + for bundledRouteID in routeChainID.bundledRouteIDs! { + success = self.removeRoute(bundledRouteID) || success + } + return success + } + + // MARK: removeChainHandler + + public func removeChainHandler(chainHandlerID: ChainHandlerID) -> Bool + { + var success = false + for bundledHandlerID in chainHandlerID.bundledHandlerIDs { + success = self.removeHandler(bundledHandlerID) || success + } + return success + } + + //-------------------------------------------------- + // 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: E, transitions: [Transition], condition: Condition? = nil) -> [RouteID] + { + return self.addRouteEvent(.Some(event), transitions: transitions, condition: condition) + } + + public func addRouteEvent(event: Event, routes: [Route]) -> [RouteID] + { + var routeIDs: [RouteID] = [] + for route in routes { + let routeID = self._addRoute(route, forEvent: event._toInternal()) + routeIDs += [routeID] + } + + return routeIDs + } + + public func addRouteEvent(event: E, routes: [Route]) -> [RouteID] + { + return self.addRouteEvent(.Some(event), routes: routes) + } + + // MARK: addRouteEvent + conditional handler + + public func addRouteEvent(event: Event, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> ([RouteID], HandlerID) + { + let routeIDs = self.addRouteEvent(event, transitions: transitions, condition: condition) + + let handlerID = self.addEventHandler(event, handler: handler) + + return (routeIDs, handlerID) + } + + public func addRouteEvent(event: E, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> ([RouteID], HandlerID) + { + return self.addRouteEvent(.Some(event), transitions: transitions, condition: 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, handler: handler) + + return (routeIDs, handlerID) + } + + public func addRouteEvent(event: E, routes: [Route], handler: Handler) -> ([RouteID], HandlerID) + { + return self.addRouteEvent(.Some(event), routes: routes, handler: handler) + } + + // MARK: addEventHandler + + public func addEventHandler(event: Event, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + let handlerID = self.addHandler(.Any => .Any, order: order) { context in + // skip if not event-based transition + guard let triggeredEvent = context.event else { + return + } + + if triggeredEvent == event.value || event == .Any { + handler(context) + } + } + + return handlerID + } + + public func addEventHandler(event: E, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID + { + return self.addEventHandler(.Some(event), order: order, handler: handler) + } + + //-------------------------------------------------- + // MARK: - RouteMapping + //-------------------------------------------------- + + // MARK: addRouteMapping + + public func addRouteMapping(routeMapping: Mapping) -> RouteMappingID + { + let key = _createUniqueString() + + self._routeMappings[key] = routeMapping + + let routeID = RouteMappingID(key: key) + + return routeID + } + + // MARK: addRouteMapping + conditional handler + + public func addRouteMapping(routeMapping: Mapping, handler: Handler) -> (RouteMappingID, HandlerID) + { + let routeMappingID = self.addRouteMapping(routeMapping) + + let handlerID = self.addHandler(.Any => .Any) { context in + if self._hasRouteMapping(fromState: context.fromState, toState: .Some(context.toState), forEvent: context.event, userInfo: context.userInfo) != nil { + + handler(context) + } + } + + return (routeMappingID, handlerID) + } + + // MARK: removeRouteMapping + + public func removeRouteMapping(routeMappingID: RouteMappingID) -> Bool + { + if self._routeMappings[routeMappingID.key] != nil { + self._routeMappings[routeMappingID.key] = nil + return true + } + else { + return false + } + } + + //-------------------------------------------------- + // MARK: - Remove All + //-------------------------------------------------- + + public func removeAllRoutes() -> Bool + { + let removingCount = self._routes.count + self._routeMappings.count + + self._routes = [:] + self._routeMappings = [:] + + return removingCount > 0 + } + + public func removeAllHandlers() -> Bool + { + let removingCount = self._handlers.count + self._errorHandlers.count + + self._handlers = [:] + self._errorHandlers = [] + + return removingCount > 0 + } + +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +// MARK: <- (tryState) + +infix operator <- { associativity right } + +public func <- (machine: Machine, state: S) -> Bool +{ + return machine.tryState(state) +} + +public func <- (machine: Machine, tuple: (S, Any?)) -> Bool +{ + return machine.tryState(tuple.0, userInfo: tuple.1) +} + +// MARK: <-! (tryEvent) + +infix operator <-! { associativity right } + +public func <-! (machine: Machine, event: E) -> Bool +{ + return machine.tryEvent(event) +} + +public func <-! (machine: Machine, tuple: (E, Any?)) -> Bool +{ + return machine.tryEvent(tuple.0, userInfo: tuple.1) +} + +//-------------------------------------------------- +// MARK: - Private +//-------------------------------------------------- + +// generate approx 126bit random string +private func _createUniqueString() -> String +{ + var uniqueString: String = "" + for _ in 1...8 { + uniqueString += String(UnicodeScalar(arc4random_uniform(0xD800))) // 0xD800 = 55296 = 15.755bit + } + return uniqueString +} + + +private func _validTransitions(fromState fromState: S, toState: S) -> [Transition] +{ + return [ + fromState => toState, + fromState => .Any, + .Any => toState, + .Any => .Any + ] +} + +private func _canPassCondition(condition: Machine.Condition?, forEvent event: E?, fromState: S, toState: S, userInfo: Any?) -> Bool +{ + return condition?((event, fromState, toState, userInfo)) ?? true +} + +private 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.. +{ + private let order: HandlerOrder + private let key: String + private let handler: Machine.Handler + + private init(order: HandlerOrder, key: String, handler: Machine.Handler) + { + self.order = order + self.key = key + self.handler = handler + } +} diff --git a/SwiftState/Route.swift b/SwiftState/Route.swift new file mode 100644 index 0000000..cc65ff6 --- /dev/null +++ b/SwiftState/Route.swift @@ -0,0 +1,70 @@ +// +// Route.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/04. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +public struct Route +{ + public let transition: Transition + public let condition: Machine.Condition? + + public init(transition: Transition, condition: Machine.Condition?) + { + self.transition = transition + self.condition = condition + } + + public func toTransition() -> Transition + { + return self.transition + } + + public func toRouteChain() -> RouteChain + { + var routes: [Route] = [] + routes += [self] + return RouteChain(routes: routes) + } +} + +//-------------------------------------------------- +// 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/SwiftState/RouteChain.swift b/SwiftState/RouteChain.swift new file mode 100644 index 0000000..0f1bab4 --- /dev/null +++ b/SwiftState/RouteChain.swift @@ -0,0 +1,58 @@ +// +// RouteChain.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/04. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +public struct RouteChain +{ + internal var routes: [Route] + + public init(routes: [Route]) + { + self.routes = routes + } + + public init(transitionChain: TransitionChain, condition: Machine.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: Machine.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: Machine.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 TransitionChain(transitions: transitions) + } + + public func toRoutes() -> [Route] + { + return self.routes + } +} \ No newline at end of file diff --git a/SwiftState/RouteChainID.swift b/SwiftState/RouteChainID.swift new file mode 100644 index 0000000..2355f12 --- /dev/null +++ b/SwiftState/RouteChainID.swift @@ -0,0 +1,17 @@ +// +// RouteChainID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +public class RouteChainID +{ + internal let bundledRouteIDs: [RouteID]? + + internal init(bundledRouteIDs: [RouteID]?) + { + self.bundledRouteIDs = bundledRouteIDs + } +} \ No newline at end of file diff --git a/SwiftState/RouteID.swift b/SwiftState/RouteID.swift new file mode 100644 index 0000000..ffe529b --- /dev/null +++ b/SwiftState/RouteID.swift @@ -0,0 +1,21 @@ +// +// RouteID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +public class RouteID +{ + internal let event: _Event + internal let transition: Transition + internal let key: String + + internal init(event: _Event, transition: Transition, key: String) + { + self.transition = transition + self.event = event + self.key = key + } +} \ No newline at end of file diff --git a/SwiftState/RouteMappingID.swift b/SwiftState/RouteMappingID.swift new file mode 100644 index 0000000..6a59316 --- /dev/null +++ b/SwiftState/RouteMappingID.swift @@ -0,0 +1,17 @@ +// +// RouteMappingID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-10. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +public class RouteMappingID +{ + internal let key: String + + internal init(key: String) + { + self.key = key + } +} \ 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 index 4c62823..591f3e2 100644 --- a/SwiftState/StateType.swift +++ b/SwiftState/StateType.swift @@ -6,7 +6,44 @@ // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. // -public protocol StateType: Hashable, NilLiteralConvertible +public protocol StateType: Hashable {} + +// MARK: State + +/// `StateType` wrapper for handling`.Any` state. +public enum State: Hashable { + case Some(S) + case Any + + public var hashValue: Int + { + switch self { + case .Some(let x): return x.hashValue + case .Any: return -4611686018427387904 + } + } + public var value: 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 } \ 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/SwiftState/Transition.swift b/SwiftState/Transition.swift new file mode 100644 index 0000000..718cacd --- /dev/null +++ b/SwiftState/Transition.swift @@ -0,0 +1,94 @@ +// +// Transition.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/03. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +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 + } + + public func toTransitionChain() -> TransitionChain + { + return TransitionChain(transition: self) + } + + public func toRoute() -> Route + { + return Route(transition: self, condition: nil) + } +} + +// 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/SwiftState/TransitionChain.swift b/SwiftState/TransitionChain.swift new file mode 100644 index 0000000..c875740 --- /dev/null +++ b/SwiftState/TransitionChain.swift @@ -0,0 +1,135 @@ +// +// TransitionChain.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/04. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +public struct TransitionChain +{ + public var states: [State] + + public init(transition: Transition) + { + self.init(transitions: [transition]) + } + + public init(transitions: [Transition]) + { + assert(transitions.count > 0, "TransitionChain must be initialized with at least 1 transition.") + + var states: [State] = [] + for i in 0..] + { + var transitions: [Transition] = [] + + 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 prepend(state: S) + { + self.states.insert(.Some(state), atIndex: 0) + } + + mutating public func append(state: State) + { + self.states += [state] + } + + mutating public func append(state: S) + { + self.states += [.Some(state)] + } + + public func toRouteChain() -> RouteChain + { + return RouteChain(transitionChain: self, condition: nil) + } + + public func toTransitions() -> [Transition] + { + return self.transitions + } +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +// e.g. (.State0 => .State1) => .State +public func => (left: Transition, right: State) -> TransitionChain +{ + return left.toTransitionChain() => right +} + +public func => (left: Transition, right: S) -> TransitionChain +{ + return left => .Some(right) +} + +public func => (var left: TransitionChain, right: State) -> TransitionChain +{ + left.append(right) + return left +} + +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 => right.toTransitionChain() +} + +public func => (left: S, right:Transition) -> TransitionChain +{ + return .Some(left) => right +} + +public func => (left: State, var right: TransitionChain) -> TransitionChain +{ + right.prepend(left) + return right +} + +public func => (left: S, right: TransitionChain) -> TransitionChain +{ + return .Some(left) => right +} diff --git a/SwiftStateTests/BasicTests.swift b/SwiftStateTests/BasicTests.swift index 8979851..bd3389b 100644 --- a/SwiftStateTests/BasicTests.swift +++ b/SwiftStateTests/BasicTests.swift @@ -13,11 +13,11 @@ class BasicTests: _TestCase { func testREADME() { - let machine = StateMachine(state: .State0) { machine in + 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)) machine.addHandler(.State0 => .State1) { context in @@ -25,39 +25,126 @@ class BasicTests: _TestCase } // add errorHandler - machine.addErrorHandler { (event, transition, order, userInfo) in - print("[ERROR] \(transition.fromState) => \(transition.toState)") + machine.addErrorHandler { event, fromState, toState, userInfo in + print("[ERROR] \(fromState) => \(toState)") } } // 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 + XCTAssertTrue(machine.state == .State1) + + print("machine.state = \(machine.state)") + } + + func testREADME_string() + { + let machine = Machine(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" + XCTAssertTrue(machine.state == ".State1") + + machine <- (".State2", "Hello") + XCTAssertTrue(machine.state == ".State2") + + machine <- (".State1", "Bye") + XCTAssertTrue(machine.state == ".State1") + + machine <- ".State0" // fail: no 1 => 0 + XCTAssertTrue(machine.state == ".State1") print("machine.state = \(machine.state)") } + + + + func testREADME_MyState2() + { + let machine = Machine(state: .State0("0")) { machine in + + machine.addRoute(.State0("0") => .State0("1")) + machine.addRoute(.Any => .State0("2")) { context in print("Any => 2, msg=\(context.userInfo)") } + machine.addRoute(.State0("2") => .Any) { context in print("2 => Any, msg=\(context.userInfo)") } + + // add handler (handlerContext = (event, transition, order, userInfo)) + machine.addHandler(.State0("0") => .State0("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 <- .State0("1") + XCTAssertTrue(machine.state == .State0("1")) + + machine <- (.State0("2"), "Hello") + XCTAssertTrue(machine.state == .State0("2")) + + machine <- (.State0("1"), "Bye") + XCTAssertTrue(machine.state == .State0("1")) + + machine <- .State0("0") // fail: no 1 => 0 + XCTAssertTrue(machine.state == .State0("1")) + + print("machine.state = \(machine.state)") + } + + + + + func testExample() { - let machine = StateMachine(state: .State0) { + let machine = Machine(state: .State0) { // add 0 => 1 $0.addRoute(.State0 => .State1) { context in - print("[Transition 0=>1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") + print("[Transition 0=>1] \(context.fromState) => \(context.toState)") } // add 0 => 1 once more $0.addRoute(.State0 => .State1) { context in - print("[Transition 0=>1b] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") + print("[Transition 0=>1b] \(context.fromState) => \(context.toState)") } // add 2 => Any - $0.addRoute(.State2 => nil) { context in - print("[Transition exit 2] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue) (Any)") + $0.addRoute(.State2 => .Any) { context in + print("[Transition exit 2] \(context.fromState) => \(context.toState) (Any)") } // add Any => 2 - $0.addRoute(nil => .State2) { context in - print("[Transition Entry 2] \(context.transition.fromState.rawValue) (Any) => \(context.transition.toState.rawValue)") + $0.addRoute(.Any => .State2) { context in + print("[Transition Entry 2] \(context.fromState) (Any) => \(context.toState)") } // add 1 => 0 (no handler) $0.addRoute(.State1 => .State0) @@ -94,35 +181,35 @@ class BasicTests: _TestCase // error $0.addErrorHandler { context in - print("[ERROR 1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") + print("[ERROR 1] \(context.fromState) => \(context.toState)") } // entry $0.addEntryHandler(.State0) { context in - print("[Entry 0] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") // NOTE: this should not be called + print("[Entry 0] \(context.fromState) => \(context.toState)") // NOTE: this should not be called } $0.addEntryHandler(.State1) { context in - print("[Entry 1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") + print("[Entry 1] \(context.fromState) => \(context.toState)") } $0.addEntryHandler(.State2) { context in - print("[Entry 2] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") + print("[Entry 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } $0.addEntryHandler(.State2) { context in - print("[Entry 2b] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") + print("[Entry 2b] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } // exit $0.addExitHandler(.State0) { context in - print("[Exit 0] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") + print("[Exit 0] \(context.fromState) => \(context.toState)") } $0.addExitHandler(.State1) { context in - print("[Exit 1] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue)") + print("[Exit 1] \(context.fromState) => \(context.toState)") } $0.addExitHandler(.State2) { context in - print("[Exit 2] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") + print("[Exit 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } $0.addExitHandler(.State2) { context in - print("[Exit 2b] \(context.transition.fromState.rawValue) => \(context.transition.toState.rawValue), userInfo = \(context.userInfo)") + print("[Exit 2b] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } } diff --git a/SwiftStateTests/BugFixTests.swift b/SwiftStateTests/BugFixTests.swift index 40544a8..17cac05 100644 --- a/SwiftStateTests/BugFixTests.swift +++ b/SwiftStateTests/BugFixTests.swift @@ -11,12 +11,13 @@ import XCTest class BugFixTests: _TestCase { - /// `hasRoute` test for "AnyEvent" & "SomeEvent" routes + // Fix hasRoute() bug when there are routes for both AnyEvent & SomeEvent. + // https://github.com/ReactKit/SwiftState/pull/19 func testHasRoute_anyEvent_someEvent() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - machine.addRoute(.State0 => .State1); + machine.addRoute(.State0 => .State1) machine.addRouteEvent(.Event0, transitions: [.State1 => .State2]) let hasRoute = machine.hasRoute(.State1 => .State2, forEvent: .Event0) diff --git a/SwiftStateTests/FrogcjnTest.swift b/SwiftStateTests/FrogcjnTest.swift index 611072f..8653faf 100644 --- a/SwiftStateTests/FrogcjnTest.swift +++ b/SwiftStateTests/FrogcjnTest.swift @@ -15,12 +15,6 @@ private enum _State: StateType, Hashable { case Pending case Loading(Int) - case AnyState - - init(nilLiteral: ()) - { - self = AnyState - } var hashValue: Int { @@ -29,8 +23,6 @@ private enum _State: StateType, Hashable return "Pending".hashValue case let .Loading(x): return "Loading\(x)".hashValue - case .AnyState: - return "AnyState".hashValue } } } @@ -42,23 +34,15 @@ private func ==(lhs: _State, rhs: _State) -> Bool return true case let (.Loading(x1), .Loading(x2)): return x1 == x2 - case (.AnyState, .AnyState): - return true default: return false } } -private enum _Event: StateEventType, Hashable +private enum _Event: EventType, Hashable { case CancelAction case LoadAction(Int) - case AnyEvent - - init(nilLiteral: ()) - { - self = AnyEvent - } var hashValue: Int { @@ -67,8 +51,6 @@ private enum _Event: StateEventType, Hashable return "CancelAction".hashValue case let .LoadAction(x): return "LoadAction\(x)".hashValue - case .AnyEvent: - return "AnyEvent".hashValue } } } @@ -80,8 +62,6 @@ private func ==(lhs: _Event, rhs: _Event) -> Bool return true case let (.LoadAction(x1), .LoadAction(x2)): return x1 == x2 - case (.AnyEvent, .AnyEvent): - return true default: return false } @@ -93,9 +73,9 @@ class FrogcjnTest: _TestCase { var count = 0 - let machine = StateMachine<_State, _Event>(state: .Pending) { machine in + let machine = Machine<_State, _Event>(state: .Pending) { machine in - machine.addRouteEvent(.CancelAction, transitions: [ .AnyState => .Pending ], condition: { $0.fromState != .Pending }) + machine.addRouteEvent(.CancelAction, transitions: [ .Any => .Pending ], condition: { $0.fromState != .Pending }) // // If you have **finite** number of `LoadActionId`s (let's say 1 to 100), @@ -103,11 +83,68 @@ class FrogcjnTest: _TestCase // (In this case, `LoadActionId` should have enum type instead) // for actionId in 1...100 { - machine.addRouteEvent(.LoadAction(actionId), transitions: [ .AnyState => .Loading(actionId) ], condition: { $0.fromState != .Loading(actionId) }) + machine.addRouteEvent(.LoadAction(actionId), transitions: [ .Any => .Loading(actionId) ], condition: { $0.fromState != .Loading(actionId) }) + } + + // increment `count` when any events i.e. `.CancelAction` and `.LoadAction(x)` succeed. + machine.addEventHandler(.Any) { event, transition, order, userInfo in + count++ + } + } + + // initial + XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(count, 0) + + // CancelAction (to .Pending state, same as before) + machine <-! .CancelAction + XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(count, 0, "`tryEvent()` failed, and `count` should not be incremented.") + + // LoadAction(1) (to .Loading(1) state) + machine <-! .LoadAction(1) + XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(count, 1) + + // LoadAction(1) (same as before) + machine <-! .LoadAction(1) + print(machine.state) + XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(count, 1, "`tryEvent()` failed, and `count` should not be incremented.") + + machine <-! .LoadAction(2) + XCTAssertTrue(machine.state == .Loading(2)) + XCTAssertEqual(count, 2) + + machine <-! .CancelAction + XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(count, 3) + } + + func testEventWithAssociatedValue2() + { + var count = 0 + + let machine = Machine<_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.addEventHandler(.AnyEvent) { event, transition, order, userInfo in + machine.addEventHandler(.Any) { event, transition, order, userInfo in count++ } } 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 index 9dcd7ae..bb640fc 100644 --- a/SwiftStateTests/MyEvent.swift +++ b/SwiftStateTests/MyEvent.swift @@ -8,13 +8,29 @@ import SwiftState -enum MyEvent: StateEventType +enum MyEvent: EventType { case Event0, Event1 - case AnyEvent // IMPORTANT: create case=Any & use it in convertFromNilLiteral() +} + +enum MyEvent2: EventType +{ + case Event0(String) - init(nilLiteral: Void) + var hashValue: Int { - self = AnyEvent + switch self { + case .Event0(let str): return str.hashValue + } + } +} + +func == (lhs: MyEvent2, rhs: MyEvent2) -> Bool +{ + switch (lhs, rhs) { + case let (.Event0(str1), .Event0(str2)): + return str1 == str2 +// default: +// return false } } \ No newline at end of file diff --git a/SwiftStateTests/MyState.swift b/SwiftStateTests/MyState.swift index 92139ce..c2d3660 100644 --- a/SwiftStateTests/MyState.swift +++ b/SwiftStateTests/MyState.swift @@ -8,24 +8,27 @@ import SwiftState -enum MyState: Int, StateType, CustomStringConvertible +enum MyState: Int, StateType { 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) +} + +enum MyState2: StateType +{ + case State0(String) - init(nilLiteral: Void) + var hashValue: Int { - self = AnyState + switch self { + case .State0(let str): return str.hashValue + } } - - var description: String - { - return "\(self.rawValue)" +} + +func == (lhs: MyState2, rhs: MyState2) -> Bool +{ + switch (lhs, rhs) { + case let (.State0(str1), .State0(str2)): + return str1 == str2 } } \ No newline at end of file diff --git a/SwiftStateTests/QiitaTests.swift b/SwiftStateTests/QiitaTests.swift index f5852ba..1f11e36 100644 --- a/SwiftStateTests/QiitaTests.swift +++ b/SwiftStateTests/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 = Machine(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/RasmusTest.swift b/SwiftStateTests/RasmusTest.swift index 2eb3355..423e13a 100644 --- a/SwiftStateTests/RasmusTest.swift +++ b/SwiftStateTests/RasmusTest.swift @@ -12,29 +12,23 @@ import XCTest enum RasmusTestState: StateType { case Any case State1, State2, State3, State4 - init(nilLiteral: Void) { - self = Any - } } -enum RasmusTestEvent: StateEventType { +enum RasmusTestEvent: EventType { case Any - case State2FromState1 - case State4FromState2OrState3 - init(nilLiteral: Void) { - self = Any - } + case State2fromState1 + case State4fromState2OrState3 } class RasmusTest: _TestCase { - func testStateRoute() { - let stateMachine = StateMachine(state: .State1) - stateMachine.addRouteEvent(.State2FromState1, transitions: [ .State1 => .State2 ]) - stateMachine.addRouteEvent(.State4FromState2OrState3, routes: [ [ .State2, .State3 ] => .State4 ]) + func testRoute() { + let stateMachine = Machine(state: .State1) + stateMachine.addRouteEvent(.State2fromState1, transitions: [ .State1 => .State2 ]) + stateMachine.addRouteEvent(.State4fromState2OrState3, routes: [ [ .State2, .State3 ] => .State4 ]) XCTAssertEqual(stateMachine.state, RasmusTestState.State1) - stateMachine <-! .State2FromState1 + stateMachine <-! .State2fromState1 XCTAssertEqual(stateMachine.state, RasmusTestState.State2) - stateMachine <-! .State4FromState2OrState3 + stateMachine <-! .State4fromState2OrState3 XCTAssertEqual(stateMachine.state, RasmusTestState.State4) } } diff --git a/SwiftStateTests/StateMachineChainTests.swift b/SwiftStateTests/StateMachineChainTests.swift index a7669a4..1f9e86d 100644 --- a/SwiftStateTests/StateMachineChainTests.swift +++ b/SwiftStateTests/StateMachineChainTests.swift @@ -1,5 +1,5 @@ // -// StateMachineChainTests.swift +// MachineChainTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/04. @@ -9,11 +9,11 @@ import SwiftState import XCTest -class StateMachineChainTests: _TestCase +class MachineChainTests: _TestCase { func testAddRouteChain() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 @@ -44,13 +44,13 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_condition() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var flag = false var invokeCount = 0 // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2, condition: flag) { context in + machine.addRouteChain(.State0 => .State1 => .State2, condition: { _ in flag }) { context in invokeCount++ return } @@ -78,7 +78,7 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_failBySkipping() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -91,7 +91,7 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_failByHangingAround() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -107,7 +107,7 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_succeedByFailingHangingAround() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 @@ -128,7 +128,7 @@ class StateMachineChainTests: _TestCase func testAddRouteChain_goBackHomeAWhile() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 @@ -150,11 +150,11 @@ class StateMachineChainTests: _TestCase // https://github.com/inamiy/SwiftState/issues/2 func testAddRouteChain_goBackHomeAWhile2() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 - machine.addRoute(nil => nil) // connect all states + 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 @@ -187,18 +187,18 @@ class StateMachineChainTests: _TestCase func testRemoveRouteChain() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 // add 0 => 1 => 2 - let (routeID, _) = machine.addRouteChain(.State0 => .State1 => .State2) { context in + let (routeChainID, _) = machine.addRouteChain(.State0 => .State1 => .State2) { context in invokeCount++ return } - // removeRoute - machine.removeRoute(routeID) + // removeRouteChain + machine.removeRouteChain(routeChainID) // tryState 0 => 1 let success = machine <- .State1 @@ -209,18 +209,18 @@ class StateMachineChainTests: _TestCase func testRemoveChainHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 // add 0 => 1 => 2 - let (_, handlerID) = machine.addRouteChain(.State0 => .State1 => .State2) { context in + let (_, chainHandlerID) = machine.addRouteChain(.State0 => .State1 => .State2) { context in invokeCount++ return } // removeHandler - XCTAssertTrue(machine.removeHandler(handlerID)) + XCTAssertTrue(machine.removeChainHandler(chainHandlerID)) // tryState 0 => 1 => 2 machine <- .State1 @@ -232,13 +232,13 @@ class StateMachineChainTests: _TestCase func testAddChainErrorHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var errorCount = 0 let transitionChain = MyState.State0 => .State1 => .State2 - machine.addRoute(nil => nil) // connect all states + machine.addRoute(.Any => .Any) // connect all states // add 0 => 1 => 2 machine.addRouteChain(transitionChain) { context in @@ -265,22 +265,22 @@ class StateMachineChainTests: _TestCase func testRemoveChainErrorHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var errorCount = 0 let transitionChain = MyState.State0 => .State1 => .State2 - machine.addRoute(nil => nil) // connect all states + machine.addRoute(.Any => .Any) // connect all states // add 0 => 1 => 2 chainErrorHandler - let handlerID = machine.addChainErrorHandler(transitionChain) { context in + let chainHandlerID = machine.addChainErrorHandler(transitionChain) { context in errorCount++ return } // remove chainErrorHandler - machine.removeHandler(handlerID) + machine.removeChainHandler(chainHandlerID) // tryState 0 (starting state) => 1 => 0 machine <- .State1 diff --git a/SwiftStateTests/StateMachineEventTests.swift b/SwiftStateTests/StateMachineEventTests.swift index ee42479..a3e429e 100644 --- a/SwiftStateTests/StateMachineEventTests.swift +++ b/SwiftStateTests/StateMachineEventTests.swift @@ -1,5 +1,5 @@ // -// StateMachineEventTests.swift +// MachineEventTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/05. @@ -9,11 +9,11 @@ import SwiftState import XCTest -class StateMachineEventTests: _TestCase +class MachineEventTests: _TestCase { func testCanTryEvent() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 & 1 => 2 // (NOTE: this is not chaining e.g. 0 => 1 => 2) @@ -23,12 +23,11 @@ class StateMachineEventTests: _TestCase ]) XCTAssertTrue(machine.canTryEvent(.Event0) != nil) - XCTAssertTrue(machine.canTryEvent(.AnyEvent) != nil) } func testTryState() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 & 1 => 2 // (NOTE: this is not chaining e.g. 0 => 1 => 2) @@ -53,7 +52,7 @@ class StateMachineEventTests: _TestCase func testTryEvent() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 => 2 machine.addRouteEvent(.Event0, transitions: [ @@ -78,8 +77,8 @@ class StateMachineEventTests: _TestCase /// https://github.com/ReactKit/SwiftState/issues/20 func testTryEvent_issue20() { - let machine = StateMachine(state: MyState.State2) { machine in - machine.addRouteEvent(.Event0, transitions: [.AnyState => .State0]) + let machine = Machine(state: MyState.State2) { machine in + machine.addRouteEvent(.Event0, transitions: [.Any => .State0]) } XCTAssertTrue(machine <-! .Event0) @@ -90,9 +89,9 @@ class StateMachineEventTests: _TestCase func testTryEvent_issue28() { var eventCount = 0 - let machine = StateMachine(state: .State0) { machine in + let machine = Machine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Event0, transitions: [nil => nil]) { _ in + machine.addRouteEvent(.Event0, transitions: [.Any => .Any]) { _ in eventCount++ } } @@ -113,7 +112,7 @@ class StateMachineEventTests: _TestCase func testTryEvent_string() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 => 2 machine.addRouteEvent("Run", transitions: [ @@ -137,7 +136,7 @@ class StateMachineEventTests: _TestCase func testAddRouteEvent_multiple() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 => 2 machine.addRouteEvent(.Event0, transitions: [ @@ -186,7 +185,7 @@ class StateMachineEventTests: _TestCase func testAddRouteEvent_handler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 @@ -212,7 +211,7 @@ class StateMachineEventTests: _TestCase func testAddEventHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 @@ -240,7 +239,7 @@ class StateMachineEventTests: _TestCase func testRemoveRouteEvent() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 @@ -273,7 +272,7 @@ class StateMachineEventTests: _TestCase func testRemoveEventHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var invokeCount = 0 diff --git a/SwiftStateTests/StateMachineTests.swift b/SwiftStateTests/StateMachineTests.swift index 7ecf416..1c942ea 100644 --- a/SwiftStateTests/StateMachineTests.swift +++ b/SwiftStateTests/StateMachineTests.swift @@ -1,6 +1,6 @@ // -// StateMachineTests.swift -// StateMachineTests +// MachineTests.swift +// MachineTests // // Created by Yasuhiro Inami on 2014/08/03. // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. @@ -9,11 +9,11 @@ import SwiftState import XCTest -class StateMachineTests: _TestCase +class MachineTests: _TestCase { func testInit() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) XCTAssertEqual(machine.state, MyState.State0) } @@ -25,7 +25,7 @@ class StateMachineTests: _TestCase // add state1 => state2 func testAddRoute() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) machine.addRoute(.State0 => .State1) @@ -37,12 +37,12 @@ class StateMachineTests: _TestCase XCTAssertFalse(machine.hasRoute(.State1 => .State2)) } - // add nil => state + // add .Any => state func testAddRoute_fromAnyState() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - machine.addRoute(nil => .State1) // Any => State1 + machine.addRoute(.Any => .State1) // Any => State1 XCTAssertFalse(machine.hasRoute(.State0 => .State0)) XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true @@ -52,12 +52,12 @@ class StateMachineTests: _TestCase XCTAssertFalse(machine.hasRoute(.State1 => .State2)) } - // add state => nil + // add state => .Any func testAddRoute_toAnyState() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - machine.addRoute(.State1 => nil) // State1 => Any + machine.addRoute(.State1 => .Any) // State1 => Any XCTAssertFalse(machine.hasRoute(.State0 => .State0)) XCTAssertFalse(machine.hasRoute(.State0 => .State1)) @@ -67,12 +67,12 @@ class StateMachineTests: _TestCase XCTAssertTrue(machine.hasRoute(.State1 => .State2)) // true } - // add nil => nil + // add .Any => .Any func testAddRoute_bothAnyState() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - machine.addRoute(nil => nil) // Any => Any + machine.addRoute(.Any => .Any) // Any => Any XCTAssertTrue(machine.hasRoute(.State0 => .State0)) // true XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true @@ -85,7 +85,7 @@ class StateMachineTests: _TestCase // add state0 => state0 func testAddRoute_sameState() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) machine.addRoute(.State0 => .State0) @@ -98,12 +98,12 @@ class StateMachineTests: _TestCase // add route + condition func testAddRoute_condition() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var flag = false // add 0 => 1 - machine.addRoute(.State0 => .State1, condition: flag) + machine.addRoute(.State0 => .State1, condition: { _ in flag }) XCTAssertFalse(machine.hasRoute(.State0 => .State1)) @@ -115,11 +115,11 @@ class StateMachineTests: _TestCase // add route + condition + blacklist func testAddRoute_condition_blacklist() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => Any, except 0 => 2 - machine.addRoute(.State0 => nil, condition: { transition in - return transition.toState != .State2 + machine.addRoute(.State0 => .Any, condition: { context in + return context.toState != .State2 }) XCTAssertTrue(machine.hasRoute(.State0 => .State0)) @@ -131,45 +131,50 @@ class StateMachineTests: _TestCase // add route + handler func testAddRoute_handler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - var returnedTransition: StateTransition? + var fromState: MyState? + var toState: MyState? machine.addRoute(.State0 => .State1) { context in - returnedTransition = context.transition + fromState = context.fromState + toState = context.toState } - XCTAssertTrue(returnedTransition == nil, "Transition has not started yet.") + XCTAssertTrue(fromState == nil, "Transition has not started yet.") + XCTAssertTrue(toState == nil, "Transition has not started yet.") // tryState 0 => 1 machine <- .State1 - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) + XCTAssertEqual(fromState, MyState.State0) + XCTAssertEqual(toState, MyState.State1) } // add route + conditional handler func testAddRoute_conditionalHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) var flag = false - var returnedTransition: StateTransition? + var fromState: MyState? + var toState: MyState? // 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 + machine.addRoute(.State0 => .State1, condition: { _ in flag }) { context in + fromState = context.fromState + toState = context.toState } // 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.") + XCTAssertTrue(fromState == nil, "Conditional handler should NOT be performed because flag=false.") + XCTAssertTrue(toState == nil, "Conditional handler should NOT be performed because flag=false.") // add 1 => 0 for resetting state machine.addRoute(.State1 => .State0) @@ -182,16 +187,15 @@ class StateMachineTests: _TestCase // tryState 0 => 1 machine <- .State1 - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) + XCTAssertEqual(fromState, MyState.State0) + XCTAssertEqual(toState, MyState.State1) } // MARK: addRoute using array func testAddRoute_array_left() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 2 or 1 => 2 machine.addRoute([.State0, .State1] => .State2) @@ -206,7 +210,7 @@ class StateMachineTests: _TestCase func testAddRoute_array_right() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 or 0 => 2 machine.addRoute(.State0 => [.State1, .State2]) @@ -221,7 +225,7 @@ class StateMachineTests: _TestCase func testAddRoute_array_both() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 2 or 0 => 3 or 1 => 2 or 1 => 3 machine.addRoute([MyState.State0, MyState.State1] => [MyState.State2, MyState.State3]) @@ -250,7 +254,7 @@ class StateMachineTests: _TestCase func testRemoveRoute() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) let routeID = machine.addRoute(.State0 => .State1) @@ -275,7 +279,7 @@ class StateMachineTests: _TestCase // machine <- state func testTryState() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // tryState 0 => 1, without registering any transitions machine <- .State1 @@ -294,7 +298,7 @@ class StateMachineTests: _TestCase func testTryState_string() { - let machine = StateMachine(state: "0") + let machine = Machine(state: "0") // tryState 0 => 1, without registering any transitions machine <- "1" @@ -317,9 +321,10 @@ class StateMachineTests: _TestCase func testAddHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - var returnedTransition: StateTransition? + var fromState: MyState? + var toState: MyState? // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -329,89 +334,91 @@ class StateMachineTests: _TestCase } machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition + fromState = context.fromState + toState = context.toState } // not tried yet - XCTAssertTrue(returnedTransition == nil, "Transition has not started yet.") + XCTAssertTrue(fromState == nil, "Transition has not started yet.") + XCTAssertTrue(toState == nil, "Transition has not started yet.") // tryState 0 => 1 machine <- .State1 - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) + XCTAssertEqual(fromState, MyState.State0) + XCTAssertEqual(toState, MyState.State1) } func testAddHandler_order() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - var returnedTransition: StateTransition? + var fromState: MyState? + var toState: MyState? // 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.") + XCTAssertTrue(fromState != nil, "`fromState` should already be set.") + XCTAssertTrue(toState != nil, "`toState` should already be set.") - returnedTransition = context.transition + fromState = context.fromState + toState = context.toState } // 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.") + XCTAssertTrue(fromState == nil, "`fromState` should NOT be set at this point.") + XCTAssertTrue(fromState == nil, "`fromState` should NOT be set at this point.") - returnedTransition = context.transition // set returnedTransition for first time + // set for first time + fromState = context.fromState + toState = context.toState } // tryState 0 => 1 machine <- .State1 - XCTAssertTrue(returnedTransition != nil) - XCTAssertEqual(returnedTransition!.fromState, MyState.State0) - XCTAssertEqual(returnedTransition!.toState, MyState.State1) + XCTAssertEqual(fromState, MyState.State0) + XCTAssertEqual(toState, MyState.State1) } func testAddHandler_multiple() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - var returnedTransition: StateTransition? - var returnedTransition2: StateTransition? + var passed1 = false + var passed2 = false // add 0 => 1 machine.addRoute(.State0 => .State1) machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition + passed1 = true } // add 0 => 1 once more machine.addRoute(.State0 => .State1) machine.addHandler(.State0 => .State1) { context in - returnedTransition2 = context.transition + passed2 = true } // tryState 0 => 1 machine <- .State1 - XCTAssertTrue(returnedTransition != nil) - XCTAssertTrue(returnedTransition2 != nil) + XCTAssertTrue(passed1) + XCTAssertTrue(passed2) } func testAddHandler_overload() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - var returnedTransition: StateTransition? + var passed = false machine.addRoute(.State0 => .State1) @@ -420,14 +427,14 @@ class StateMachineTests: _TestCase } machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition + passed = true } - XCTAssertTrue(returnedTransition == nil) + XCTAssertFalse(passed) machine <- .State1 - XCTAssertTrue(returnedTransition != nil) + XCTAssertTrue(passed) } //-------------------------------------------------- @@ -436,16 +443,14 @@ class StateMachineTests: _TestCase func testRemoveHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - var returnedTransition: StateTransition? - var returnedTransition2: StateTransition? + var passed = false // add 0 => 1 machine.addRoute(.State0 => .State1) let handlerID = machine.addHandler(.State0 => .State1) { context in - returnedTransition = context.transition XCTFail("Should never reach here") } @@ -453,7 +458,7 @@ class StateMachineTests: _TestCase machine.addRoute(.State0 => .State1) machine.addHandler(.State0 => .State1) { context in - returnedTransition2 = context.transition + passed = true } machine.removeHandler(handlerID) @@ -461,13 +466,12 @@ class StateMachineTests: _TestCase // tryState 0 => 1 machine <- .State1 - XCTAssertTrue(returnedTransition == nil, "Handler should be removed and never performed.") - XCTAssertTrue(returnedTransition2 != nil) + XCTAssertTrue(passed) } func testRemoveHandler_unregistered() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -485,16 +489,14 @@ class StateMachineTests: _TestCase func testRemoveErrorHandler() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) - var returnedTransition: StateTransition? - var returnedTransition2: StateTransition? + var passed = false // add 2 => 1 machine.addRoute(.State2 => .State1) let handlerID = machine.addErrorHandler { context in - returnedTransition = context.transition XCTFail("Should never reach here") } @@ -502,7 +504,7 @@ class StateMachineTests: _TestCase machine.addRoute(.State2 => .State1) machine.addErrorHandler { context in - returnedTransition2 = context.transition + passed = true } machine.removeHandler(handlerID) @@ -510,13 +512,12 @@ class StateMachineTests: _TestCase // tryState 0 => 1 machine <- .State1 - XCTAssertTrue(returnedTransition == nil, "Handler should be removed and never performed.") - XCTAssertTrue(returnedTransition2 != nil) + XCTAssertTrue(passed) } func testRemoveErrorHandler_unregistered() { - let machine = StateMachine(state: .State0) + let machine = Machine(state: .State0) // add 0 => 1 machine.addRoute(.State0 => .State1) diff --git a/SwiftStateTests/StateRouteTests.swift b/SwiftStateTests/StateRouteTests.swift index 3dfa2bc..221d966 100644 --- a/SwiftStateTests/StateRouteTests.swift +++ b/SwiftStateTests/StateRouteTests.swift @@ -1,5 +1,5 @@ // -// StateRouteTests.swift +// RouteTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/04. @@ -9,23 +9,23 @@ import SwiftState import XCTest -class StateRouteTests: _TestCase +class RouteTests: _TestCase { func testInit() { - let route = StateRoute(transition: .State0 => .State1, condition: nil) - XCTAssertEqual(route.transition.fromState, MyState.State0) - XCTAssertEqual(route.transition.toState, MyState.State1) + let route = Route(transition: .State0 => .State1, condition: nil) + XCTAssertEqual(route.transition.fromState.value, MyState.State0) + XCTAssertEqual(route.transition.toState.value, 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) + let route2 = Route(transition: .State1 => .State2, condition: { _ in false }) + XCTAssertEqual(route2.transition.fromState.value, MyState.State1) + XCTAssertEqual(route2.transition.toState.value, 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) + let route3 = Route(transition: .State2 => .State3, condition: { context in false }) + XCTAssertEqual(route3.transition.fromState.value, MyState.State2) + XCTAssertEqual(route3.transition.toState.value, MyState.State3) XCTAssertTrue(route3.condition != nil) } } \ No newline at end of file diff --git a/SwiftStateTests/StateTransitionChainTests.swift b/SwiftStateTests/StateTransitionChainTests.swift index cc8e812..5f23611 100644 --- a/SwiftStateTests/StateTransitionChainTests.swift +++ b/SwiftStateTests/StateTransitionChainTests.swift @@ -1,5 +1,5 @@ // -// StateTransitionChainTests.swift +// TransitionChainTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/04. @@ -9,7 +9,7 @@ import SwiftState import XCTest -class StateTransitionChainTests: _TestCase +class TransitionChainTests: _TestCase { func testInit() { @@ -17,22 +17,22 @@ class StateTransitionChainTests: _TestCase var chain = MyState.State0 => .State1 => .State2 XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State2) + XCTAssertEqual(chain.firstState.value, MyState.State0) + XCTAssertEqual(chain.lastState.value, MyState.State2) // (1 => 2) => 3 chain = MyState.State1 => .State2 => .State3 XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State1) - XCTAssertEqual(chain.lastState, MyState.State3) + XCTAssertEqual(chain.firstState.value, MyState.State1) + XCTAssertEqual(chain.lastState.value, MyState.State3) // 2 => (3 => 0) chain = MyState.State2 => (.State3 => .State0) XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState, MyState.State2) - XCTAssertEqual(chain.lastState, MyState.State0) + XCTAssertEqual(chain.firstState.value, MyState.State2) + XCTAssertEqual(chain.lastState.value, MyState.State0) } func testAppend() @@ -42,20 +42,20 @@ class StateTransitionChainTests: _TestCase var chain = transition.toTransitionChain() XCTAssertEqual(chain.numberOfTransitions, 1) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State1) + XCTAssertEqual(chain.firstState.value, MyState.State0) + XCTAssertEqual(chain.lastState.value, 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) + XCTAssertEqual(chain.firstState.value, MyState.State0) + XCTAssertEqual(chain.lastState.value, MyState.State2) // 0 => 1 => 2 => 3 chain.append(.State3) XCTAssertEqual(chain.numberOfTransitions, 3) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State3) + XCTAssertEqual(chain.firstState.value, MyState.State0) + XCTAssertEqual(chain.lastState.value, MyState.State3) } func testPrepend() @@ -65,19 +65,19 @@ class StateTransitionChainTests: _TestCase var chain = transition.toTransitionChain() XCTAssertEqual(chain.numberOfTransitions, 1) - XCTAssertEqual(chain.firstState, MyState.State0) - XCTAssertEqual(chain.lastState, MyState.State1) + XCTAssertEqual(chain.firstState.value, MyState.State0) + XCTAssertEqual(chain.lastState.value, 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) + XCTAssertEqual(chain.firstState.value, MyState.State2) + XCTAssertEqual(chain.lastState.value, MyState.State1) // 3 => 2 => 0 => 1 chain.prepend(.State3) XCTAssertEqual(chain.numberOfTransitions, 3) - XCTAssertEqual(chain.firstState, MyState.State3) - XCTAssertEqual(chain.lastState, MyState.State1) + XCTAssertEqual(chain.firstState.value, MyState.State3) + XCTAssertEqual(chain.lastState.value, MyState.State1) } } \ No newline at end of file diff --git a/SwiftStateTests/StateTransitionTests.swift b/SwiftStateTests/StateTransitionTests.swift index 705a67f..e5248f8 100644 --- a/SwiftStateTests/StateTransitionTests.swift +++ b/SwiftStateTests/StateTransitionTests.swift @@ -1,5 +1,5 @@ // -// StateTransitionTests.swift +// TransitionTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/03. @@ -9,35 +9,35 @@ import SwiftState import XCTest -class StateTransitionTests: _TestCase +class TransitionTests: _TestCase { func testInit() { - let transition = StateTransition(fromState: .State0, toState: .State1) - XCTAssertEqual(transition.fromState, MyState.State0) - XCTAssertEqual(transition.toState, MyState.State1) + let transition = Transition(fromState: .State0, toState: .State1) + XCTAssertEqual(transition.fromState.value, MyState.State0) + XCTAssertEqual(transition.toState.value, MyState.State1) // shorthand let transition2 = MyState.State1 => .State0 - XCTAssertEqual(transition2.fromState, MyState.State1) - XCTAssertEqual(transition2.toState, MyState.State0) + XCTAssertEqual(transition2.fromState.value, MyState.State1) + XCTAssertEqual(transition2.toState.value, MyState.State0) } func testNil() { - // nil => state - let transition = nil => MyState.State0 - XCTAssertEqual(transition.fromState, nil as MyState) - XCTAssertEqual(transition.toState, MyState.State0) + // .Any => state + let transition = .Any => MyState.State0 + XCTAssertTrue(transition.fromState == .Any) + XCTAssertTrue(transition.toState == .State0) - // state => nil - let transition2 = MyState.State0 => nil - XCTAssertEqual(transition2.fromState, MyState.State0) - XCTAssertEqual(transition2.toState, nil as MyState) + // state => .Any + let transition2 = MyState.State0 => .Any + XCTAssertTrue(transition2.fromState == .State0) + XCTAssertTrue(transition2.toState == .Any) - // nil => nil - let transition3: StateTransition = nil => nil - XCTAssertEqual(transition3.fromState, nil as MyState) - XCTAssertEqual(transition3.toState, nil as MyState) + // .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/String+TestExt.swift b/SwiftStateTests/String+TestExt.swift new file mode 100644 index 0000000..098070d --- /dev/null +++ b/SwiftStateTests/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 From 465146a43c74161b0537bff5b12988f80405c5b4 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sat, 14 Nov 2015 22:15:44 +0900 Subject: [PATCH 03/27] [Test] Remove unnecessary `case Any`. --- SwiftStateTests/RasmusTest.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/SwiftStateTests/RasmusTest.swift b/SwiftStateTests/RasmusTest.swift index 423e13a..e7b7f02 100644 --- a/SwiftStateTests/RasmusTest.swift +++ b/SwiftStateTests/RasmusTest.swift @@ -10,12 +10,10 @@ import SwiftState import XCTest enum RasmusTestState: StateType { - case Any case State1, State2, State3, State4 } enum RasmusTestEvent: EventType { - case Any case State2fromState1 case State4fromState2OrState3 } From da9b9b4b7a8cab98f453588bd0bec68aa903304c Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Tue, 24 Nov 2015 23:35:20 +0900 Subject: [PATCH 04/27] Remove unnecessary methods. --- SwiftState/Machine.swift | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift index eae6168..19f8250 100644 --- a/SwiftState/Machine.swift +++ b/SwiftState/Machine.swift @@ -294,12 +294,7 @@ public class Machine // 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) + public func addRoute(transition: Transition, condition: Condition? = nil, handler: Handler) -> (RouteID, HandlerID) { let route = Route(transition: transition, condition: condition) return self.addRoute(route, handler: handler) @@ -444,12 +439,7 @@ public class Machine // MARK: addRouteChain + conditional handler - public func addRouteChain(chain: TransitionChain, handler: Handler) -> (RouteChainID, ChainHandlerID) - { - return self.addRouteChain(chain, condition: nil, handler: handler) - } - - public func addRouteChain(chain: TransitionChain, condition: Condition?, handler: Handler) -> (RouteChainID, ChainHandlerID) + public func addRouteChain(chain: TransitionChain, condition: Condition? = nil, handler: Handler) -> (RouteChainID, ChainHandlerID) { let routeChain = RouteChain(transitionChain: chain, condition: condition) return self.addRouteChain(routeChain, handler: handler) From c9f6db9d758bb13d58472fabf5e48aa6661329ef Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 25 Nov 2015 00:32:46 +0900 Subject: [PATCH 05/27] Add comment. --- SwiftState/Machine.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift index 19f8250..e8068d4 100644 --- a/SwiftState/Machine.swift +++ b/SwiftState/Machine.swift @@ -86,7 +86,13 @@ public class Machine return self.hasRoute(fromState: fromState, toState: toState, forEvent: .Some(event), userInfo: userInfo) } + /// /// Check for `_routes`. + /// + /// - Parameter event: + /// If `event` is nil, all registered routes will be examined. + /// Otherwise, only routes for `event` and `.Any` will be examined. + /// private func _hasRoute(fromState fromState: S, toState: S, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool { let validTransitions = _validTransitions(fromState: fromState, toState: toState) From 32630e2d600fadf7fa26375178ef5108fa2db578 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 25 Nov 2015 00:36:20 +0900 Subject: [PATCH 06/27] Rename `Mapping` to `RouteMapping`. --- SwiftState/Machine.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift index e8068d4..5529b52 100644 --- a/SwiftState/Machine.swift +++ b/SwiftState/Machine.swift @@ -20,11 +20,11 @@ public class Machine public typealias Handler = Context -> () - public typealias Mapping = (event: E?, fromState: S, userInfo: Any?) -> S? + public typealias RouteMapping = (event: E?, fromState: S, userInfo: Any?) -> S? private var _routes: [_Event : [Transition : [String : Condition?]]] = [:] - private var _routeMappings: [String : Mapping] = [:] + private var _routeMappings: [String : RouteMapping] = [:] private var _handlers: [Transition : [_HandlerInfo]] = [:] private var _errorHandlers: [_HandlerInfo] = [] @@ -686,7 +686,7 @@ public class Machine // MARK: addRouteMapping - public func addRouteMapping(routeMapping: Mapping) -> RouteMappingID + public func addRouteMapping(routeMapping: RouteMapping) -> RouteMappingID { let key = _createUniqueString() @@ -699,7 +699,7 @@ public class Machine // MARK: addRouteMapping + conditional handler - public func addRouteMapping(routeMapping: Mapping, handler: Handler) -> (RouteMappingID, HandlerID) + public func addRouteMapping(routeMapping: RouteMapping, handler: Handler) -> (RouteMappingID, HandlerID) { let routeMappingID = self.addRouteMapping(routeMapping) From 93f184bf6588df32eb0574385500a994cf1fd8ba Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 25 Nov 2015 01:37:44 +0900 Subject: [PATCH 07/27] [Test] Organize tests. --- SwiftState.xcodeproj/project.pbxproj | 96 +++--- SwiftStateTests/BasicTests.swift | 14 +- SwiftStateTests/BugFixTests.swift | 26 -- ...eMachineTests.swift => MachineTests.swift} | 325 +++++++++--------- SwiftStateTests/RasmusTest.swift | 32 -- ...ChainTests.swift => RouteChainTests.swift} | 190 +++++----- ...gcjnTest.swift => RouteMappingTests.swift} | 62 +--- ...StateRouteTests.swift => RouteTests.swift} | 0 ...Tests.swift => TransitionChainTests.swift} | 0 ...itionTests.swift => TransitionTests.swift} | 0 ...neEventTests.swift => TryEventTests.swift} | 204 ++++++----- 11 files changed, 453 insertions(+), 496 deletions(-) delete mode 100644 SwiftStateTests/BugFixTests.swift rename SwiftStateTests/{StateMachineTests.swift => MachineTests.swift} (60%) delete mode 100644 SwiftStateTests/RasmusTest.swift rename SwiftStateTests/{StateMachineChainTests.swift => RouteChainTests.swift} (55%) rename SwiftStateTests/{FrogcjnTest.swift => RouteMappingTests.swift} (62%) rename SwiftStateTests/{StateRouteTests.swift => RouteTests.swift} (100%) rename SwiftStateTests/{StateTransitionChainTests.swift => TransitionChainTests.swift} (100%) rename SwiftStateTests/{StateTransitionTests.swift => TransitionTests.swift} (100%) rename SwiftStateTests/{StateMachineEventTests.swift => TryEventTests.swift} (64%) diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index 924d653..2466c70 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -9,8 +9,8 @@ /* 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 */; }; - 1F27771E1BE68D1D00C57CC9 /* FrogcjnTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */; }; - 1F27771F1BE68D1E00C57CC9 /* FrogcjnTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */; }; + 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; + 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; 1F70FB661BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; 1F70FB691BF0F46E00E5AC8C /* RouteChainID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */; }; @@ -32,27 +32,25 @@ 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 */; }; - 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 */; }; + 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; + 1FA62034199660CA00460108 /* TryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* TryEventTests.swift */; }; + 1FA62035199660CA00460108 /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* MachineTests.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 */; }; - 1FB4B39C1AAB3B190072E65D /* BugFixTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */; }; - 1FB4B39D1AAB3B190072E65D /* BugFixTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB4B39B1AAB3B190072E65D /* BugFixTests.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 */; }; + 4822F0AD19D008EB00F5F572 /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* MachineTests.swift */; }; + 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; + 4822F0AF19D008EB00F5F572 /* TryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* TryEventTests.swift */; }; + 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* TransitionTests.swift */; }; + 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* TransitionChainTests.swift */; }; + 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* RouteTests.swift */; }; 48797D5D19B42CBE0085D80F /* SwiftState.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FA620051996601000460108 /* SwiftState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48797D5E19B42CCE0085D80F /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* Machine.swift */; }; 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* Transition.swift */; }; @@ -61,8 +59,6 @@ 48797D6219B42CCE0085D80F /* RouteChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201C1996606300460108 /* RouteChain.swift */; }; 48797D6319B42CD40085D80F /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201F1996606300460108 /* StateType.swift */; }; 48797D6419B42CD40085D80F /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* EventType.swift */; }; - C662B6F51B861CC400479524 /* RasmusTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C662B6F41B861CC400479524 /* RasmusTest.swift */; }; - C662B6F61B861CC400479524 /* RasmusTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C662B6F41B861CC400479524 /* RasmusTest.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -77,7 +73,7 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; - 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrogcjnTest.swift; sourceTree = ""; }; + 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteMappingTests.swift; sourceTree = ""; }; 1F70FB651BF0F46000E5AC8C /* RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteID.swift; sourceTree = ""; }; 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainID.swift; sourceTree = ""; }; 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteMappingID.swift; sourceTree = ""; }; @@ -98,17 +94,15 @@ 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 = ""; }; - 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 = ""; }; + 1FA6202A199660CA00460108 /* RouteChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainTests.swift; sourceTree = ""; }; + 1FA6202B199660CA00460108 /* TryEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TryEventTests.swift; sourceTree = ""; }; + 1FA6202C199660CA00460108 /* MachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineTests.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 = ""; }; - 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BugFixTests.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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -214,16 +208,14 @@ 1FA62027199660CA00460108 /* _TestCase.swift */, 1FB1EC8D199E609900ABD937 /* State & Event */, 1FA62028199660CA00460108 /* BasicTests.swift */, - 1FB4B39B1AAB3B190072E65D /* BugFixTests.swift */, 1F198C5B19972320001C3700 /* QiitaTests.swift */, - C662B6F41B861CC400479524 /* RasmusTest.swift */, - 1F27771C1BE68C7F00C57CC9 /* FrogcjnTest.swift */, - 1FA6202A199660CA00460108 /* StateMachineChainTests.swift */, - 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */, - 1FA6202C199660CA00460108 /* StateMachineTests.swift */, - 1FA6202D199660CA00460108 /* StateRouteTests.swift */, - 1FA6202E199660CA00460108 /* StateTransitionChainTests.swift */, - 1FA6202F199660CA00460108 /* StateTransitionTests.swift */, + 1FA6202C199660CA00460108 /* MachineTests.swift */, + 1FA6202B199660CA00460108 /* TryEventTests.swift */, + 1FA6202F199660CA00460108 /* TransitionTests.swift */, + 1FA6202E199660CA00460108 /* TransitionChainTests.swift */, + 1FA6202D199660CA00460108 /* RouteTests.swift */, + 1FA6202A199660CA00460108 /* RouteChainTests.swift */, + 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */, 1FA6200D1996601000460108 /* Supporting Files */, ); path = SwiftStateTests; @@ -447,17 +439,15 @@ 1FB1EC8A199E515B00ABD937 /* MyEvent.swift in Sources */, 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */, 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */, - 1FA62038199660CA00460108 /* StateTransitionTests.swift in Sources */, - 1FA62033199660CA00460108 /* StateMachineChainTests.swift in Sources */, + 1FA62038199660CA00460108 /* TransitionTests.swift in Sources */, + 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */, 1FA62030199660CA00460108 /* _TestCase.swift in Sources */, - 1FA62036199660CA00460108 /* StateRouteTests.swift in Sources */, + 1FA62036199660CA00460108 /* RouteTests.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 */, - 1F27771E1BE68D1D00C57CC9 /* FrogcjnTest.swift in Sources */, + 1FA62034199660CA00460108 /* TryEventTests.swift in Sources */, + 1FA62035199660CA00460108 /* MachineTests.swift in Sources */, + 1FA62037199660CA00460108 /* TransitionChainTests.swift in Sources */, + 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */, 1FA62032199660CA00460108 /* MyState.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -469,18 +459,16 @@ 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */, 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */, 1F70FB7A1BF0FB7100E5AC8C /* String+TestExt.swift in Sources */, - 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */, + 4822F0AF19D008EB00F5F572 /* TryEventTests.swift in Sources */, 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, - 4822F0AE19D008EB00F5F572 /* StateMachineChainTests.swift in Sources */, - 4822F0B019D008EB00F5F572 /* StateTransitionTests.swift in Sources */, + 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */, + 4822F0B019D008EB00F5F572 /* TransitionTests.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 */, - 1F27771F1BE68D1E00C57CC9 /* FrogcjnTest.swift in Sources */, - 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */, + 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */, + 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */, + 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */, + 4822F0AD19D008EB00F5F572 /* MachineTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftStateTests/BasicTests.swift b/SwiftStateTests/BasicTests.swift index bd3389b..47dfe43 100644 --- a/SwiftStateTests/BasicTests.swift +++ b/SwiftStateTests/BasicTests.swift @@ -13,7 +13,7 @@ class BasicTests: _TestCase { func testREADME() { - let machine = Machine(state: .State0) { machine in + let machine = Machine(state: .State0) { machine in machine.addRoute(.State0 => .State1) machine.addRoute(.Any => .State2) { context in print("Any => 2, msg=\(context.userInfo)") } @@ -49,7 +49,7 @@ class BasicTests: _TestCase func testREADME_string() { - let machine = Machine(state: ".State0") { machine in + let machine = Machine(state: ".State0") { machine in machine.addRoute(".State0" => ".State1") machine.addRoute(.Any => ".State2") { context in print("Any => 2, msg=\(context.userInfo)") } @@ -66,7 +66,7 @@ class BasicTests: _TestCase } } - // tryState 0 => 1 => 2 => 1 => 0 + // tryState 0 => 1 => 2 => 1 => 0 machine <- ".State1" XCTAssertTrue(machine.state == ".State1") @@ -83,9 +83,7 @@ class BasicTests: _TestCase print("machine.state = \(machine.state)") } - - - + // StateType + associated value func testREADME_MyState2() { let machine = Machine(state: .State0("0")) { machine in @@ -122,10 +120,6 @@ class BasicTests: _TestCase print("machine.state = \(machine.state)") } - - - - func testExample() { let machine = Machine(state: .State0) { diff --git a/SwiftStateTests/BugFixTests.swift b/SwiftStateTests/BugFixTests.swift deleted file mode 100644 index 17cac05..0000000 --- a/SwiftStateTests/BugFixTests.swift +++ /dev/null @@ -1,26 +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 -{ - // Fix hasRoute() bug when there are routes for both AnyEvent & SomeEvent. - // https://github.com/ReactKit/SwiftState/pull/19 - func testHasRoute_anyEvent_someEvent() - { - let machine = Machine(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/StateMachineTests.swift b/SwiftStateTests/MachineTests.swift similarity index 60% rename from SwiftStateTests/StateMachineTests.swift rename to SwiftStateTests/MachineTests.swift index 1c942ea..91bf1e8 100644 --- a/SwiftStateTests/StateMachineTests.swift +++ b/SwiftStateTests/MachineTests.swift @@ -25,9 +25,9 @@ class MachineTests: _TestCase // add state1 => state2 func testAddRoute() { - let machine = Machine(state: .State0) - - machine.addRoute(.State0 => .State1) + let machine = Machine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) + } XCTAssertFalse(machine.hasRoute(.State0 => .State0)) XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true @@ -40,9 +40,9 @@ class MachineTests: _TestCase // add .Any => state func testAddRoute_fromAnyState() { - let machine = Machine(state: .State0) - - machine.addRoute(.Any => .State1) // Any => State1 + let machine = Machine(state: .State0) { machine in + machine.addRoute(.Any => .State1) // Any => State1 + } XCTAssertFalse(machine.hasRoute(.State0 => .State0)) XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true @@ -55,9 +55,9 @@ class MachineTests: _TestCase // add state => .Any func testAddRoute_toAnyState() { - let machine = Machine(state: .State0) - - machine.addRoute(.State1 => .Any) // State1 => Any + let machine = Machine(state: .State0) { machine in + machine.addRoute(.State1 => .Any) // State1 => Any + } XCTAssertFalse(machine.hasRoute(.State0 => .State0)) XCTAssertFalse(machine.hasRoute(.State0 => .State1)) @@ -70,9 +70,9 @@ class MachineTests: _TestCase // add .Any => .Any func testAddRoute_bothAnyState() { - let machine = Machine(state: .State0) - - machine.addRoute(.Any => .Any) // Any => Any + let machine = Machine(state: .State0) { machine in + machine.addRoute(.Any => .Any) // Any => Any + } XCTAssertTrue(machine.hasRoute(.State0 => .State0)) // true XCTAssertTrue(machine.hasRoute(.State0 => .State1)) // true @@ -85,9 +85,9 @@ class MachineTests: _TestCase // add state0 => state0 func testAddRoute_sameState() { - let machine = Machine(state: .State0) - - machine.addRoute(.State0 => .State0) + let machine = Machine(state: .State0) { machine in + machine.addRoute(.State0 => .State0) + } XCTAssertTrue(machine.hasRoute(.State0 => .State0)) @@ -98,12 +98,13 @@ class MachineTests: _TestCase // add route + condition func testAddRoute_condition() { - let machine = Machine(state: .State0) - var flag = false - // add 0 => 1 - machine.addRoute(.State0 => .State1, condition: { _ in flag }) + let machine = Machine(state: .State0) { machine in + // add 0 => 1 + machine.addRoute(.State0 => .State1, condition: { _ in flag }) + + } XCTAssertFalse(machine.hasRoute(.State0 => .State1)) @@ -115,12 +116,12 @@ class MachineTests: _TestCase // add route + condition + blacklist func testAddRoute_condition_blacklist() { - let machine = Machine(state: .State0) - - // add 0 => Any, except 0 => 2 - machine.addRoute(.State0 => .Any, condition: { context in - return context.toState != .State2 - }) + let machine = Machine(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)) @@ -131,74 +132,80 @@ class MachineTests: _TestCase // add route + handler func testAddRoute_handler() { - let machine = Machine(state: .State0) - - var fromState: MyState? - var toState: MyState? + var invokedCount = 0 - machine.addRoute(.State0 => .State1) { context in - fromState = context.fromState - toState = context.toState + let machine = Machine(state: .State0) { machine in + + machine.addRoute(.State0 => .State1) { context in + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ + } + } - XCTAssertTrue(fromState == nil, "Transition has not started yet.") - XCTAssertTrue(toState == nil, "Transition has not started yet.") + XCTAssertEqual(invokedCount, 0, "Transition has not started yet.") // tryState 0 => 1 machine <- .State1 - XCTAssertEqual(fromState, MyState.State0) - XCTAssertEqual(toState, MyState.State1) + XCTAssertEqual(invokedCount, 1) } // add route + conditional handler func testAddRoute_conditionalHandler() { - let machine = Machine(state: .State0) - + var invokedCount = 0 var flag = false - var fromState: MyState? - var toState: MyState? - // add 0 => 1 without condition to guarantee 0 => 1 transition - machine.addRoute(.State0 => .State1) + let machine = Machine(state: .State0) { machine in - // add 0 => 1 with condition + conditionalHandler - machine.addRoute(.State0 => .State1, condition: { _ in flag }) { context in - fromState = context.fromState - toState = context.toState + // 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 , "0 => 1 transition should be performed.") - XCTAssertTrue(fromState == nil, "Conditional handler should NOT be performed because flag=false.") - XCTAssertTrue(toState == nil, "Conditional handler should NOT be performed because flag=false.") - - // add 1 => 0 for resetting state - machine.addRoute(.State1 => .State0) + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertEqual(invokedCount, 0, "Conditional handler should NOT be performed because flag=false.") - // tryState 1 => 0 + // tryState 1 => 0 (resetting to 0) machine <- .State0 + XCTAssertEqual(machine.state, MyState.State0) + flag = true // tryState 0 => 1 machine <- .State1 - XCTAssertEqual(fromState, MyState.State0) - XCTAssertEqual(toState, MyState.State1) + XCTAssertEqual(machine.state, MyState.State1) + XCTAssertEqual(invokedCount, 1) + } // MARK: addRoute using array func testAddRoute_array_left() { - let machine = Machine(state: .State0) - - // add 0 => 2 or 1 => 2 - machine.addRoute([.State0, .State1] => .State2) + let machine = Machine(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)) @@ -210,10 +217,10 @@ class MachineTests: _TestCase func testAddRoute_array_right() { - let machine = Machine(state: .State0) - - // add 0 => 1 or 0 => 2 - machine.addRoute(.State0 => [.State1, .State2]) + let machine = Machine(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)) @@ -225,10 +232,10 @@ class MachineTests: _TestCase func testAddRoute_array_both() { - let machine = Machine(state: .State0) - - // add 0 => 2 or 0 => 3 or 1 => 2 or 1 => 3 - machine.addRoute([MyState.State0, MyState.State1] => [MyState.State2, MyState.State3]) + let machine = Machine(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)) @@ -298,7 +305,7 @@ class MachineTests: _TestCase func testTryState_string() { - let machine = Machine(state: "0") + let machine = Machine(state: "0") // tryState 0 => 1, without registering any transitions machine <- "1" @@ -321,90 +328,92 @@ class MachineTests: _TestCase func testAddHandler() { - let machine = Machine(state: .State0) - - var fromState: MyState? - var toState: MyState? + var invokedCount = 0 - // add 0 => 1 - machine.addRoute(.State0 => .State1) + let machine = Machine(state: .State0) { machine in - machine.addHandler(.State0 => .State1) { context in -// returnedTransition = context.transition + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + machine.addHandler(.State0 => .State1) { context in + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ } - machine.addHandler(.State0 => .State1) { context in - fromState = context.fromState - toState = context.toState } // not tried yet - XCTAssertTrue(fromState == nil, "Transition has not started yet.") - XCTAssertTrue(toState == nil, "Transition has not started yet.") + XCTAssertEqual(invokedCount, 0, "Transition has not started yet.") // tryState 0 => 1 machine <- .State1 - XCTAssertEqual(fromState, MyState.State0) - XCTAssertEqual(toState, MyState.State1) + XCTAssertEqual(invokedCount, 1) } func testAddHandler_order() { - let machine = Machine(state: .State0) - - var fromState: MyState? - var toState: MyState? + var invokedCount = 0 - // add 0 => 1 - machine.addRoute(.State0 => .State1) + let machine = Machine(state: .State0) { machine in - // order = 100 (default) - machine.addHandler(.State0 => .State1) { context in - XCTAssertTrue(fromState != nil, "`fromState` should already be set.") - XCTAssertTrue(toState != nil, "`toState` should already be set.") + // add 0 => 1 + machine.addRoute(.State0 => .State1) - fromState = context.fromState - toState = context.toState - } - - // order = 99 - machine.addHandler(.State0 => .State1, order: 99) { context in - XCTAssertTrue(fromState == nil, "`fromState` should NOT be set at this point.") - XCTAssertTrue(fromState == nil, "`fromState` should NOT be set at this point.") + // order = 100 (default) + machine.addHandler(.State0 => .State1) { context in + XCTAssertEqual(invokedCount, 1) + + XCTAssertEqual(context.fromState, MyState.State0) + XCTAssertEqual(context.toState, MyState.State1) + + invokedCount++ + } - // set for first time - fromState = context.fromState - toState = context.toState + // 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(fromState, MyState.State0) - XCTAssertEqual(toState, MyState.State1) + XCTAssertEqual(invokedCount, 2) } func testAddHandler_multiple() { - let machine = Machine(state: .State0) - var passed1 = false var passed2 = false - // add 0 => 1 - machine.addRoute(.State0 => .State1) + let machine = Machine(state: .State0) { machine in - machine.addHandler(.State0 => .State1) { context in - passed1 = true - } - - // add 0 => 1 once more - machine.addRoute(.State0 => .State1) + // 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 + } - machine.addHandler(.State0 => .State1) { context in - passed2 = true } // tryState 0 => 1 @@ -416,20 +425,22 @@ class MachineTests: _TestCase func testAddHandler_overload() { - let machine = Machine(state: .State0) - var passed = false - machine.addRoute(.State0 => .State1) + let machine = Machine(state: .State0) { machine in - machine.addHandler(.State0 => .State1) { context in - // empty + machine.addRoute(.State0 => .State1) + + machine.addHandler(.State0 => .State1) { context in + // empty + } + + machine.addHandler(.State0 => .State1) { context in + passed = true + } + } - machine.addHandler(.State0 => .State1) { context in - passed = true - } - XCTAssertFalse(passed) machine <- .State1 @@ -443,25 +454,29 @@ class MachineTests: _TestCase func testRemoveHandler() { - let machine = Machine(state: .State0) - var passed = false - // add 0 => 1 - machine.addRoute(.State0 => .State1) + let machine = Machine(state: .State0) { machine in - let handlerID = machine.addHandler(.State0 => .State1) { context in - XCTFail("Should never reach here") - } - - // add 0 => 1 once more - machine.addRoute(.State0 => .State1) + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + let handlerID = 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 + } + + machine.removeHandler(handlerID) - machine.addHandler(.State0 => .State1) { context in - passed = true } - machine.removeHandler(handlerID) + XCTAssertFalse(passed) // tryState 0 => 1 machine <- .State1 @@ -489,26 +504,28 @@ class MachineTests: _TestCase func testRemoveErrorHandler() { - let machine = Machine(state: .State0) - var passed = false - // add 2 => 1 - machine.addRoute(.State2 => .State1) - - let handlerID = machine.addErrorHandler { context in - XCTFail("Should never reach here") - } + let machine = Machine(state: .State0) { machine in - // add 2 => 1 once more - machine.addRoute(.State2 => .State1) + // add 2 => 1 + machine.addRoute(.State2 => .State1) + + let handlerID = machine.addErrorHandler { context in + XCTFail("Should never reach here") + } + + // add 2 => 1 once more + machine.addRoute(.State2 => .State1) + + machine.addErrorHandler { context in + passed = true + } + + machine.removeHandler(handlerID) - machine.addErrorHandler { context in - passed = true } - machine.removeHandler(handlerID) - // tryState 0 => 1 machine <- .State1 diff --git a/SwiftStateTests/RasmusTest.swift b/SwiftStateTests/RasmusTest.swift deleted file mode 100644 index e7b7f02..0000000 --- a/SwiftStateTests/RasmusTest.swift +++ /dev/null @@ -1,32 +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 State1, State2, State3, State4 -} - -enum RasmusTestEvent: EventType { - case State2fromState1 - case State4fromState2OrState3 -} - -class RasmusTest: _TestCase { - func testRoute() { - let stateMachine = Machine(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/StateMachineChainTests.swift b/SwiftStateTests/RouteChainTests.swift similarity index 55% rename from SwiftStateTests/StateMachineChainTests.swift rename to SwiftStateTests/RouteChainTests.swift index 1f9e86d..7693b83 100644 --- a/SwiftStateTests/StateMachineChainTests.swift +++ b/SwiftStateTests/RouteChainTests.swift @@ -13,14 +13,16 @@ class MachineChainTests: _TestCase { func testAddRouteChain() { - let machine = Machine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return + let machine = Machine(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 MachineChainTests: _TestCase func testAddRouteChain_condition() { - let machine = Machine(state: .State0) - var flag = false var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2, condition: { _ in flag }) { context in - invokeCount++ - return + let machine = Machine(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 MachineChainTests: _TestCase func testAddRouteChain_failBySkipping() { - let machine = Machine(state: .State0) + let machine = Machine(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 MachineChainTests: _TestCase func testAddRouteChain_failByHangingAround() { - let machine = Machine(state: .State0) + let machine = Machine(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 MachineChainTests: _TestCase func testAddRouteChain_succeedByFailingHangingAround() { - let machine = Machine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return + let machine = Machine(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 MachineChainTests: _TestCase func testAddRouteChain_goBackHomeAWhile() { - let machine = Machine(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 = Machine(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 MachineChainTests: _TestCase // https://github.com/inamiy/SwiftState/issues/2 func testAddRouteChain_goBackHomeAWhile2() { - let machine = Machine(state: .State0) - var invokeCount = 0 - machine.addRoute(.Any => .Any) // connect all states + let machine = Machine(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,19 +201,21 @@ class MachineChainTests: _TestCase func testRemoveRouteChain() { - let machine = Machine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - let (routeChainID, _) = machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + let (routeChainID, _) = machine.addRouteChain(.State0 => .State1 => .State2) { context in + invokeCount++ + return + } + + // removeRouteChain + machine.removeRouteChain(routeChainID) + } - // removeRouteChain - machine.removeRouteChain(routeChainID) - // tryState 0 => 1 let success = machine <- .State1 @@ -209,19 +225,21 @@ class MachineChainTests: _TestCase func testRemoveChainHandler() { - let machine = Machine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - let (_, chainHandlerID) = machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + let (_, chainHandlerID) = machine.addRouteChain(.State0 => .State1 => .State2) { context in + invokeCount++ + return + } + + // removeHandler + machine.removeChainHandler(chainHandlerID) + } - // removeHandler - XCTAssertTrue(machine.removeChainHandler(chainHandlerID)) - // tryState 0 => 1 => 2 machine <- .State1 let success = machine <- .State2 @@ -232,24 +250,26 @@ class MachineChainTests: _TestCase func testAddChainErrorHandler() { - let machine = Machine(state: .State0) - var errorCount = 0 - let transitionChain = MyState.State0 => .State1 => .State2 + let machine = Machine(state: .State0) { machine in - machine.addRoute(.Any => .Any) // 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 +285,25 @@ class MachineChainTests: _TestCase func testRemoveChainErrorHandler() { - let machine = Machine(state: .State0) - var errorCount = 0 - let transitionChain = MyState.State0 => .State1 => .State2 - - machine.addRoute(.Any => .Any) // connect all states + let machine = Machine(state: .State0) { machine in + + let transitionChain = MyState.State0 => .State1 => .State2 + + machine.addRoute(.Any => .Any) // connect all states + + // add 0 => 1 => 2 chainErrorHandler + let chainHandlerID = machine.addChainErrorHandler(transitionChain) { context in + errorCount++ + return + } + + // remove chainErrorHandler + machine.removeChainHandler(chainHandlerID) - // add 0 => 1 => 2 chainErrorHandler - let chainHandlerID = machine.addChainErrorHandler(transitionChain) { context in - errorCount++ - return } - // remove chainErrorHandler - machine.removeChainHandler(chainHandlerID) - // tryState 0 (starting state) => 1 => 0 machine <- .State1 machine <- .State0 diff --git a/SwiftStateTests/FrogcjnTest.swift b/SwiftStateTests/RouteMappingTests.swift similarity index 62% rename from SwiftStateTests/FrogcjnTest.swift rename to SwiftStateTests/RouteMappingTests.swift index 8653faf..ba1e14c 100644 --- a/SwiftStateTests/FrogcjnTest.swift +++ b/SwiftStateTests/RouteMappingTests.swift @@ -1,5 +1,5 @@ // -// FrogcjnTest.swift +// RouteMappingTests.swift // SwiftState // // Created by Yasuhiro Inami on 2015-11-02. @@ -9,7 +9,12 @@ 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 { @@ -67,64 +72,12 @@ private func ==(lhs: _Event, rhs: _Event) -> Bool } } -class FrogcjnTest: _TestCase +class RouteMappingTests: _TestCase { func testEventWithAssociatedValue() { var count = 0 - let machine = Machine<_State, _Event>(state: .Pending) { machine in - - machine.addRouteEvent(.CancelAction, transitions: [ .Any => .Pending ], condition: { $0.fromState != .Pending }) - - // - // If you have **finite** number of `LoadActionId`s (let's say 1 to 100), - // you can `addRouteEvent()` in finite number of times. - // (In this case, `LoadActionId` should have enum type instead) - // - for actionId in 1...100 { - machine.addRouteEvent(.LoadAction(actionId), transitions: [ .Any => .Loading(actionId) ], condition: { $0.fromState != .Loading(actionId) }) - } - - // increment `count` when any events i.e. `.CancelAction` and `.LoadAction(x)` succeed. - machine.addEventHandler(.Any) { event, transition, order, userInfo in - count++ - } - } - - // initial - XCTAssertTrue(machine.state == .Pending) - XCTAssertEqual(count, 0) - - // CancelAction (to .Pending state, same as before) - machine <-! .CancelAction - XCTAssertTrue(machine.state == .Pending) - XCTAssertEqual(count, 0, "`tryEvent()` failed, and `count` should not be incremented.") - - // LoadAction(1) (to .Loading(1) state) - machine <-! .LoadAction(1) - XCTAssertTrue(machine.state == .Loading(1)) - XCTAssertEqual(count, 1) - - // LoadAction(1) (same as before) - machine <-! .LoadAction(1) - print(machine.state) - XCTAssertTrue(machine.state == .Loading(1)) - XCTAssertEqual(count, 1, "`tryEvent()` failed, and `count` should not be incremented.") - - machine <-! .LoadAction(2) - XCTAssertTrue(machine.state == .Loading(2)) - XCTAssertEqual(count, 2) - - machine <-! .CancelAction - XCTAssertTrue(machine.state == .Pending) - XCTAssertEqual(count, 3) - } - - func testEventWithAssociatedValue2() - { - var count = 0 - let machine = Machine<_State, _Event>(state: .Pending) { machine in machine.addRouteMapping { event, fromState, userInfo in @@ -147,6 +100,7 @@ class FrogcjnTest: _TestCase machine.addEventHandler(.Any) { event, transition, order, userInfo in count++ } + } // initial diff --git a/SwiftStateTests/StateRouteTests.swift b/SwiftStateTests/RouteTests.swift similarity index 100% rename from SwiftStateTests/StateRouteTests.swift rename to SwiftStateTests/RouteTests.swift diff --git a/SwiftStateTests/StateTransitionChainTests.swift b/SwiftStateTests/TransitionChainTests.swift similarity index 100% rename from SwiftStateTests/StateTransitionChainTests.swift rename to SwiftStateTests/TransitionChainTests.swift diff --git a/SwiftStateTests/StateTransitionTests.swift b/SwiftStateTests/TransitionTests.swift similarity index 100% rename from SwiftStateTests/StateTransitionTests.swift rename to SwiftStateTests/TransitionTests.swift diff --git a/SwiftStateTests/StateMachineEventTests.swift b/SwiftStateTests/TryEventTests.swift similarity index 64% rename from SwiftStateTests/StateMachineEventTests.swift rename to SwiftStateTests/TryEventTests.swift index a3e429e..25a8f90 100644 --- a/SwiftStateTests/StateMachineEventTests.swift +++ b/SwiftStateTests/TryEventTests.swift @@ -1,5 +1,5 @@ // -// MachineEventTests.swift +// TryEventTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/05. @@ -9,7 +9,7 @@ import SwiftState import XCTest -class MachineEventTests: _TestCase +class TryEventTests: _TestCase { func testCanTryEvent() { @@ -74,7 +74,31 @@ class MachineEventTests: _TestCase XCTAssertFalse(success, "Event0 doesn't have 2 => Any") } - /// https://github.com/ReactKit/SwiftState/issues/20 + func testTryEvent_string() + { + let machine = Machine(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") + } + + // https://github.com/ReactKit/SwiftState/issues/20 func testTryEvent_issue20() { let machine = Machine(state: MyState.State2) { machine in @@ -85,10 +109,11 @@ class MachineEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State0) } - /// https://github.com/ReactKit/SwiftState/issues/28 + // https://github.com/ReactKit/SwiftState/issues/28 func testTryEvent_issue28() { var eventCount = 0 + let machine = Machine(state: .State0) { machine in machine.addRoute(.State0 => .State1) machine.addRouteEvent(.Event0, transitions: [.Any => .Any]) { _ in @@ -110,45 +135,53 @@ class MachineEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State1, "State should NOT be changed") } - func testTryEvent_string() - { - let machine = Machine(state: .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.addRouteEvent(.Event0, transitions: [ .State0 => .State1 ]) + machine.addRouteEvent(.Event1, routes: [ [ .State1, .State2 ] => .State3 ]) + } - // add 0 => 1 => 2 - machine.addRouteEvent("Run", transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) + XCTAssertEqual(machine.state, MyState.State0) - // tryEvent - machine <-! "Run" + machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) - // tryEvent - machine <-! "Run" - XCTAssertEqual(machine.state, MyState.State2) + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State3) + } + + // Fix hasRoute() bug when there are routes for no-event & with-event. + // https://github.com/ReactKit/SwiftState/pull/19 + func testHasRoute_issue19() + { + let machine = Machine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) // no-event + machine.addRouteEvent(.Event0, transitions: [.State1 => .State2]) // with-event + } - // tryEvent - let success = machine <-! "Run" - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event=Run doesn't have 2 => Any") + let hasRoute = machine.hasRoute(.State1 => .State2, forEvent: .Event0) + XCTAssertTrue(hasRoute) } + func testAddRouteEvent_multiple() { - let machine = Machine(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, - ]) + let machine = Machine(state: .State0) { machine in + + // 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 @@ -185,18 +218,19 @@ class MachineEventTests: _TestCase func testAddRouteEvent_handler() { - let machine = Machine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ], handler: { context in - invokeCount++ - return - }) + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRouteEvent(.Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ], handler: { context in + invokeCount++ + return + }) + } // tryEvent machine <-! .Event0 @@ -211,19 +245,21 @@ class MachineEventTests: _TestCase func testAddEventHandler() { - let machine = Machine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) + let machine = Machine(state: .State0) { machine in - machine.addEventHandler(.Event0) { context in - invokeCount++ - return + // add 0 => 1 => 2 + machine.addRouteEvent(.Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + machine.addEventHandler(.Event0) { context in + invokeCount++ + return + } + } // tryEvent @@ -239,24 +275,26 @@ class MachineEventTests: _TestCase func testRemoveRouteEvent() { - let machine = Machine(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 - } + let machine = Machine(state: .State0) { machine in - // removeRoute - for routeID in routeIDs { - machine.removeRoute(routeID) + // 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 @@ -272,23 +310,25 @@ class MachineEventTests: _TestCase func testRemoveEventHandler() { - let machine = Machine(state: .State0) - var invokeCount = 0 - // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ - .State0 => .State1, - .State1 => .State2, - ]) + let machine = Machine(state: .State0) { machine in - let handlerID = machine.addEventHandler(.Event0) { context in - invokeCount++ - return - } + // 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) + // removeHandler + machine.removeHandler(handlerID) + + } // tryEvent machine <-! .Event0 From bfd9ed2c42d44bceeb60643d9865ffe2656cba16 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 25 Nov 2015 01:41:21 +0900 Subject: [PATCH 08/27] Remove verbose methods in `Transition` & `Route`. --- SwiftState/Machine.swift | 4 +- SwiftState/Route.swift | 12 --- SwiftState/RouteChain.swift | 38 +------- SwiftState/Transition.swift | 10 -- SwiftState/TransitionChain.swift | 78 +++------------ SwiftStateTests/BasicTests.swift | 2 +- SwiftStateTests/MachineTests.swift | 2 +- SwiftStateTests/RouteChainTests.swift | 1 + SwiftStateTests/TransitionChainTests.swift | 73 +++++++------- SwiftStateTests/TransitionTests.swift | 24 +++++ SwiftStateTests/TryEventTests.swift | 105 ++++++++++++--------- 11 files changed, 143 insertions(+), 206 deletions(-) diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift index 5529b52..7142830 100644 --- a/SwiftState/Machine.swift +++ b/SwiftState/Machine.swift @@ -471,7 +471,7 @@ public class Machine public func addChainHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID { - return self.addChainHandler(chain.toRouteChain(), order: order, handler: handler) + return self.addChainHandler(RouteChain(transitionChain: chain), order: order, handler: handler) } public func addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID @@ -483,7 +483,7 @@ public class Machine public func addChainErrorHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID { - return self.addChainErrorHandler(chain.toRouteChain(), order: order, handler: handler) + return self.addChainErrorHandler(RouteChain(transitionChain: chain), order: order, handler: handler) } public func addChainErrorHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID diff --git a/SwiftState/Route.swift b/SwiftState/Route.swift index cc65ff6..ef77f27 100644 --- a/SwiftState/Route.swift +++ b/SwiftState/Route.swift @@ -16,18 +16,6 @@ public struct Route self.transition = transition self.condition = condition } - - public func toTransition() -> Transition - { - return self.transition - } - - public func toRouteChain() -> RouteChain - { - var routes: [Route] = [] - routes += [self] - return RouteChain(routes: routes) - } } //-------------------------------------------------- diff --git a/SwiftState/RouteChain.swift b/SwiftState/RouteChain.swift index 0f1bab4..162967a 100644 --- a/SwiftState/RouteChain.swift +++ b/SwiftState/RouteChain.swift @@ -8,51 +8,19 @@ public struct RouteChain { - internal var routes: [Route] + public private(set) var routes: [Route] public init(routes: [Route]) { self.routes = routes } - public init(transitionChain: TransitionChain, condition: Machine.Condition?) + public init(transitionChain: TransitionChain, condition: Machine.Condition? = nil) { 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: Machine.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: Machine.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 TransitionChain(transitions: transitions) - } - - public func toRoutes() -> [Route] - { - return self.routes + self.init(routes: routes) } } \ No newline at end of file diff --git a/SwiftState/Transition.swift b/SwiftState/Transition.swift index 718cacd..3116e9a 100644 --- a/SwiftState/Transition.swift +++ b/SwiftState/Transition.swift @@ -36,16 +36,6 @@ public struct Transition: Hashable { return self.fromState.hashValue &+ self.toState.hashValue.byteSwapped } - - public func toTransitionChain() -> TransitionChain - { - return TransitionChain(transition: self) - } - - public func toRoute() -> Route - { - return Route(transition: self, condition: nil) - } } // for Transition Equatable diff --git a/SwiftState/TransitionChain.swift b/SwiftState/TransitionChain.swift index c875740..3b8f8f0 100644 --- a/SwiftState/TransitionChain.swift +++ b/SwiftState/TransitionChain.swift @@ -8,25 +8,16 @@ public struct TransitionChain { - public var states: [State] + public private(set) var states: [State] - public init(transition: Transition) + public init(states: [State]) { - self.init(transitions: [transition]) + self.states = states } - public init(transitions: [Transition]) + public init(transition: Transition) { - assert(transitions.count > 0, "TransitionChain must be initialized with at least 1 transition.") - - var states: [State] = [] - for i in 0..] @@ -39,51 +30,6 @@ public struct TransitionChain 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 prepend(state: S) - { - self.states.insert(.Some(state), atIndex: 0) - } - - mutating public func append(state: State) - { - self.states += [state] - } - - mutating public func append(state: S) - { - self.states += [.Some(state)] - } - - public func toRouteChain() -> RouteChain - { - return RouteChain(transitionChain: self, condition: nil) - } - - public func toTransitions() -> [Transition] - { - return self.transitions - } } //-------------------------------------------------- @@ -93,7 +39,7 @@ public struct TransitionChain // e.g. (.State0 => .State1) => .State public func => (left: Transition, right: State) -> TransitionChain { - return left.toTransitionChain() => right + return TransitionChain(states: [left.fromState, left.toState]) => right } public func => (left: Transition, right: S) -> TransitionChain @@ -101,10 +47,9 @@ public func => (left: Transition, right: S) -> TransitionChain< return left => .Some(right) } -public func => (var left: TransitionChain, right: State) -> TransitionChain +public func => (left: TransitionChain, right: State) -> TransitionChain { - left.append(right) - return left + return TransitionChain(states: left.states + [right]) } public func => (left: TransitionChain, right: S) -> TransitionChain @@ -115,7 +60,7 @@ public func => (left: TransitionChain, right: S) -> TransitionC // e.g. .State0 => (.State1 => .State) public func => (left: State, right:Transition) -> TransitionChain { - return left => right.toTransitionChain() + return left => TransitionChain(states: [right.fromState, right.toState]) } public func => (left: S, right:Transition) -> TransitionChain @@ -123,10 +68,9 @@ public func => (left: S, right:Transition) -> TransitionChain right } -public func => (left: State, var right: TransitionChain) -> TransitionChain +public func => (left: State, right: TransitionChain) -> TransitionChain { - right.prepend(left) - return right + return TransitionChain(states: [left] + right.states) } public func => (left: S, right: TransitionChain) -> TransitionChain diff --git a/SwiftStateTests/BasicTests.swift b/SwiftStateTests/BasicTests.swift index 47dfe43..40762d7 100644 --- a/SwiftStateTests/BasicTests.swift +++ b/SwiftStateTests/BasicTests.swift @@ -66,7 +66,7 @@ class BasicTests: _TestCase } } - // tryState 0 => 1 => 2 => 1 => 0 + // tryState 0 => 1 => 2 => 1 => 0 machine <- ".State1" XCTAssertTrue(machine.state == ".State1") diff --git a/SwiftStateTests/MachineTests.swift b/SwiftStateTests/MachineTests.swift index 91bf1e8..da3b0e0 100644 --- a/SwiftStateTests/MachineTests.swift +++ b/SwiftStateTests/MachineTests.swift @@ -280,7 +280,7 @@ class MachineTests: _TestCase } //-------------------------------------------------- - // MARK: - tryState a.k.a <- + // MARK: - tryState a.k.a `<-` //-------------------------------------------------- // machine <- state diff --git a/SwiftStateTests/RouteChainTests.swift b/SwiftStateTests/RouteChainTests.swift index 7693b83..e1d144b 100644 --- a/SwiftStateTests/RouteChainTests.swift +++ b/SwiftStateTests/RouteChainTests.swift @@ -313,4 +313,5 @@ class MachineChainTests: _TestCase machine <- .State2 XCTAssertEqual(errorCount, 0, "Chain error, but chainErrorHandler should NOT be performed.") } + } \ No newline at end of file diff --git a/SwiftStateTests/TransitionChainTests.swift b/SwiftStateTests/TransitionChainTests.swift index 5f23611..ac98a20 100644 --- a/SwiftStateTests/TransitionChainTests.swift +++ b/SwiftStateTests/TransitionChainTests.swift @@ -16,68 +16,77 @@ class TransitionChainTests: _TestCase // 0 => 1 => 2 var chain = MyState.State0 => .State1 => .State2 - XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState.value, MyState.State0) - XCTAssertEqual(chain.lastState.value, MyState.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.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState.value, MyState.State1) - XCTAssertEqual(chain.lastState.value, MyState.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.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState.value, MyState.State2) - XCTAssertEqual(chain.lastState.value, MyState.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 = transition.toTransitionChain() + var chain = TransitionChain(transition: transition) - XCTAssertEqual(chain.numberOfTransitions, 1) - XCTAssertEqual(chain.firstState.value, MyState.State0) - XCTAssertEqual(chain.lastState.value, MyState.State1) + XCTAssertEqual(chain.states.count, 2) + XCTAssertTrue(chain.states[0] == .State0) + XCTAssertTrue(chain.states[1] == .State1) // 0 => 1 => 2 - chain = chain => .State2 // same as append - XCTAssertEqual(chain.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState.value, MyState.State0) - XCTAssertEqual(chain.lastState.value, MyState.State2) + 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.append(.State3) - XCTAssertEqual(chain.numberOfTransitions, 3) - XCTAssertEqual(chain.firstState.value, MyState.State0) - XCTAssertEqual(chain.lastState.value, MyState.State3) + 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 = transition.toTransitionChain() + var chain = TransitionChain(transition: transition) - XCTAssertEqual(chain.numberOfTransitions, 1) - XCTAssertEqual(chain.firstState.value, MyState.State0) - XCTAssertEqual(chain.lastState.value, MyState.State1) + 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.numberOfTransitions, 2) - XCTAssertEqual(chain.firstState.value, MyState.State2) - XCTAssertEqual(chain.lastState.value, MyState.State1) + 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.prepend(.State3) - XCTAssertEqual(chain.numberOfTransitions, 3) - XCTAssertEqual(chain.firstState.value, MyState.State3) - XCTAssertEqual(chain.lastState.value, MyState.State1) + 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/SwiftStateTests/TransitionTests.swift b/SwiftStateTests/TransitionTests.swift index e5248f8..473d477 100644 --- a/SwiftStateTests/TransitionTests.swift +++ b/SwiftStateTests/TransitionTests.swift @@ -23,6 +23,30 @@ class TransitionTests: _TestCase XCTAssertEqual(transition2.toState.value, MyState.State0) } + func testInit_fromAny() + { + let transition = Transition(fromState: .Any, toState: .State1) + XCTAssertNil(transition.fromState.value) + XCTAssertEqual(transition.toState.value, MyState.State1) + + // shorthand + let transition2 = .Any => MyState.State0 + XCTAssertNil(transition2.fromState.value) + XCTAssertEqual(transition2.toState.value, MyState.State0) + } + + func testInit_toAny() + { + let transition = Transition(fromState: .State0, toState: .Any) + XCTAssertEqual(transition.fromState.value, MyState.State0) + XCTAssertNil(transition.toState.value) + + // shorthand + let transition2 = MyState.State1 => .Any + XCTAssertEqual(transition2.fromState.value, MyState.State1) + XCTAssertNil(transition2.toState.value) + } + func testNil() { // .Any => state diff --git a/SwiftStateTests/TryEventTests.swift b/SwiftStateTests/TryEventTests.swift index 25a8f90..98820e2 100644 --- a/SwiftStateTests/TryEventTests.swift +++ b/SwiftStateTests/TryEventTests.swift @@ -25,30 +25,9 @@ class TryEventTests: _TestCase XCTAssertTrue(machine.canTryEvent(.Event0) != nil) } - func testTryState() - { - let machine = Machine(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.") - } + //-------------------------------------------------- + // MARK: - tryEvent a.k.a `<-!` + //-------------------------------------------------- func testTryEvent() { @@ -165,6 +144,36 @@ class TryEventTests: _TestCase XCTAssertTrue(hasRoute) } + //-------------------------------------------------- + // MARK: - add/removeRouteEvent + //-------------------------------------------------- + + func testAddRouteEvent_tryState() + { + let machine = Machine(state: .State0) { machine in + + // 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 testAddRouteEvent_multiple() { @@ -243,14 +252,14 @@ class TryEventTests: _TestCase XCTAssertEqual(invokeCount, 2) } - func testAddEventHandler() + func testRemoveRouteEvent() { var invokeCount = 0 let machine = Machine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + let routeIDs = machine.addRouteEvent(.Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -260,52 +269,56 @@ class TryEventTests: _TestCase return } + // removeRoute + for routeID in routeIDs { + machine.removeRoute(routeID) + } + } // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State1) + var success = machine <-! .Event0 + XCTAssertFalse(success, "RouteEvent should be removed.") // tryEvent - machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) + success = machine <-! .Event0 + XCTAssertFalse(success, "RouteEvent should be removed.") - XCTAssertEqual(invokeCount, 2) + XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") } - func testRemoveRouteEvent() + //-------------------------------------------------- + // MARK: - add/removeEventHandler + //-------------------------------------------------- + + func testAddEventHandler() { var invokeCount = 0 let machine = Machine(state: .State0) { machine in - + // add 0 => 1 => 2 - let routeIDs = machine.addRouteEvent(.Event0, transitions: [ + 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.") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) // tryEvent - success = machine <-! .Event0 - XCTAssertFalse(success, "RouteEvent should be removed.") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) - XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") + XCTAssertEqual(invokeCount, 2) } func testRemoveEventHandler() @@ -318,7 +331,7 @@ class TryEventTests: _TestCase machine.addRouteEvent(.Event0, transitions: [ .State0 => .State1, .State1 => .State2, - ]) + ]) let handlerID = machine.addEventHandler(.Event0) { context in invokeCount++ From 439d5a85e58eca03a1021d927665faa5355326c5 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sat, 28 Nov 2015 23:51:39 +0900 Subject: [PATCH 09/27] Organize code. --- SwiftState/EventType.swift | 85 +++++++++++++++++++------------------- SwiftState/Machine.swift | 10 ++--- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/SwiftState/EventType.swift b/SwiftState/EventType.swift index 9edb650..613487c 100644 --- a/SwiftState/EventType.swift +++ b/SwiftState/EventType.swift @@ -8,47 +8,6 @@ public protocol EventType: Hashable {} -// MARK: _Event (internal) - -internal enum _Event: Hashable -{ - case Some(E) - case Any // represents any `Some(E)` events but not `.None` - case None // default internal value for `addRoute()` without event - - internal var hashValue: Int - { - switch self { - case .Some(let x): return x.hashValue - case .Any: return -4611686018427387904 - case .None: return -4611686018427387905 - } - } - - internal var value: E? - { - switch self { - case .Some(let x): return x - default: return nil - } - } -} - -internal func == (lhs: _Event, rhs: _Event) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} - -internal func == (lhs: _Event, rhs: E) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} - -internal func == (lhs: E, rhs: _Event) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} - // MARK: Event (public) /// `EventType` wrapper for handling`.Any` event. @@ -100,4 +59,46 @@ public enum NoEvent: EventType public func == (lhs: NoEvent, rhs: NoEvent) -> Bool { return true -} \ No newline at end of file +} + +// MARK: _Event (internal) + +/// Internal `EventType` wrapper for `Event` + `Optional` + `Hashable`. +internal enum _Event: Hashable +{ + case Some(E) + case Any // represents any `Some(E)` events but not `.None`, for `addRouteEvent(.Any)` + case None // default internal value for `addRoute()` without event + + internal var hashValue: Int + { + switch self { + case .Some(let x): return x.hashValue + case .Any: return -4611686018427387904 + case .None: return -4611686018427387905 + } + } + + internal var value: E? + { + switch self { + case .Some(let x): return x + default: return nil + } + } +} + +internal func == (lhs: _Event, rhs: _Event) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +internal func == (lhs: _Event, rhs: E) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} + +internal func == (lhs: E, rhs: _Event) -> Bool +{ + return lhs.hashValue == rhs.hashValue +} diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift index 7142830..d4ab82a 100644 --- a/SwiftState/Machine.swift +++ b/SwiftState/Machine.swift @@ -81,11 +81,6 @@ public class Machine return false } - public func hasRoute(fromState fromState: S, toState: S, forEvent event: E, userInfo: Any? = nil) -> Bool - { - return self.hasRoute(fromState: fromState, toState: toState, forEvent: .Some(event), userInfo: userInfo) - } - /// /// Check for `_routes`. /// @@ -102,8 +97,9 @@ public class Machine var transitionDicts: [[Transition : [String : Condition?]]] = [] if let event = event { - for (ev, transitionDict) in self._routes { - if ev.value == event || ev == .Any { // NOTE: no .Default + for (_event, transitionDict) in self._routes { + // NOTE: `_event = .None` should be excluded + if _event.value == event || _event == .Any { transitionDicts += [transitionDict] } } From 12cb3b89a1435970d9c2bda30ea20e05c5769a12 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sat, 28 Nov 2015 23:54:07 +0900 Subject: [PATCH 10/27] [Test] Add testHasRoute_anyEvent() --- SwiftStateTests/TryEventTests.swift | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/SwiftStateTests/TryEventTests.swift b/SwiftStateTests/TryEventTests.swift index 98820e2..c443650 100644 --- a/SwiftStateTests/TryEventTests.swift +++ b/SwiftStateTests/TryEventTests.swift @@ -116,7 +116,8 @@ class TryEventTests: _TestCase // Fix for transitioning of routes w/ multiple from-states // https://github.com/ReactKit/SwiftState/pull/32 - func testTryEvent_issue32() { + func testTryEvent_issue32() + { let machine = Machine(state: .State0) { machine in machine.addRouteEvent(.Event0, transitions: [ .State0 => .State1 ]) machine.addRouteEvent(.Event1, routes: [ [ .State1, .State2 ] => .State3 ]) @@ -131,6 +132,31 @@ class TryEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State3) } + // MARK: hasRoute + event + + func testHasRoute_anyEvent() + { + ({ + let machine = Machine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) + machine.addRouteEvent(.Any, transitions: [.State0 => .State1]) + } + + let hasRoute = machine.hasRoute(.State0 => .State1, forEvent: .Event0) + XCTAssertTrue(hasRoute) + })() + + ({ + let machine = Machine(state: .State0) { machine in + machine.addRoute(.State0 => .State1) + machine.addRouteEvent(.Any, transitions: [.State2 => .State3]) + } + + let hasRoute = machine.hasRoute(.State0 => .State1, forEvent: .Event0) + XCTAssertFalse(hasRoute) + })() + } + // Fix hasRoute() bug when there are routes for no-event & with-event. // https://github.com/ReactKit/SwiftState/pull/19 func testHasRoute_issue19() From 2eeff1fecf589d6c3cf8942ef3de17293e54e36f Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sun, 29 Nov 2015 00:11:39 +0900 Subject: [PATCH 11/27] Add `final` modifiers. --- SwiftState.xcodeproj/project.pbxproj | 6 ++++++ SwiftState/ChainHandlerID.swift | 17 +++++++++++++++++ SwiftState/HandlerID.swift | 12 +----------- SwiftState/Machine.swift | 2 +- SwiftState/RouteChainID.swift | 2 +- SwiftState/RouteID.swift | 2 +- SwiftState/RouteMappingID.swift | 2 +- 7 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 SwiftState/ChainHandlerID.swift diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index 2466c70..b094c9e 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; + 1F1F74BC1C09FAA000675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; + 1F1F74C01C0A017E00675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; @@ -73,6 +75,7 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; + 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChainHandlerID.swift; sourceTree = ""; }; 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteMappingTests.swift; sourceTree = ""; }; 1F70FB651BF0F46000E5AC8C /* RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteID.swift; sourceTree = ""; }; 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainID.swift; sourceTree = ""; }; @@ -146,6 +149,7 @@ 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */, 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */, 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */, + 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */, ); name = Identifiers; sourceTree = ""; @@ -423,6 +427,7 @@ 1FA620241996606300460108 /* Transition.swift in Sources */, 1F70FB661BF0F46000E5AC8C /* RouteID.swift in Sources */, 1FA620201996606300460108 /* EventType.swift in Sources */, + 1F1F74BC1C09FAA000675EAA /* ChainHandlerID.swift in Sources */, 1F70FB6C1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */, 1FA620221996606300460108 /* Route.swift in Sources */, 1F70FB6F1BF0F59600E5AC8C /* HandlerID.swift in Sources */, @@ -481,6 +486,7 @@ 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */, 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */, 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */, + 1F1F74C01C0A017E00675EAA /* ChainHandlerID.swift in Sources */, 1F70FB6D1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */, 48797D6319B42CD40085D80F /* StateType.swift in Sources */, 1F70FB701BF0F59600E5AC8C /* HandlerID.swift in Sources */, diff --git a/SwiftState/ChainHandlerID.swift b/SwiftState/ChainHandlerID.swift new file mode 100644 index 0000000..32afe0d --- /dev/null +++ b/SwiftState/ChainHandlerID.swift @@ -0,0 +1,17 @@ +// +// ChainHandlerID.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-11-29. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +public final class ChainHandlerID +{ + internal let bundledHandlerIDs: [HandlerID] + + internal init(bundledHandlerIDs: [HandlerID]) + { + self.bundledHandlerIDs = bundledHandlerIDs + } +} \ No newline at end of file diff --git a/SwiftState/HandlerID.swift b/SwiftState/HandlerID.swift index 20904e2..5b02858 100644 --- a/SwiftState/HandlerID.swift +++ b/SwiftState/HandlerID.swift @@ -6,7 +6,7 @@ // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public class HandlerID +public final class HandlerID { /// - Note: `nil` is used for error-handlerID internal let transition: Transition? @@ -18,14 +18,4 @@ public class HandlerID self.transition = transition self.key = key } -} - -public class ChainHandlerID -{ - internal let bundledHandlerIDs: [HandlerID] - - internal init(bundledHandlerIDs: [HandlerID]) - { - self.bundledHandlerIDs = bundledHandlerIDs - } } \ No newline at end of file diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift index d4ab82a..99929b5 100644 --- a/SwiftState/Machine.swift +++ b/SwiftState/Machine.swift @@ -11,7 +11,7 @@ import Darwin public typealias HandlerOrder = UInt8 private let _defaultOrder: HandlerOrder = 100 -public class Machine +public final class Machine { // NOTE: `event = nil` is equivalent to `_Event.None`, which happens when non-event-based transition e.g. `tryState()` occurs. public typealias Context = (event: E?, fromState: S, toState: S, userInfo: Any?) diff --git a/SwiftState/RouteChainID.swift b/SwiftState/RouteChainID.swift index 2355f12..65abb36 100644 --- a/SwiftState/RouteChainID.swift +++ b/SwiftState/RouteChainID.swift @@ -6,7 +6,7 @@ // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public class RouteChainID +public final class RouteChainID { internal let bundledRouteIDs: [RouteID]? diff --git a/SwiftState/RouteID.swift b/SwiftState/RouteID.swift index ffe529b..e52d350 100644 --- a/SwiftState/RouteID.swift +++ b/SwiftState/RouteID.swift @@ -6,7 +6,7 @@ // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public class RouteID +public final class RouteID { internal let event: _Event internal let transition: Transition diff --git a/SwiftState/RouteMappingID.swift b/SwiftState/RouteMappingID.swift index 6a59316..f36a11f 100644 --- a/SwiftState/RouteMappingID.swift +++ b/SwiftState/RouteMappingID.swift @@ -6,7 +6,7 @@ // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public class RouteMappingID +public final class RouteMappingID { internal let key: String From 1be67826b3cc9187dfaac85c2e70613f3129fad6 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Mon, 30 Nov 2015 07:11:24 +0900 Subject: [PATCH 12/27] [Test] Re-add HierarchicalMachineTests which was deleted in ef07416. --- SwiftState.xcodeproj/project.pbxproj | 6 + .../HierarchicalMachineTests.swift | 285 ++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 SwiftStateTests/HierarchicalMachineTests.swift diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index b094c9e..d3087e6 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; 1F1F74BC1C09FAA000675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; 1F1F74C01C0A017E00675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; + 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; + 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; @@ -76,6 +78,7 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChainHandlerID.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 = ""; }; 1F70FB651BF0F46000E5AC8C /* RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteID.swift; sourceTree = ""; }; 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainID.swift; sourceTree = ""; }; @@ -220,6 +223,7 @@ 1FA6202D199660CA00460108 /* RouteTests.swift */, 1FA6202A199660CA00460108 /* RouteChainTests.swift */, 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */, + 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */, 1FA6200D1996601000460108 /* Supporting Files */, ); path = SwiftStateTests; @@ -447,6 +451,7 @@ 1FA62038199660CA00460108 /* TransitionTests.swift in Sources */, 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */, 1FA62030199660CA00460108 /* _TestCase.swift in Sources */, + 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */, 1FA62036199660CA00460108 /* RouteTests.swift in Sources */, 1FA62031199660CA00460108 /* BasicTests.swift in Sources */, 1FA62034199660CA00460108 /* TryEventTests.swift in Sources */, @@ -467,6 +472,7 @@ 4822F0AF19D008EB00F5F572 /* TryEventTests.swift in Sources */, 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, + 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */, 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */, 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */, 4822F0A919D008E700F5F572 /* MyState.swift in Sources */, diff --git a/SwiftStateTests/HierarchicalMachineTests.swift b/SwiftStateTests/HierarchicalMachineTests.swift new file mode 100644 index 0000000..7d39e62 --- /dev/null +++ b/SwiftStateTests/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: Machine<_MainState, NoEvent>? + + private var subMachine1: Machine<_SubState, NoEvent>? + private var subMachine2: Machine<_SubState, NoEvent>? + + override func setUp() + { + super.setUp() + + let subMachine1 = Machine<_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 = Machine<_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 = Machine<_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 { _, fromState, _ -> _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) + } + +} From 8e093daf42bc6a44d6918cf93984201a9e151a9b Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Tue, 1 Dec 2015 07:58:10 +0900 Subject: [PATCH 13/27] Update README.md & BasicTests. --- README.md | 79 +++++++++++++++++--------------- SwiftStateTests/BasicTests.swift | 8 ++-- SwiftStateTests/MyState.swift | 2 +- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 6a2e989..050cbb2 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` ([Test Case](https://github.com/ReactKit/SwiftState/blob/1be67826b3cc9187dfaac85c2e70613f3129fad6/SwiftStateTests/TryEventTests.swift#L32-L54)). ```swift -enum MyEvent: StateEventType { +enum MyEvent: EventType { case Event0, Event1 - case AnyEvent // create case=Any - - init(nilLiteral: Void) { - self = AnyEvent - } } ``` @@ -95,6 +94,8 @@ XCTAssertEqual(machine.state, MyState.State2) XCTAssertFalse(success, "Event0 doesn't have 2 => Any") ``` +If there is no `Event`-based transition, use built-in `NoEvent` instead. + For more examples, please see XCTest cases. @@ -106,27 +107,29 @@ For more examples, please see XCTest cases. - Try transition + 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) + - Using `Condition` + - Using `.Any` state/event + - Blacklisting: `.Any => .Any` + `Condition` + - Route Mapping (closure-based routing): [#36](https://github.com/ReactKit/SwiftState/pull/36) +- Success/Error/Entry/Exit handlers with `order: UInt8` (more flexible than before/after handlers) - Removable routes and handlers - Chaining: `.State0 => .State1 => .State2` -- Event: `machine.addRouteEvent("WakeUp", transitions); machine <-! "WakeUp"` - 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. +Handler | `Context -> Void` | Transition callback invoked after state has been changed. +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/SwiftStateTests/BasicTests.swift b/SwiftStateTests/BasicTests.swift index 40762d7..0c2b9d3 100644 --- a/SwiftStateTests/BasicTests.swift +++ b/SwiftStateTests/BasicTests.swift @@ -13,13 +13,14 @@ class BasicTests: _TestCase { func testREADME() { + // setup state machine let machine = Machine(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)) + // add handler (`context = (event, fromState, toState, userInfo)`) machine.addHandler(.State0 => .State1) { context in print("0 => 1") } @@ -30,6 +31,9 @@ class BasicTests: _TestCase } } + // initial + XCTAssertTrue(machine.state == .State0) + // tryState 0 => 1 => 2 => 1 => 0 machine <- .State1 @@ -43,8 +47,6 @@ class BasicTests: _TestCase machine <- .State0 // fail: no 1 => 0 XCTAssertTrue(machine.state == .State1) - - print("machine.state = \(machine.state)") } func testREADME_string() diff --git a/SwiftStateTests/MyState.swift b/SwiftStateTests/MyState.swift index c2d3660..2f8058a 100644 --- a/SwiftStateTests/MyState.swift +++ b/SwiftStateTests/MyState.swift @@ -8,7 +8,7 @@ import SwiftState -enum MyState: Int, StateType +enum MyState: StateType { case State0, State1, State2, State3 } From 2986012a18c35907adb5d6d32f7412bdd3728be9 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Tue, 1 Dec 2015 08:24:53 +0900 Subject: [PATCH 14/27] Resize logo.png --- Screenshots/logo.png | Bin 42438 -> 26971 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Screenshots/logo.png b/Screenshots/logo.png index 57be251ab870a6a3ded05f4fca83a2baa818e481..308596bb292df81eb10c4b0735c7f79a81e17055 100644 GIT binary patch literal 26971 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 o@|zRrOTrmFy$`7Yiui5dZ)H literal 42438 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

`>)5N-0qMD4fN&_X43eH%h<@wZ+^* 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 Date: Sat, 5 Dec 2015 22:12:40 +0900 Subject: [PATCH 15/27] Refactor code by separating event-only-driven `Machine` and `StateMachine`. - Add `Machine` (event-only-driven state machine). - Add `Disposable` derived from ReactiveCocoa. - Add Package.swift & change directory structure for Swift-Package-Manager. --- .gitignore | 2 + Package.swift | 13 + README.md | 12 +- Sources/Disposable.swift | 45 + Sources/EventType.swift | 62 ++ {SwiftState => Sources}/Info.plist | 0 Sources/Machine.swift | 581 ++++++++++++ {SwiftState => Sources}/Route.swift | 1 + {SwiftState => Sources}/RouteChain.swift | 1 + Sources/StateMachine.swift | 511 +++++++++++ {SwiftState => Sources}/StateType.swift | 8 +- {SwiftState => Sources}/SwiftState.h | 0 {SwiftState => Sources}/Transition.swift | 4 + {SwiftState => Sources}/TransitionChain.swift | 1 + .../_HandlerID.swift | 9 +- Sources/_HandlerInfo.swift | 21 + Sources/_Random.swift | 22 + .../RouteID.swift => Sources/_RouteID.swift | 10 +- .../_RouteMappingID.swift | 4 +- SwiftState.xcodeproj/project.pbxproj | 166 ++-- .../xcschemes/SwiftState-OSX.xcscheme | 8 +- .../xcschemes/SwiftState-iOS.xcscheme | 8 +- SwiftState/ChainHandlerID.swift | 17 - SwiftState/EventType.swift | 104 --- SwiftState/Machine.swift | 851 ------------------ SwiftState/RouteChainID.swift | 17 - Tests/BasicTests.swift | 75 ++ .../HierarchicalMachineTests.swift | 14 +- {SwiftStateTests => Tests}/Info.plist | 0 Tests/MachineTests.swift | 300 ++++++ .../BasicTests.swift => Tests/MiscTests.swift | 115 +-- {SwiftStateTests => Tests}/MyEvent.swift | 0 {SwiftStateTests => Tests}/MyState.swift | 0 {SwiftStateTests => Tests}/QiitaTests.swift | 2 +- .../RouteChainTests.swift | 59 +- .../RouteMappingTests.swift | 16 +- {SwiftStateTests => Tests}/RouteTests.swift | 0 .../StateMachineEventTests.swift | 165 ++-- .../StateMachineTests.swift | 230 +++-- .../String+TestExt.swift | 0 .../TransitionChainTests.swift | 0 .../TransitionTests.swift | 0 {SwiftStateTests => Tests}/_TestCase.swift | 0 43 files changed, 2061 insertions(+), 1393 deletions(-) create mode 100644 Package.swift create mode 100644 Sources/Disposable.swift create mode 100644 Sources/EventType.swift rename {SwiftState => Sources}/Info.plist (100%) create mode 100644 Sources/Machine.swift rename {SwiftState => Sources}/Route.swift (98%) rename {SwiftState => Sources}/RouteChain.swift (95%) create mode 100644 Sources/StateMachine.swift rename {SwiftState => Sources}/StateType.swift (88%) rename {SwiftState => Sources}/SwiftState.h (100%) rename {SwiftState => Sources}/Transition.swift (94%) rename {SwiftState => Sources}/TransitionChain.swift (96%) rename SwiftState/HandlerID.swift => Sources/_HandlerID.swift (60%) create mode 100644 Sources/_HandlerInfo.swift create mode 100644 Sources/_Random.swift rename SwiftState/RouteID.swift => Sources/_RouteID.swift (64%) rename SwiftState/RouteMappingID.swift => Sources/_RouteMappingID.swift (78%) delete mode 100644 SwiftState/ChainHandlerID.swift delete mode 100644 SwiftState/EventType.swift delete mode 100644 SwiftState/Machine.swift delete mode 100644 SwiftState/RouteChainID.swift create mode 100644 Tests/BasicTests.swift rename {SwiftStateTests => Tests}/HierarchicalMachineTests.swift (95%) rename {SwiftStateTests => Tests}/Info.plist (100%) create mode 100644 Tests/MachineTests.swift rename SwiftStateTests/BasicTests.swift => Tests/MiscTests.swift (66%) rename {SwiftStateTests => Tests}/MyEvent.swift (100%) rename {SwiftStateTests => Tests}/MyState.swift (100%) rename {SwiftStateTests => Tests}/QiitaTests.swift (93%) rename {SwiftStateTests => Tests}/RouteChainTests.swift (80%) rename {SwiftStateTests => Tests}/RouteMappingTests.swift (86%) rename {SwiftStateTests => Tests}/RouteTests.swift (100%) rename SwiftStateTests/TryEventTests.swift => Tests/StateMachineEventTests.swift (59%) rename SwiftStateTests/MachineTests.swift => Tests/StateMachineTests.swift (72%) rename {SwiftStateTests => Tests}/String+TestExt.swift (100%) rename {SwiftStateTests => Tests}/TransitionChainTests.swift (100%) rename {SwiftStateTests => Tests}/TransitionTests.swift (100%) rename {SwiftStateTests => Tests}/_TestCase.swift (100%) 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/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 050cbb2..9275388 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ enum MyEvent: EventType { let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoute(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -89,9 +89,8 @@ 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") +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. @@ -126,8 +125,9 @@ State Machine | `Machine` | State transition manager which c 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. -Handler | `Context -> Void` | Transition callback invoked after state has been changed. +Event 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`. 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` 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..9303ef1 --- /dev/null +++ b/Sources/EventType.swift @@ -0,0 +1,62 @@ +// +// 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: Hashable +{ + case Some(E) + case Any + + public var value: E? + { + switch self { + case .Some(let x): return x + default: return nil + } + } + + public var hashValue: Int + { + switch self { + case .Some(let x): return x.hashValue + case .Any: return _hashValueForAny + } + } +} + +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 + } +} + +// 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..c379673 --- /dev/null +++ b/Sources/Machine.swift @@ -0,0 +1,581 @@ +// +// 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 `EventRouteMapping` 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 EventRouteMapping = (event: E?, fromState: S, userInfo: Any?) -> S? + + internal typealias _RouteDict = [Transition : [String : Condition?]] + + private lazy var _routes: [Event : _RouteDict] = [:] + private lazy var _routeMappings: [String : EventRouteMapping] = [:] + + /// `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.value, + toState = transition.toState.value 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.value == 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.value ?? 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 routeDisposable = self.addRoutes(event: event, transitions: transitions, condition: condition) + let handlerDisposable = self.addHandler(event: event, handler: handler) + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + 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: - EventRouteMapping + //-------------------------------------------------- + + // MARK: addRouteMapping + + public func addRouteMapping(routeMapping: EventRouteMapping) -> 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: EventRouteMapping, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + { + let routeDisposable = self.addRouteMapping(routeMapping) + + let handlerDisposable = self._addHandler(event: .Any, order: order) { context in + guard let event = context.event else { return } + + if self._hasRouteMappingInDict(event: event, fromState: context.fromState, toState: .Some(context.toState), userInfo: context.userInfo) != nil { + 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.value || 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 diff --git a/SwiftState/RouteChain.swift b/Sources/RouteChain.swift similarity index 95% rename from SwiftState/RouteChain.swift rename to Sources/RouteChain.swift index 162967a..f2b6d57 100644 --- a/SwiftState/RouteChain.swift +++ b/Sources/RouteChain.swift @@ -6,6 +6,7 @@ // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. // +/// Group of continuous `Route`s. public struct RouteChain { public private(set) var routes: [Route] diff --git a/Sources/StateMachine.swift b/Sources/StateMachine.swift new file mode 100644 index 0000000..c37a22c --- /dev/null +++ b/Sources/StateMachine.swift @@ -0,0 +1,511 @@ +// +// 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`. + public typealias StateRouteMapping = (fromState: S, userInfo: Any?) -> [S]? + + private lazy var _routes: _RouteDict = [:] + private lazy var _routeMappings: [String : StateRouteMapping] = [:] // NOTE: `StateRouteMapping`, not `EventRouteMapping` + + /// `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.value, + toState = transition.toState.value 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: addRouteMapping + + public func addRouteMapping(routeMapping: StateRouteMapping) -> 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: StateRouteMapping, handler: Handler) -> Disposable + { + let routeDisposable = self.addRouteMapping(routeMapping) + + let handlerDisposable = self.addHandler(.Any => .Any) { context in + if self._hasRouteMappingInDict(fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) != nil { + 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: - 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/SwiftState/StateType.swift b/Sources/StateType.swift similarity index 88% rename from SwiftState/StateType.swift rename to Sources/StateType.swift index 591f3e2..bb538ac 100644 --- a/SwiftState/StateType.swift +++ b/Sources/StateType.swift @@ -20,7 +20,7 @@ public enum State: Hashable { switch self { case .Some(let x): return x.hashValue - case .Any: return -4611686018427387904 + case .Any: return _hashValueForAny } } @@ -46,4 +46,8 @@ public func == (lhs: State, rhs: S) -> Bool public func == (lhs: S, rhs: State) -> Bool { return lhs.hashValue == rhs.hashValue -} \ No newline at end of file +} + +// 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/SwiftState/Transition.swift b/Sources/Transition.swift similarity index 94% rename from SwiftState/Transition.swift rename to Sources/Transition.swift index 3116e9a..e37aabc 100644 --- a/SwiftState/Transition.swift +++ b/Sources/Transition.swift @@ -6,6 +6,10 @@ // 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 diff --git a/SwiftState/TransitionChain.swift b/Sources/TransitionChain.swift similarity index 96% rename from SwiftState/TransitionChain.swift rename to Sources/TransitionChain.swift index 3b8f8f0..29306f0 100644 --- a/SwiftState/TransitionChain.swift +++ b/Sources/TransitionChain.swift @@ -6,6 +6,7 @@ // 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] diff --git a/SwiftState/HandlerID.swift b/Sources/_HandlerID.swift similarity index 60% rename from SwiftState/HandlerID.swift rename to Sources/_HandlerID.swift index 5b02858..8f8daf6 100644 --- a/SwiftState/HandlerID.swift +++ b/Sources/_HandlerID.swift @@ -1,20 +1,23 @@ // -// HandlerID.swift +// _HandlerID.swift // SwiftState // // Created by Yasuhiro Inami on 2015-11-10. // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public final class HandlerID +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(transition: Transition?, key: String) + internal init(event: Event?, transition: Transition?, key: String) { + self.event = event self.transition = transition self.key = key } 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..43c01e4 --- /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) +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/SwiftState/RouteID.swift b/Sources/_RouteID.swift similarity index 64% rename from SwiftState/RouteID.swift rename to Sources/_RouteID.swift index e52d350..e3de663 100644 --- a/SwiftState/RouteID.swift +++ b/Sources/_RouteID.swift @@ -1,21 +1,21 @@ // -// RouteID.swift +// _RouteID.swift // SwiftState // // Created by Yasuhiro Inami on 2015-11-10. // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public final class RouteID +internal final class _RouteID { - internal let event: _Event + internal let event: Event? internal let transition: Transition internal let key: String - internal init(event: _Event, transition: Transition, key: String) + internal init(event: Event?, transition: Transition, key: String) { - self.transition = transition self.event = event + self.transition = transition self.key = key } } \ No newline at end of file diff --git a/SwiftState/RouteMappingID.swift b/Sources/_RouteMappingID.swift similarity index 78% rename from SwiftState/RouteMappingID.swift rename to Sources/_RouteMappingID.swift index f36a11f..7fae7ce 100644 --- a/SwiftState/RouteMappingID.swift +++ b/Sources/_RouteMappingID.swift @@ -1,12 +1,12 @@ // -// RouteMappingID.swift +// _RouteMappingID.swift // SwiftState // // Created by Yasuhiro Inami on 2015-11-10. // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public final class RouteMappingID +internal final class _RouteMappingID { internal let key: String diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index d3087e6..0d85c15 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -8,26 +8,29 @@ /* Begin PBXBuildFile section */ 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; - 1F1F74BC1C09FAA000675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; - 1F1F74C01C0A017E00675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; - 1F70FB661BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; - 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; - 1F70FB691BF0F46E00E5AC8C /* RouteChainID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */; }; - 1F70FB6A1BF0F46E00E5AC8C /* RouteChainID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */; }; - 1F70FB6C1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */; }; - 1F70FB6D1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */; }; - 1F70FB6F1BF0F59600E5AC8C /* HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */; }; - 1F70FB701BF0F59600E5AC8C /* HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */; }; + 1F532C541C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; + 1F532C551C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; + 1F532C571C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; + 1F532C581C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; + 1F532C611C12D7BD00D3813A /* MiscTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C601C12D7BD00D3813A /* MiscTests.swift */; }; + 1F532C621C12D7BD00D3813A /* MiscTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C601C12D7BD00D3813A /* MiscTests.swift */; }; + 1F532C641C12EA8000D3813A /* _Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C631C12EA8000D3813A /* _Random.swift */; }; + 1F532C651C12EA8000D3813A /* _Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C631C12EA8000D3813A /* _Random.swift */; }; + 1F70FB661BF0F46000E5AC8C /* _RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */; }; + 1F70FB671BF0F46000E5AC8C /* _RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */; }; + 1F70FB6C1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */; }; + 1F70FB6D1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */; }; + 1F70FB6F1BF0F59600E5AC8C /* _HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */; }; + 1F70FB701BF0F59600E5AC8C /* _HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */; }; 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */; }; 1F70FB7A1BF0FB7100E5AC8C /* 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 /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* EventType.swift */; }; - 1FA620211996606300460108 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* Machine.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 */; }; @@ -37,8 +40,8 @@ 1FA62031199660CA00460108 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62028199660CA00460108 /* BasicTests.swift */; }; 1FA62032199660CA00460108 /* MyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62029199660CA00460108 /* MyState.swift */; }; 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; - 1FA62034199660CA00460108 /* TryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* TryEventTests.swift */; }; - 1FA62035199660CA00460108 /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* MachineTests.swift */; }; + 1FA62034199660CA00460108 /* StateMachineEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */; }; + 1FA62035199660CA00460108 /* StateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* StateMachineTests.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 */; }; @@ -49,14 +52,19 @@ 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 /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* MachineTests.swift */; }; + 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* StateMachineTests.swift */; }; 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; - 4822F0AF19D008EB00F5F572 /* TryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* TryEventTests.swift */; }; + 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */; }; 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* TransitionTests.swift */; }; 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* TransitionChainTests.swift */; }; 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* RouteTests.swift */; }; + 4836FF5A1C0EFD700038B7D2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4836FF591C0EFD700038B7D2 /* StateMachine.swift */; }; + 4836FF5B1C0EFD700038B7D2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4836FF591C0EFD700038B7D2 /* StateMachine.swift */; }; + 4836FF5C1C0EFD720038B7D2 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483F35561C0EB192007C70D7 /* Machine.swift */; }; + 483F35571C0EB192007C70D7 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483F35561C0EB192007C70D7 /* Machine.swift */; }; + 4876510F1C0ECBEB005961AC /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4876510E1C0ECBEB005961AC /* MachineTests.swift */; }; + 487651101C0ECBEB005961AC /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4876510E1C0ECBEB005961AC /* MachineTests.swift */; }; 48797D5D19B42CBE0085D80F /* SwiftState.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FA620051996601000460108 /* SwiftState.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48797D5E19B42CCE0085D80F /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* Machine.swift */; }; 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* Transition.swift */; }; 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201E1996606300460108 /* TransitionChain.swift */; }; 48797D6119B42CCE0085D80F /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* Route.swift */; }; @@ -77,21 +85,21 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; - 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChainHandlerID.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 = ""; }; - 1F70FB651BF0F46000E5AC8C /* RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteID.swift; sourceTree = ""; }; - 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainID.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 = ""; }; + 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; }; 1FA6200E1996601000460108 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1FA620191996606200460108 /* EventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventType.swift; sourceTree = ""; }; - 1FA6201A1996606300460108 /* Machine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Machine.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 = ""; }; @@ -101,14 +109,17 @@ 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 /* RouteChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainTests.swift; sourceTree = ""; }; - 1FA6202B199660CA00460108 /* TryEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TryEventTests.swift; sourceTree = ""; }; - 1FA6202C199660CA00460108 /* MachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineTests.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 /* 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 = ""; }; 4822F0A619D0085E00F5F572 /* SwiftStateTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftStateTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; 4872D5AC19B4211900F326B5 /* SwiftState.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftState.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4876510E1C0ECBEB005961AC /* MachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -145,16 +156,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1F70FB741BF0FAB900E5AC8C /* Identifiers */ = { + 1F70FB741BF0FAB900E5AC8C /* Internals */ = { isa = PBXGroup; children = ( - 1F70FB651BF0F46000E5AC8C /* RouteID.swift */, - 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */, - 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */, - 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */, - 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */, + 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */, + 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */, + 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */, + 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */, + 1F532C631C12EA8000D3813A /* _Random.swift */, ); - name = Identifiers; + name = Internals; sourceTree = ""; }; 1F70FB751BF0FAE800E5AC8C /* Routes */ = { @@ -171,8 +182,8 @@ 1FA61FF61996601000460108 = { isa = PBXGroup; children = ( - 1FA620021996601000460108 /* SwiftState */, - 1FA6200C1996601000460108 /* SwiftStateTests */, + 1FA620021996601000460108 /* Sources */, + 1FA6200C1996601000460108 /* Tests */, 1FA620011996601000460108 /* Products */, ); sourceTree = ""; @@ -188,36 +199,31 @@ name = Products; sourceTree = ""; }; - 1FA620021996601000460108 /* SwiftState */ = { + 1FA620021996601000460108 /* Sources */ = { isa = PBXGroup; children = ( 1FA620051996601000460108 /* SwiftState.h */, - 1FA6201A1996606300460108 /* Machine.swift */, + 483F35561C0EB192007C70D7 /* Machine.swift */, + 4836FF591C0EFD700038B7D2 /* StateMachine.swift */, + 1F532C531C12B5BC00D3813A /* Disposable.swift */, 1FB1EC88199E4ABC00ABD937 /* Protocols */, 1F70FB751BF0FAE800E5AC8C /* Routes */, - 1F70FB741BF0FAB900E5AC8C /* Identifiers */, - 1FA620031996601000460108 /* Supporting Files */, + 1F70FB741BF0FAB900E5AC8C /* Internals */, ); - path = SwiftState; + 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 */, + 1F532C601C12D7BD00D3813A /* MiscTests.swift */, 1F198C5B19972320001C3700 /* QiitaTests.swift */, - 1FA6202C199660CA00460108 /* MachineTests.swift */, - 1FA6202B199660CA00460108 /* TryEventTests.swift */, + 4876510E1C0ECBEB005961AC /* MachineTests.swift */, + 1FA6202C199660CA00460108 /* StateMachineTests.swift */, + 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */, 1FA6202F199660CA00460108 /* TransitionTests.swift */, 1FA6202E199660CA00460108 /* TransitionChainTests.swift */, 1FA6202D199660CA00460108 /* RouteTests.swift */, @@ -226,7 +232,7 @@ 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */, 1FA6200D1996601000460108 /* Supporting Files */, ); - path = SwiftStateTests; + path = Tests; sourceTree = ""; }; 1FA6200D1996601000460108 /* Supporting Files */ = { @@ -426,16 +432,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1F70FB691BF0F46E00E5AC8C /* RouteChainID.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 */, + 1F70FB661BF0F46000E5AC8C /* _RouteID.swift in Sources */, 1FA620201996606300460108 /* EventType.swift in Sources */, - 1F1F74BC1C09FAA000675EAA /* ChainHandlerID.swift in Sources */, - 1F70FB6C1BF0F47700E5AC8C /* RouteMappingID.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 */, - 1FA620211996606300460108 /* Machine.swift in Sources */, + 1F70FB6F1BF0F59600E5AC8C /* _HandlerID.swift in Sources */, 1FA620261996606300460108 /* StateType.swift in Sources */, 1FA620231996606300460108 /* RouteChain.swift in Sources */, ); @@ -448,14 +456,16 @@ 1FB1EC8A199E515B00ABD937 /* MyEvent.swift in Sources */, 1F198C5C19972320001C3700 /* QiitaTests.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 */, 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */, 1FA62036199660CA00460108 /* RouteTests.swift in Sources */, + 1F532C611C12D7BD00D3813A /* MiscTests.swift in Sources */, 1FA62031199660CA00460108 /* BasicTests.swift in Sources */, - 1FA62034199660CA00460108 /* TryEventTests.swift in Sources */, - 1FA62035199660CA00460108 /* MachineTests.swift in Sources */, + 1FA62034199660CA00460108 /* StateMachineEventTests.swift in Sources */, + 1FA62035199660CA00460108 /* StateMachineTests.swift in Sources */, 1FA62037199660CA00460108 /* TransitionChainTests.swift in Sources */, 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */, 1FA62032199660CA00460108 /* MyState.swift in Sources */, @@ -469,17 +479,19 @@ 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */, 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */, 1F70FB7A1BF0FB7100E5AC8C /* String+TestExt.swift in Sources */, - 4822F0AF19D008EB00F5F572 /* TryEventTests.swift in Sources */, + 487651101C0ECBEB005961AC /* MachineTests.swift in Sources */, + 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */, 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */, 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */, + 1F532C621C12D7BD00D3813A /* MiscTests.swift in Sources */, 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */, 4822F0A919D008E700F5F572 /* MyState.swift in Sources */, 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */, 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */, 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */, - 4822F0AD19D008EB00F5F572 /* MachineTests.swift in Sources */, + 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -487,16 +499,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1F70FB6A1BF0F46E00E5AC8C /* RouteChainID.swift in Sources */, + 1F532C651C12EA8000D3813A /* _Random.swift in Sources */, 48797D6219B42CCE0085D80F /* RouteChain.swift in Sources */, + 1F532C551C12B5BC00D3813A /* Disposable.swift in Sources */, 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */, - 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */, + 1F70FB671BF0F46000E5AC8C /* _RouteID.swift in Sources */, + 4836FF5C1C0EFD720038B7D2 /* Machine.swift in Sources */, + 1F532C581C12BBBB00D3813A /* _HandlerInfo.swift in Sources */, 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */, - 1F1F74C01C0A017E00675EAA /* ChainHandlerID.swift in Sources */, - 1F70FB6D1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */, + 1F70FB6D1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */, 48797D6319B42CD40085D80F /* StateType.swift in Sources */, - 1F70FB701BF0F59600E5AC8C /* HandlerID.swift in Sources */, - 48797D5E19B42CCE0085D80F /* Machine.swift in Sources */, + 4836FF5B1C0EFD700038B7D2 /* StateMachine.swift in Sources */, + 1F70FB701BF0F59600E5AC8C /* _HandlerID.swift in Sources */, 48797D6119B42CCE0085D80F /* Route.swift in Sources */, 48797D6419B42CD40085D80F /* EventType.swift in Sources */, ); @@ -609,7 +623,7 @@ 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; @@ -631,7 +645,7 @@ 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; @@ -639,6 +653,7 @@ PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -654,7 +669,7 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -670,7 +685,7 @@ "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -689,7 +704,7 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/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)"; @@ -705,7 +720,7 @@ "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/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)"; @@ -728,7 +743,7 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = "$(SRCROOT)/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"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; @@ -748,13 +763,14 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/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"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; diff --git a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme index 1231e10..c5db47c 100644 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme +++ b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme @@ -23,10 +23,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -52,11 +52,11 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -52,11 +52,11 @@ -{ - internal let bundledHandlerIDs: [HandlerID] - - internal init(bundledHandlerIDs: [HandlerID]) - { - self.bundledHandlerIDs = bundledHandlerIDs - } -} \ No newline at end of file diff --git a/SwiftState/EventType.swift b/SwiftState/EventType.swift deleted file mode 100644 index 613487c..0000000 --- a/SwiftState/EventType.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// 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 (public) - -/// `EventType` wrapper for handling`.Any` event. -public enum Event: Equatable -{ - case Some(E) - case Any - - public var value: E? - { - switch self { - case .Some(let x): return x - default: return nil - } - } - - internal func _toInternal() -> _Event - { - switch self { - case .Some(let x): return .Some(x) - case .Any: return .Any - } - } -} - -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 - } -} - -// MARK: NoEvent - -/// Useful for creating StateMachine without events, i.e. `Machine`. -public enum NoEvent: EventType -{ - public var hashValue: Int - { - return 0 - } -} - -public func == (lhs: NoEvent, rhs: NoEvent) -> Bool -{ - return true -} - -// MARK: _Event (internal) - -/// Internal `EventType` wrapper for `Event` + `Optional` + `Hashable`. -internal enum _Event: Hashable -{ - case Some(E) - case Any // represents any `Some(E)` events but not `.None`, for `addRouteEvent(.Any)` - case None // default internal value for `addRoute()` without event - - internal var hashValue: Int - { - switch self { - case .Some(let x): return x.hashValue - case .Any: return -4611686018427387904 - case .None: return -4611686018427387905 - } - } - - internal var value: E? - { - switch self { - case .Some(let x): return x - default: return nil - } - } -} - -internal func == (lhs: _Event, rhs: _Event) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} - -internal func == (lhs: _Event, rhs: E) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} - -internal func == (lhs: E, rhs: _Event) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift deleted file mode 100644 index 99929b5..0000000 --- a/SwiftState/Machine.swift +++ /dev/null @@ -1,851 +0,0 @@ -// -// Machine.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import Darwin - -public typealias HandlerOrder = UInt8 -private let _defaultOrder: HandlerOrder = 100 - -public final class Machine -{ - // NOTE: `event = nil` is equivalent to `_Event.None`, which happens when non-event-based transition e.g. `tryState()` occurs. - public typealias Context = (event: E?, fromState: S, toState: S, userInfo: Any?) - - public typealias Condition = (Context -> Bool) - - public typealias Handler = Context -> () - - public typealias RouteMapping = (event: E?, fromState: S, userInfo: Any?) -> S? - - private var _routes: [_Event : [Transition : [String : Condition?]]] = [:] - - private var _routeMappings: [String : RouteMapping] = [:] - - private var _handlers: [Transition : [_HandlerInfo]] = [:] - private 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) - } - - //-------------------------------------------------- - // MARK: - State/Event/Transition - //-------------------------------------------------- - - public var state: S - { - return self._state - } - - public func hasRoute(transition: Transition, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool - { - guard let fromState = transition.fromState.value, - toState = transition.toState.value else - { - assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") - return false - } - - return self.hasRoute(fromState: fromState, toState: toState, forEvent: event, userInfo: userInfo) - } - - public func hasRoute(fromState fromState: S, toState: S, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool - { - if _hasRoute(fromState: fromState, toState: toState, forEvent: event, userInfo: userInfo) { - return true - } - - if _hasRouteMapping(fromState: fromState, toState: .Some(toState), forEvent: event, userInfo: userInfo) != nil { - return true - } - - return false - } - - /// - /// Check for `_routes`. - /// - /// - Parameter event: - /// If `event` is nil, all registered routes will be examined. - /// Otherwise, only routes for `event` and `.Any` will be examined. - /// - private func _hasRoute(fromState fromState: S, toState: S, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool - { - let validTransitions = _validTransitions(fromState: fromState, toState: toState) - - for validTransition in validTransitions { - - var transitionDicts: [[Transition : [String : Condition?]]] = [] - - if let event = event { - for (_event, transitionDict) in self._routes { - // NOTE: `_event = .None` should be excluded - if _event.value == event || _event == .Any { - transitionDicts += [transitionDict] - } - } - } - else { - transitionDicts += self._routes.values.lazy - } - - // check for `_routes - for transitionDict in transitionDicts { - if let keyConditionDict = transitionDict[validTransition] { - for (_, condition) in keyConditionDict { - if _canPassCondition(condition, forEvent: event, fromState: fromState, toState: toState, userInfo: userInfo) { - return true - } - } - } - } - } - - return false - } - - /// - /// Check for `_routeMappings`. - /// - /// - Returns: Preferred `mapped-toState` in case of `toState = .Any`. - /// - private func _hasRouteMapping(fromState fromState: S, toState: State, forEvent event: E? = nil, userInfo: Any? = nil) -> S? - { - for mapping in self._routeMappings.values { - if let mappedToState = mapping(event: event, fromState: fromState, userInfo: userInfo) - where mappedToState == toState.value || toState == .Any - { - return mappedToState - } - } - - return nil - } - - public func canTryState(toState: S, forEvent event: E? = nil) -> Bool - { - let fromState = self.state - - return self.hasRoute(fromState: fromState, toState: toState, forEvent: event) - } - - public func canTryState(toState: S, forEvent event: E) -> Bool - { - return self.canTryState(toState, forEvent: .Some(event)) - } - - public func tryState(toState: S, userInfo: Any? = nil) -> Bool - { - return self._tryState(toState, userInfo: userInfo, forEvent: nil) - } - - internal func _tryState(toState: S, userInfo: Any? = nil, forEvent event: E?) -> Bool - { - var didTransit = false - - let fromState = self.state - - if self.canTryState(toState, forEvent: event) { - - // collect valid handlers before updating state - let validHandlerInfos = self._validHandlerInfosForTransition(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 Machine 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: event, fromState: fromState, toState: toState, userInfo: userInfo)) - } - - didTransit = true - } - else { - for handlerInfo in self._errorHandlers { - handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) - } - } - - return didTransit - } - - private func _validHandlerInfosForTransition(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 - } - - public func canTryEvent(event: E, userInfo: Any? = nil) -> S? - { - if let transitionDict = self._routes[_Event.Some(event)] { - for (transition, keyConditionDict) in transitionDict { - if transition.fromState == .Some(self.state) || transition.fromState == .Any { - for (_, condition) in keyConditionDict { - // if toState is `.Any`, it means identity transition - let toState = transition.toState.value ?? self.state - - if _canPassCondition(condition, forEvent: .Some(event), fromState: self.state, toState: toState, userInfo: userInfo) { - return toState - } - } - } - } - } - - if let toState = _hasRouteMapping(fromState: self.state, toState: .Any, forEvent: event, userInfo: userInfo) { - return toState - } - - return nil - } - - public func tryEvent(event: E, userInfo: Any? = nil) -> Bool - { - if let toState = self.canTryEvent(event, userInfo: userInfo) { - self._tryState(toState, userInfo: userInfo, forEvent: .Some(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(route: Route) -> RouteID - { - return self._addRoute(route) - } - - internal func _addRoute(route: Route, forEvent event: _Event = .None) -> 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 key = _createUniqueString() - - var keyConditionDict = transitionDict[transition]! - keyConditionDict[key] = condition - transitionDict[transition] = keyConditionDict - - self._routes[event] = transitionDict - - let routeID = RouteID(event: event, transition: transition, key: key) - - return routeID - } - - // MARK: addRoute + conditional handler - - public func addRoute(transition: Transition, condition: Condition? = nil, handler: Handler) -> (RouteID, HandlerID) - { - let route = Route(transition: transition, condition: condition) - return self.addRoute(route, 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) { context in - if _canPassCondition(condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { - handler(context) - } - } - - return (routeID, handlerID) - } - - // MARK: removeRoute - - public func removeRoute(routeID: RouteID) -> Bool - { - let event = routeID.event - let transition = routeID.transition - - if var transitionDict = self._routes[event] { - if var keyConditionDict = transitionDict[transition] { - keyConditionDict[routeID.key] = nil - if keyConditionDict.count > 0 { - transitionDict[transition] = keyConditionDict - } - else { - transitionDict[transition] = nil - } - } - - if transitionDict.count > 0 { - self._routes[event] = transitionDict - } - else { - self._routes[event] = nil - } - - return true - } - - return false - } - - //-------------------------------------------------- - // MARK: - Handler - //-------------------------------------------------- - - public func addHandler(transition: Transition, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - 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(transition: transition, key: key) - - return handlerID - } - - // MARK: addEntryHandler - - public func addEntryHandler(state: State, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(.Any => state, handler: handler) - } - - public func addEntryHandler(state: S, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(.Any => .Some(state), handler: handler) - } - - // MARK: addExitHandler - - public func addExitHandler(state: State, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(state => .Any, handler: handler) - } - - public func addExitHandler(state: S, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(.Some(state) => .Any, handler: handler) - } - - // MARK: removeHandler - - public func removeHandler(handlerID: HandlerID) -> Bool - { - if let transition = handlerID.transition { - if var handlerInfos = self._handlers[transition] { - - if _removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { - self._handlers[transition] = handlerInfos - return true - } - } - } - // `transition = nil` means errorHandler - else { - if _removeHandlerFromArray(&self._errorHandlers, removingHandlerID: handlerID) { - return true - } - return false - } - - return false - } - - // MARK: addErrorHandler - - public func addErrorHandler(order order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - let key = _createUniqueString() - - let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) - _insertHandlerIntoArray(&self._errorHandlers, newHandlerInfo: newHandlerInfo) - - let handlerID = HandlerID(transition: nil, key: key) - - return handlerID - } - - //-------------------------------------------------- - // MARK: - RouteChain - //-------------------------------------------------- - // NOTE: handler is required for addRouteChain - - // MARK: addRouteChain + conditional handler - - public func addRouteChain(chain: TransitionChain, condition: Condition? = nil, handler: Handler) -> (RouteChainID, ChainHandlerID) - { - let routeChain = RouteChain(transitionChain: chain, condition: condition) - return self.addRouteChain(routeChain, handler: handler) - } - - public func addRouteChain(chain: RouteChain, handler: Handler) -> (RouteChainID, ChainHandlerID) - { - var routeIDs: [RouteID] = [] - - for route in chain.routes { - let routeID = self.addRoute(route) - routeIDs += [routeID] - } - - let chainHandlerID = self.addChainHandler(chain, handler: handler) - - let routeChainID = RouteChainID(bundledRouteIDs: routeIDs) - - return (routeChainID, chainHandlerID) - } - - // MARK: addChainHandler - - public func addChainHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self.addChainHandler(RouteChain(transitionChain: chain), order: order, handler: handler) - } - - public func addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self._addChainHandler(chain, order: order, handler: handler, isError: false) - } - - // MARK: addChainErrorHandler - - public func addChainErrorHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self.addChainErrorHandler(RouteChain(transitionChain: chain), order: order, handler: handler) - } - - public func addChainErrorHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self._addChainHandler(chain, order: order, handler: handler, isError: true) - } - - private func _addChainHandler(chain: RouteChain, order: HandlerOrder, handler: Handler, isError: Bool) -> ChainHandlerID - { - 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) { 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 - } - } - } - handlerIDs += [handlerID] - - // increment chainingCount on every route - for route in chain.routes { - - handlerID = 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 - } - } - } - handlerIDs += [handlerID] - } - - // increment allCount (+ invoke chainErrorHandler) on any routes - handlerID = self.addHandler(.Any => .Any, 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) { 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) - } - } - } - } - handlerIDs += [handlerID] - - let chainHandlerID = ChainHandlerID(bundledHandlerIDs: handlerIDs) - - return chainHandlerID - } - - // MARK: removeRouteChain - - public func removeRouteChain(routeChainID: RouteChainID) -> Bool - { - var success = false - for bundledRouteID in routeChainID.bundledRouteIDs! { - success = self.removeRoute(bundledRouteID) || success - } - return success - } - - // MARK: removeChainHandler - - public func removeChainHandler(chainHandlerID: ChainHandlerID) -> Bool - { - var success = false - for bundledHandlerID in chainHandlerID.bundledHandlerIDs { - success = self.removeHandler(bundledHandlerID) || success - } - return success - } - - //-------------------------------------------------- - // 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: E, transitions: [Transition], condition: Condition? = nil) -> [RouteID] - { - return self.addRouteEvent(.Some(event), transitions: transitions, condition: condition) - } - - public func addRouteEvent(event: Event, routes: [Route]) -> [RouteID] - { - var routeIDs: [RouteID] = [] - for route in routes { - let routeID = self._addRoute(route, forEvent: event._toInternal()) - routeIDs += [routeID] - } - - return routeIDs - } - - public func addRouteEvent(event: E, routes: [Route]) -> [RouteID] - { - return self.addRouteEvent(.Some(event), routes: routes) - } - - // MARK: addRouteEvent + conditional handler - - public func addRouteEvent(event: Event, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> ([RouteID], HandlerID) - { - let routeIDs = self.addRouteEvent(event, transitions: transitions, condition: condition) - - let handlerID = self.addEventHandler(event, handler: handler) - - return (routeIDs, handlerID) - } - - public func addRouteEvent(event: E, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> ([RouteID], HandlerID) - { - return self.addRouteEvent(.Some(event), transitions: transitions, condition: 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, handler: handler) - - return (routeIDs, handlerID) - } - - public func addRouteEvent(event: E, routes: [Route], handler: Handler) -> ([RouteID], HandlerID) - { - return self.addRouteEvent(.Some(event), routes: routes, handler: handler) - } - - // MARK: addEventHandler - - public func addEventHandler(event: Event, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - let handlerID = self.addHandler(.Any => .Any, order: order) { context in - // skip if not event-based transition - guard let triggeredEvent = context.event else { - return - } - - if triggeredEvent == event.value || event == .Any { - handler(context) - } - } - - return handlerID - } - - public func addEventHandler(event: E, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addEventHandler(.Some(event), order: order, handler: handler) - } - - //-------------------------------------------------- - // MARK: - RouteMapping - //-------------------------------------------------- - - // MARK: addRouteMapping - - public func addRouteMapping(routeMapping: RouteMapping) -> RouteMappingID - { - let key = _createUniqueString() - - self._routeMappings[key] = routeMapping - - let routeID = RouteMappingID(key: key) - - return routeID - } - - // MARK: addRouteMapping + conditional handler - - public func addRouteMapping(routeMapping: RouteMapping, handler: Handler) -> (RouteMappingID, HandlerID) - { - let routeMappingID = self.addRouteMapping(routeMapping) - - let handlerID = self.addHandler(.Any => .Any) { context in - if self._hasRouteMapping(fromState: context.fromState, toState: .Some(context.toState), forEvent: context.event, userInfo: context.userInfo) != nil { - - handler(context) - } - } - - return (routeMappingID, handlerID) - } - - // MARK: removeRouteMapping - - public func removeRouteMapping(routeMappingID: RouteMappingID) -> Bool - { - if self._routeMappings[routeMappingID.key] != nil { - self._routeMappings[routeMappingID.key] = nil - return true - } - else { - return false - } - } - - //-------------------------------------------------- - // MARK: - Remove All - //-------------------------------------------------- - - public func removeAllRoutes() -> Bool - { - let removingCount = self._routes.count + self._routeMappings.count - - self._routes = [:] - self._routeMappings = [:] - - return removingCount > 0 - } - - public func removeAllHandlers() -> Bool - { - let removingCount = self._handlers.count + self._errorHandlers.count - - self._handlers = [:] - self._errorHandlers = [] - - return removingCount > 0 - } - -} - -//-------------------------------------------------- -// MARK: - Custom Operators -//-------------------------------------------------- - -// MARK: <- (tryState) - -infix operator <- { associativity right } - -public func <- (machine: Machine, state: S) -> Bool -{ - return machine.tryState(state) -} - -public func <- (machine: Machine, tuple: (S, Any?)) -> Bool -{ - return machine.tryState(tuple.0, userInfo: tuple.1) -} - -// MARK: <-! (tryEvent) - -infix operator <-! { associativity right } - -public func <-! (machine: Machine, event: E) -> Bool -{ - return machine.tryEvent(event) -} - -public func <-! (machine: Machine, tuple: (E, Any?)) -> Bool -{ - return machine.tryEvent(tuple.0, userInfo: tuple.1) -} - -//-------------------------------------------------- -// MARK: - Private -//-------------------------------------------------- - -// generate approx 126bit random string -private func _createUniqueString() -> String -{ - var uniqueString: String = "" - for _ in 1...8 { - uniqueString += String(UnicodeScalar(arc4random_uniform(0xD800))) // 0xD800 = 55296 = 15.755bit - } - return uniqueString -} - - -private func _validTransitions(fromState fromState: S, toState: S) -> [Transition] -{ - return [ - fromState => toState, - fromState => .Any, - .Any => toState, - .Any => .Any - ] -} - -private func _canPassCondition(condition: Machine.Condition?, forEvent event: E?, fromState: S, toState: S, userInfo: Any?) -> Bool -{ - return condition?((event, fromState, toState, userInfo)) ?? true -} - -private 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.. -{ - private let order: HandlerOrder - private let key: String - private let handler: Machine.Handler - - private init(order: HandlerOrder, key: String, handler: Machine.Handler) - { - self.order = order - self.key = key - self.handler = handler - } -} diff --git a/SwiftState/RouteChainID.swift b/SwiftState/RouteChainID.swift deleted file mode 100644 index 65abb36..0000000 --- a/SwiftState/RouteChainID.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// RouteChainID.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2015-11-10. -// Copyright © 2015 Yasuhiro Inami. All rights reserved. -// - -public final class RouteChainID -{ - internal let bundledRouteIDs: [RouteID]? - - internal init(bundledRouteIDs: [RouteID]?) - { - self.bundledRouteIDs = bundledRouteIDs - } -} \ No newline at end of file diff --git a/Tests/BasicTests.swift b/Tests/BasicTests.swift new file mode 100644 index 0000000..919ae78 --- /dev/null +++ b/Tests/BasicTests.swift @@ -0,0 +1,75 @@ +// +// 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, + ]) + } + + // 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") + } +} \ No newline at end of file diff --git a/SwiftStateTests/HierarchicalMachineTests.swift b/Tests/HierarchicalMachineTests.swift similarity index 95% rename from SwiftStateTests/HierarchicalMachineTests.swift rename to Tests/HierarchicalMachineTests.swift index 7d39e62..103bd7d 100644 --- a/SwiftStateTests/HierarchicalMachineTests.swift +++ b/Tests/HierarchicalMachineTests.swift @@ -64,16 +64,16 @@ class HierarchicalMachineTests: _TestCase /// - Warning: /// This is a naive implementation and easily lose consistency when `subMachine.state` is changed directly, e.g. `subMachine1 <- .SubState1`. /// - private var mainMachine: Machine<_MainState, NoEvent>? + private var mainMachine: StateMachine<_MainState, NoEvent>? - private var subMachine1: Machine<_SubState, NoEvent>? - private var subMachine2: Machine<_SubState, NoEvent>? + private var subMachine1: StateMachine<_SubState, NoEvent>? + private var subMachine2: StateMachine<_SubState, NoEvent>? override func setUp() { super.setUp() - let subMachine1 = Machine<_SubState, NoEvent>(state: .SubState0) { subMachine1 in + let subMachine1 = StateMachine<_SubState, NoEvent>(state: .SubState0) { subMachine1 in // add Sub1-0 => Sub1-1 subMachine1.addRoute(.SubState0 => .SubState1) @@ -81,7 +81,7 @@ class HierarchicalMachineTests: _TestCase subMachine1.addErrorHandler { print("[ERROR][Sub1] \($0.fromState) => \($0.toState)") } } - let subMachine2 = Machine<_SubState, NoEvent>(state: .SubState0) { subMachine2 in + let subMachine2 = StateMachine<_SubState, NoEvent>(state: .SubState0) { subMachine2 in // add Sub2-0 => Sub2-1 subMachine2.addRoute(.SubState0 => .SubState1) @@ -89,7 +89,7 @@ class HierarchicalMachineTests: _TestCase subMachine2.addErrorHandler { print("[ERROR][Sub2] \($0.fromState) => \($0.toState)") } } - let mainMachine = Machine<_MainState, NoEvent>(state: .MainState0) { mainMachine in + 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 @@ -115,7 +115,7 @@ class HierarchicalMachineTests: _TestCase }) // add routes for mainMachine-state transitions (submachine switching) - mainMachine.addRouteMapping { _, fromState, _ -> _MainState? in + mainMachine.addRouteMapping { event, fromState, userInfo -> _MainState? in // NOTE: use external submachine's states only for evaluating `toState`, but not for `fromState` switch fromState { 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..b3b8740 --- /dev/null +++ b/Tests/MachineTests.swift @@ -0,0 +1,300 @@ +// +// 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 +{ + //-------------------------------------------------- + // 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, + ]) + } + + // 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_customOperator() + { + 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") + } + + //-------------------------------------------------- + // 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 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") + } +} \ No newline at end of file diff --git a/SwiftStateTests/BasicTests.swift b/Tests/MiscTests.swift similarity index 66% rename from SwiftStateTests/BasicTests.swift rename to Tests/MiscTests.swift index 0c2b9d3..b28ede7 100644 --- a/SwiftStateTests/BasicTests.swift +++ b/Tests/MiscTests.swift @@ -1,57 +1,20 @@ // -// BasicTests.swift +// MiscTests.swift // SwiftState // -// Created by Yasuhiro Inami on 2014/08/08. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. // import SwiftState import XCTest -class BasicTests: _TestCase +/// Unarranged tests. +class MiscTests: _TestCase { - func testREADME() - { - // setup state machine - let machine = Machine(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 - 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 - XCTAssertTrue(machine.state == .State1) - } - func testREADME_string() { - let machine = Machine(state: ".State0") { machine in + let machine = StateMachine(state: ".State0") { machine in machine.addRoute(".State0" => ".State1") machine.addRoute(.Any => ".State2") { context in print("Any => 2, msg=\(context.userInfo)") } @@ -71,24 +34,24 @@ class BasicTests: _TestCase // tryState 0 => 1 => 2 => 1 => 0 machine <- ".State1" - XCTAssertTrue(machine.state == ".State1") + XCTAssertEqual(machine.state, ".State1") machine <- (".State2", "Hello") - XCTAssertTrue(machine.state == ".State2") + XCTAssertEqual(machine.state, ".State2") machine <- (".State1", "Bye") - XCTAssertTrue(machine.state == ".State1") + XCTAssertEqual(machine.state, ".State1") machine <- ".State0" // fail: no 1 => 0 - XCTAssertTrue(machine.state == ".State1") + XCTAssertEqual(machine.state, ".State1") print("machine.state = \(machine.state)") } // StateType + associated value - func testREADME_MyState2() + func testREADME_associatedValue() { - let machine = Machine(state: .State0("0")) { machine in + let machine = StateMachine(state: .State0("0")) { machine in machine.addRoute(.State0("0") => .State0("1")) machine.addRoute(.Any => .State0("2")) { context in print("Any => 2, msg=\(context.userInfo)") } @@ -108,24 +71,24 @@ class BasicTests: _TestCase // tryState 0 => 1 => 2 => 1 => 0 machine <- .State0("1") - XCTAssertTrue(machine.state == .State0("1")) + XCTAssertEqual(machine.state, MyState2.State0("1")) machine <- (.State0("2"), "Hello") - XCTAssertTrue(machine.state == .State0("2")) + XCTAssertEqual(machine.state, MyState2.State0("2")) machine <- (.State0("1"), "Bye") - XCTAssertTrue(machine.state == .State0("1")) + XCTAssertEqual(machine.state, MyState2.State0("1")) machine <- .State0("0") // fail: no 1 => 0 - XCTAssertTrue(machine.state == .State0("1")) + XCTAssertEqual(machine.state, MyState2.State0("1")) print("machine.state = \(machine.state)") } func testExample() { - let machine = Machine(state: .State0) { - + let machine = StateMachine(state: .State0) { + // add 0 => 1 $0.addRoute(.State0 => .State1) { context in print("[Transition 0=>1] \(context.fromState) => \(context.toState)") @@ -175,47 +138,57 @@ class BasicTests: _TestCase machine.configure { - // error + // add error handlers $0.addErrorHandler { context in print("[ERROR 1] \(context.fromState) => \(context.toState)") } - // entry - $0.addEntryHandler(.State0) { context in + // add entry handlers + $0.addHandler(.Any => .State0) { context in print("[Entry 0] \(context.fromState) => \(context.toState)") // NOTE: this should not be called } - $0.addEntryHandler(.State1) { context in + $0.addHandler(.Any => .State1) { context in print("[Entry 1] \(context.fromState) => \(context.toState)") } - $0.addEntryHandler(.State2) { context in + $0.addHandler(.Any => .State2) { context in print("[Entry 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } - $0.addEntryHandler(.State2) { context in + $0.addHandler(.Any => .State2) { context in print("[Entry 2b] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } - // exit - $0.addExitHandler(.State0) { context in + // add exit handlers + $0.addHandler(.State0 => .Any) { context in print("[Exit 0] \(context.fromState) => \(context.toState)") } - $0.addExitHandler(.State1) { context in + $0.addHandler(.State1 => .Any) { context in print("[Exit 1] \(context.fromState) => \(context.toState)") } - $0.addExitHandler(.State2) { context in + $0.addHandler(.State2 => .Any) { context in print("[Exit 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } - $0.addExitHandler(.State2) { context in + $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 - XCTAssertTrue(machine <- .State1) - XCTAssertTrue(machine <- (.State2, "State2 activate")) - XCTAssertTrue(machine <- (.State1, "State2 deactivate")) - XCTAssertTrue(machine <- .State0) - XCTAssertFalse(machine <- .State3) + 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/SwiftStateTests/MyEvent.swift b/Tests/MyEvent.swift similarity index 100% rename from SwiftStateTests/MyEvent.swift rename to Tests/MyEvent.swift diff --git a/SwiftStateTests/MyState.swift b/Tests/MyState.swift similarity index 100% rename from SwiftStateTests/MyState.swift rename to Tests/MyState.swift diff --git a/SwiftStateTests/QiitaTests.swift b/Tests/QiitaTests.swift similarity index 93% rename from SwiftStateTests/QiitaTests.swift rename to Tests/QiitaTests.swift index 1f11e36..ee594bb 100644 --- a/SwiftStateTests/QiitaTests.swift +++ b/Tests/QiitaTests.swift @@ -25,7 +25,7 @@ class QiitaTests: _TestCase { var success = false - let machine = Machine(state: .None) { machine in + let machine = StateMachine(state: .None) { machine in // connect all states machine.addRoute(.Any => .Any) diff --git a/SwiftStateTests/RouteChainTests.swift b/Tests/RouteChainTests.swift similarity index 80% rename from SwiftStateTests/RouteChainTests.swift rename to Tests/RouteChainTests.swift index e1d144b..e258fea 100644 --- a/SwiftStateTests/RouteChainTests.swift +++ b/Tests/RouteChainTests.swift @@ -15,7 +15,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -49,7 +49,7 @@ class MachineChainTests: _TestCase var flag = false var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2, condition: { _ in flag }) { context in @@ -82,7 +82,7 @@ class MachineChainTests: _TestCase func testAddRouteChain_failBySkipping() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -97,7 +97,7 @@ class MachineChainTests: _TestCase func testAddRouteChain_failByHangingAround() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -117,7 +117,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -140,7 +140,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 => 0 (back home) => 2 machine.addRouteChain(.State0 => .State1 => .State2 => .State0 => .State2) { context in @@ -164,7 +164,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.Any => .Any) // connect all states @@ -203,48 +203,25 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - let (routeChainID, _) = machine.addRouteChain(.State0 => .State1 => .State2) { context in + let chainDisposable = machine.addRouteChain(.State0 => .State1 => .State2) { context in invokeCount++ return } - // removeRouteChain - machine.removeRouteChain(routeChainID) - - } - - // tryState 0 => 1 - let success = machine <- .State1 - - XCTAssertFalse(success, "RouteChain should be removed.") - XCTAssertEqual(invokeCount, 0, "ChainHandler should NOT be performed.") - } - - func testRemoveChainHandler() - { - var invokeCount = 0 - - let machine = Machine(state: .State0) { machine in - - // add 0 => 1 => 2 - let (_, chainHandlerID) = machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return - } - - // removeHandler - machine.removeChainHandler(chainHandlerID) + // remove chain + chainDisposable.dispose() } // 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.") } @@ -252,7 +229,7 @@ class MachineChainTests: _TestCase { var errorCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in let transitionChain = MyState.State0 => .State1 => .State2 @@ -287,20 +264,20 @@ class MachineChainTests: _TestCase { var errorCount = 0 - let machine = Machine(state: .State0) { machine in + 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 chainHandlerID = machine.addChainErrorHandler(transitionChain) { context in + let chainErrorHandlerDisposable = machine.addChainErrorHandler(transitionChain) { context in errorCount++ return } // remove chainErrorHandler - machine.removeChainHandler(chainHandlerID) + chainErrorHandlerDisposable.dispose() } diff --git a/SwiftStateTests/RouteMappingTests.swift b/Tests/RouteMappingTests.swift similarity index 86% rename from SwiftStateTests/RouteMappingTests.swift rename to Tests/RouteMappingTests.swift index ba1e14c..2a147bd 100644 --- a/SwiftStateTests/RouteMappingTests.swift +++ b/Tests/RouteMappingTests.swift @@ -78,7 +78,7 @@ class RouteMappingTests: _TestCase { var count = 0 - let machine = Machine<_State, _Event>(state: .Pending) { machine in + let machine = StateMachine<_State, _Event>(state: .Pending) { machine in machine.addRouteMapping { event, fromState, userInfo in // no routes for no event @@ -97,38 +97,38 @@ class RouteMappingTests: _TestCase } // increment `count` when any events i.e. `.CancelAction` and `.LoadAction(x)` succeed. - machine.addEventHandler(.Any) { event, transition, order, userInfo in + machine.addHandler(event: .Any) { event, transition, order, userInfo in count++ } } // initial - XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(machine.state, _State.Pending) XCTAssertEqual(count, 0) // CancelAction (to .Pending state, same as before) machine <-! .CancelAction - XCTAssertTrue(machine.state == .Pending) + 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) - XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(machine.state, _State.Loading(1)) XCTAssertEqual(count, 1) // LoadAction(1) (same as before) machine <-! .LoadAction(1) print(machine.state) - XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(machine.state, _State.Loading(1)) XCTAssertEqual(count, 1, "`tryEvent()` failed, and `count` should not be incremented.") machine <-! .LoadAction(2) - XCTAssertTrue(machine.state == .Loading(2)) + XCTAssertEqual(machine.state, _State.Loading(2)) XCTAssertEqual(count, 2) machine <-! .CancelAction - XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(machine.state, _State.Pending) XCTAssertEqual(count, 3) } } diff --git a/SwiftStateTests/RouteTests.swift b/Tests/RouteTests.swift similarity index 100% rename from SwiftStateTests/RouteTests.swift rename to Tests/RouteTests.swift diff --git a/SwiftStateTests/TryEventTests.swift b/Tests/StateMachineEventTests.swift similarity index 59% rename from SwiftStateTests/TryEventTests.swift rename to Tests/StateMachineEventTests.swift index c443650..9f85b98 100644 --- a/SwiftStateTests/TryEventTests.swift +++ b/Tests/StateMachineEventTests.swift @@ -1,5 +1,5 @@ // -// TryEventTests.swift +// StateMachineEventTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/05. @@ -9,15 +9,15 @@ import SwiftState import XCTest -class TryEventTests: _TestCase +class StateMachineEventTests: _TestCase { func testCanTryEvent() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 & 1 => 2 // (NOTE: this is not chaining e.g. 0 => 1 => 2) - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -31,10 +31,10 @@ class TryEventTests: _TestCase func testTryEvent() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -48,17 +48,16 @@ class TryEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State2) // tryEvent - let success = machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event0 doesn't have 2 => Any") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") } func testTryEvent_string() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 => 2 - machine.addRouteEvent("Run", transitions: [ + machine.addRoutes(event: "Run", transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -72,19 +71,19 @@ class TryEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State2) // tryEvent - let success = machine <-! "Run" - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event=Run doesn't have 2 => Any") + 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.addRouteEvent(.Event0, transitions: [.Any => .State0]) + let machine = StateMachine(state: MyState.State2) { machine in + machine.addRoutes(event: .Event0, transitions: [.Any => .State0]) } - XCTAssertTrue(machine <-! .Event0) + // tryEvent + machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State0) } @@ -93,22 +92,25 @@ class TryEventTests: _TestCase { var eventCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Event0, transitions: [.Any => .Any]) { _ in + 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") @@ -118,16 +120,18 @@ class TryEventTests: _TestCase // https://github.com/ReactKit/SwiftState/pull/32 func testTryEvent_issue32() { - let machine = Machine(state: .State0) { machine in - machine.addRouteEvent(.Event0, transitions: [ .State0 => .State1 ]) - machine.addRouteEvent(.Event1, routes: [ [ .State1, .State2 ] => .State3 ]) + 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) } @@ -137,22 +141,22 @@ class TryEventTests: _TestCase func testHasRoute_anyEvent() { ({ - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Any, transitions: [.State0 => .State1]) + machine.addRoutes(event: .Any, transitions: [.State0 => .State1]) } - let hasRoute = machine.hasRoute(.State0 => .State1, forEvent: .Event0) + let hasRoute = machine.hasRoute(event: .Event0, transition: .State0 => .State1) XCTAssertTrue(hasRoute) })() ({ - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Any, transitions: [.State2 => .State3]) + machine.addRoutes(event: .Any, transitions: [.State2 => .State3]) } - let hasRoute = machine.hasRoute(.State0 => .State1, forEvent: .Event0) + let hasRoute = machine.hasRoute(event: .Event0, transition: .State0 => .State1) XCTAssertFalse(hasRoute) })() } @@ -161,26 +165,26 @@ class TryEventTests: _TestCase // https://github.com/ReactKit/SwiftState/pull/19 func testHasRoute_issue19() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) // no-event - machine.addRouteEvent(.Event0, transitions: [.State1 => .State2]) // with-event + machine.addRoutes(event: .Event0, transitions: [.State1 => .State2]) // with-event } - let hasRoute = machine.hasRoute(.State1 => .State2, forEvent: .Event0) + let hasRoute = machine.hasRoute(event: .Event0, transition: .State1 => .State2) XCTAssertTrue(hasRoute) } //-------------------------------------------------- - // MARK: - add/removeRouteEvent + // MARK: - add/removeRoute //-------------------------------------------------- - func testAddRouteEvent_tryState() + func testAddRoute_tryState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 & 1 => 2 // (NOTE: this is not chaining e.g. 0 => 1 => 2) - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -196,69 +200,63 @@ class TryEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State2) // tryState 2 => 3 - let success = machine <- .State3 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "2 => 3 is not registered.") + machine <- .State3 + XCTAssertEqual(machine.state, MyState.State2, "2 => 3 is not registered.") } - func testAddRouteEvent_multiple() + func testAddRoute_multiple() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) // add 2 => 1 => 0 - machine.addRouteEvent(.Event1, transitions: [ + machine.addRoutes(event: .Event1, transitions: [ .State2 => .State1, .State1 => .State0, ]) } - var success: Bool + // initial + XCTAssertEqual(machine.state, MyState.State0) // tryEvent - success = machine <-! .Event1 - XCTAssertEqual(machine.state, MyState.State0) - XCTAssertFalse(success, "Event1 doesn't have 0 => Any.") + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0, "Event1 doesn't have 0 => Any.") // tryEvent - success = machine <-! .Event0 + machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) - XCTAssertTrue(success) // tryEvent - success = machine <-! .Event0 + 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.") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any.") // tryEvent - success = machine <-! .Event1 + machine <-! .Event1 XCTAssertEqual(machine.state, MyState.State1) - XCTAssertTrue(success) // tryEvent - success = machine <-! .Event1 + machine <-! .Event1 XCTAssertEqual(machine.state, MyState.State0) - XCTAssertTrue(success) } - func testAddRouteEvent_handler() + func testAddRoute_handler() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ], handler: { context in @@ -278,58 +276,55 @@ class TryEventTests: _TestCase XCTAssertEqual(invokeCount, 2) } - func testRemoveRouteEvent() + func testRemoveRoute() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - let routeIDs = machine.addRouteEvent(.Event0, transitions: [ + let routeDisposable = machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) - machine.addEventHandler(.Event0) { context in + machine.addHandler(event: .Event0) { context in invokeCount++ return } // removeRoute - for routeID in routeIDs { - machine.removeRoute(routeID) - } + routeDisposable.dispose() } - // tryEvent - var success = machine <-! .Event0 - XCTAssertFalse(success, "RouteEvent should be removed.") + // initial + XCTAssertEqual(machine.state, MyState.State0) // tryEvent - success = machine <-! .Event0 - XCTAssertFalse(success, "RouteEvent should be removed.") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0, "Route should be removed.") - XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") } //-------------------------------------------------- - // MARK: - add/removeEventHandler + // MARK: - add/removeHandler //-------------------------------------------------- - func testAddEventHandler() + func testAddHandler() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) - machine.addEventHandler(.Event0) { context in + machine.addHandler(event: .Event0) { context in invokeCount++ return } @@ -347,25 +342,25 @@ class TryEventTests: _TestCase XCTAssertEqual(invokeCount, 2) } - func testRemoveEventHandler() + func testRemoveHandler() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) - let handlerID = machine.addEventHandler(.Event0) { context in + let handlerDisposable = machine.addHandler(event: .Event0) { context in invokeCount++ return } - // removeHandler - machine.removeHandler(handlerID) + // remove handler + handlerDisposable.dispose() } @@ -377,6 +372,6 @@ class TryEventTests: _TestCase machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2, "1 => 2 should be succesful") - XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") } } \ No newline at end of file diff --git a/SwiftStateTests/MachineTests.swift b/Tests/StateMachineTests.swift similarity index 72% rename from SwiftStateTests/MachineTests.swift rename to Tests/StateMachineTests.swift index da3b0e0..1407f03 100644 --- a/SwiftStateTests/MachineTests.swift +++ b/Tests/StateMachineTests.swift @@ -1,6 +1,6 @@ // -// MachineTests.swift -// MachineTests +// StateMachineTests.swift +// SwiftState // // Created by Yasuhiro Inami on 2014/08/03. // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. @@ -9,15 +9,54 @@ import SwiftState import XCTest -class MachineTests: _TestCase +class StateMachineTests: _TestCase { func testInit() { - let machine = Machine(state: .State0) + 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 //-------------------------------------------------- @@ -25,7 +64,7 @@ class MachineTests: _TestCase // add state1 => state2 func testAddRoute() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) } @@ -40,7 +79,7 @@ class MachineTests: _TestCase // add .Any => state func testAddRoute_fromAnyState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.Any => .State1) // Any => State1 } @@ -55,7 +94,7 @@ class MachineTests: _TestCase // add state => .Any func testAddRoute_toAnyState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State1 => .Any) // State1 => Any } @@ -70,7 +109,7 @@ class MachineTests: _TestCase // add .Any => .Any func testAddRoute_bothAnyState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.Any => .Any) // Any => Any } @@ -85,14 +124,11 @@ class MachineTests: _TestCase // add state0 => state0 func testAddRoute_sameState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State0) } XCTAssertTrue(machine.hasRoute(.State0 => .State0)) - - // tryState 0 => 0 - XCTAssertTrue(machine <- .State0) } // add route + condition @@ -100,7 +136,7 @@ class MachineTests: _TestCase { var flag = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1, condition: { _ in flag }) @@ -116,7 +152,7 @@ class MachineTests: _TestCase // add route + condition + blacklist func testAddRoute_condition_blacklist() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => Any, except 0 => 2 machine.addRoute(.State0 => .Any, condition: { context in return context.toState != .State2 @@ -134,7 +170,7 @@ class MachineTests: _TestCase { var invokedCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) { context in XCTAssertEqual(context.fromState, MyState.State0) @@ -159,7 +195,7 @@ class MachineTests: _TestCase var invokedCount = 0 var flag = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 without condition to guarantee 0 => 1 transition machine.addRoute(.State0 => .State1) @@ -202,7 +238,7 @@ class MachineTests: _TestCase func testAddRoute_array_left() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 2 or 1 => 2 machine.addRoute([.State0, .State1] => .State2) } @@ -217,7 +253,7 @@ class MachineTests: _TestCase func testAddRoute_array_right() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 or 0 => 2 machine.addRoute(.State0 => [.State1, .State2]) } @@ -232,7 +268,7 @@ class MachineTests: _TestCase func testAddRoute_array_both() { - let machine = Machine(state: .State0) { machine in + 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]) } @@ -261,65 +297,16 @@ class MachineTests: _TestCase func testRemoveRoute() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) - let routeID = machine.addRoute(.State0 => .State1) + let routeDisposable = machine.addRoute(.State0 => .State1) XCTAssertTrue(machine.hasRoute(.State0 => .State1)) - var success: Bool - success = machine.removeRoute(routeID) + // remove route + routeDisposable.dispose() - 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 = Machine(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 = Machine(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") } //-------------------------------------------------- @@ -330,7 +317,7 @@ class MachineTests: _TestCase { var invokedCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -357,7 +344,7 @@ class MachineTests: _TestCase { var invokedCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -398,7 +385,7 @@ class MachineTests: _TestCase var passed1 = false var passed2 = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -427,7 +414,7 @@ class MachineTests: _TestCase { var passed = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) @@ -456,12 +443,12 @@ class MachineTests: _TestCase { var passed = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) - let handlerID = machine.addHandler(.State0 => .State1) { context in + let handlerDisposable = machine.addHandler(.State0 => .State1) { context in XCTFail("Should never reach here") } @@ -472,7 +459,8 @@ class MachineTests: _TestCase passed = true } - machine.removeHandler(handlerID) + // remove handler + handlerDisposable.dispose() } @@ -486,32 +474,34 @@ class MachineTests: _TestCase func testRemoveHandler_unregistered() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 machine.addRoute(.State0 => .State1) - let handlerID = machine.addHandler(.State0 => .State1) { context in + let handlerDisposable = machine.addHandler(.State0 => .State1) { context in // empty } + XCTAssertFalse(handlerDisposable.disposed) + // remove handler - XCTAssertTrue(machine.removeHandler(handlerID)) + handlerDisposable.dispose() // remove already unregistered handler - XCTAssertFalse(machine.removeHandler(handlerID), "removeHandler should fail because handler is already removed.") + XCTAssertTrue(handlerDisposable.disposed, "removeHandler should fail because handler is already removed.") } func testRemoveErrorHandler() { var passed = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 2 => 1 machine.addRoute(.State2 => .State1) - let handlerID = machine.addErrorHandler { context in + let handlerDisposable = machine.addErrorHandler { context in XCTFail("Should never reach here") } @@ -522,7 +512,8 @@ class MachineTests: _TestCase passed = true } - machine.removeHandler(handlerID) + // remove handler + handlerDisposable.dispose() } @@ -534,19 +525,78 @@ class MachineTests: _TestCase func testRemoveErrorHandler_unregistered() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 machine.addRoute(.State0 => .State1) - let handlerID = machine.addErrorHandler { context in + let handlerDisposable = machine.addErrorHandler { context in // empty } + XCTAssertFalse(handlerDisposable.disposed) + // remove handler - XCTAssertTrue(machine.removeHandler(handlerID)) + handlerDisposable.dispose() // remove already unregistered handler - XCTAssertFalse(machine.removeHandler(handlerID), "removeHandler should fail because handler is already removed.") + XCTAssertTrue(handlerDisposable.disposed, "removeHandler should fail because handler is already removed.") + } + + //-------------------------------------------------- + // MARK: - StateRouteMapping + //-------------------------------------------------- + + func testStateRouteMapping() + { + var routeMappingDisposable: Disposable? + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 & 0 => 2 (using `StateRouteMapping` closure) + routeMappingDisposable = machine.addRouteMapping { fromState, userInfo -> [MyState]? in + if fromState == .State0 { + return [.State1, .State2] + } + else { + return nil + } + } + + // add 1 => 0 (can also use `EventRouteMapping` 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/SwiftStateTests/String+TestExt.swift b/Tests/String+TestExt.swift similarity index 100% rename from SwiftStateTests/String+TestExt.swift rename to Tests/String+TestExt.swift diff --git a/SwiftStateTests/TransitionChainTests.swift b/Tests/TransitionChainTests.swift similarity index 100% rename from SwiftStateTests/TransitionChainTests.swift rename to Tests/TransitionChainTests.swift diff --git a/SwiftStateTests/TransitionTests.swift b/Tests/TransitionTests.swift similarity index 100% rename from SwiftStateTests/TransitionTests.swift rename to Tests/TransitionTests.swift diff --git a/SwiftStateTests/_TestCase.swift b/Tests/_TestCase.swift similarity index 100% rename from SwiftStateTests/_TestCase.swift rename to Tests/_TestCase.swift From 8ade68e098bbe201ce00360c684f34f706398ef1 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sat, 5 Dec 2015 23:20:17 +0900 Subject: [PATCH 16/27] [Test] Add more RouteMapping tests. --- Tests/MachineTests.swift | 70 +++++++++++++++++++++++++++++++++++ Tests/RouteMappingTests.swift | 53 +++++++++++++++++++++++++- Tests/StateMachineTests.swift | 5 ++- 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/Tests/MachineTests.swift b/Tests/MachineTests.swift index b3b8740..a37e009 100644 --- a/Tests/MachineTests.swift +++ b/Tests/MachineTests.swift @@ -297,4 +297,74 @@ class MachineTests: _TestCase XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") } + + //-------------------------------------------------- + // MARK: - EventRouteMapping + //-------------------------------------------------- + + func testAddEventRouteMapping() + { + var invokeCount = 0 + + let machine = Machine(state: .State0("initial")) { machine in + + // add EventRouteMapping + machine.addRouteMapping { event, fromState, userInfo -> MyState2? in + // no route for no-event + guard let event = event else { return nil } + + switch (event, fromState) { + case (.Event0("gogogo"), .State0("initial")): + return .State0("Phase 1") + case (.Event0("gogogo"), .State0("Phase 1")): + return .State0("Phase 2") + case (.Event0("finish"), .State0("Phase 2")): + return .State0("end") + default: + return nil + } + } + + machine.addHandler(event: .Event0("gogogo")) { context in + invokeCount++ + return + } + + } + + // initial + XCTAssertEqual(machine.state, MyState2.State0("initial")) + + // tryEvent (fails) + machine <-! .Event0("go?") + XCTAssertEqual(machine.state, MyState2.State0("initial"), "No change.") + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + + // tryEvent + machine <-! .Event0("gogogo") + XCTAssertEqual(machine.state, MyState2.State0("Phase 1")) + XCTAssertEqual(invokeCount, 1) + + // tryEvent (fails) + machine <-! .Event0("finish") + XCTAssertEqual(machine.state, MyState2.State0("Phase 1"), "No change.") + XCTAssertEqual(invokeCount, 1, "Handler should NOT be performed") + + // tryEvent + machine <-! .Event0("gogogo") + XCTAssertEqual(machine.state, MyState2.State0("Phase 2")) + XCTAssertEqual(invokeCount, 2) + + // tryEvent (fails) + machine <-! .Event0("gogogo") + XCTAssertEqual(machine.state, MyState2.State0("Phase 2"), "No change.") + XCTAssertEqual(invokeCount, 2, "Handler should NOT be performed") + + // tryEvent + machine <-! .Event0("finish") + XCTAssertEqual(machine.state, MyState2.State0("end")) + XCTAssertEqual(invokeCount, 2, "gogogo-Handler should NOT be performed") + + } + } \ No newline at end of file diff --git a/Tests/RouteMappingTests.swift b/Tests/RouteMappingTests.swift index 2a147bd..1fbc214 100644 --- a/Tests/RouteMappingTests.swift +++ b/Tests/RouteMappingTests.swift @@ -80,6 +80,7 @@ class RouteMappingTests: _TestCase let machine = StateMachine<_State, _Event>(state: .Pending) { machine in + // add EventRouteMapping machine.addRouteMapping { event, fromState, userInfo in // no routes for no event guard let event = event else { @@ -119,7 +120,6 @@ class RouteMappingTests: _TestCase // LoadAction(1) (same as before) machine <-! .LoadAction(1) - print(machine.state) XCTAssertEqual(machine.state, _State.Loading(1)) XCTAssertEqual(count, 1, "`tryEvent()` failed, and `count` should not be incremented.") @@ -131,4 +131,55 @@ class RouteMappingTests: _TestCase XCTAssertEqual(machine.state, _State.Pending) XCTAssertEqual(count, 3) } + + func testStateWithAssociatedValue() + { + var count = 0 + + let machine = StateMachine<_State, _Event>(state: .Pending) { machine in + + // add StateRouteMapping + machine.addRouteMapping { 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/StateMachineTests.swift b/Tests/StateMachineTests.swift index 1407f03..87e0f36 100644 --- a/Tests/StateMachineTests.swift +++ b/Tests/StateMachineTests.swift @@ -544,10 +544,11 @@ class StateMachineTests: _TestCase } //-------------------------------------------------- - // MARK: - StateRouteMapping + // MARK: - Event/StateRouteMapping //-------------------------------------------------- - func testStateRouteMapping() + /// Test `Event/StateRouteMapping`s. + func testRouteMapping() { var routeMappingDisposable: Disposable? From e9c1bf3e044b83825cad0f9fbde3124bde12ec28 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Sun, 6 Dec 2015 00:03:29 +0900 Subject: [PATCH 17/27] [Test] Improve 8ade68e & update README.md --- README.md | 77 ++++++++++++++++++++++++++++++++++++++-- Tests/BasicTests.swift | 56 ++++++++++++++++++++++++++++- Tests/MachineTests.swift | 44 +++++++++++------------ Tests/MiscTests.swift | 26 +++++++------- Tests/MyEvent.swift | 10 +++--- Tests/MyState.swift | 10 +++--- 6 files changed, 174 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 9275388..7be26f8 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Any => 2, msg=Optional("Hello") ### Transition by Event -Use `<-!` operator to try transition by `Event` rather than specifying target `State` ([Test Case](https://github.com/ReactKit/SwiftState/blob/1be67826b3cc9187dfaac85c2e70613f3129fad6/SwiftStateTests/TryEventTests.swift#L32-L54)). +Use `<-!` operator to try transition by `Event` rather than specifying target `State`. ```swift enum MyEvent: EventType { @@ -79,7 +79,10 @@ let machine = StateMachine(state: .State0) { machine in .State1 => .State2, ]) } - + +// initial +XCTAssertEqual(machine.state, MyState.State0) + // tryEvent machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) @@ -88,13 +91,81 @@ XCTAssertEqual(machine.state, MyState.State1) machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2) -// tryEvent +// tryEvent (fails) machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") ``` If there is no `Event`-based transition, use built-in `NoEvent` instead. +### State & Event enums with associated values + +Above examples use _arrow-style routing_ which are easy to understand, but it lacks in ability to handle state & event enums with associated values. In such cases, use `machine.addRouteMapping()` and pass either of the following closure types (_closure-style routing_): + +- `EventRouteMapping`: `(event: E?, fromState: S, userInfo: Any?) -> S?` +- `StateRouteMapping`: `(fromState: S, userInfo: Any?) -> [S]?` + +For example: + +```swift +enum StrState: StateType { + case Str(String) ... +} +enum StrEvent: EventType { + case Str(String) ... +} + +let machine = Machine(state: .Str("initial")) { machine in + + // add EventRouteMapping + machine.addRouteMapping { event, fromState, userInfo -> StrState? in + // no route for no-event + guard let event = event else { return nil } + + switch (event, fromState) { + case (.Str("gogogo"), .Str("initial")): + return .Str("Phase 1") + case (.Str("gogogo"), .Str("Phase 1")): + return .Str("Phase 2") + case (.Str("finish"), .Str("Phase 2")): + return .Str("end") + default: + return nil + } + } + +} + +// initial +XCTAssertEqual(machine.state, StrState.Str("initial")) + +// tryEvent (fails) +machine <-! .Str("go?") +XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.") + +// tryEvent +machine <-! .Str("gogogo") +XCTAssertEqual(machine.state, StrState.Str("Phase 1")) + +// tryEvent (fails) +machine <-! .Str("finish") +XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.") + +// tryEvent +machine <-! .Str("gogogo") +XCTAssertEqual(machine.state, StrState.Str("Phase 2")) + +// tryEvent (fails) +machine <-! .Str("gogogo") +XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.") + +// tryEvent +machine <-! .Str("finish") +XCTAssertEqual(machine.state, StrState.Str("end")) +``` + +This behaves very similar to JavaScript's safe state-container [rackt/Redux](https://github.com/rackt/redux), where `EventRouteMapping` can be interpretted as `Redux.Reducer`. + For more examples, please see XCTest cases. diff --git a/Tests/BasicTests.swift b/Tests/BasicTests.swift index 919ae78..04e7b9c 100644 --- a/Tests/BasicTests.swift +++ b/Tests/BasicTests.swift @@ -60,6 +60,9 @@ class BasicTests: _TestCase ]) } + // initial + XCTAssertEqual(machine.state, MyState.State0) + // tryEvent machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) @@ -68,8 +71,59 @@ class BasicTests: _TestCase machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2) - // tryEvent + // tryEvent (fails) machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") } + + func testREADME_routeMapping() + { + let machine = Machine(state: .Str("initial")) { machine in + + // add EventRouteMapping + machine.addRouteMapping { event, fromState, userInfo -> StrState? in + // no route for no-event + guard let event = event else { return nil } + + switch (event, fromState) { + case (.Str("gogogo"), .Str("initial")): + return .Str("Phase 1") + case (.Str("gogogo"), .Str("Phase 1")): + return .Str("Phase 2") + case (.Str("finish"), .Str("Phase 2")): + return .Str("end") + default: + return nil + } + } + + } + + // initial + XCTAssertEqual(machine.state, StrState.Str("initial")) + + // tryEvent (fails) + machine <-! .Str("go?") + XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.") + + // tryEvent + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 1")) + + // tryEvent (fails) + machine <-! .Str("finish") + XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.") + + // tryEvent + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 2")) + + // tryEvent (fails) + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.") + + // tryEvent + machine <-! .Str("finish") + XCTAssertEqual(machine.state, StrState.Str("end")) + } } \ No newline at end of file diff --git a/Tests/MachineTests.swift b/Tests/MachineTests.swift index a37e009..820250f 100644 --- a/Tests/MachineTests.swift +++ b/Tests/MachineTests.swift @@ -306,26 +306,26 @@ class MachineTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0("initial")) { machine in + let machine = Machine(state: .Str("initial")) { machine in // add EventRouteMapping - machine.addRouteMapping { event, fromState, userInfo -> MyState2? in + machine.addRouteMapping { event, fromState, userInfo -> StrState? in // no route for no-event guard let event = event else { return nil } switch (event, fromState) { - case (.Event0("gogogo"), .State0("initial")): - return .State0("Phase 1") - case (.Event0("gogogo"), .State0("Phase 1")): - return .State0("Phase 2") - case (.Event0("finish"), .State0("Phase 2")): - return .State0("end") + case (.Str("gogogo"), .Str("initial")): + return .Str("Phase 1") + case (.Str("gogogo"), .Str("Phase 1")): + return .Str("Phase 2") + case (.Str("finish"), .Str("Phase 2")): + return .Str("end") default: return nil } } - machine.addHandler(event: .Event0("gogogo")) { context in + machine.addHandler(event: .Str("gogogo")) { context in invokeCount++ return } @@ -333,36 +333,36 @@ class MachineTests: _TestCase } // initial - XCTAssertEqual(machine.state, MyState2.State0("initial")) + XCTAssertEqual(machine.state, StrState.Str("initial")) // tryEvent (fails) - machine <-! .Event0("go?") - XCTAssertEqual(machine.state, MyState2.State0("initial"), "No change.") + machine <-! .Str("go?") + XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.") XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") // tryEvent - machine <-! .Event0("gogogo") - XCTAssertEqual(machine.state, MyState2.State0("Phase 1")) + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 1")) XCTAssertEqual(invokeCount, 1) // tryEvent (fails) - machine <-! .Event0("finish") - XCTAssertEqual(machine.state, MyState2.State0("Phase 1"), "No change.") + machine <-! .Str("finish") + XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.") XCTAssertEqual(invokeCount, 1, "Handler should NOT be performed") // tryEvent - machine <-! .Event0("gogogo") - XCTAssertEqual(machine.state, MyState2.State0("Phase 2")) + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 2")) XCTAssertEqual(invokeCount, 2) // tryEvent (fails) - machine <-! .Event0("gogogo") - XCTAssertEqual(machine.state, MyState2.State0("Phase 2"), "No change.") + machine <-! .Str("gogogo") + XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.") XCTAssertEqual(invokeCount, 2, "Handler should NOT be performed") // tryEvent - machine <-! .Event0("finish") - XCTAssertEqual(machine.state, MyState2.State0("end")) + machine <-! .Str("finish") + XCTAssertEqual(machine.state, StrState.Str("end")) XCTAssertEqual(invokeCount, 2, "gogogo-Handler should NOT be performed") } diff --git a/Tests/MiscTests.swift b/Tests/MiscTests.swift index b28ede7..d46782a 100644 --- a/Tests/MiscTests.swift +++ b/Tests/MiscTests.swift @@ -51,14 +51,14 @@ class MiscTests: _TestCase // StateType + associated value func testREADME_associatedValue() { - let machine = StateMachine(state: .State0("0")) { machine in + let machine = StateMachine(state: .Str("0")) { machine in - machine.addRoute(.State0("0") => .State0("1")) - machine.addRoute(.Any => .State0("2")) { context in print("Any => 2, msg=\(context.userInfo)") } - machine.addRoute(.State0("2") => .Any) { context in print("2 => Any, msg=\(context.userInfo)") } + machine.addRoute(.Str("0") => .Str("1")) + machine.addRoute(.Any => .Str("2")) { context in print("Any => 2, msg=\(context.userInfo)") } + machine.addRoute(.Str("2") => .Any) { context in print("2 => Any, msg=\(context.userInfo)") } // add handler (handlerContext = (event, transition, order, userInfo)) - machine.addHandler(.State0("0") => .State0("1")) { context in + machine.addHandler(.Str("0") => .Str("1")) { context in print("0 => 1") } @@ -70,17 +70,17 @@ class MiscTests: _TestCase // tryState 0 => 1 => 2 => 1 => 0 - machine <- .State0("1") - XCTAssertEqual(machine.state, MyState2.State0("1")) + machine <- .Str("1") + XCTAssertEqual(machine.state, StrState.Str("1")) - machine <- (.State0("2"), "Hello") - XCTAssertEqual(machine.state, MyState2.State0("2")) + machine <- (.Str("2"), "Hello") + XCTAssertEqual(machine.state, StrState.Str("2")) - machine <- (.State0("1"), "Bye") - XCTAssertEqual(machine.state, MyState2.State0("1")) + machine <- (.Str("1"), "Bye") + XCTAssertEqual(machine.state, StrState.Str("1")) - machine <- .State0("0") // fail: no 1 => 0 - XCTAssertEqual(machine.state, MyState2.State0("1")) + machine <- .Str("0") // fail: no 1 => 0 + XCTAssertEqual(machine.state, StrState.Str("1")) print("machine.state = \(machine.state)") } diff --git a/Tests/MyEvent.swift b/Tests/MyEvent.swift index bb640fc..6c7d7b3 100644 --- a/Tests/MyEvent.swift +++ b/Tests/MyEvent.swift @@ -13,22 +13,22 @@ enum MyEvent: EventType case Event0, Event1 } -enum MyEvent2: EventType +enum StrEvent: EventType { - case Event0(String) + case Str(String) var hashValue: Int { switch self { - case .Event0(let str): return str.hashValue + case .Str(let str): return str.hashValue } } } -func == (lhs: MyEvent2, rhs: MyEvent2) -> Bool +func == (lhs: StrEvent, rhs: StrEvent) -> Bool { switch (lhs, rhs) { - case let (.Event0(str1), .Event0(str2)): + case let (.Str(str1), .Str(str2)): return str1 == str2 // default: // return false diff --git a/Tests/MyState.swift b/Tests/MyState.swift index 2f8058a..ab4f214 100644 --- a/Tests/MyState.swift +++ b/Tests/MyState.swift @@ -13,22 +13,22 @@ enum MyState: StateType case State0, State1, State2, State3 } -enum MyState2: StateType +enum StrState: StateType { - case State0(String) + case Str(String) var hashValue: Int { switch self { - case .State0(let str): return str.hashValue + case .Str(let str): return str.hashValue } } } -func == (lhs: MyState2, rhs: MyState2) -> Bool +func == (lhs: StrState, rhs: StrState) -> Bool { switch (lhs, rhs) { - case let (.State0(str1), .State0(str2)): + case let (.Str(str1), .Str(str2)): return str1 == str2 } } \ No newline at end of file From c1c18ec93395f4043d553fd91d8bdb03c549328a Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Mon, 7 Dec 2015 09:17:18 +0900 Subject: [PATCH 18/27] Add `addStateRouteMapping()` & rename `EventRouteMapping` to `RouteMapping`. --- README.md | 12 +++++++----- Sources/Machine.swift | 12 ++++++------ Sources/StateMachine.swift | 20 ++++++++++---------- Tests/BasicTests.swift | 1 - Tests/MachineTests.swift | 5 ++--- Tests/RouteMappingTests.swift | 8 +++++--- Tests/StateMachineTests.swift | 6 +++--- 7 files changed, 33 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7be26f8..6dfc7b1 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,13 @@ If there is no `Event`-based transition, use built-in `NoEvent` instead. ### State & Event enums with associated values -Above examples use _arrow-style routing_ which are easy to understand, but it lacks in ability to handle state & event enums with associated values. In such cases, use `machine.addRouteMapping()` and pass either of the following closure types (_closure-style routing_): +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_: -- `EventRouteMapping`: `(event: E?, fromState: S, userInfo: Any?) -> S?` -- `StateRouteMapping`: `(fromState: S, userInfo: Any?) -> [S]?` +- `machine.addRouteMapping(routeMapping)` + - `RouteMapping`: `(event: E?, fromState: S, userInfo: Any?) -> S?` +- `machine.addStateRouteMapping(stateRouteMapping)` + - `StateRouteMapping`: `(fromState: S, userInfo: Any?) -> [S]?` + - This is a synonym for multiple routing e.g. `.State0 => [.State1, .State2]` For example: @@ -117,7 +120,6 @@ enum StrEvent: EventType { let machine = Machine(state: .Str("initial")) { machine in - // add EventRouteMapping machine.addRouteMapping { event, fromState, userInfo -> StrState? in // no route for no-event guard let event = event else { return nil } @@ -164,7 +166,7 @@ machine <-! .Str("finish") XCTAssertEqual(machine.state, StrState.Str("end")) ``` -This behaves very similar to JavaScript's safe state-container [rackt/Redux](https://github.com/rackt/redux), where `EventRouteMapping` can be interpretted as `Redux.Reducer`. +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. diff --git a/Sources/Machine.swift b/Sources/Machine.swift index c379673..bd1f412 100644 --- a/Sources/Machine.swift +++ b/Sources/Machine.swift @@ -12,7 +12,7 @@ /// 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 `EventRouteMapping` can be interpretted as `Redux.Reducer`. +/// where `RouteMapping` can be interpretted as `Redux.Reducer`. /// public class Machine { @@ -28,12 +28,12 @@ public class Machine /// Closure-based route, mainly for `tryEvent()` (and also works for subclass's `tryState()`). /// - Returns: Preferred `toState`. - public typealias EventRouteMapping = (event: E?, fromState: S, userInfo: Any?) -> S? + 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 : EventRouteMapping] = [:] + private lazy var _routeMappings: [String : RouteMapping] = [:] /// `tryEvent()`-based handler collection. private lazy var _handlers: [Event : [_HandlerInfo]] = [:] @@ -356,12 +356,12 @@ public class Machine } //-------------------------------------------------- - // MARK: - EventRouteMapping + // MARK: - RouteMapping //-------------------------------------------------- // MARK: addRouteMapping - public func addRouteMapping(routeMapping: EventRouteMapping) -> Disposable + public func addRouteMapping(routeMapping: RouteMapping) -> Disposable { let key = _createUniqueString() @@ -376,7 +376,7 @@ public class Machine // MARK: addRouteMapping + conditional handler - public func addRouteMapping(routeMapping: EventRouteMapping, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + public func addRouteMapping(routeMapping: RouteMapping, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable { let routeDisposable = self.addRouteMapping(routeMapping) diff --git a/Sources/StateMachine.swift b/Sources/StateMachine.swift index c37a22c..f5052f6 100644 --- a/Sources/StateMachine.swift +++ b/Sources/StateMachine.swift @@ -15,11 +15,11 @@ public final class StateMachine: Machine { /// Closure-based routes for `tryState()`. - /// - Returns: Multiple `toState`s from single `fromState`. + /// - 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 `EventRouteMapping` + private lazy var _routeMappings: [String : StateRouteMapping] = [:] // NOTE: `StateRouteMapping`, not `RouteMapping` /// `tryState()`-based handler collection. private lazy var _handlers: [Transition : [_HandlerInfo]] = [:] @@ -442,9 +442,9 @@ public final class StateMachine: Machine // MARK: - StateRouteMapping //-------------------------------------------------- - // MARK: addRouteMapping + // MARK: addStateRouteMapping - public func addRouteMapping(routeMapping: StateRouteMapping) -> Disposable + public func addStateRouteMapping(routeMapping: StateRouteMapping) -> Disposable { let key = _createUniqueString() @@ -453,15 +453,15 @@ public final class StateMachine: Machine let routeMappingID = _RouteMappingID(key: key) return ActionDisposable.init { [weak self] in - self?._removeRouteMapping(routeMappingID) + self?._removeStateRouteMapping(routeMappingID) } } - // MARK: addRouteMapping + conditional handler + // MARK: addStateRouteMapping + conditional handler - public func addRouteMapping(routeMapping: StateRouteMapping, handler: Handler) -> Disposable + public func addStateRouteMapping(routeMapping: StateRouteMapping, handler: Handler) -> Disposable { - let routeDisposable = self.addRouteMapping(routeMapping) + let routeDisposable = self.addStateRouteMapping(routeMapping) let handlerDisposable = self.addHandler(.Any => .Any) { context in if self._hasRouteMappingInDict(fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) != nil { @@ -475,9 +475,9 @@ public final class StateMachine: Machine } } - // MARK: removeRouteMapping + // MARK: removeStateRouteMapping - private func _removeRouteMapping(routeMappingID: _RouteMappingID) -> Bool + private func _removeStateRouteMapping(routeMappingID: _RouteMappingID) -> Bool { if self._routeMappings[routeMappingID.key] != nil { self._routeMappings[routeMappingID.key] = nil diff --git a/Tests/BasicTests.swift b/Tests/BasicTests.swift index 04e7b9c..1363a71 100644 --- a/Tests/BasicTests.swift +++ b/Tests/BasicTests.swift @@ -80,7 +80,6 @@ class BasicTests: _TestCase { let machine = Machine(state: .Str("initial")) { machine in - // add EventRouteMapping machine.addRouteMapping { event, fromState, userInfo -> StrState? in // no route for no-event guard let event = event else { return nil } diff --git a/Tests/MachineTests.swift b/Tests/MachineTests.swift index 820250f..afdd9fd 100644 --- a/Tests/MachineTests.swift +++ b/Tests/MachineTests.swift @@ -299,16 +299,15 @@ class MachineTests: _TestCase } //-------------------------------------------------- - // MARK: - EventRouteMapping + // MARK: - RouteMapping //-------------------------------------------------- - func testAddEventRouteMapping() + func testAddRouteMapping() { var invokeCount = 0 let machine = Machine(state: .Str("initial")) { machine in - // add EventRouteMapping machine.addRouteMapping { event, fromState, userInfo -> StrState? in // no route for no-event guard let event = event else { return nil } diff --git a/Tests/RouteMappingTests.swift b/Tests/RouteMappingTests.swift index 1fbc214..b041189 100644 --- a/Tests/RouteMappingTests.swift +++ b/Tests/RouteMappingTests.swift @@ -80,7 +80,6 @@ class RouteMappingTests: _TestCase let machine = StateMachine<_State, _Event>(state: .Pending) { machine in - // add EventRouteMapping machine.addRouteMapping { event, fromState, userInfo in // no routes for no event guard let event = event else { @@ -138,8 +137,11 @@ class RouteMappingTests: _TestCase let machine = StateMachine<_State, _Event>(state: .Pending) { machine in - // add StateRouteMapping - machine.addRouteMapping { fromState, userInfo 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)] diff --git a/Tests/StateMachineTests.swift b/Tests/StateMachineTests.swift index 87e0f36..512125d 100644 --- a/Tests/StateMachineTests.swift +++ b/Tests/StateMachineTests.swift @@ -554,8 +554,8 @@ class StateMachineTests: _TestCase let machine = StateMachine(state: .State0) { machine in - // add 0 => 1 & 0 => 2 (using `StateRouteMapping` closure) - routeMappingDisposable = machine.addRouteMapping { fromState, userInfo -> [MyState]? in + // add 0 => 1 & 0 => 2 + routeMappingDisposable = machine.addStateRouteMapping { fromState, userInfo -> [MyState]? in if fromState == .State0 { return [.State1, .State2] } @@ -564,7 +564,7 @@ class StateMachineTests: _TestCase } } - // add 1 => 0 (can also use `EventRouteMapping` closure for single-`toState`) + // add 1 => 0 (can also use `RouteMapping` closure for single-`toState`) machine.addRouteMapping { event, fromState, userInfo -> MyState? in guard event == nil else { return nil } From b42ac6d010f64f1d69768314dd1164d2a65fc84e Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Mon, 7 Dec 2015 09:35:43 +0900 Subject: [PATCH 19/27] Update README.md --- README.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6dfc7b1..e8dfc9c 100644 --- a/README.md +++ b/README.md @@ -100,13 +100,12 @@ 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_: +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?` + - `RouteMapping`: `(event: E?, fromState: S, userInfo: Any?) -> S?` - `machine.addStateRouteMapping(stateRouteMapping)` - - `StateRouteMapping`: `(fromState: S, userInfo: Any?) -> [S]?` - - This is a synonym for multiple routing e.g. `.State0 => [.State1, .State2]` + - `StateRouteMapping`: `(fromState: S, userInfo: Any?) -> [S]?` For example: @@ -175,17 +174,21 @@ 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 `.Any` state/event - - Blacklisting: `.Any => .Any` + `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/Entry/Exit handlers with `order: UInt8` (more flexible than before/after handlers) -- Removable routes and handlers -- Chaining: `.State0 => .State1 => .State2` +- 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 @@ -198,8 +201,8 @@ State Machine | `Machine` | State transition manager which c 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. -Event 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`. See [#36](https://github.com/ReactKit/SwiftState/pull/36) for more info. +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` From 4c15f864af0fe1b81e41ea5b6422e0aef1f7dde1 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Tue, 8 Dec 2015 22:18:03 +0900 Subject: [PATCH 20/27] Conform State & Event to RawRepresentable. --- Sources/EventType.swift | 44 +++++++++++++++++++++++++++++-------- Sources/Machine.swift | 10 ++++----- Sources/StateMachine.swift | 4 ++-- Sources/StateType.swift | 24 ++++++++++++++++---- Tests/RouteTests.swift | 12 +++++----- Tests/TransitionTests.swift | 24 ++++++++++---------- 6 files changed, 80 insertions(+), 38 deletions(-) diff --git a/Sources/EventType.swift b/Sources/EventType.swift index 9303ef1..7b13c04 100644 --- a/Sources/EventType.swift +++ b/Sources/EventType.swift @@ -10,25 +10,41 @@ public protocol EventType: Hashable {} // MARK: Event -/// `EventType` wrapper for handling`.Any` event. -public enum Event: Hashable +/// `EventType` wrapper for handling `.Any` event. +public enum Event { case Some(E) case Any - - public var value: E? +} + +extension Event: Hashable +{ + public var hashValue: Int { switch self { - case .Some(let x): return x - default: return nil + 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 hashValue: Int + public var rawValue: E? { switch self { - case .Some(let x): return x.hashValue - case .Any: return _hashValueForAny + case .Some(let x): return x + default: return nil } } } @@ -45,6 +61,16 @@ public func == (lhs: Event, rhs: Event) -> Bool } } +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`. diff --git a/Sources/Machine.swift b/Sources/Machine.swift index bd1f412..3fdd8d0 100644 --- a/Sources/Machine.swift +++ b/Sources/Machine.swift @@ -70,8 +70,8 @@ public class Machine /// Check for added routes & routeMappings. public func hasRoute(event event: E, transition: Transition, userInfo: Any? = nil) -> Bool { - guard let fromState = transition.fromState.value, - toState = transition.toState.value else + guard let fromState = transition.fromState.rawValue, + toState = transition.toState.rawValue else { assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") return false @@ -110,7 +110,7 @@ public class Machine if let event = event { for (ev, routeDict) in self._routes { - if ev.value == event || ev == .Any { + if ev.rawValue == event || ev == .Any { routeDicts += [routeDict] } } @@ -165,7 +165,7 @@ public class Machine 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.value ?? self.state + let toState = transition.toState.rawValue ?? self.state if _canPassCondition(condition, forEvent: event, fromState: self.state, toState: toState, userInfo: userInfo) { return toState @@ -426,7 +426,7 @@ public class Machine return } - if triggeredEvent == event.value || event == .Any { + if triggeredEvent == event.rawValue || event == .Any { handler(context) } } diff --git a/Sources/StateMachine.swift b/Sources/StateMachine.swift index f5052f6..d348856 100644 --- a/Sources/StateMachine.swift +++ b/Sources/StateMachine.swift @@ -49,8 +49,8 @@ public final class StateMachine: Machine /// - Note: This method also checks for event-based-routes. public func hasRoute(transition: Transition, userInfo: Any? = nil) -> Bool { - guard let fromState = transition.fromState.value, - toState = transition.toState.value else + guard let fromState = transition.fromState.rawValue, + toState = transition.toState.rawValue else { assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") return false diff --git a/Sources/StateType.swift b/Sources/StateType.swift index bb538ac..663bcb9 100644 --- a/Sources/StateType.swift +++ b/Sources/StateType.swift @@ -10,12 +10,15 @@ public protocol StateType: Hashable {} // MARK: State -/// `StateType` wrapper for handling`.Any` state. -public enum State: Hashable +/// `StateType` wrapper for handling `.Any` state. +public enum State { case Some(S) case Any - +} + +extension State: Hashable +{ public var hashValue: Int { switch self { @@ -23,8 +26,21 @@ public enum State: Hashable case .Any: return _hashValueForAny } } +} + +extension State: RawRepresentable +{ + public init(rawValue: S?) + { + if let rawValue = rawValue { + self = .Some(rawValue) + } + else { + self = .Any + } + } - public var value: S? + public var rawValue: S? { switch self { case .Some(let x): return x diff --git a/Tests/RouteTests.swift b/Tests/RouteTests.swift index 221d966..23712b2 100644 --- a/Tests/RouteTests.swift +++ b/Tests/RouteTests.swift @@ -14,18 +14,18 @@ class RouteTests: _TestCase func testInit() { let route = Route(transition: .State0 => .State1, condition: nil) - XCTAssertEqual(route.transition.fromState.value, MyState.State0) - XCTAssertEqual(route.transition.toState.value, MyState.State1) + 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.value, MyState.State1) - XCTAssertEqual(route2.transition.toState.value, MyState.State2) + 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.value, MyState.State2) - XCTAssertEqual(route3.transition.toState.value, MyState.State3) + 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/TransitionTests.swift b/Tests/TransitionTests.swift index 473d477..f3347f0 100644 --- a/Tests/TransitionTests.swift +++ b/Tests/TransitionTests.swift @@ -14,37 +14,37 @@ class TransitionTests: _TestCase func testInit() { let transition = Transition(fromState: .State0, toState: .State1) - XCTAssertEqual(transition.fromState.value, MyState.State0) - XCTAssertEqual(transition.toState.value, MyState.State1) + XCTAssertEqual(transition.fromState.rawValue, MyState.State0) + XCTAssertEqual(transition.toState.rawValue, MyState.State1) // shorthand let transition2 = MyState.State1 => .State0 - XCTAssertEqual(transition2.fromState.value, MyState.State1) - XCTAssertEqual(transition2.toState.value, MyState.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.value) - XCTAssertEqual(transition.toState.value, MyState.State1) + XCTAssertNil(transition.fromState.rawValue) + XCTAssertEqual(transition.toState.rawValue, MyState.State1) // shorthand let transition2 = .Any => MyState.State0 - XCTAssertNil(transition2.fromState.value) - XCTAssertEqual(transition2.toState.value, 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.value, MyState.State0) - XCTAssertNil(transition.toState.value) + XCTAssertEqual(transition.fromState.rawValue, MyState.State0) + XCTAssertNil(transition.toState.rawValue) // shorthand let transition2 = MyState.State1 => .Any - XCTAssertEqual(transition2.fromState.value, MyState.State1) - XCTAssertNil(transition2.toState.value) + XCTAssertEqual(transition2.fromState.rawValue, MyState.State1) + XCTAssertNil(transition2.toState.rawValue) } func testNil() From edc0f3e275b26dd47b2f21fbf07991ecd1098e87 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Tue, 8 Dec 2015 22:22:32 +0900 Subject: [PATCH 21/27] Set codeCoverageEnabled=YES. --- .../xcshareddata/xcschemes/SwiftState-OSX.xcscheme | 3 ++- .../xcshareddata/xcschemes/SwiftState-iOS.xcscheme | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme index c5db47c..cc7fe82 100644 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme +++ b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> diff --git a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme index f149494..687921f 100644 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme +++ b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme @@ -26,7 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> From 2c9844f85096fac16a7342f210c1457391882be6 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Tue, 8 Dec 2015 22:34:24 +0900 Subject: [PATCH 22/27] [Test] Add StateTests & EventTests. --- SwiftState.xcodeproj/project.pbxproj | 12 +++++++++ Tests/EventTests.swift | 39 ++++++++++++++++++++++++++++ Tests/StateTests.swift | 39 ++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 Tests/EventTests.swift create mode 100644 Tests/StateTests.swift diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index 0d85c15..69aa6fb 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -13,6 +13,10 @@ 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; + 1F4336AD1C17113F00E7C1AC /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AC1C17113F00E7C1AC /* StateTests.swift */; }; + 1F4336AE1C17113F00E7C1AC /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AC1C17113F00E7C1AC /* StateTests.swift */; }; + 1F4336B01C17115700E7C1AC /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AF1C17115700E7C1AC /* EventTests.swift */; }; + 1F4336B11C17115700E7C1AC /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AF1C17115700E7C1AC /* EventTests.swift */; }; 1F532C541C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; 1F532C551C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; 1F532C571C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; @@ -87,6 +91,8 @@ 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 = ""; }; @@ -224,6 +230,8 @@ 4876510E1C0ECBEB005961AC /* MachineTests.swift */, 1FA6202C199660CA00460108 /* StateMachineTests.swift */, 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */, + 1F4336AC1C17113F00E7C1AC /* StateTests.swift */, + 1F4336AF1C17115700E7C1AC /* EventTests.swift */, 1FA6202F199660CA00460108 /* TransitionTests.swift */, 1FA6202E199660CA00460108 /* TransitionChainTests.swift */, 1FA6202D199660CA00460108 /* RouteTests.swift */, @@ -461,6 +469,7 @@ 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */, 1FA62030199660CA00460108 /* _TestCase.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 */, @@ -468,6 +477,7 @@ 1FA62035199660CA00460108 /* StateMachineTests.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; @@ -484,6 +494,7 @@ 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */, + 1F4336AE1C17113F00E7C1AC /* StateTests.swift in Sources */, 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */, 1F532C621C12D7BD00D3813A /* MiscTests.swift in Sources */, 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */, @@ -491,6 +502,7 @@ 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */, 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */, 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */, + 1F4336B11C17115700E7C1AC /* EventTests.swift in Sources */, 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; 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/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 From ce2ef2afc5cd12c4dee762a80ba45221d9ef749a Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Tue, 8 Dec 2015 23:11:30 +0900 Subject: [PATCH 23/27] Fix RouteMapping + handler. --- Sources/Machine.swift | 9 ++++++--- Sources/StateMachine.swift | 11 +++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Sources/Machine.swift b/Sources/Machine.swift index 3fdd8d0..d63d817 100644 --- a/Sources/Machine.swift +++ b/Sources/Machine.swift @@ -381,11 +381,14 @@ public class Machine let routeDisposable = self.addRouteMapping(routeMapping) let handlerDisposable = self._addHandler(event: .Any, order: order) { context in - guard let event = context.event else { return } - if self._hasRouteMappingInDict(event: event, fromState: context.fromState, toState: .Some(context.toState), userInfo: context.userInfo) != nil { - handler(context) + guard let preferredToState = routeMapping(event: context.event, fromState: context.fromState, userInfo: context.userInfo) + where preferredToState == context.toState else + { + return } + + handler(context) } return ActionDisposable.init { diff --git a/Sources/StateMachine.swift b/Sources/StateMachine.swift index d348856..217b8de 100644 --- a/Sources/StateMachine.swift +++ b/Sources/StateMachine.swift @@ -464,9 +464,16 @@ public final class StateMachine: Machine let routeDisposable = self.addStateRouteMapping(routeMapping) let handlerDisposable = self.addHandler(.Any => .Any) { context in - if self._hasRouteMappingInDict(fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) != nil { - handler(context) + + 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 { From 56b8c763fe588d4b7694f46ad5b7993f8d232b26 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 9 Dec 2015 00:04:50 +0900 Subject: [PATCH 24/27] Simplify `machine.addRoutes()`. --- Sources/Machine.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/Machine.swift b/Sources/Machine.swift index d63d817..48d571c 100644 --- a/Sources/Machine.swift +++ b/Sources/Machine.swift @@ -294,13 +294,8 @@ public class Machine public func addRoutes(event event: Event, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> Disposable { - let routeDisposable = self.addRoutes(event: event, transitions: transitions, condition: condition) - let handlerDisposable = self.addHandler(event: event, handler: handler) - - return ActionDisposable.init { - routeDisposable.dispose() - handlerDisposable.dispose() - } + 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 From e49b1c6dc3bf84f4c1cd4cfedf01074cbe1e6a30 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 9 Dec 2015 00:07:32 +0900 Subject: [PATCH 25/27] [Test] Improve code coverage. --- Tests/MachineTests.swift | 173 +++++++++++++++++++++++++++++++- Tests/RouteMappingTests.swift | 6 +- Tests/StateMachineTests.swift | 179 +++++++++++++++++++++++++++++++++- 3 files changed, 354 insertions(+), 4 deletions(-) diff --git a/Tests/MachineTests.swift b/Tests/MachineTests.swift index afdd9fd..fda2995 100644 --- a/Tests/MachineTests.swift +++ b/Tests/MachineTests.swift @@ -11,6 +11,17 @@ 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 `<-!` //-------------------------------------------------- @@ -39,6 +50,9 @@ class MachineTests: _TestCase ]) } + // initial + XCTAssertEqual(machine.state, MyState.State0) + // tryEvent machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) @@ -52,7 +66,36 @@ class MachineTests: _TestCase XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") } - func testTryEvent_customOperator() + 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 @@ -231,6 +274,33 @@ class MachineTests: _TestCase 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 //-------------------------------------------------- @@ -265,6 +335,26 @@ class MachineTests: _TestCase 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 @@ -366,4 +456,85 @@ class MachineTests: _TestCase } + 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/RouteMappingTests.swift b/Tests/RouteMappingTests.swift index b041189..93b21bf 100644 --- a/Tests/RouteMappingTests.swift +++ b/Tests/RouteMappingTests.swift @@ -74,7 +74,8 @@ private func ==(lhs: _Event, rhs: _Event) -> Bool class RouteMappingTests: _TestCase { - func testEventWithAssociatedValue() + /// Test for state & event with associated values + func testAddRouteMapping() { var count = 0 @@ -131,7 +132,8 @@ class RouteMappingTests: _TestCase XCTAssertEqual(count, 3) } - func testStateWithAssociatedValue() + /// Test for state with associated values + func testAddStateRouteMapping() { var count = 0 diff --git a/Tests/StateMachineTests.swift b/Tests/StateMachineTests.swift index 512125d..dfd90ff 100644 --- a/Tests/StateMachineTests.swift +++ b/Tests/StateMachineTests.swift @@ -309,6 +309,20 @@ class StateMachineTests: _TestCase 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 //-------------------------------------------------- @@ -543,12 +557,175 @@ class StateMachineTests: _TestCase 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 testRouteMapping() + func testAddBothRouteMappings() { var routeMappingDisposable: Disposable? From c1751bf462caf9f5fe1696d2e7f37ab8aed38a12 Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Wed, 9 Dec 2015 02:04:23 +0900 Subject: [PATCH 26/27] Create universal framework to support watchOS & tvOS by using xcconfigs. --- .gitmodules | 3 + Cartfile.private | 1 + Cartfile.resolved | 1 + Carthage/Checkouts/xcconfigs | 1 + Configurations/Base.xcconfig | 13 + Configurations/Debug.xcconfig | 11 + Configurations/Release.xcconfig | 11 + Sources/_Random.swift | 2 +- SwiftState.xcodeproj/project.pbxproj | 334 +++--------------- .../xcschemes/SwiftState-iOS.xcscheme | 100 ------ ...State-OSX.xcscheme => SwiftState.xcscheme} | 12 +- circle.yml | 11 +- 12 files changed, 114 insertions(+), 386 deletions(-) create mode 100644 .gitmodules create mode 100644 Cartfile.private create mode 100644 Cartfile.resolved create mode 160000 Carthage/Checkouts/xcconfigs create mode 100644 Configurations/Base.xcconfig create mode 100644 Configurations/Debug.xcconfig create mode 100644 Configurations/Release.xcconfig delete mode 100644 SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme rename SwiftState.xcodeproj/xcshareddata/xcschemes/{SwiftState-OSX.xcscheme => SwiftState.xcscheme} (92%) 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/Sources/_Random.swift b/Sources/_Random.swift index 43c01e4..cf38838 100644 --- a/Sources/_Random.swift +++ b/Sources/_Random.swift @@ -6,7 +6,7 @@ // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -#if os(OSX) || os(iOS) +#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) import Darwin #else import Glibc diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index 69aa6fb..464dafc 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -9,30 +9,17 @@ /* Begin PBXBuildFile section */ 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; - 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; - 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; - 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; 1F4336AD1C17113F00E7C1AC /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AC1C17113F00E7C1AC /* StateTests.swift */; }; - 1F4336AE1C17113F00E7C1AC /* StateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AC1C17113F00E7C1AC /* StateTests.swift */; }; 1F4336B01C17115700E7C1AC /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AF1C17115700E7C1AC /* EventTests.swift */; }; - 1F4336B11C17115700E7C1AC /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4336AF1C17115700E7C1AC /* EventTests.swift */; }; 1F532C541C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; - 1F532C551C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; 1F532C571C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; - 1F532C581C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; 1F532C611C12D7BD00D3813A /* MiscTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C601C12D7BD00D3813A /* MiscTests.swift */; }; - 1F532C621C12D7BD00D3813A /* MiscTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C601C12D7BD00D3813A /* MiscTests.swift */; }; 1F532C641C12EA8000D3813A /* _Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C631C12EA8000D3813A /* _Random.swift */; }; - 1F532C651C12EA8000D3813A /* _Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C631C12EA8000D3813A /* _Random.swift */; }; 1F70FB661BF0F46000E5AC8C /* _RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */; }; - 1F70FB671BF0F46000E5AC8C /* _RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */; }; 1F70FB6C1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */; }; - 1F70FB6D1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */; }; 1F70FB6F1BF0F59600E5AC8C /* _HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */; }; - 1F70FB701BF0F59600E5AC8C /* _HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */; }; 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */; }; - 1F70FB7A1BF0FB7100E5AC8C /* 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 /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* EventType.swift */; }; 1FA620221996606300460108 /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* Route.swift */; }; @@ -51,30 +38,9 @@ 1FA62038199660CA00460108 /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* TransitionTests.swift */; }; 1FB1EC8A199E515B00ABD937 /* MyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB1EC89199E515B00ABD937 /* MyEvent.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 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; - 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */; }; - 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* TransitionTests.swift */; }; - 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* TransitionChainTests.swift */; }; - 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* RouteTests.swift */; }; 4836FF5A1C0EFD700038B7D2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4836FF591C0EFD700038B7D2 /* StateMachine.swift */; }; - 4836FF5B1C0EFD700038B7D2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4836FF591C0EFD700038B7D2 /* StateMachine.swift */; }; - 4836FF5C1C0EFD720038B7D2 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483F35561C0EB192007C70D7 /* Machine.swift */; }; 483F35571C0EB192007C70D7 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483F35561C0EB192007C70D7 /* Machine.swift */; }; 4876510F1C0ECBEB005961AC /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4876510E1C0ECBEB005961AC /* MachineTests.swift */; }; - 487651101C0ECBEB005961AC /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4876510E1C0ECBEB005961AC /* MachineTests.swift */; }; - 48797D5D19B42CBE0085D80F /* SwiftState.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FA620051996601000460108 /* SwiftState.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* Transition.swift */; }; - 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201E1996606300460108 /* TransitionChain.swift */; }; - 48797D6119B42CCE0085D80F /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* Route.swift */; }; - 48797D6219B42CCE0085D80F /* RouteChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201C1996606300460108 /* RouteChain.swift */; }; - 48797D6319B42CD40085D80F /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201F1996606300460108 /* StateType.swift */; }; - 48797D6419B42CD40085D80F /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* EventType.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -103,7 +69,7 @@ 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; }; 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 /* 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 = ""; }; @@ -121,10 +87,14 @@ 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 = ""; }; - 4822F0A619D0085E00F5F572 /* SwiftStateTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftStateTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; - 4872D5AC19B4211900F326B5 /* SwiftState.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftState.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4876510E1C0ECBEB005961AC /* MachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -144,21 +114,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4822F0A119D0085E00F5F572 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4872D5A819B4211900F326B5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -188,6 +143,7 @@ 1FA61FF61996601000460108 = { isa = PBXGroup; children = ( + 1FD79F9E1C173C6C00CE7060 /* Configurations */, 1FA620021996601000460108 /* Sources */, 1FA6200C1996601000460108 /* Tests */, 1FA620011996601000460108 /* Products */, @@ -198,9 +154,7 @@ isa = PBXGroup; children = ( 1FA620001996601000460108 /* SwiftState.framework */, - 1FA6200B1996601000460108 /* SwiftState-OSXTests.xctest */, - 4872D5AC19B4211900F326B5 /* SwiftState.framework */, - 4822F0A619D0085E00F5F572 /* SwiftStateTests-iOS.xctest */, + 1FA6200B1996601000460108 /* SwiftStateTests.xctest */, ); name = Products; sourceTree = ""; @@ -270,6 +224,37 @@ 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 = 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 */ /* Begin PBXHeadersBuildPhase section */ @@ -281,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 */, @@ -305,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 */, @@ -323,46 +300,11 @@ dependencies = ( 1FF69206199662A000E3CE40 /* PBXTargetDependency */, ); - name = "SwiftState-OSXTests"; + name = SwiftStateTests; productName = SwiftStateTests; - productReference = 1FA6200B1996601000460108 /* SwiftState-OSXTests.xctest */; + productReference = 1FA6200B1996601000460108 /* SwiftStateTests.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 */; - 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 */ @@ -379,9 +321,6 @@ 1FA6200A1996601000460108 = { CreatedOnToolsVersion = 6.0; }; - 4872D5AB19B4211900F326B5 = { - CreatedOnToolsVersion = 6.0; - }; }; }; buildConfigurationList = 1FA61FFA1996601000460108 /* Build configuration list for PBXProject "SwiftState" */; @@ -396,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 */ @@ -419,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 */ @@ -482,58 +405,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 4822F09F19D0085E00F5F572 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */, - 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */, - 1F70FB7A1BF0FB7100E5AC8C /* String+TestExt.swift in Sources */, - 487651101C0ECBEB005961AC /* MachineTests.swift in Sources */, - 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */, - 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, - 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, - 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */, - 1F4336AE1C17113F00E7C1AC /* StateTests.swift in Sources */, - 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */, - 1F532C621C12D7BD00D3813A /* MiscTests.swift in Sources */, - 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */, - 4822F0A919D008E700F5F572 /* MyState.swift in Sources */, - 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */, - 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */, - 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */, - 1F4336B11C17115700E7C1AC /* EventTests.swift in Sources */, - 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4872D5A719B4211900F326B5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1F532C651C12EA8000D3813A /* _Random.swift in Sources */, - 48797D6219B42CCE0085D80F /* RouteChain.swift in Sources */, - 1F532C551C12B5BC00D3813A /* Disposable.swift in Sources */, - 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */, - 1F70FB671BF0F46000E5AC8C /* _RouteID.swift in Sources */, - 4836FF5C1C0EFD720038B7D2 /* Machine.swift in Sources */, - 1F532C581C12BBBB00D3813A /* _HandlerInfo.swift in Sources */, - 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */, - 1F70FB6D1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */, - 48797D6319B42CD40085D80F /* StateType.swift in Sources */, - 4836FF5B1C0EFD700038B7D2 /* StateMachine.swift in Sources */, - 1F70FB701BF0F59600E5AC8C /* _HandlerID.swift in Sources */, - 48797D6119B42CCE0085D80F /* Route.swift in Sources */, - 48797D6419B42CD40085D80F /* EventType.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 */ @@ -541,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"; @@ -588,6 +466,7 @@ }; 1FA620121996601000460108 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79FA61C17412600CE7060 /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; @@ -627,6 +506,7 @@ }; 1FA620141996601000460108 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F971C1736D600CE7060 /* UniversalFramework_Framework.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; @@ -649,6 +529,7 @@ }; 1FA620151996601000460108 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F971C1736D600CE7060 /* UniversalFramework_Framework.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; @@ -671,6 +552,7 @@ }; 1FA620171996601000460108 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F981C1736D600CE7060 /* UniversalFramework_Test.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -691,6 +573,7 @@ }; 1FA620181996601000460108 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1FD79F981C1736D600CE7060 /* UniversalFramework_Test.xcconfig */; buildSettings = { COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -705,89 +588,6 @@ }; name = Release; }; - 4822F0A419D0085E00F5F572 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = Tests/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 = Tests/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 = Sources/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 = Sources/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; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -800,7 +600,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 */, @@ -809,7 +609,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 */, @@ -818,24 +618,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 687921f..0000000 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-iOS.xcscheme +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState.xcscheme similarity index 92% rename from SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme rename to SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState.xcscheme index cc7fe82..87e399e 100644 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme +++ b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState.xcscheme @@ -16,7 +16,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "1FA61FFF1996601000460108" BuildableName = "SwiftState.framework" - BlueprintName = "SwiftState-OSX" + BlueprintName = "SwiftState" ReferencedContainer = "container:SwiftState.xcodeproj"> @@ -34,8 +34,8 @@ @@ -45,7 +45,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "1FA61FFF1996601000460108" BuildableName = "SwiftState.framework" - BlueprintName = "SwiftState-OSX" + BlueprintName = "SwiftState" ReferencedContainer = "container:SwiftState.xcodeproj"> @@ -67,7 +67,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "1FA61FFF1996601000460108" BuildableName = "SwiftState.framework" - BlueprintName = "SwiftState-OSX" + BlueprintName = "SwiftState" ReferencedContainer = "container:SwiftState.xcodeproj"> @@ -85,7 +85,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "1FA61FFF1996601000460108" BuildableName = "SwiftState.framework" - BlueprintName = "SwiftState-OSX" + BlueprintName = "SwiftState" ReferencedContainer = "container:SwiftState.xcodeproj"> 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 From 83af536798d5770d0ccdb46616c24624b7f47e2a Mon Sep 17 00:00:00 2001 From: Yasuhiro Inami Date: Thu, 10 Dec 2015 00:24:56 +0900 Subject: [PATCH 27/27] Remove unnecessary xcodeproj settings. --- SwiftState.xcodeproj/project.pbxproj | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index 464dafc..6b476b0 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -453,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 = ""; @@ -495,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 = ""; }; @@ -517,13 +511,9 @@ FRAMEWORK_VERSION = A; 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; }; @@ -540,13 +530,9 @@ FRAMEWORK_VERSION = A; 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 = "-Owholemodule"; }; name = Release; }; @@ -564,10 +550,8 @@ "$(inherited)", ); INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; }; name = Debug; }; @@ -581,10 +565,8 @@ "$(inherited)", ); INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; }; name = Release; };