state-fu 0.11.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/LICENSE +40 -0
- data/README.textile +293 -0
- data/Rakefile +114 -0
- data/lib/binding.rb +292 -0
- data/lib/event.rb +192 -0
- data/lib/executioner.rb +120 -0
- data/lib/hooks.rb +39 -0
- data/lib/interface.rb +132 -0
- data/lib/lathe.rb +538 -0
- data/lib/machine.rb +184 -0
- data/lib/method_factory.rb +243 -0
- data/lib/persistence.rb +116 -0
- data/lib/persistence/active_record.rb +34 -0
- data/lib/persistence/attribute.rb +47 -0
- data/lib/persistence/base.rb +100 -0
- data/lib/persistence/relaxdb.rb +23 -0
- data/lib/persistence/session.rb +7 -0
- data/lib/sprocket.rb +58 -0
- data/lib/state-fu.rb +56 -0
- data/lib/state.rb +48 -0
- data/lib/support/active_support_lite/array.rb +9 -0
- data/lib/support/active_support_lite/array/access.rb +60 -0
- data/lib/support/active_support_lite/array/conversions.rb +202 -0
- data/lib/support/active_support_lite/array/extract_options.rb +21 -0
- data/lib/support/active_support_lite/array/grouping.rb +109 -0
- data/lib/support/active_support_lite/array/random_access.rb +13 -0
- data/lib/support/active_support_lite/array/wrapper.rb +25 -0
- data/lib/support/active_support_lite/blank.rb +67 -0
- data/lib/support/active_support_lite/cattr_reader.rb +57 -0
- data/lib/support/active_support_lite/keys.rb +57 -0
- data/lib/support/active_support_lite/misc.rb +59 -0
- data/lib/support/active_support_lite/module.rb +1 -0
- data/lib/support/active_support_lite/module/delegation.rb +130 -0
- data/lib/support/active_support_lite/object.rb +9 -0
- data/lib/support/active_support_lite/string.rb +38 -0
- data/lib/support/active_support_lite/symbol.rb +16 -0
- data/lib/support/applicable.rb +41 -0
- data/lib/support/arrays.rb +197 -0
- data/lib/support/core_ext.rb +90 -0
- data/lib/support/exceptions.rb +106 -0
- data/lib/support/has_options.rb +16 -0
- data/lib/support/logger.rb +165 -0
- data/lib/support/methodical.rb +17 -0
- data/lib/support/no_stdout.rb +55 -0
- data/lib/support/plotter.rb +62 -0
- data/lib/support/vizier.rb +300 -0
- data/lib/tasks/spec_last.rake +55 -0
- data/lib/tasks/state_fu.rake +57 -0
- data/lib/transition.rb +338 -0
- data/lib/transition_query.rb +224 -0
- data/spec/custom_formatter.rb +49 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/method_missing_only_once_spec.rb +28 -0
- data/spec/features/not_requirements_spec.rb +118 -0
- data/spec/features/plotter_spec.rb +97 -0
- data/spec/features/shared_log_spec.rb +7 -0
- data/spec/features/singleton_machine_spec.rb +39 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
- data/spec/features/transition_boolean_comparison_spec.rb +101 -0
- data/spec/helper.rb +13 -0
- data/spec/integration/active_record_persistence_spec.rb +202 -0
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/class_accessor_spec.rb +117 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/example_01_document_spec.rb +133 -0
- data/spec/integration/example_02_string_spec.rb +88 -0
- data/spec/integration/instance_accessor_spec.rb +97 -0
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/machine_duplication_spec.rb +101 -0
- data/spec/integration/relaxdb_persistence_spec.rb +97 -0
- data/spec/integration/requirement_reflection_spec.rb +270 -0
- data/spec/integration/state_definition_spec.rb +163 -0
- data/spec/integration/transition_spec.rb +1033 -0
- data/spec/spec.opts +9 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/state_fu_spec.rb +948 -0
- data/spec/units/binding_spec.rb +192 -0
- data/spec/units/event_spec.rb +214 -0
- data/spec/units/exceptions_spec.rb +82 -0
- data/spec/units/lathe_spec.rb +570 -0
- data/spec/units/machine_spec.rb +229 -0
- data/spec/units/method_factory_spec.rb +366 -0
- data/spec/units/sprocket_spec.rb +69 -0
- data/spec/units/state_spec.rb +59 -0
- metadata +171 -0
@@ -0,0 +1,163 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
##
|
4
|
+
##
|
5
|
+
##
|
6
|
+
|
7
|
+
describe "Adding states to a Machine" do
|
8
|
+
|
9
|
+
include MySpecHelper
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
make_pristine_class 'Klass'
|
13
|
+
@k = Klass.new()
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should allow me to call machine() { state(:egg) }" do
|
17
|
+
lambda {Klass.state_fu_machine(){ state :egg } }.should_not raise_error()
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "having called machine() { state(:egg) }" do
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
Klass.state_fu_machine(){ state :egg }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return [:egg] given machine.state_names" do
|
27
|
+
Klass.state_fu_machine.should respond_to(:state_names)
|
28
|
+
Klass.state_fu_machine.state_names.should == [:egg]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return [<StateFu::State @name=:egg>] given machine.states" do
|
32
|
+
Klass.state_fu_machine.should respond_to(:states)
|
33
|
+
Klass.state_fu_machine.states.length.should == 1
|
34
|
+
Klass.state_fu_machine.states.first.should be_kind_of( StateFu::State )
|
35
|
+
Klass.state_fu_machine.states.first.name.should == :egg
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return :egg given machine.states.first.name" do
|
39
|
+
Klass.state_fu_machine.should respond_to(:states)
|
40
|
+
Klass.state_fu_machine.states.length.should == 1
|
41
|
+
Klass.state_fu_machine.states.first.should respond_to(:name)
|
42
|
+
Klass.state_fu_machine.states.first.name.should == :egg
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return a <StateFu::State @name=:egg> given machine.states[:egg]" do
|
46
|
+
Klass.state_fu_machine.should respond_to(:states)
|
47
|
+
result = Klass.state_fu_machine.states[:egg]
|
48
|
+
result.should_not be_nil
|
49
|
+
result.should be_kind_of( StateFu::State )
|
50
|
+
result.name.should == :egg
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
it "should allow me to call machine(){ state(:chick) }" do
|
55
|
+
lambda {Klass.state_fu_machine(){ state :chick } }.should_not raise_error()
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "having called machine() { state(:chick) }" do
|
59
|
+
before do
|
60
|
+
Klass.state_fu_machine() { state :chick }
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return [:egg] given machine.state_names" do
|
64
|
+
Klass.state_fu_machine.should respond_to(:state_names)
|
65
|
+
Klass.state_fu_machine.state_names.should == [:egg, :chick]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should return a <StateFu::State @name=:chick> given machine.states[:egg]" do
|
69
|
+
Klass.state_fu_machine.should respond_to(:states)
|
70
|
+
result = Klass.state_fu_machine.states[:chick]
|
71
|
+
result.should_not be_nil
|
72
|
+
result.should be_kind_of( StateFu::State )
|
73
|
+
result.name.should == :chick
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "calling machine() { state(:bird) {|s| .. } }" do
|
79
|
+
|
80
|
+
it "should yield the state to the block as |s|" do
|
81
|
+
state = nil
|
82
|
+
Klass.state_fu_machine() do
|
83
|
+
state(:bird) do |s|
|
84
|
+
state = s
|
85
|
+
end
|
86
|
+
end
|
87
|
+
state.should be_kind_of( StateFu::State )
|
88
|
+
state.name.should == :bird
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "calling machine() { state(:bird) { ... } }" do
|
94
|
+
|
95
|
+
it "should instance_eval the block as a StateFu::Lathe" do
|
96
|
+
lathe = nil
|
97
|
+
Klass.state_fu_machine() do
|
98
|
+
state(:bird) do
|
99
|
+
lathe = self
|
100
|
+
end
|
101
|
+
end
|
102
|
+
lathe.should be_kind_of(StateFu::Lathe)
|
103
|
+
lathe.state_or_event.should be_kind_of(StateFu::State)
|
104
|
+
lathe.state_or_event.name.should == :bird
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "calling state(:bird) consecutive times" do
|
110
|
+
|
111
|
+
it "should yield the same state each time" do
|
112
|
+
Klass.state_fu_machine() { state :bird }
|
113
|
+
bird_1 = Klass.state_fu_machine.states[:bird]
|
114
|
+
Klass.state_fu_machine() { state :bird }
|
115
|
+
bird_2 = Klass.state_fu_machine.states[:bird]
|
116
|
+
bird_1.should == bird_2
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "calling machine() { states(:egg, :chick, :bird, :poultry => true) }" do
|
123
|
+
|
124
|
+
it "should create 3 states" do
|
125
|
+
Klass.state_fu_machine().should be_empty
|
126
|
+
Klass.state_fu_machine() { states(:egg, :chick, :bird, :poultry => true) }
|
127
|
+
Klass.state_fu_machine().state_names().should == [:egg, :chick, :bird]
|
128
|
+
Klass.state_fu_machine().states.length.should == 3
|
129
|
+
Klass.state_fu_machine().states.map(&:name).should == [:egg, :chick, :bird]
|
130
|
+
Klass.state_fu_machine().states().each do |s|
|
131
|
+
s.options[:poultry].should be_true
|
132
|
+
s.should be_kind_of(StateFu::State)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "merging options" do
|
137
|
+
before do
|
138
|
+
make_pristine_class('Klass')
|
139
|
+
end
|
140
|
+
it "should merge options when states are mentioned more than once" do
|
141
|
+
# reset!
|
142
|
+
machine = Klass.state_fu_machine
|
143
|
+
machine.states.length.should == 0
|
144
|
+
Klass.state_fu_machine() { states(:egg, :chick, :bird, :poultry => true) }
|
145
|
+
machine = Klass.state_fu_machine
|
146
|
+
machine.states.length.should == 3
|
147
|
+
|
148
|
+
# make sure they're the same states
|
149
|
+
states_1 = machine.states
|
150
|
+
Klass.state_fu_machine(){ states( :egg, :chick, :bird, :covering => 'feathers')}
|
151
|
+
states_1.should == machine.states
|
152
|
+
|
153
|
+
# ensure options were merged
|
154
|
+
machine.states().each do |s|
|
155
|
+
s.options[:poultry].should be_true
|
156
|
+
s.options[:covering].should == 'feathers'
|
157
|
+
s.should be_kind_of(StateFu::State)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
@@ -0,0 +1,1033 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
2
|
+
|
3
|
+
# TODO - refactor me into manageable chunks
|
4
|
+
|
5
|
+
describe StateFu::Transition do
|
6
|
+
include MySpecHelper
|
7
|
+
before do
|
8
|
+
reset!
|
9
|
+
make_pristine_class("Klass")
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "transition args / options" do
|
13
|
+
before do
|
14
|
+
make_pristine_class('Alphabet') do
|
15
|
+
machine do
|
16
|
+
connect_states :a, :b
|
17
|
+
end
|
18
|
+
end
|
19
|
+
@abc = Alphabet.new
|
20
|
+
evt = Alphabet.machine.events[:a_to_b]
|
21
|
+
tgt = Alphabet.machine.states[:b]
|
22
|
+
@t = StateFu::Transition.new(@abc.stfu, evt, tgt,
|
23
|
+
:a, :b, 'c' => 'cat')
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should behave like this" do
|
27
|
+
@t.args.should == [:a, :b, {'c' => 'cat'}]
|
28
|
+
@t.options.should == {:c => 'cat'}
|
29
|
+
|
30
|
+
@t.apply!({'d' => :e})
|
31
|
+
@t.options.should == {:c => 'cat', :d => :e}
|
32
|
+
|
33
|
+
@t.args.should == [:a, :b, {'c' => 'cat'}]
|
34
|
+
|
35
|
+
@t.args = [:A, :B]
|
36
|
+
@t.args.should == [:A, :B]
|
37
|
+
@t.options.should == {:c => 'cat', :d => :e}
|
38
|
+
|
39
|
+
@t.args = [:X, :Y, {:scale => :metric }]
|
40
|
+
|
41
|
+
@t.options.should == { :c => 'cat', :d => :e , :scale => :metric }
|
42
|
+
@t.args.options.should == @t.options
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
#
|
48
|
+
#
|
49
|
+
|
50
|
+
describe "A simple machine with 2 states and a single event" do
|
51
|
+
before do
|
52
|
+
@machine = Klass.state_fu_machine do
|
53
|
+
state :src do
|
54
|
+
event :transfer, :to => :dest
|
55
|
+
end
|
56
|
+
end
|
57
|
+
@origin = @machine.states[:src]
|
58
|
+
@target = @machine.states[:dest]
|
59
|
+
@event = @machine.events.first
|
60
|
+
@obj = Klass.new
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have two states named :src and :dest" do
|
64
|
+
@machine.states.length.should == 2
|
65
|
+
@machine.states.should == [@origin, @target]
|
66
|
+
@origin.name.should == :src
|
67
|
+
@target.name.should == :dest
|
68
|
+
@machine.state_names.should == [:src, :dest]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should have one event :transfer, from :src to :dest" do
|
72
|
+
@machine.events.length.should == 1
|
73
|
+
@event.origin.should == @origin
|
74
|
+
@event.target.should == @target
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "instance methods on a transition" do
|
78
|
+
before do
|
79
|
+
@t = @obj.state_fu.transition( :transfer )
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "the transition before firing" do
|
83
|
+
it "should not be fired" do
|
84
|
+
@t.should_not be_fired
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should not be halted" do
|
88
|
+
@t.should_not be_halted
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should not be accepted" do
|
92
|
+
@t.should_not be_accepted
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should have a current_state of the origin state" do
|
96
|
+
@t.current_state.should == @origin
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should have a current_hook of nil" do
|
100
|
+
@t.current_hook.should == nil
|
101
|
+
end
|
102
|
+
end # transition before fire!
|
103
|
+
|
104
|
+
describe "calling fire! on a transition with no conditions or hooks" do
|
105
|
+
it "should change the state of the binding" do
|
106
|
+
@obj.state_fu.state.should == @origin
|
107
|
+
@t.fire!
|
108
|
+
@obj.state_fu.state.should == @target
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should have an empty set of hooks" do
|
112
|
+
@t.hooks.map(&:last).flatten.should == []
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should change the field when persistence is via an attribute" do
|
116
|
+
@obj.state_fu.persister.should be_kind_of( StateFu::Persistence::Attribute )
|
117
|
+
@obj.state_fu.persister.field_name.to_s.should == StateFu::DEFAULT_FIELD.to_s
|
118
|
+
@obj.send( :state_fu_field ).should == "src"
|
119
|
+
@t.fire!
|
120
|
+
@obj.send( :state_fu_field ).should == "dest"
|
121
|
+
end
|
122
|
+
end # transition.fire!
|
123
|
+
|
124
|
+
describe "the transition after firing is complete" do
|
125
|
+
before do
|
126
|
+
@t.fire!()
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should be fired" do
|
130
|
+
@t.should be_fired
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should not be halted" do
|
134
|
+
@t.should_not be_halted
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should be accepted" do
|
138
|
+
@t.should be_accepted
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should have a current_state of the target state" do
|
142
|
+
@t.current_state.should == @target
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should have a current_hook && current_hook_slot of nil" do
|
146
|
+
@t.current_hook.should == nil
|
147
|
+
@t.current_hook_slot.should == nil
|
148
|
+
end
|
149
|
+
end # transition after fire
|
150
|
+
end # transition instance methods
|
151
|
+
|
152
|
+
# binding instance methods
|
153
|
+
# TODO move these to binding spec
|
154
|
+
describe "instance methods on the binding" do
|
155
|
+
describe "constructing a new transition with state_fu.transition" do
|
156
|
+
|
157
|
+
it "should raise an ArgumentError if a bad event name is given" do
|
158
|
+
lambda do
|
159
|
+
trans = @obj.state_fu.transition( :transfibrillate )
|
160
|
+
end.should raise_error( ArgumentError )
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should create a new transition given an event_name" do
|
164
|
+
trans = @obj.state_fu.transition( :transfer )
|
165
|
+
trans.should be_kind_of( StateFu::Transition )
|
166
|
+
trans.binding.should == @obj.state_fu
|
167
|
+
trans.object.should == @obj
|
168
|
+
trans.origin.should == @origin
|
169
|
+
trans.target.should == @target
|
170
|
+
trans.options.should == {}
|
171
|
+
trans.errors.should == []
|
172
|
+
trans.args.should == []
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should create a new transition given a StateFu::Event" do
|
176
|
+
e = @obj.state_fu.machine.events.first
|
177
|
+
e.name.should == :transfer
|
178
|
+
trans = @obj.state_fu.transition( e )
|
179
|
+
trans.should be_kind_of( StateFu::Transition )
|
180
|
+
trans.binding.should == @obj.state_fu
|
181
|
+
trans.object.should == @obj
|
182
|
+
trans.origin.should == @origin
|
183
|
+
trans.target.should == @target
|
184
|
+
trans.options.should == {}
|
185
|
+
trans.errors.should == []
|
186
|
+
trans.args.should == []
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should define any methods declared in a block given to .transition" do
|
190
|
+
trans = @obj.state_fu.transition( :transfer ) do
|
191
|
+
def snoo
|
192
|
+
return [self]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
trans.should be_kind_of( StateFu::Transition )
|
196
|
+
trans.should respond_to(:snoo)
|
197
|
+
trans.snoo.should == [trans]
|
198
|
+
t2 = @obj.state_fu.transition( :transfer )
|
199
|
+
t2.should_not respond_to( :snoo)
|
200
|
+
end
|
201
|
+
end # state_fu.transition
|
202
|
+
|
203
|
+
describe "state_fu.events" do
|
204
|
+
it "should be an array with the only event as its single element" do
|
205
|
+
@obj.state_fu.events.should == [@event]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "state_fu.fire!( :transfer )" do
|
210
|
+
it "should change the state when called" do
|
211
|
+
@obj.state_fu.should respond_to( :fire_transition! )
|
212
|
+
@obj.state_fu.state.should == @origin
|
213
|
+
@obj.state_fu.fire_transition!( :transfer )
|
214
|
+
@obj.state_fu.state.should == @target
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should return a transition object" do
|
218
|
+
@obj.state_fu.fire_transition!( :transfer ).should be_kind_of( StateFu::Transition )
|
219
|
+
end
|
220
|
+
|
221
|
+
end # state_fu.fire!
|
222
|
+
|
223
|
+
describe "calling cycle!()" do
|
224
|
+
it "should raise a TransitionNotFound error" do
|
225
|
+
lambda { @obj.state_fu.cycle!() }.should raise_error( StateFu::TransitionNotFound )
|
226
|
+
end
|
227
|
+
end # cycle!
|
228
|
+
|
229
|
+
describe "calling next!()" do
|
230
|
+
it "should change the state" do
|
231
|
+
@obj.state_fu.state.should == @origin
|
232
|
+
t = @obj.state_fu.transfer
|
233
|
+
t.should be_valid
|
234
|
+
@obj.state_fu.valid_transitions.length.should == 1
|
235
|
+
@obj.state_fu.next!
|
236
|
+
@obj.state_fu.state.should == @target
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should return a transition" do
|
240
|
+
trans = @obj.state_fu.next!()
|
241
|
+
trans.should be_kind_of( StateFu::Transition )
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should define any methods declared in a block given to .transition" do
|
245
|
+
trans = @obj.state_fu.next_transition do
|
246
|
+
def snoo
|
247
|
+
return [self]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
trans.should be_kind_of( StateFu::Transition )
|
251
|
+
# trans.should respond_to(:snoo)
|
252
|
+
trans.snoo.should == [trans]
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should raise an error when there is no next state" do
|
256
|
+
Klass.state_fu_machine(:noop) {}
|
257
|
+
lambda { @obj.noop.next! }.should raise_error( StateFu::TransitionNotFound )
|
258
|
+
end
|
259
|
+
it "should raise an error when there is more than one next state" do
|
260
|
+
Klass.state_fu_machine(:toomany) { event( :go, :from => :one, :to => [:a,:b,:c] ) }
|
261
|
+
lambda { @obj.toomany.next! }.should raise_error( StateFu::TransitionNotFound )
|
262
|
+
end
|
263
|
+
end # next!
|
264
|
+
|
265
|
+
describe "passing args / options to the transition" do
|
266
|
+
before do
|
267
|
+
@args = [:a, :b, {:c => :d }]
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "calling transition( :transfer, :a, :b, :c => :d )" do
|
271
|
+
it "should set args and options on the transition" do
|
272
|
+
t = @obj.state_fu.transition( :transfer, *@args )
|
273
|
+
t.args.should == [ :a, :b, {:c => :d} ]
|
274
|
+
t.options.should == { :c => :d }
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe "calling next!( :a, :b, :c => :d )" do
|
279
|
+
it "should set args and options on the transition" do
|
280
|
+
t = @obj.state_fu.next!( *@args )
|
281
|
+
t.args.should == [ :a, :b, {:c => :d}]
|
282
|
+
t.options.should == { :c => :d }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end # passing args / options
|
286
|
+
end # binding instance methods
|
287
|
+
end # simple machine w/ 2 states, 1 transition
|
288
|
+
|
289
|
+
#
|
290
|
+
#
|
291
|
+
#
|
292
|
+
|
293
|
+
describe "A simple machine with 1 state and an event cycling at the same state" do
|
294
|
+
|
295
|
+
before do
|
296
|
+
@machine = Klass.state_fu_machine do
|
297
|
+
state :state_fuega do
|
298
|
+
event :transfer, :to => :state_fuega
|
299
|
+
end
|
300
|
+
end
|
301
|
+
@state = @machine.states[:state_fuega]
|
302
|
+
@event = @machine.events.first
|
303
|
+
@obj = Klass.new
|
304
|
+
end
|
305
|
+
|
306
|
+
describe "state_fu instance methods" do
|
307
|
+
describe "calling state_fu.cycle!()" do
|
308
|
+
it "should not change the state" do
|
309
|
+
@obj.state_fu.state.should == @state
|
310
|
+
@obj.state_fu.cycle!
|
311
|
+
@obj.state_fu.state.should == @state
|
312
|
+
end
|
313
|
+
|
314
|
+
it "should pass args / options to the transition" do
|
315
|
+
t = @obj.state_fu.cycle!( nil, :a, :b , { :c => :d } )
|
316
|
+
t.args.should == [ :a, :b, { :c => :d } ]
|
317
|
+
t.options.should == { :c => :d }
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should not raise an error" do
|
321
|
+
@obj.state_fu.cycle!
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should return an accepted transition" do
|
325
|
+
@obj.state_fu.state.should == @state
|
326
|
+
t = @obj.state_fu.cycle!
|
327
|
+
t.should be_kind_of( StateFu::Transition )
|
328
|
+
t.should be_accepted
|
329
|
+
end
|
330
|
+
|
331
|
+
end # state_fu.cycle!
|
332
|
+
end # state_fu instance methods
|
333
|
+
end # 1 state w/ cyclic event
|
334
|
+
|
335
|
+
#
|
336
|
+
#
|
337
|
+
#
|
338
|
+
|
339
|
+
describe "A simple machine with 3 states and an event to & from multiple states" do
|
340
|
+
|
341
|
+
before do
|
342
|
+
@machine = Klass.state_fu_machine do
|
343
|
+
states :a, :b
|
344
|
+
states :x, :y
|
345
|
+
|
346
|
+
event( :go ) do
|
347
|
+
from :a, :b
|
348
|
+
to :x, :y
|
349
|
+
end
|
350
|
+
|
351
|
+
initial_state :a
|
352
|
+
end
|
353
|
+
@a = @machine.states[:a]
|
354
|
+
@b = @machine.states[:b]
|
355
|
+
@x = @machine.states[:x]
|
356
|
+
@y = @machine.states[:y]
|
357
|
+
@event = @machine.events.first
|
358
|
+
@obj = Klass.new
|
359
|
+
end
|
360
|
+
|
361
|
+
it "should have an event from [:a, :b] to [:x, :y]" do
|
362
|
+
@event.origins.should == [@a, @b]
|
363
|
+
@event.targets.should == [@x, @y]
|
364
|
+
@obj.state_fu.state.should == @a
|
365
|
+
end
|
366
|
+
|
367
|
+
describe "transition instance methods" do
|
368
|
+
end
|
369
|
+
|
370
|
+
describe "state_fu instance methods" do
|
371
|
+
describe "state_fu.transition" do
|
372
|
+
it "should raise StateFu::UnknownTarget unless a valid targets state is supplied or can be inferred" do
|
373
|
+
lambda do
|
374
|
+
@obj.state_fu.transition( :go )
|
375
|
+
end.should raise_error( StateFu::UnknownTarget )
|
376
|
+
|
377
|
+
lambda do
|
378
|
+
@obj.state_fu.transition( [:go, nil] )
|
379
|
+
end.should raise_error( StateFu::UnknownTarget )
|
380
|
+
|
381
|
+
lambda do
|
382
|
+
@obj.state_fu.transition( [:go, :awol] )
|
383
|
+
end.should raise_error( StateFu::UnknownTarget )
|
384
|
+
|
385
|
+
lambda do
|
386
|
+
@obj.state_fu.transition( [:go, :x] )
|
387
|
+
@obj.state_fu.transition( [:go, :y] )
|
388
|
+
end.should_not raise_error( StateFu::UnknownTarget )
|
389
|
+
end
|
390
|
+
|
391
|
+
it "should return a transition with the specified destination" do
|
392
|
+
t = @obj.state_fu.transition( [:go, :x] )
|
393
|
+
t.should be_kind_of( StateFu::Transition )
|
394
|
+
t.event.name.should == :go
|
395
|
+
t.target.name.should == :x
|
396
|
+
|
397
|
+
lambda do
|
398
|
+
@obj.state_fu.transition( [:go, :y] )
|
399
|
+
end.should_not raise_error( )
|
400
|
+
end
|
401
|
+
end # state_fu.transition
|
402
|
+
|
403
|
+
describe "state_fu.fire_transition!" do
|
404
|
+
it "should raise an StateFu::UnknownTarget unless a valid targets state is supplied" do
|
405
|
+
lambda do
|
406
|
+
@obj.state_fu.fire_transition!( :go )
|
407
|
+
end.should raise_error( StateFu::UnknownTarget )
|
408
|
+
|
409
|
+
lambda do
|
410
|
+
@obj.state_fu.fire_transition!( [ :go, :awol ] )
|
411
|
+
end.should raise_error( StateFu::UnknownTarget )
|
412
|
+
end
|
413
|
+
end # state_fu.fire!
|
414
|
+
|
415
|
+
describe "state_fu.next!" do
|
416
|
+
it "should raise an StateFu::TransitionNotFound" do
|
417
|
+
lambda do
|
418
|
+
@obj.state_fu.next!
|
419
|
+
end.should raise_error( StateFu::TransitionNotFound )
|
420
|
+
end
|
421
|
+
end # next!
|
422
|
+
|
423
|
+
describe "state_fu.cycle!" do
|
424
|
+
it "should raise StateFu::TransitionNotFound" do
|
425
|
+
lambda do
|
426
|
+
@obj.state_fu.cycle!
|
427
|
+
end.should raise_error( StateFu::TransitionNotFound )
|
428
|
+
end
|
429
|
+
end # cycle!
|
430
|
+
|
431
|
+
end # state_fu instance methods
|
432
|
+
end # 1 state w/ cyclic event
|
433
|
+
|
434
|
+
describe "A simple machine w/ 2 states, 1 event and named hooks " do
|
435
|
+
before do
|
436
|
+
Klass.class_eval do
|
437
|
+
attr_reader :calls
|
438
|
+
|
439
|
+
def called name
|
440
|
+
(@calls ||= [])<< name
|
441
|
+
end
|
442
|
+
|
443
|
+
def before_go ; called :before_go end
|
444
|
+
def after_go ; called :after_go end
|
445
|
+
def execute_go ; called :execute_go end
|
446
|
+
def entering_a ; called :entering_a end
|
447
|
+
def accepted_a ; called :accepted_a end
|
448
|
+
def exiting_a ; called :exiting_a end
|
449
|
+
def entering_b ; called :entering_b end
|
450
|
+
def accepted_b ; called :accepted_b end
|
451
|
+
def exiting_b ; called :exiting_b end
|
452
|
+
|
453
|
+
end
|
454
|
+
|
455
|
+
@machine = Klass.state_fu_machine do
|
456
|
+
|
457
|
+
state :a do
|
458
|
+
on_exit( :exiting_a )
|
459
|
+
end
|
460
|
+
|
461
|
+
state :b do
|
462
|
+
on_entry( :entering_b )
|
463
|
+
accepted( :accepted_b )
|
464
|
+
end
|
465
|
+
|
466
|
+
event( :go ) do
|
467
|
+
from :a, :to => :b
|
468
|
+
|
469
|
+
before :before_go
|
470
|
+
execute :execute_go
|
471
|
+
after :after_go
|
472
|
+
end
|
473
|
+
|
474
|
+
initial_state :a
|
475
|
+
end
|
476
|
+
|
477
|
+
@a = @machine.states[:a]
|
478
|
+
@b = @machine.states[:b]
|
479
|
+
@event = @machine.events[:go]
|
480
|
+
@obj = Klass.new
|
481
|
+
end # before
|
482
|
+
|
483
|
+
describe "state :a" do
|
484
|
+
it "should have a hook for on_exit" do
|
485
|
+
@a.hooks[:exit].should == [ :exiting_a ]
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
describe "state :b" do
|
490
|
+
it "should have a hook for on_entry" do
|
491
|
+
@b.hooks[:entry].should == [ :entering_b ]
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
describe "event :go" do
|
496
|
+
it "should have a hook for before" do
|
497
|
+
@event.hooks[:before].should == [ :before_go ]
|
498
|
+
end
|
499
|
+
|
500
|
+
it "should have a hook for execute" do
|
501
|
+
@event.hooks[:execute].should == [ :execute_go ]
|
502
|
+
end
|
503
|
+
|
504
|
+
it "should have a hook for after" do
|
505
|
+
@event.hooks[:execute].should == [ :execute_go ]
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
|
510
|
+
describe "a transition for the event" do
|
511
|
+
|
512
|
+
it "should have all defined hooks in correct order of execution" do
|
513
|
+
t = @obj.state_fu.transition( :go )
|
514
|
+
hooks = t.hooks.map(&:last).flatten
|
515
|
+
hooks.should be_kind_of( Array )
|
516
|
+
hooks.should_not be_empty
|
517
|
+
hooks.should == [ :before_go,
|
518
|
+
:exiting_a,
|
519
|
+
:execute_go,
|
520
|
+
:entering_b,
|
521
|
+
:after_go,
|
522
|
+
:accepted_b ]
|
523
|
+
end
|
524
|
+
end # a transition ..
|
525
|
+
|
526
|
+
describe "fire! calling hooks" do
|
527
|
+
before do
|
528
|
+
@t = @obj.state_fu.transition( :go )
|
529
|
+
end
|
530
|
+
|
531
|
+
it "should update the object's state after state:entering and before event:after" do
|
532
|
+
@binding = @obj.state_fu
|
533
|
+
pending
|
534
|
+
@t.fire!
|
535
|
+
end
|
536
|
+
|
537
|
+
it "should be accepted after state:entering and before event:after" do
|
538
|
+
pending
|
539
|
+
mock( @obj ).entering_b( @t ) { @t.should_not be_accepted }
|
540
|
+
mock( @obj ).after_go(@t) { @t.should be_accepted }
|
541
|
+
mock( @obj ).accepted_b(@t) { @t.should be_accepted }
|
542
|
+
@t.fire!
|
543
|
+
end
|
544
|
+
|
545
|
+
it "should call the method for each hook on @obj in order, with the transition" do
|
546
|
+
pending
|
547
|
+
mock( @obj ).before_go(@t) { @called << :before_go }
|
548
|
+
mock( @obj ).exiting_a(@t) { @called << :exiting_a }
|
549
|
+
mock( @obj ).execute_go(@t) { @called << :execute_go }
|
550
|
+
mock( @obj ).entering_b(@t) { @called << :entering_b }
|
551
|
+
mock( @obj ).after_go(@t) { @called << :after_go }
|
552
|
+
mock( @obj ).accepted_b(@t) { @called << :accepted_b }
|
553
|
+
|
554
|
+
@t.fire!()
|
555
|
+
end
|
556
|
+
|
557
|
+
describe "adding an anonymous hook for event.hooks[:execute]" do
|
558
|
+
before do
|
559
|
+
called = @called # get us a ref for the closure
|
560
|
+
Klass.state_fu_machine do
|
561
|
+
event( :go ) do
|
562
|
+
execute do |ctx|
|
563
|
+
called( :execute_proc )
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
it "should be called at the correct point" do
|
570
|
+
@event.hooks[:execute].length.should == 2
|
571
|
+
@event.hooks[:execute].first.class.should == Symbol
|
572
|
+
@event.hooks[:execute].last.class.should == Proc
|
573
|
+
@t.fire!()
|
574
|
+
@obj.calls.should == [ :before_go,
|
575
|
+
:exiting_a,
|
576
|
+
:execute_go,
|
577
|
+
:execute_proc,
|
578
|
+
:entering_b,
|
579
|
+
:after_go,
|
580
|
+
:accepted_b ]
|
581
|
+
end
|
582
|
+
|
583
|
+
it "should be replace the previous proc for a slot if redefined" do
|
584
|
+
pending
|
585
|
+
called = @called # get us a ref for the closure
|
586
|
+
Klass.state_fu_machine do
|
587
|
+
event( :go ) do
|
588
|
+
execute do |ctx|
|
589
|
+
called << :execute_proc_2
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
@event.hooks[:execute].length.should == 2
|
595
|
+
@event.hooks[:execute].first.class.should == Symbol
|
596
|
+
@event.hooks[:execute].last.class.should == Proc
|
597
|
+
|
598
|
+
@t.fire!()
|
599
|
+
@called.should == [ :before_go,
|
600
|
+
:exiting_a,
|
601
|
+
:execute_go,
|
602
|
+
:execute_proc_2,
|
603
|
+
:entering_b,
|
604
|
+
:after_go,
|
605
|
+
:accepted_b ]
|
606
|
+
end
|
607
|
+
end # anonymous hook
|
608
|
+
|
609
|
+
describe "adding a named hook with a block" do
|
610
|
+
describe "with arity of -1/0" do
|
611
|
+
it "should call the block in the context of the transition" do
|
612
|
+
pending
|
613
|
+
called = @called # get us a ref for the closure
|
614
|
+
Klass.state_fu_machine do
|
615
|
+
event( :go ) do
|
616
|
+
execute(:named_execute) do
|
617
|
+
raise self.class.inspect unless self.is_a?( StateFu::Transition )
|
618
|
+
called << :execute_named_proc
|
619
|
+
end
|
620
|
+
end
|
621
|
+
end
|
622
|
+
@t.fire!()
|
623
|
+
@called.should == [ :before_go,
|
624
|
+
:exiting_a,
|
625
|
+
:execute_go,
|
626
|
+
:execute_named_proc,
|
627
|
+
:entering_b,
|
628
|
+
:after_go,
|
629
|
+
:accepted_b ]
|
630
|
+
end
|
631
|
+
end # arity 0
|
632
|
+
|
633
|
+
describe "with arity of 1" do
|
634
|
+
it "should call the proc in the context of the object, passing the transition as the argument" do
|
635
|
+
pending
|
636
|
+
called = @called # get us a ref for the closure
|
637
|
+
Klass.state_fu_machine do
|
638
|
+
event( :go ) do
|
639
|
+
execute(:named_execute) do |ctx|
|
640
|
+
raise ctx.class.inspect unless ctx.is_a?( StateFu::Transition )
|
641
|
+
raise self.class.inspect unless self.is_a?( Klass )
|
642
|
+
called << :execute_named_proc
|
643
|
+
end
|
644
|
+
end
|
645
|
+
end
|
646
|
+
@t.fire!()
|
647
|
+
@called.should == [ :before_go,
|
648
|
+
:exiting_a,
|
649
|
+
:execute_go,
|
650
|
+
:execute_named_proc,
|
651
|
+
:entering_b,
|
652
|
+
:after_go,
|
653
|
+
:accepted_b ]
|
654
|
+
end
|
655
|
+
end # arity 1
|
656
|
+
end # named proc
|
657
|
+
|
658
|
+
describe "halting the transition during the execute hook" do
|
659
|
+
|
660
|
+
before do
|
661
|
+
Klass.state_fu_machine do
|
662
|
+
event( :go ) do
|
663
|
+
execute do
|
664
|
+
halt!("stop")
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end # before
|
669
|
+
|
670
|
+
it "should prevent the transition from being accepted" do
|
671
|
+
@obj.state_fu.state.name.should == :a
|
672
|
+
@t.fire!()
|
673
|
+
@obj.state_fu.state.name.should == :a
|
674
|
+
@t.should be_kind_of( StateFu::Transition )
|
675
|
+
@t.should be_halted
|
676
|
+
@t.should_not be_accepted
|
677
|
+
@obj.calls.flatten.should == [ :before_go,
|
678
|
+
:exiting_a,
|
679
|
+
:execute_go ]
|
680
|
+
end
|
681
|
+
|
682
|
+
it "should have current_hook_slot set to where it halted" do
|
683
|
+
@obj.state_fu.state.name.should == :a
|
684
|
+
@t.fire!()
|
685
|
+
@t.current_hook_slot.should == [:event, :execute]
|
686
|
+
end
|
687
|
+
|
688
|
+
it "should have current_hook set to where it halted" do
|
689
|
+
@obj.state_fu.state.name.should == :a
|
690
|
+
@t.fire!()
|
691
|
+
@t.current_hook.should be_kind_of( Proc )
|
692
|
+
end
|
693
|
+
|
694
|
+
end # halting from execute
|
695
|
+
end # fire! calling hooks
|
696
|
+
|
697
|
+
end # machine w/ hooks
|
698
|
+
|
699
|
+
describe "A binding for a machine with an event transition requirement" do
|
700
|
+
before do
|
701
|
+
@machine = Klass.state_fu_machine do
|
702
|
+
event( :go, :from => :a, :to => :b ) do
|
703
|
+
requires( :ok? )
|
704
|
+
end
|
705
|
+
|
706
|
+
initial_state :a
|
707
|
+
end
|
708
|
+
Klass.class_eval do
|
709
|
+
attr_accessor :ok
|
710
|
+
def ok?; ok; end
|
711
|
+
end
|
712
|
+
@obj = Klass.new
|
713
|
+
@binding = @obj.state_fu
|
714
|
+
@event = @machine.events[:go]
|
715
|
+
@a = @machine.states[:a]
|
716
|
+
@b = @machine.states[:b]
|
717
|
+
# stub(@obj).ok? { true }
|
718
|
+
end
|
719
|
+
|
720
|
+
describe "when no block is supplied for the requirement" do
|
721
|
+
|
722
|
+
it "should have an event named :go" do
|
723
|
+
@machine.events[:go].requirements.should == [:ok?]
|
724
|
+
@machine.events[:go].targets.should_not be_blank
|
725
|
+
@machine.events[:go].origins.should_not be_blank
|
726
|
+
@machine.states.map(&:name).sort_by(&:to_s).should == [:a, :b]
|
727
|
+
@a.should be_kind_of( StateFu::State )
|
728
|
+
@event.should be_kind_of( StateFu::Event )
|
729
|
+
@event.origins.map(&:name).should == [:a]
|
730
|
+
@binding.current_state.should == @machine.states[:a]
|
731
|
+
@event.from?( @machine.states[:a] ).should be_true
|
732
|
+
@machine.events[:go].from?( @binding.current_state ).should be_true
|
733
|
+
@binding.events.should_not be_empty
|
734
|
+
end
|
735
|
+
|
736
|
+
|
737
|
+
it "should contain the event in @binding.valid_events if @obj.ok? is true" do
|
738
|
+
# stub( @binding ).ok?() { true }
|
739
|
+
# set_method_arity(@binding,:ok, 0)
|
740
|
+
@obj.ok = true
|
741
|
+
@binding.current_state.should == @machine.initial_state
|
742
|
+
@binding.events.should == @machine.events
|
743
|
+
@binding.valid_events.should == [@event]
|
744
|
+
end
|
745
|
+
|
746
|
+
it "should not contain :go in @binding.valid_events if !@obj.ok?" do
|
747
|
+
# stub( @binding ).ok?() { false }
|
748
|
+
@obj.ok = false
|
749
|
+
@binding.events.should == @machine.events
|
750
|
+
@binding.valid_events.should == []
|
751
|
+
end
|
752
|
+
|
753
|
+
it "should raise a RequirementError if requirements are not satisfied" do
|
754
|
+
#stub( @binding ).ok? { false }
|
755
|
+
@obj.ok = false
|
756
|
+
lambda do
|
757
|
+
@obj.state_fu.fire_transition!( :go )
|
758
|
+
end.should raise_error( StateFu::RequirementError )
|
759
|
+
end
|
760
|
+
|
761
|
+
end # no block
|
762
|
+
|
763
|
+
describe "when a block is supplied for the requirement" do
|
764
|
+
|
765
|
+
it "should be a valid event if the block is true " do
|
766
|
+
@machine.named_procs[:ok?] = Proc.new() { true }
|
767
|
+
@binding.valid_events.should == [@event]
|
768
|
+
|
769
|
+
@machine.named_procs[:ok?] = Proc.new() { |binding| true }
|
770
|
+
@binding.valid_events.should == [@event]
|
771
|
+
|
772
|
+
end
|
773
|
+
|
774
|
+
it "should not be a valid event if the block is false" do
|
775
|
+
@machine.named_procs[:ok?] = Proc.new() { false }
|
776
|
+
@binding.valid_events.should == []
|
777
|
+
|
778
|
+
@machine.named_procs[:ok?] = Proc.new() { |binding| false }
|
779
|
+
@binding.valid_events.should == []
|
780
|
+
end
|
781
|
+
|
782
|
+
end # block supplied
|
783
|
+
|
784
|
+
end # machine w/guard conditions
|
785
|
+
|
786
|
+
describe "A binding for a machine with a state transition requirement" do
|
787
|
+
before do
|
788
|
+
@machine = Klass.state_fu_machine do
|
789
|
+
event( :go, :from => :a, :to => :b )
|
790
|
+
state( :b ) do
|
791
|
+
requires :entry_ok?
|
792
|
+
end
|
793
|
+
end
|
794
|
+
Klass.class_eval do
|
795
|
+
attr_accessor :entry_ok
|
796
|
+
def entry_ok?
|
797
|
+
entry_ok
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
@obj = Klass.new
|
802
|
+
@binding = @obj.state_fu
|
803
|
+
@obj.entry_ok = true
|
804
|
+
@event = @machine.events[:go]
|
805
|
+
@a = @machine.states[:a]
|
806
|
+
@b = @machine.states[:b]
|
807
|
+
end
|
808
|
+
|
809
|
+
describe "when no block is supplied for the requirement" do
|
810
|
+
|
811
|
+
it "should be valid if @binding.valid_transitions' values includes the state" do
|
812
|
+
t = @binding.transition([@event, @b])
|
813
|
+
@binding.valid_next_states.should == [@b]
|
814
|
+
end
|
815
|
+
|
816
|
+
it "should be invalid if @obj.entry_ok? is false" do
|
817
|
+
#mock( @obj ).entry_ok? { false }
|
818
|
+
@obj.entry_ok = false
|
819
|
+
@b.entry_requirements.should == [:entry_ok?]
|
820
|
+
@binding.valid_next_states.should == []
|
821
|
+
end
|
822
|
+
|
823
|
+
it "should be valid if @obj.entry_ok? is true" do
|
824
|
+
# mock( @obj ).entry_ok? { true }
|
825
|
+
@obj.entry_ok = true
|
826
|
+
@binding.valid_next_states.should == [@b]
|
827
|
+
end
|
828
|
+
|
829
|
+
end # no block
|
830
|
+
|
831
|
+
describe "when a block is supplied for the requirement" do
|
832
|
+
|
833
|
+
it "should be a valid event if the block is true " do
|
834
|
+
@machine.named_procs[:entry_ok?] = Proc.new() { true }
|
835
|
+
@binding.valid_next_states.should == [@b]
|
836
|
+
|
837
|
+
@machine.named_procs[:entry_ok?] = Proc.new() { |binding| true }
|
838
|
+
@binding.valid_next_states.should == [@b]
|
839
|
+
end
|
840
|
+
|
841
|
+
it "should not be a valid event if the block is false" do
|
842
|
+
@machine.named_procs[:entry_ok?] = Proc.new() { false }
|
843
|
+
@binding.valid_next_states.should == []
|
844
|
+
|
845
|
+
@machine.named_procs[:entry_ok?] = Proc.new() { |binding| false }
|
846
|
+
@binding.valid_next_states.should == []
|
847
|
+
end
|
848
|
+
|
849
|
+
end # block supplied
|
850
|
+
end # machine with state transition requirement
|
851
|
+
|
852
|
+
describe "a hook method accessing the transition, object, binding and arguments to fire!" do
|
853
|
+
before do
|
854
|
+
reset!
|
855
|
+
make_pristine_class("Klass")
|
856
|
+
@machine = Klass.state_fu_machine do
|
857
|
+
event(:run, :from => :start, :to => :finish ) do
|
858
|
+
execute( :run_exec )
|
859
|
+
end
|
860
|
+
end # machine
|
861
|
+
@obj = Klass.new()
|
862
|
+
end # before
|
863
|
+
|
864
|
+
describe "a method defined on the stateful object" do
|
865
|
+
|
866
|
+
it "should be able to conditionally execute code based on whether the transition is a test" do
|
867
|
+
pending
|
868
|
+
testing = nil
|
869
|
+
@obj.__define_singleton_method(:run_exec) do
|
870
|
+
testing = t.testing?
|
871
|
+
end
|
872
|
+
@obj.state_fu.fire! :run do |t|
|
873
|
+
t.test_only = true
|
874
|
+
end
|
875
|
+
testing.should == true
|
876
|
+
end
|
877
|
+
|
878
|
+
it "should be able to call methods on the transition mixed in via machine.helper" do
|
879
|
+
t1 = @obj.state_fu.transition( :run)
|
880
|
+
t1.should_not respond_to(:my_rad_method)
|
881
|
+
|
882
|
+
@machine.helper :my_rad_helper
|
883
|
+
module ::MyRadHelper
|
884
|
+
def my_rad_method( x )
|
885
|
+
x
|
886
|
+
end
|
887
|
+
end
|
888
|
+
t2 = @obj.state_fu.transition( :run )
|
889
|
+
t2.should respond_to( :my_rad_method )
|
890
|
+
t2.my_rad_method( 6 ).should == 6
|
891
|
+
|
892
|
+
@machine.instance_eval do
|
893
|
+
helpers.pop
|
894
|
+
end
|
895
|
+
t3 = @obj.state_fu.transition( :run )
|
896
|
+
|
897
|
+
# triple check for contamination
|
898
|
+
t1.should_not respond_to(:my_rad_method)
|
899
|
+
t2.should respond_to(:my_rad_method)
|
900
|
+
t3.should_not respond_to(:my_rad_method)
|
901
|
+
end
|
902
|
+
|
903
|
+
it "should be able to access the args / options passed to fire! via transition.args" do
|
904
|
+
pending
|
905
|
+
# NOTE a trailing hash gets munged into options - not args
|
906
|
+
args = [:a, :b, { 'c' => :d }]
|
907
|
+
@obj.__define_singleton_method(:run_exec) do
|
908
|
+
t.args.should == [:a, :b,{'c' => :d}]
|
909
|
+
t.options.should == {}
|
910
|
+
end
|
911
|
+
trans = @obj.state_fu.fire!( :run, *args )
|
912
|
+
trans.should be_accepted
|
913
|
+
end
|
914
|
+
end # method defined on object
|
915
|
+
|
916
|
+
describe "a block passed to binding.transition" do
|
917
|
+
it "should execute in the context of the transition initializer after it's set up" do
|
918
|
+
pending
|
919
|
+
@obj.__define_singleton_method(:run_exec) do
|
920
|
+
t.args.should == ['who','yo','daddy?']
|
921
|
+
t.options.should == {:hi => :mum}
|
922
|
+
end
|
923
|
+
trans = @obj.state_fu.transition( :run ) do
|
924
|
+
@args = %w/ who yo daddy? /
|
925
|
+
@options = {:hi => :mum}
|
926
|
+
|
927
|
+
end
|
928
|
+
trans.fire!()
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
end # args with fire!
|
933
|
+
|
934
|
+
describe "next_transition" do
|
935
|
+
describe "when there are multiple events but only one is fireable?" do
|
936
|
+
before do
|
937
|
+
pending
|
938
|
+
reset!
|
939
|
+
make_pristine_class("Klass")
|
940
|
+
@machine = Klass.state_fu_machine do
|
941
|
+
initial_state :alive do
|
942
|
+
event :impossibility do
|
943
|
+
to :afterlife
|
944
|
+
requires :truth_of_patent_falsehoods? do
|
945
|
+
false
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
949
|
+
event :inevitability do
|
950
|
+
to :plain_old_dead
|
951
|
+
end
|
952
|
+
end
|
953
|
+
end
|
954
|
+
@obj = Klass.new()
|
955
|
+
@binding = @obj.state_fu
|
956
|
+
@binding.events.length.should == 2
|
957
|
+
#@machine.events[:impossibility].fireable_by?( @binding ).should == false
|
958
|
+
#@machine.events[:inevitability].fireable_by?( @binding ).should == true
|
959
|
+
end
|
960
|
+
|
961
|
+
describe "when the fireable? event has only one target" do
|
962
|
+
it "should return a transition for the fireable event & its target" do
|
963
|
+
@machine.events[:inevitability].targets.length.should == 1
|
964
|
+
t = @binding.next_transition
|
965
|
+
t.should be_kind_of( StateFu::Transition )
|
966
|
+
t.from.should == @binding.current_state
|
967
|
+
t.to.should == @machine.states[:plain_old_dead]
|
968
|
+
t.event.should == @machine.events[:inevitability]
|
969
|
+
end
|
970
|
+
end
|
971
|
+
|
972
|
+
describe "when the fireable? event has multiple targets but only one can be entered" do
|
973
|
+
before do
|
974
|
+
reset!
|
975
|
+
make_pristine_class("Klass")
|
976
|
+
@machine = Klass.state_fu_machine do
|
977
|
+
initial_state :alive
|
978
|
+
|
979
|
+
state :cremated
|
980
|
+
|
981
|
+
state :buried do
|
982
|
+
requires :plot_at_cemetary? do
|
983
|
+
false
|
984
|
+
end
|
985
|
+
end
|
986
|
+
|
987
|
+
event :inevitability do
|
988
|
+
from :alive
|
989
|
+
to :cremated, :buried
|
990
|
+
end
|
991
|
+
end
|
992
|
+
@obj = Klass.new()
|
993
|
+
@binding = @obj.state_fu
|
994
|
+
@machine.events[:inevitability].should be_kind_of(StateFu::Event)
|
995
|
+
@binding.valid_events.map(&:name).should == [@machine.events[:inevitability]].map(&:name)
|
996
|
+
@binding.valid_events.should == [@machine.events[:inevitability]]
|
997
|
+
@binding.valid_transitions.map(&:target).map(&:name).should == [:cremated]
|
998
|
+
end # before
|
999
|
+
|
1000
|
+
it "should return a transition for the fireable event & the enterable target" do
|
1001
|
+
t = @binding.next_transition
|
1002
|
+
t.should be_kind_of( StateFu::Transition )
|
1003
|
+
t.from.should == @binding.current_state
|
1004
|
+
t.to.should == @machine.states[:cremated]
|
1005
|
+
t.event.should == @machine.events[:inevitability]
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
describe "when the fireable? event has multiple targets and more than one can be entered" do
|
1010
|
+
before do
|
1011
|
+
@machine.lathe do
|
1012
|
+
event :inevitability do
|
1013
|
+
to :cremated, :buried
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
@obj = Klass.new()
|
1017
|
+
@binding = @obj.state_fu
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
it "should not return a transition" do
|
1021
|
+
t = @binding.next_transition
|
1022
|
+
t.should be_nil
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
it "should raise an IllegalTransition if next! is called" do
|
1026
|
+
lambda { @binding.next! }.should raise_error( StateFu::IllegalTransition )
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
end
|
1033
|
+
|