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