state-fu 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|