motion-state-machine 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +145 -0
- data/Rakefile +31 -0
- data/lib/motion-state-machine.rb +9 -0
- data/lib/motion-state-machine/base.rb +223 -0
- data/lib/motion-state-machine/spec_app_delegate.rb +7 -0
- data/lib/motion-state-machine/state.rb +379 -0
- data/lib/motion-state-machine/transition.rb +407 -0
- data/lib/motion-state-machine/version.rb +3 -0
- data/motion-state-machine.gemspec +19 -0
- data/spec/motion-state-machine/base_spec.rb +118 -0
- data/spec/motion-state-machine/benchmark_spec.rb +74 -0
- data/spec/motion-state-machine/notification_transition_spec.rb +57 -0
- data/spec/motion-state-machine/send_event_transition_spec.rb +53 -0
- data/spec/motion-state-machine/state_spec.rb +219 -0
- data/spec/motion-state-machine/timed_transition_spec.rb +48 -0
- data/spec/motion-state-machine/transition_spec.rb +157 -0
- metadata +90 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/motion-state-machine/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Sebastian Burkhart"]
|
6
|
+
gem.email = ["sebastianburkhart@me.com"]
|
7
|
+
gem.description = %q{A finite state machine for RubyMotion with a flavor of Grand Central Dispatch.}
|
8
|
+
gem.summary = %q{Comes with a nice syntax for state and transition definition. Supports triggering via events, timeouts and NSNotifications.}
|
9
|
+
gem.homepage = "https://github.com/opyh/motion-state-machine"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "motion-state-machine"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = StateMachine::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rake'
|
19
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
describe StateMachine::Base do
|
2
|
+
|
3
|
+
describe "#initialize" do
|
4
|
+
|
5
|
+
it "should raise if not given a start state" do
|
6
|
+
lambda {state_machine = StateMachine::Base.new}.
|
7
|
+
should.raise(ArgumentError)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "when given a start state" do
|
11
|
+
before do
|
12
|
+
@fsm = StateMachine::Base.new start_state: :start
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should initialize and use the internal state dictionary" do
|
16
|
+
dictionary = @fsm.instance_variable_get(:@state_symbols_to_states)
|
17
|
+
dictionary.class.should == Hash
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should create an internal waiting_for_start state" do
|
21
|
+
dictionary = @fsm.instance_variable_get(:@state_symbols_to_states)
|
22
|
+
dictionary.count.should == 2
|
23
|
+
state = dictionary[:waiting_for_start]
|
24
|
+
state.class.should == StateMachine::State
|
25
|
+
state.transition_map.count.should == 1
|
26
|
+
transition = state.transition_map[:on][:start].first
|
27
|
+
transition.options[:from].should == :waiting_for_start
|
28
|
+
transition.options[:to].should ==:start
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should set its current state to :waiting_for_start" do
|
32
|
+
state = @fsm.current_state
|
33
|
+
state.class.should == StateMachine::State
|
34
|
+
state.symbol.should == :waiting_for_start
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "after correct initialization" do
|
41
|
+
|
42
|
+
before do
|
43
|
+
@fsm = StateMachine::Base.new start_state: :awake
|
44
|
+
@fsm.when(:awake) do |state|
|
45
|
+
state.die :on => :terminate
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#state(symbol, name = nil)" do
|
50
|
+
it "should create & return a new state for the given symbol if not existing" do
|
51
|
+
states_hash = @fsm.instance_variable_get(:@state_symbols_to_states)
|
52
|
+
state_count_before = states_hash.count
|
53
|
+
states_hash.has_key?(:some_other_state).should == false
|
54
|
+
state = @fsm.state(:some_other_state, "Fake State")
|
55
|
+
state.class.should == StateMachine::State
|
56
|
+
states_hash.count.should == state_count_before + 1
|
57
|
+
states_hash.has_key?(:some_other_state).should == true
|
58
|
+
state.name.should == "Fake State"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return the state with the given symbol if existing" do
|
62
|
+
states_hash = @fsm.instance_variable_get(:@state_symbols_to_states)
|
63
|
+
state_count_before = states_hash.count
|
64
|
+
states_hash[:foo] = "bar"
|
65
|
+
states_hash.count.should == state_count_before + 1
|
66
|
+
@fsm.state(:foo).should == "bar"
|
67
|
+
states_hash.count.should == state_count_before + 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#start!" do
|
72
|
+
it "should remember the initial queue if called from main queue" do
|
73
|
+
Dispatch::Queue.current.to_s.should == Dispatch::Queue.main.to_s
|
74
|
+
@fsm.start!
|
75
|
+
@fsm.initial_queue.to_s.should == Dispatch::Queue.main.to_s
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should remember the initial queue if called from another queue" do
|
79
|
+
other_queue = Dispatch::Queue.concurrent(:default)
|
80
|
+
other_queue.to_s.should != Dispatch::Queue.main.to_s
|
81
|
+
other_queue.sync do
|
82
|
+
@fsm.start!
|
83
|
+
end
|
84
|
+
@fsm.initial_queue.to_s.should == other_queue.to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should change the current state to the start state" do
|
88
|
+
@fsm.current_state.symbol.should == :waiting_for_start
|
89
|
+
@fsm.start!
|
90
|
+
@fsm.current_state.symbol.should == :awake
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#terminated?" do
|
95
|
+
it "should return false until the machine is terminated" do
|
96
|
+
@fsm.current_state.symbol.should == :waiting_for_start
|
97
|
+
@fsm.terminated?.should == false
|
98
|
+
@fsm.start!
|
99
|
+
@fsm.current_state.symbol.should == :awake
|
100
|
+
@fsm.terminated?.should == false
|
101
|
+
@fsm.event(:terminate)
|
102
|
+
@fsm.terminated?.should == true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# describe "#stop_and_cleanup" do
|
107
|
+
# it "should unregister all notifications"
|
108
|
+
# it "should remove references to self from all registered states"
|
109
|
+
# end
|
110
|
+
|
111
|
+
# it "should not be too slow"
|
112
|
+
#
|
113
|
+
# it "should be thread-safe"
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Benchmark that checks if the machine is fast enough.
|
2
|
+
# If it should ever happen that some implementation change
|
3
|
+
# slows it down, this spec will be red.
|
4
|
+
|
5
|
+
# Test state machine that loops in 3 states.
|
6
|
+
|
7
|
+
class LoopingThreeStateMachine < StateMachine::Base
|
8
|
+
attr_accessor :steps, :loops
|
9
|
+
attr_accessor :is_dead
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super(start_state: :first_state)
|
13
|
+
|
14
|
+
@steps = 0
|
15
|
+
@loops = 0
|
16
|
+
|
17
|
+
self.when :first_state do |state|
|
18
|
+
state.transition_to :second_state, on: :next,
|
19
|
+
action: proc { @steps += 1 }
|
20
|
+
end
|
21
|
+
|
22
|
+
self.when :second_state do |state|
|
23
|
+
state.transition_to :third_state, on: :next,
|
24
|
+
action: proc { @steps += 1 }
|
25
|
+
end
|
26
|
+
|
27
|
+
self.when :third_state do |state|
|
28
|
+
state.transition_to :first_state, on: :next,
|
29
|
+
action: proc { @steps += 1; @loops += 1 }
|
30
|
+
end
|
31
|
+
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe LoopingThreeStateMachine do
|
37
|
+
before do
|
38
|
+
@fsm = LoopingThreeStateMachine.new
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should loop correctly" do
|
42
|
+
@fsm.start!
|
43
|
+
100.times { @fsm.event :next }
|
44
|
+
@fsm.steps.should == 100
|
45
|
+
@fsm.loops.should == @fsm.steps / 3
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should prove that the state machine can handle more than 10k events per second" do
|
49
|
+
other_queue = Dispatch::Queue.new('org.screenfashion.motion-state-machine')
|
50
|
+
other_queue.sync { @fsm.start! }
|
51
|
+
started_on = NSDate.date
|
52
|
+
dispatch_group = Dispatch::Group.new
|
53
|
+
event_count = 100000
|
54
|
+
|
55
|
+
event_count.times do |i|
|
56
|
+
other_queue.async(dispatch_group) { @fsm.event :next }
|
57
|
+
end
|
58
|
+
|
59
|
+
send_time = NSDate.date.timeIntervalSinceDate started_on
|
60
|
+
|
61
|
+
dispatch_group.wait # wait for the events to be handled
|
62
|
+
|
63
|
+
handle_time = NSDate.date.timeIntervalSinceDate started_on
|
64
|
+
|
65
|
+
send_time.should < 0.2
|
66
|
+
|
67
|
+
frequency = event_count / handle_time
|
68
|
+
frequency.should > 10000
|
69
|
+
|
70
|
+
puts "\nNeeded #{send_time}s to send #{event_count} events, #{handle_time}s to handle them."
|
71
|
+
puts "That's a frequency of #{frequency} state changes per second.\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
describe StateMachine::NotificationTransition do
|
2
|
+
|
3
|
+
before do
|
4
|
+
@state_machine = StateMachine::Base.new start_state: :awaiting_notification
|
5
|
+
action = proc {@fired = true}
|
6
|
+
@state_machine.when :awaiting_notification do |state|
|
7
|
+
@transition = state.transition_to(:notified, on_notification: "SomeNotification", action: action).first
|
8
|
+
state.transition_to :canceled, on: :cancel
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be created correctly" do
|
13
|
+
@transition.should.is_a(StateMachine::NotificationTransition)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "when running in main queue" do
|
17
|
+
it "should be executed when receiving the notification and unarm correctly" do
|
18
|
+
@fired = false
|
19
|
+
@state_machine.start! # will arm the transition
|
20
|
+
@state_machine.current_state.symbol.should == :awaiting_notification
|
21
|
+
@fired.should == false
|
22
|
+
|
23
|
+
NSNotificationCenter.defaultCenter.postNotificationName "SomeNotification", object: nil
|
24
|
+
@state_machine.current_state.symbol.should == :notified
|
25
|
+
@fired.should == true
|
26
|
+
@fired = false
|
27
|
+
|
28
|
+
NSNotificationCenter.defaultCenter.postNotificationName "SomeNotification", object: nil
|
29
|
+
@state_machine.current_state.symbol.should == :notified
|
30
|
+
@fired.should == false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "when running in other queue" do
|
35
|
+
it "should be executed when receiving the notification and unarm correctly" do
|
36
|
+
@fired = false
|
37
|
+
|
38
|
+
other_queue = Dispatch::Queue.concurrent
|
39
|
+
other_queue.sync do
|
40
|
+
@state_machine.start! # will arm the transition
|
41
|
+
end
|
42
|
+
@state_machine.current_state.symbol.should == :awaiting_notification
|
43
|
+
@fired.should == false
|
44
|
+
|
45
|
+
NSNotificationCenter.defaultCenter.postNotificationName "SomeNotification", object: nil
|
46
|
+
sleep 0.1
|
47
|
+
@state_machine.current_state.symbol.should == :notified
|
48
|
+
@fired.should == true
|
49
|
+
@fired = false
|
50
|
+
|
51
|
+
NSNotificationCenter.defaultCenter.postNotificationName "SomeNotification", object: nil
|
52
|
+
@state_machine.current_state.symbol.should == :notified
|
53
|
+
@fired.should == false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
describe StateMachine::SendEventTransition do
|
2
|
+
before do
|
3
|
+
@state_machine = StateMachine::Base.new start_state: :awake
|
4
|
+
@source_state = @state_machine.state :awake
|
5
|
+
@destination_state = @state_machine.state :tired
|
6
|
+
@options = {
|
7
|
+
state_machine: @state_machine,
|
8
|
+
from: :awake,
|
9
|
+
to: :tired,
|
10
|
+
type: :on,
|
11
|
+
on: :work_done
|
12
|
+
}
|
13
|
+
@transition = StateMachine::Transition.make @options
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should correctly register in the factory" do
|
17
|
+
@transition.should.is_a(StateMachine::SendEventTransition)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#initialize(options)" do
|
21
|
+
it "should not arm the transition" do
|
22
|
+
@state_machine.event(:work_done)
|
23
|
+
@state_machine.current_state.symbol.should == :waiting_for_start
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#arm" do
|
28
|
+
it "should execute the transition when the event is sent to the state machine" do
|
29
|
+
@state_machine.start!
|
30
|
+
@state_machine.current_state.symbol.should == :awake
|
31
|
+
@state_machine.event(:work_done)
|
32
|
+
@state_machine.current_state.symbol.should == :awake
|
33
|
+
@transition.arm
|
34
|
+
# necessary for the event to work
|
35
|
+
@source_state.register(@transition)
|
36
|
+
@state_machine.event(:work_done)
|
37
|
+
@state_machine.current_state.symbol.should == :tired
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#unarm" do
|
42
|
+
it "should make sure the transition is not executed when the event is sent to the state machine" do
|
43
|
+
transition = StateMachine::Transition.make @options
|
44
|
+
@state_machine.start!
|
45
|
+
@state_machine.current_state.symbol.should == :awake
|
46
|
+
@transition.unarm
|
47
|
+
@source_state.register(@transition)
|
48
|
+
@state_machine.event(:work_done)
|
49
|
+
@state_machine.current_state.symbol.should == :awake
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
describe StateMachine::State do
|
2
|
+
before do
|
3
|
+
@living = StateMachine::State.new "stub", symbol: :awake, name: "Living"
|
4
|
+
end
|
5
|
+
|
6
|
+
describe "Entering/exiting" do
|
7
|
+
before do
|
8
|
+
# These seem not to be correctly deinitialized after single specs,
|
9
|
+
# so we have to reinitialize them ourselves.
|
10
|
+
|
11
|
+
@entry_action_called = false
|
12
|
+
@other_entry_action_called = false
|
13
|
+
@exit_action_called = false
|
14
|
+
@other_exit_action_called = false
|
15
|
+
|
16
|
+
@state_machine = StateMachine::Base.new start_state: :awake
|
17
|
+
@state_machine.when(:awake) do |state|
|
18
|
+
state.transition_to :tired, on: :work_done
|
19
|
+
state.on_exit do
|
20
|
+
@exit_action_called = true
|
21
|
+
end
|
22
|
+
state.on_exit do
|
23
|
+
@other_exit_action_called = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@state_machine.when(:tired) do |state|
|
27
|
+
state.transition_to :very_excited, on: :something_happened
|
28
|
+
state.on_entry do
|
29
|
+
@entry_action_called = true
|
30
|
+
end
|
31
|
+
state.on_entry do
|
32
|
+
@other_entry_action_called = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@state_machine.start!
|
37
|
+
@state_machine.current_state.symbol.should == :awake
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#enter!" do
|
41
|
+
it "should set the state machine's current state" do
|
42
|
+
@state_machine.state(:tired).send :enter!
|
43
|
+
@state_machine.current_state.symbol.should == :tired
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should execute the entry actions" do
|
47
|
+
@entry_action_called.should == false
|
48
|
+
@other_entry_action_called.should == false
|
49
|
+
@state_machine.event(:work_done)
|
50
|
+
@entry_action_called.should == true
|
51
|
+
@other_entry_action_called.should == true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should arm the transitions" do
|
55
|
+
@state_machine.event(:something_happened) # should be ignored
|
56
|
+
@state_machine.current_state.symbol.should == :awake
|
57
|
+
@state_machine.event(:work_done)
|
58
|
+
@state_machine.current_state.symbol.should == :tired
|
59
|
+
@state_machine.event(:something_happened)
|
60
|
+
@state_machine.current_state.symbol.should == :very_excited
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#exit!" do
|
66
|
+
it "should set the state machine's current state to nil" do
|
67
|
+
@state_machine.state(:awake).send :exit!
|
68
|
+
@state_machine.current_state.should == nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should execute the exit actions" do
|
72
|
+
@exit_action_called.should == false
|
73
|
+
@other_exit_action_called.should == false
|
74
|
+
@state_machine.event(:work_done)
|
75
|
+
@exit_action_called.should == true
|
76
|
+
@other_exit_action_called.should == true
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should unarm the transitions" do
|
80
|
+
@state_machine.state(:awake).send :exit!
|
81
|
+
@state_machine.event(:work_done) # should be ignored
|
82
|
+
@state_machine.current_state.should == nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#guarded_execute(options)" do
|
89
|
+
before do
|
90
|
+
@state_machine = StateMachine::Base.new start_state: :awake
|
91
|
+
end
|
92
|
+
it "should not do anything when on a terminating state" do
|
93
|
+
@state_machine.when :awake do |state|
|
94
|
+
state.die on: :kill
|
95
|
+
end
|
96
|
+
@state_machine.start!
|
97
|
+
@state_machine.current_state.symbol.should == :awake
|
98
|
+
@state_machine.current_state.send :guarded_execute, :on, :kill
|
99
|
+
@state_machine.current_state.should.be.terminating
|
100
|
+
end
|
101
|
+
|
102
|
+
describe ":if and :unless guards" do
|
103
|
+
|
104
|
+
# if/unless logic table is tested in transition specs
|
105
|
+
|
106
|
+
it "should raise if multiple non-guarded transitions would be possible for the same event" do
|
107
|
+
@state_machine.when :awake do |state|
|
108
|
+
state.transition_to :state2, on: :work_done
|
109
|
+
state.transition_to :state3, on: :work_done
|
110
|
+
end
|
111
|
+
@state_machine.start!
|
112
|
+
lambda {@state_machine.current_state.send :guarded_execute, :on, :work_done}.should.raise RuntimeError
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should raise if multiple guarded transitions would be possible for the same event" do
|
116
|
+
@state_machine.when :awake do |state|
|
117
|
+
state.transition_to :state2, on: :work_done, :if => proc { true }
|
118
|
+
state.transition_to :state3, on: :work_done, :if => proc { true }
|
119
|
+
end
|
120
|
+
@state_machine.start!
|
121
|
+
lambda {@state_machine.current_state.send :guarded_execute, :on, :work_done}.should.raise RuntimeError
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should execute right transition of multiple are allowed" do
|
125
|
+
@state_machine.when :awake do |state|
|
126
|
+
state.transition_to :state2, on: :work_done, :if => proc { false }
|
127
|
+
state.transition_to :state3, on: :work_done, :if => proc { true }
|
128
|
+
end
|
129
|
+
@state_machine.start!
|
130
|
+
@state_machine.current_state.send :guarded_execute, :on, :work_done
|
131
|
+
@state_machine.current_state.symbol.should == :state3
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should not execute any transition if all are disallowed" do
|
135
|
+
@state_machine.when :awake do |state|
|
136
|
+
state.transition_to :state2, on: :work_done, :if => proc { false }
|
137
|
+
state.transition_to :state3, on: :work_done, :if => proc { false }
|
138
|
+
end
|
139
|
+
@state_machine.start!
|
140
|
+
@state_machine.current_state.send :guarded_execute, :on, :work_done
|
141
|
+
@state_machine.current_state.symbol.should == :awake
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "Definition DSL" do
|
149
|
+
before do
|
150
|
+
@state_machine = StateMachine::Base.new start_state: :awake
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "#transition_to" do
|
154
|
+
it "should return an array of created transitions" do
|
155
|
+
@state_machine.when(:awake) do |state|
|
156
|
+
transitions = state.transition_to(:tired, after: 42)
|
157
|
+
transitions.class.should == Array
|
158
|
+
transitions.first.class.should == StateMachine::TimedTransition
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "Argument error handling" do
|
163
|
+
it "should raise if not given a destination state symbol" do
|
164
|
+
proc do
|
165
|
+
@state_machine.when(:awake) do |state|
|
166
|
+
state.transition_to Hash.new, on: :eat
|
167
|
+
end
|
168
|
+
end.should.raise ArgumentError, /No destination state given/
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should raise if not given a trigger event" do
|
172
|
+
proc do
|
173
|
+
@state_machine.when(:awake) do |state|
|
174
|
+
state.transition_to :sleepy
|
175
|
+
end
|
176
|
+
end.should.raise ArgumentError, /No trigger event/
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should create the destination state if not existent" do
|
180
|
+
created = proc {@state_machine.states.collect(&:symbol).include?(:sleepy)}
|
181
|
+
created.call.should == false
|
182
|
+
@state_machine.when(:awake) do |state|
|
183
|
+
state.transition_to :sleepy, on: :hard_work_done
|
184
|
+
end
|
185
|
+
created.call.should == true
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "#die" do
|
191
|
+
it "should return an array of created transitions" do
|
192
|
+
@state_machine.when(:awake) do |state|
|
193
|
+
transitions = state.die(on: :eat)
|
194
|
+
transitions.class.should == Array
|
195
|
+
transitions.first.class.should == StateMachine::SendEventTransition
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should create termination states with different symbols" do
|
200
|
+
@state_machine.when(:awake) do |state|
|
201
|
+
# so many options!
|
202
|
+
transitions1 = state.die on: :suffocation
|
203
|
+
transitions2 = state.die on: :starving
|
204
|
+
transitions1.first.destination_state.symbol.should != transitions2.first.destination_state.symbol
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should set the termination states' terminating flags" do
|
209
|
+
@state_machine.when(:awake) do |state|
|
210
|
+
non_terminating_transitions = state.transition_to :tired, on: :work_done
|
211
|
+
terminating_transitions = state.die on: :suffocation
|
212
|
+
non_terminating_transitions.first.destination_state.should.not.be.terminating
|
213
|
+
terminating_transitions.first.destination_state.should.be.terminating
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|