joelind-state_machine 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +297 -0
- data/LICENSE +20 -0
- data/README.rdoc +466 -0
- data/Rakefile +98 -0
- data/examples/AutoShop_state.png +0 -0
- data/examples/Car_state.png +0 -0
- data/examples/TrafficLight_state.png +0 -0
- data/examples/Vehicle_state.png +0 -0
- data/examples/auto_shop.rb +11 -0
- data/examples/car.rb +19 -0
- data/examples/merb-rest/controller.rb +51 -0
- data/examples/merb-rest/model.rb +28 -0
- data/examples/merb-rest/view_edit.html.erb +24 -0
- data/examples/merb-rest/view_index.html.erb +23 -0
- data/examples/merb-rest/view_new.html.erb +13 -0
- data/examples/merb-rest/view_show.html.erb +17 -0
- data/examples/rails-rest/controller.rb +43 -0
- data/examples/rails-rest/migration.rb +11 -0
- data/examples/rails-rest/model.rb +23 -0
- data/examples/rails-rest/view_edit.html.erb +25 -0
- data/examples/rails-rest/view_index.html.erb +23 -0
- data/examples/rails-rest/view_new.html.erb +14 -0
- data/examples/rails-rest/view_show.html.erb +17 -0
- data/examples/traffic_light.rb +7 -0
- data/examples/vehicle.rb +31 -0
- data/init.rb +1 -0
- data/lib/state_machine.rb +388 -0
- data/lib/state_machine/assertions.rb +36 -0
- data/lib/state_machine/callback.rb +189 -0
- data/lib/state_machine/condition_proxy.rb +94 -0
- data/lib/state_machine/eval_helpers.rb +67 -0
- data/lib/state_machine/event.rb +252 -0
- data/lib/state_machine/event_collection.rb +122 -0
- data/lib/state_machine/extensions.rb +149 -0
- data/lib/state_machine/guard.rb +230 -0
- data/lib/state_machine/integrations.rb +68 -0
- data/lib/state_machine/integrations/active_record.rb +492 -0
- data/lib/state_machine/integrations/active_record/locale.rb +11 -0
- data/lib/state_machine/integrations/active_record/observer.rb +41 -0
- data/lib/state_machine/integrations/data_mapper.rb +351 -0
- data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
- data/lib/state_machine/integrations/sequel.rb +322 -0
- data/lib/state_machine/machine.rb +1467 -0
- data/lib/state_machine/machine_collection.rb +155 -0
- data/lib/state_machine/matcher.rb +123 -0
- data/lib/state_machine/matcher_helpers.rb +54 -0
- data/lib/state_machine/node_collection.rb +152 -0
- data/lib/state_machine/state.rb +249 -0
- data/lib/state_machine/state_collection.rb +112 -0
- data/lib/state_machine/transition.rb +394 -0
- data/tasks/state_machine.rake +1 -0
- data/tasks/state_machine.rb +30 -0
- data/test/classes/switch.rb +11 -0
- data/test/functional/state_machine_test.rb +941 -0
- data/test/test_helper.rb +4 -0
- data/test/unit/assertions_test.rb +40 -0
- data/test/unit/callback_test.rb +455 -0
- data/test/unit/condition_proxy_test.rb +328 -0
- data/test/unit/eval_helpers_test.rb +120 -0
- data/test/unit/event_collection_test.rb +326 -0
- data/test/unit/event_test.rb +743 -0
- data/test/unit/guard_test.rb +908 -0
- data/test/unit/integrations/active_record_test.rb +1374 -0
- data/test/unit/integrations/data_mapper_test.rb +962 -0
- data/test/unit/integrations/sequel_test.rb +859 -0
- data/test/unit/integrations_test.rb +42 -0
- data/test/unit/invalid_event_test.rb +7 -0
- data/test/unit/invalid_transition_test.rb +7 -0
- data/test/unit/machine_collection_test.rb +938 -0
- data/test/unit/machine_test.rb +2004 -0
- data/test/unit/matcher_helpers_test.rb +37 -0
- data/test/unit/matcher_test.rb +155 -0
- data/test/unit/node_collection_test.rb +207 -0
- data/test/unit/state_collection_test.rb +280 -0
- data/test/unit/state_machine_test.rb +31 -0
- data/test/unit/state_test.rb +795 -0
- data/test/unit/transition_test.rb +1212 -0
- metadata +163 -0
@@ -0,0 +1,743 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class EventByDefaultTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@klass = Class.new
|
6
|
+
@machine = StateMachine::Machine.new(@klass)
|
7
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
8
|
+
|
9
|
+
@object = @klass.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_have_a_machine
|
13
|
+
assert_equal @machine, @event.machine
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_have_a_name
|
17
|
+
assert_equal :ignite, @event.name
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_have_a_qualified_name
|
21
|
+
assert_equal :ignite, @event.qualified_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_not_have_any_guards
|
25
|
+
assert @event.guards.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_should_have_no_known_states
|
29
|
+
assert @event.known_states.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_not_be_able_to_fire
|
33
|
+
assert !@event.can_fire?(@object)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_should_not_have_a_transition
|
37
|
+
assert_nil @event.transition_for(@object)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_should_define_a_predicate
|
41
|
+
assert @object.respond_to?(:can_ignite?)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_should_define_a_transition_accessor
|
45
|
+
assert @object.respond_to?(:ignite_transition)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_should_define_an_action
|
49
|
+
assert @object.respond_to?(:ignite)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_should_define_a_bang_action
|
53
|
+
assert @object.respond_to?(:ignite!)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class EventTest < Test::Unit::TestCase
|
58
|
+
def setup
|
59
|
+
@machine = StateMachine::Machine.new(Class.new)
|
60
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
61
|
+
@event.transition :parked => :idling
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_should_allow_changing_machine
|
65
|
+
new_machine = StateMachine::Machine.new(Class.new)
|
66
|
+
@event.machine = new_machine
|
67
|
+
assert_equal new_machine, @event.machine
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_should_provide_matcher_helpers_during_initialization
|
71
|
+
matchers = []
|
72
|
+
|
73
|
+
@event.instance_eval do
|
74
|
+
matchers = [all, any, same]
|
75
|
+
end
|
76
|
+
|
77
|
+
assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_should_use_pretty_inspect
|
81
|
+
assert_match "#<StateMachine::Event name=:ignite transitions=[:parked => :idling]>", @event.inspect
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class EventWithConflictingHelpersTest < Test::Unit::TestCase
|
86
|
+
def setup
|
87
|
+
@klass = Class.new do
|
88
|
+
def can_ignite?
|
89
|
+
0
|
90
|
+
end
|
91
|
+
|
92
|
+
def ignite_transition
|
93
|
+
0
|
94
|
+
end
|
95
|
+
|
96
|
+
def ignite
|
97
|
+
0
|
98
|
+
end
|
99
|
+
|
100
|
+
def ignite!
|
101
|
+
0
|
102
|
+
end
|
103
|
+
end
|
104
|
+
@machine = StateMachine::Machine.new(@klass)
|
105
|
+
@state = StateMachine::Event.new(@machine, :ignite)
|
106
|
+
@object = @klass.new
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_should_not_redefine_predicate
|
110
|
+
assert_equal 0, @object.can_ignite?
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_should_not_redefine_transition_accessor
|
114
|
+
assert_equal 0, @object.ignite_transition
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_should_not_redefine_action
|
118
|
+
assert_equal 0, @object.ignite
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_should_not_redefine_bang_action
|
122
|
+
assert_equal 0, @object.ignite!
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_should_allow_super_chaining
|
126
|
+
@klass.class_eval do
|
127
|
+
def can_ignite?
|
128
|
+
super ? 1 : 0
|
129
|
+
end
|
130
|
+
|
131
|
+
def ignite_transition
|
132
|
+
super ? 1 : 0
|
133
|
+
end
|
134
|
+
|
135
|
+
def ignite
|
136
|
+
super ? 1 : 0
|
137
|
+
end
|
138
|
+
|
139
|
+
def ignite!
|
140
|
+
begin
|
141
|
+
super
|
142
|
+
1
|
143
|
+
rescue Exception => ex
|
144
|
+
0
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
assert_equal 0, @object.can_ignite?
|
150
|
+
assert_equal 0, @object.ignite_transition
|
151
|
+
assert_equal 0, @object.ignite
|
152
|
+
assert_equal 1, @object.ignite!
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class EventWithNamespaceTest < Test::Unit::TestCase
|
157
|
+
def setup
|
158
|
+
@klass = Class.new
|
159
|
+
@machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
|
160
|
+
@event = StateMachine::Event.new(@machine, :enable)
|
161
|
+
@object = @klass.new
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_should_have_a_name
|
165
|
+
assert_equal :enable, @event.name
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_should_have_a_qualified_name
|
169
|
+
assert_equal :enable_alarm, @event.qualified_name
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_should_namespace_predicate
|
173
|
+
assert @object.respond_to?(:can_enable_alarm?)
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_should_namespace_transition_accessor
|
177
|
+
assert @object.respond_to?(:enable_alarm_transition)
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_should_namespace_action
|
181
|
+
assert @object.respond_to?(:enable_alarm)
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_should_namespace_bang_action
|
185
|
+
assert @object.respond_to?(:enable_alarm!)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class EventTransitionsTest < Test::Unit::TestCase
|
190
|
+
def setup
|
191
|
+
@machine = StateMachine::Machine.new(Class.new)
|
192
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_should_not_raise_exception_if_implicit_option_specified
|
196
|
+
assert_nothing_raised {@event.transition(:invalid => :valid)}
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_should_not_allow_on_option
|
200
|
+
exception = assert_raise(ArgumentError) {@event.transition(:on => :ignite)}
|
201
|
+
assert_equal 'Invalid key(s): on', exception.message
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_should_automatically_set_on_option
|
205
|
+
guard = @event.transition(:to => :idling)
|
206
|
+
assert_instance_of StateMachine::WhitelistMatcher, guard.event_requirement
|
207
|
+
assert_equal [:ignite], guard.event_requirement.values
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_should_not_allow_except_to_option
|
211
|
+
exception = assert_raise(ArgumentError) {@event.transition(:except_to => :parked)}
|
212
|
+
assert_equal 'Invalid key(s): except_to', exception.message
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_should_not_allow_except_on_option
|
216
|
+
exception = assert_raise(ArgumentError) {@event.transition(:except_on => :ignite)}
|
217
|
+
assert_equal 'Invalid key(s): except_on', exception.message
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_should_allow_transitioning_without_a_to_state
|
221
|
+
assert_nothing_raised {@event.transition(:from => :parked)}
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_should_allow_transitioning_without_a_from_state
|
225
|
+
assert_nothing_raised {@event.transition(:to => :idling)}
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_should_allow_except_from_option
|
229
|
+
assert_nothing_raised {@event.transition(:except_from => :idling)}
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_should_allow_transitioning_from_a_single_state
|
233
|
+
assert @event.transition(:parked => :idling)
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_should_allow_transitioning_from_multiple_states
|
237
|
+
assert @event.transition([:parked, :idling] => :idling)
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_should_have_transitions
|
241
|
+
guard = @event.transition(:to => :idling)
|
242
|
+
assert_equal [guard], @event.guards
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class EventAfterBeingCopiedTest < Test::Unit::TestCase
|
247
|
+
def setup
|
248
|
+
@machine = StateMachine::Machine.new(Class.new)
|
249
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
250
|
+
@copied_event = @event.dup
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_should_not_have_the_same_collection_of_guards
|
254
|
+
assert_not_same @event.guards, @copied_event.guards
|
255
|
+
end
|
256
|
+
|
257
|
+
def test_should_not_have_the_same_collection_of_known_states
|
258
|
+
assert_not_same @event.known_states, @copied_event.known_states
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class EventWithoutTransitionsTest < Test::Unit::TestCase
|
263
|
+
def setup
|
264
|
+
@klass = Class.new
|
265
|
+
@machine = StateMachine::Machine.new(@klass)
|
266
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
267
|
+
@object = @klass.new
|
268
|
+
end
|
269
|
+
|
270
|
+
def test_should_not_be_able_to_fire
|
271
|
+
assert !@event.can_fire?(@object)
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_should_not_have_a_transition
|
275
|
+
assert_nil @event.transition_for(@object)
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_should_not_fire
|
279
|
+
assert !@event.fire(@object)
|
280
|
+
end
|
281
|
+
|
282
|
+
def test_should_not_change_the_current_state
|
283
|
+
@event.fire(@object)
|
284
|
+
assert_nil @object.state
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class EventWithTransitionsTest < Test::Unit::TestCase
|
289
|
+
def setup
|
290
|
+
@klass = Class.new
|
291
|
+
@machine = StateMachine::Machine.new(@klass)
|
292
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
293
|
+
@event.transition(:parked => :idling)
|
294
|
+
@event.transition(:first_gear => :idling)
|
295
|
+
end
|
296
|
+
|
297
|
+
def test_should_include_all_transition_states_in_known_states
|
298
|
+
assert_equal [:parked, :idling, :first_gear], @event.known_states
|
299
|
+
end
|
300
|
+
|
301
|
+
def test_should_include_new_transition_states_after_calling_known_states
|
302
|
+
@event.known_states
|
303
|
+
@event.transition(:stalled => :idling)
|
304
|
+
|
305
|
+
assert_equal [:parked, :idling, :first_gear, :stalled], @event.known_states
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_should_use_pretty_inspect
|
309
|
+
assert_match "#<StateMachine::Event name=:ignite transitions=[:parked => :idling, :first_gear => :idling]>", @event.inspect
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class EventWithoutMatchingTransitionsTest < Test::Unit::TestCase
|
314
|
+
def setup
|
315
|
+
@klass = Class.new
|
316
|
+
@machine = StateMachine::Machine.new(@klass)
|
317
|
+
@machine.state :parked, :idling
|
318
|
+
|
319
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
320
|
+
@event.transition(:parked => :idling)
|
321
|
+
|
322
|
+
@object = @klass.new
|
323
|
+
@object.state = 'idling'
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_should_not_be_able_to_fire
|
327
|
+
assert !@event.can_fire?(@object)
|
328
|
+
end
|
329
|
+
|
330
|
+
def test_should_not_have_a_transition
|
331
|
+
assert_nil @event.transition_for(@object)
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_should_not_fire
|
335
|
+
assert !@event.fire(@object)
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_should_not_change_the_current_state
|
339
|
+
@event.fire(@object)
|
340
|
+
assert_equal 'idling', @object.state
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase
|
345
|
+
def setup
|
346
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
347
|
+
def invalidate(object, attribute, message, values = [])
|
348
|
+
(object.errors ||= []) << generate_message(message, values)
|
349
|
+
end
|
350
|
+
|
351
|
+
def reset(object)
|
352
|
+
object.errors = []
|
353
|
+
end
|
354
|
+
end)
|
355
|
+
|
356
|
+
@klass = Class.new do
|
357
|
+
attr_accessor :errors
|
358
|
+
end
|
359
|
+
|
360
|
+
@machine = StateMachine::Machine.new(@klass, :integration => :custom)
|
361
|
+
@machine.state :parked, :idling
|
362
|
+
|
363
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
364
|
+
@event.transition(:parked => :idling, :if => lambda {false})
|
365
|
+
|
366
|
+
@object = @klass.new
|
367
|
+
@object.state = 'parked'
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_should_not_be_able_to_fire
|
371
|
+
assert !@event.can_fire?(@object)
|
372
|
+
end
|
373
|
+
|
374
|
+
def test_should_not_have_a_transition
|
375
|
+
assert_nil @event.transition_for(@object)
|
376
|
+
end
|
377
|
+
|
378
|
+
def test_should_not_fire
|
379
|
+
assert !@event.fire(@object)
|
380
|
+
end
|
381
|
+
|
382
|
+
def test_should_not_change_the_current_state
|
383
|
+
@event.fire(@object)
|
384
|
+
assert_equal 'parked', @object.state
|
385
|
+
end
|
386
|
+
|
387
|
+
def test_should_invalidate_the_state
|
388
|
+
@event.fire(@object)
|
389
|
+
assert_equal ['cannot transition via "ignite"'], @object.errors
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_should_reset_existing_error
|
393
|
+
@object.errors = ['invalid']
|
394
|
+
|
395
|
+
@event.fire(@object)
|
396
|
+
assert_equal ['cannot transition via "ignite"'], @object.errors
|
397
|
+
end
|
398
|
+
|
399
|
+
def teardown
|
400
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
class EventWithMatchingEnabledTransitionsTest < Test::Unit::TestCase
|
405
|
+
def setup
|
406
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
407
|
+
def invalidate(object, attribute, message, values = [])
|
408
|
+
(object.errors ||= []) << generate_message(message, values)
|
409
|
+
end
|
410
|
+
|
411
|
+
def reset(object)
|
412
|
+
object.errors = []
|
413
|
+
end
|
414
|
+
end)
|
415
|
+
|
416
|
+
@klass = Class.new do
|
417
|
+
attr_accessor :errors
|
418
|
+
end
|
419
|
+
|
420
|
+
@machine = StateMachine::Machine.new(@klass, :integration => :custom)
|
421
|
+
@machine.state :parked, :idling
|
422
|
+
@machine.event :ignite
|
423
|
+
|
424
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
425
|
+
@event.transition(:parked => :idling)
|
426
|
+
|
427
|
+
@object = @klass.new
|
428
|
+
@object.state = 'parked'
|
429
|
+
end
|
430
|
+
|
431
|
+
def test_should_be_able_to_fire
|
432
|
+
assert @event.can_fire?(@object)
|
433
|
+
end
|
434
|
+
|
435
|
+
def test_should_have_a_transition
|
436
|
+
transition = @event.transition_for(@object)
|
437
|
+
assert_not_nil transition
|
438
|
+
assert_equal 'parked', transition.from
|
439
|
+
assert_equal 'idling', transition.to
|
440
|
+
assert_equal :ignite, transition.event
|
441
|
+
end
|
442
|
+
|
443
|
+
def test_should_fire
|
444
|
+
assert @event.fire(@object)
|
445
|
+
end
|
446
|
+
|
447
|
+
def test_should_change_the_current_state
|
448
|
+
@event.fire(@object)
|
449
|
+
assert_equal 'idling', @object.state
|
450
|
+
end
|
451
|
+
|
452
|
+
def test_should_reset_existing_error
|
453
|
+
@object.errors = ['invalid']
|
454
|
+
|
455
|
+
@event.fire(@object)
|
456
|
+
assert_equal [], @object.errors
|
457
|
+
end
|
458
|
+
|
459
|
+
def test_should_not_invalidate_the_state
|
460
|
+
@event.fire(@object)
|
461
|
+
assert_equal [], @object.errors
|
462
|
+
end
|
463
|
+
|
464
|
+
def teardown
|
465
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
class EventWithTransitionWithoutToStateTest < Test::Unit::TestCase
|
470
|
+
def setup
|
471
|
+
@klass = Class.new
|
472
|
+
@machine = StateMachine::Machine.new(@klass)
|
473
|
+
@machine.state :parked
|
474
|
+
@machine.event :park
|
475
|
+
|
476
|
+
@event = StateMachine::Event.new(@machine, :park)
|
477
|
+
@event.transition(:from => :parked)
|
478
|
+
|
479
|
+
@object = @klass.new
|
480
|
+
@object.state = 'parked'
|
481
|
+
end
|
482
|
+
|
483
|
+
def test_should_be_able_to_fire
|
484
|
+
assert @event.can_fire?(@object)
|
485
|
+
end
|
486
|
+
|
487
|
+
def test_should_have_a_transition
|
488
|
+
transition = @event.transition_for(@object)
|
489
|
+
assert_not_nil transition
|
490
|
+
assert_equal 'parked', transition.from
|
491
|
+
assert_equal 'parked', transition.to
|
492
|
+
assert_equal :park, transition.event
|
493
|
+
end
|
494
|
+
|
495
|
+
def test_should_fire
|
496
|
+
assert @event.fire(@object)
|
497
|
+
end
|
498
|
+
|
499
|
+
def test_should_not_change_the_current_state
|
500
|
+
@event.fire(@object)
|
501
|
+
assert_equal 'parked', @object.state
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
class EventWithTransitionWithNilToStateTest < Test::Unit::TestCase
|
506
|
+
def setup
|
507
|
+
@klass = Class.new
|
508
|
+
@machine = StateMachine::Machine.new(@klass)
|
509
|
+
@machine.state nil, :idling
|
510
|
+
@machine.event :park
|
511
|
+
|
512
|
+
@event = StateMachine::Event.new(@machine, :park)
|
513
|
+
@event.transition(:idling => nil)
|
514
|
+
|
515
|
+
@object = @klass.new
|
516
|
+
@object.state = 'idling'
|
517
|
+
end
|
518
|
+
|
519
|
+
def test_should_be_able_to_fire
|
520
|
+
assert @event.can_fire?(@object)
|
521
|
+
end
|
522
|
+
|
523
|
+
def test_should_have_a_transition
|
524
|
+
transition = @event.transition_for(@object)
|
525
|
+
assert_not_nil transition
|
526
|
+
assert_equal 'idling', transition.from
|
527
|
+
assert_equal nil, transition.to
|
528
|
+
assert_equal :park, transition.event
|
529
|
+
end
|
530
|
+
|
531
|
+
def test_should_fire
|
532
|
+
assert @event.fire(@object)
|
533
|
+
end
|
534
|
+
|
535
|
+
def test_should_not_change_the_current_state
|
536
|
+
@event.fire(@object)
|
537
|
+
assert_equal nil, @object.state
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
class EventWithMultipleTransitionsTest < Test::Unit::TestCase
|
542
|
+
def setup
|
543
|
+
@klass = Class.new
|
544
|
+
@machine = StateMachine::Machine.new(@klass)
|
545
|
+
@machine.state :parked, :idling
|
546
|
+
@machine.event :ignite
|
547
|
+
|
548
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
549
|
+
@event.transition(:idling => :idling)
|
550
|
+
@event.transition(:parked => :idling) # This one should get used
|
551
|
+
@event.transition(:parked => :parked)
|
552
|
+
|
553
|
+
@object = @klass.new
|
554
|
+
@object.state = 'parked'
|
555
|
+
end
|
556
|
+
|
557
|
+
def test_should_be_able_to_fire
|
558
|
+
assert @event.can_fire?(@object)
|
559
|
+
end
|
560
|
+
|
561
|
+
def test_should_have_a_transition
|
562
|
+
transition = @event.transition_for(@object)
|
563
|
+
assert_not_nil transition
|
564
|
+
assert_equal 'parked', transition.from
|
565
|
+
assert_equal 'idling', transition.to
|
566
|
+
assert_equal :ignite, transition.event
|
567
|
+
end
|
568
|
+
|
569
|
+
def test_should_allow_specific_transition_selection_using_from
|
570
|
+
transition = @event.transition_for(@object, :from => :idling)
|
571
|
+
|
572
|
+
assert_not_nil transition
|
573
|
+
assert_equal 'idling', transition.from
|
574
|
+
assert_equal 'idling', transition.to
|
575
|
+
assert_equal :ignite, transition.event
|
576
|
+
end
|
577
|
+
|
578
|
+
def test_should_allow_specific_transition_selection_using_to
|
579
|
+
transition = @event.transition_for(@object, :from => :parked, :to => :parked)
|
580
|
+
|
581
|
+
assert_not_nil transition
|
582
|
+
assert_equal 'parked', transition.from
|
583
|
+
assert_equal 'parked', transition.to
|
584
|
+
assert_equal :ignite, transition.event
|
585
|
+
end
|
586
|
+
|
587
|
+
def test_should_allow_specific_transition_selection_using_on
|
588
|
+
transition = @event.transition_for(@object, :on => :park)
|
589
|
+
assert_nil transition
|
590
|
+
|
591
|
+
transition = @event.transition_for(@object, :on => :ignite)
|
592
|
+
assert_not_nil transition
|
593
|
+
end
|
594
|
+
|
595
|
+
def test_should_fire
|
596
|
+
assert @event.fire(@object)
|
597
|
+
end
|
598
|
+
|
599
|
+
def test_should_change_the_current_state
|
600
|
+
@event.fire(@object)
|
601
|
+
assert_equal 'idling', @object.state
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
class EventWithMachineActionTest < Test::Unit::TestCase
|
606
|
+
def setup
|
607
|
+
@klass = Class.new do
|
608
|
+
attr_reader :saved
|
609
|
+
|
610
|
+
def save
|
611
|
+
@saved = true
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
@machine = StateMachine::Machine.new(@klass, :action => :save)
|
616
|
+
@machine.state :parked, :idling
|
617
|
+
|
618
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
619
|
+
@event.transition(:parked => :idling)
|
620
|
+
|
621
|
+
@object = @klass.new
|
622
|
+
@object.state = 'parked'
|
623
|
+
end
|
624
|
+
|
625
|
+
def test_should_run_action_on_fire
|
626
|
+
@event.fire(@object)
|
627
|
+
assert @object.saved
|
628
|
+
end
|
629
|
+
|
630
|
+
def test_should_not_run_action_if_configured_to_skip
|
631
|
+
@event.fire(@object, false)
|
632
|
+
assert !@object.saved
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
class EventWithInvalidCurrentStateTest < Test::Unit::TestCase
|
637
|
+
def setup
|
638
|
+
@klass = Class.new
|
639
|
+
@machine = StateMachine::Machine.new(@klass)
|
640
|
+
@machine.state :parked, :idling
|
641
|
+
@machine.event :ignite
|
642
|
+
|
643
|
+
@event = StateMachine::Event.new(@machine, :ignite)
|
644
|
+
@event.transition(:parked => :idling)
|
645
|
+
|
646
|
+
@object = @klass.new
|
647
|
+
@object.state = 'invalid'
|
648
|
+
end
|
649
|
+
|
650
|
+
def test_should_raise_exception_when_checking_availability
|
651
|
+
exception = assert_raise(ArgumentError) { @event.can_fire?(@object) }
|
652
|
+
assert_equal '"invalid" is not a known state value', exception.message
|
653
|
+
end
|
654
|
+
|
655
|
+
def test_should_raise_exception_when_finding_transition
|
656
|
+
exception = assert_raise(ArgumentError) { @event.transition_for(@object) }
|
657
|
+
assert_equal '"invalid" is not a known state value', exception.message
|
658
|
+
end
|
659
|
+
|
660
|
+
def test_should_raise_exception_when_firing
|
661
|
+
exception = assert_raise(ArgumentError) { @event.fire(@object) }
|
662
|
+
assert_equal '"invalid" is not a known state value', exception.message
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
class EventWithMarshallingTest < Test::Unit::TestCase
|
667
|
+
def setup
|
668
|
+
@klass = Class.new do
|
669
|
+
def save
|
670
|
+
true
|
671
|
+
end
|
672
|
+
end
|
673
|
+
self.class.const_set('Example', @klass)
|
674
|
+
|
675
|
+
@machine = StateMachine::Machine.new(@klass, :action => :save)
|
676
|
+
@machine.state :parked, :idling
|
677
|
+
|
678
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
679
|
+
@event.transition(:parked => :idling)
|
680
|
+
|
681
|
+
@object = @klass.new
|
682
|
+
@object.state = 'parked'
|
683
|
+
end
|
684
|
+
|
685
|
+
def test_should_marshal_during_before_callbacks
|
686
|
+
@machine.before_transition {|object, transition| Marshal.dump(object)}
|
687
|
+
assert_nothing_raised { @event.fire(@object) }
|
688
|
+
end
|
689
|
+
|
690
|
+
def test_should_marshal_during_action
|
691
|
+
@klass.class_eval do
|
692
|
+
def save
|
693
|
+
Marshal.dump(self)
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
assert_nothing_raised { @event.fire(@object) }
|
698
|
+
end
|
699
|
+
|
700
|
+
def test_should_marshal_during_after_callbacks
|
701
|
+
@machine.after_transition {|object, transition| Marshal.dump(object)}
|
702
|
+
assert_nothing_raised { @event.fire(@object) }
|
703
|
+
end
|
704
|
+
|
705
|
+
def teardown
|
706
|
+
self.class.send(:remove_const, 'Example')
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
begin
|
711
|
+
# Load library
|
712
|
+
require 'rubygems'
|
713
|
+
require 'graphviz'
|
714
|
+
|
715
|
+
class EventDrawingTest < Test::Unit::TestCase
|
716
|
+
def setup
|
717
|
+
states = [:parked, :idling, :first_gear]
|
718
|
+
|
719
|
+
@machine = StateMachine::Machine.new(Class.new, :initial => :parked)
|
720
|
+
@machine.other_states(*states)
|
721
|
+
|
722
|
+
graph = GraphViz.new('G')
|
723
|
+
states.each {|state| graph.add_node(state.to_s)}
|
724
|
+
|
725
|
+
@event = StateMachine::Event.new(@machine , :park)
|
726
|
+
@event.transition :parked => :idling
|
727
|
+
@event.transition :first_gear => :parked
|
728
|
+
@event.transition :except_from => :parked, :to => :parked
|
729
|
+
|
730
|
+
@edges = @event.draw(graph)
|
731
|
+
end
|
732
|
+
|
733
|
+
def test_should_generate_edges_for_each_transition
|
734
|
+
assert_equal 4, @edges.size
|
735
|
+
end
|
736
|
+
|
737
|
+
def test_should_use_event_name_for_edge_label
|
738
|
+
assert_equal 'park', @edges.first['label']
|
739
|
+
end
|
740
|
+
end
|
741
|
+
rescue LoadError
|
742
|
+
$stderr.puts 'Skipping GraphViz StateMachine::Event tests. `gem install ruby-graphviz` and try again.'
|
743
|
+
end
|