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.
Files changed (78) hide show
  1. data/CHANGELOG.rdoc +297 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +466 -0
  4. data/Rakefile +98 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine.rb +388 -0
  28. data/lib/state_machine/assertions.rb +36 -0
  29. data/lib/state_machine/callback.rb +189 -0
  30. data/lib/state_machine/condition_proxy.rb +94 -0
  31. data/lib/state_machine/eval_helpers.rb +67 -0
  32. data/lib/state_machine/event.rb +252 -0
  33. data/lib/state_machine/event_collection.rb +122 -0
  34. data/lib/state_machine/extensions.rb +149 -0
  35. data/lib/state_machine/guard.rb +230 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +492 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +351 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +322 -0
  43. data/lib/state_machine/machine.rb +1467 -0
  44. data/lib/state_machine/machine_collection.rb +155 -0
  45. data/lib/state_machine/matcher.rb +123 -0
  46. data/lib/state_machine/matcher_helpers.rb +54 -0
  47. data/lib/state_machine/node_collection.rb +152 -0
  48. data/lib/state_machine/state.rb +249 -0
  49. data/lib/state_machine/state_collection.rb +112 -0
  50. data/lib/state_machine/transition.rb +394 -0
  51. data/tasks/state_machine.rake +1 -0
  52. data/tasks/state_machine.rb +30 -0
  53. data/test/classes/switch.rb +11 -0
  54. data/test/functional/state_machine_test.rb +941 -0
  55. data/test/test_helper.rb +4 -0
  56. data/test/unit/assertions_test.rb +40 -0
  57. data/test/unit/callback_test.rb +455 -0
  58. data/test/unit/condition_proxy_test.rb +328 -0
  59. data/test/unit/eval_helpers_test.rb +120 -0
  60. data/test/unit/event_collection_test.rb +326 -0
  61. data/test/unit/event_test.rb +743 -0
  62. data/test/unit/guard_test.rb +908 -0
  63. data/test/unit/integrations/active_record_test.rb +1374 -0
  64. data/test/unit/integrations/data_mapper_test.rb +962 -0
  65. data/test/unit/integrations/sequel_test.rb +859 -0
  66. data/test/unit/integrations_test.rb +42 -0
  67. data/test/unit/invalid_event_test.rb +7 -0
  68. data/test/unit/invalid_transition_test.rb +7 -0
  69. data/test/unit/machine_collection_test.rb +938 -0
  70. data/test/unit/machine_test.rb +2004 -0
  71. data/test/unit/matcher_helpers_test.rb +37 -0
  72. data/test/unit/matcher_test.rb +155 -0
  73. data/test/unit/node_collection_test.rb +207 -0
  74. data/test/unit/state_collection_test.rb +280 -0
  75. data/test/unit/state_machine_test.rb +31 -0
  76. data/test/unit/state_test.rb +795 -0
  77. data/test/unit/transition_test.rb +1212 -0
  78. metadata +163 -0
@@ -0,0 +1,1212 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class TransitionTest < Test::Unit::TestCase
4
+ def setup
5
+ @klass = Class.new
6
+ @machine = StateMachine::Machine.new(@klass)
7
+ @machine.state :parked, :idling
8
+ @machine.event :ignite
9
+
10
+ @object = @klass.new
11
+ @object.state = 'parked'
12
+
13
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
14
+ end
15
+
16
+ def test_should_have_an_object
17
+ assert_equal @object, @transition.object
18
+ end
19
+
20
+ def test_should_have_a_machine
21
+ assert_equal @machine, @transition.machine
22
+ end
23
+
24
+ def test_should_have_an_event
25
+ assert_equal :ignite, @transition.event
26
+ end
27
+
28
+ def test_should_have_a_qualified_event
29
+ assert_equal :ignite, @transition.qualified_event
30
+ end
31
+
32
+ def test_should_have_a_from_value
33
+ assert_equal 'parked', @transition.from
34
+ end
35
+
36
+ def test_should_have_a_from_name
37
+ assert_equal :parked, @transition.from_name
38
+ end
39
+
40
+ def test_should_have_a_qualified_from_name
41
+ assert_equal :parked, @transition.qualified_from_name
42
+ end
43
+
44
+ def test_should_have_a_to_value
45
+ assert_equal 'idling', @transition.to
46
+ end
47
+
48
+ def test_should_have_a_to_name
49
+ assert_equal :idling, @transition.to_name
50
+ end
51
+
52
+ def test_should_have_a_qualified_to_name
53
+ assert_equal :idling, @transition.qualified_to_name
54
+ end
55
+
56
+ def test_should_have_an_attribute
57
+ assert_equal :state, @transition.attribute
58
+ end
59
+
60
+ def test_should_not_have_an_action
61
+ assert_nil @transition.action
62
+ end
63
+
64
+ def test_should_generate_attributes
65
+ expected = {:object => @object, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
66
+ assert_equal expected, @transition.attributes
67
+ end
68
+
69
+ def test_should_have_empty_args
70
+ assert_equal [], @transition.args
71
+ end
72
+
73
+ def test_should_not_have_a_result
74
+ assert_nil @transition.result
75
+ end
76
+
77
+ def test_should_use_pretty_inspect
78
+ assert_equal '#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>', @transition.inspect
79
+ end
80
+ end
81
+
82
+ class TransitionWithInvalidNodesTest < Test::Unit::TestCase
83
+ def setup
84
+ @klass = Class.new
85
+ @machine = StateMachine::Machine.new(@klass)
86
+ @machine.state :parked, :idling
87
+ @machine.event :ignite
88
+
89
+ @object = @klass.new
90
+ @object.state = 'parked'
91
+ end
92
+
93
+ def test_should_raise_exception_without_event
94
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, nil, :parked, :idling) }
95
+ end
96
+
97
+ def test_should_raise_exception_with_invalid_event
98
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :invalid, :parked, :idling) }
99
+ end
100
+
101
+ def test_should_raise_exception_with_invalid_from_state
102
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :invalid, :idling) }
103
+ end
104
+
105
+ def test_should_raise_exception_with_invalid_to_state
106
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :parked, :invalid) }
107
+ end
108
+ end
109
+
110
+ class TransitionWithDynamicToValueTest < Test::Unit::TestCase
111
+ def setup
112
+ @klass = Class.new
113
+ @machine = StateMachine::Machine.new(@klass)
114
+ @machine.state :parked
115
+ @machine.state :idling, :value => lambda {1}
116
+ @machine.event :ignite
117
+
118
+ @object = @klass.new
119
+ @object.state = 'parked'
120
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
121
+ end
122
+
123
+ def test_should_evaluate_to_value
124
+ assert_equal 1, @transition.to
125
+ end
126
+ end
127
+
128
+ class TransitionLoopbackTest < Test::Unit::TestCase
129
+ def setup
130
+ @klass = Class.new
131
+ @machine = StateMachine::Machine.new(@klass)
132
+ @machine.state :parked
133
+ @machine.event :park
134
+
135
+ @object = @klass.new
136
+ @object.state = 'parked'
137
+ @transition = StateMachine::Transition.new(@object, @machine, :park, :parked, :parked)
138
+ end
139
+
140
+ def test_should_be_loopback
141
+ assert @transition.loopback?
142
+ end
143
+ end
144
+
145
+ class TransitionWithDifferentStatesTest < Test::Unit::TestCase
146
+ def setup
147
+ @klass = Class.new
148
+ @machine = StateMachine::Machine.new(@klass)
149
+ @machine.state :parked, :idling
150
+ @machine.event :ignite
151
+
152
+ @object = @klass.new
153
+ @object.state = 'parked'
154
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
155
+ end
156
+
157
+ def test_should_not_be_loopback
158
+ assert !@transition.loopback?
159
+ end
160
+ end
161
+
162
+ class TransitionWithNamespaceTest < Test::Unit::TestCase
163
+ def setup
164
+ @klass = Class.new
165
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
166
+ @machine.state :off, :active
167
+ @machine.event :activate
168
+
169
+ @object = @klass.new
170
+ @object.state = 'off'
171
+
172
+ @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active)
173
+ end
174
+
175
+ def test_should_have_an_event
176
+ assert_equal :activate, @transition.event
177
+ end
178
+
179
+ def test_should_have_a_qualified_event
180
+ assert_equal :activate_alarm, @transition.qualified_event
181
+ end
182
+
183
+ def test_should_have_a_from_name
184
+ assert_equal :off, @transition.from_name
185
+ end
186
+
187
+ def test_should_have_a_qualified_from_name
188
+ assert_equal :alarm_off, @transition.qualified_from_name
189
+ end
190
+
191
+ def test_should_have_a_to_name
192
+ assert_equal :active, @transition.to_name
193
+ end
194
+
195
+ def test_should_have_a_qualified_to_name
196
+ assert_equal :alarm_active, @transition.qualified_to_name
197
+ end
198
+ end
199
+
200
+ class TransitionWithCustomMachineAttributeTest < Test::Unit::TestCase
201
+ def setup
202
+ @klass = Class.new
203
+ @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id)
204
+ @machine.state :off, :value => 1
205
+ @machine.state :active, :value => 2
206
+ @machine.event :activate
207
+
208
+ @object = @klass.new
209
+ @object.state_id = 1
210
+
211
+ @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active)
212
+ end
213
+
214
+ def test_should_persist
215
+ @transition.persist
216
+ assert_equal 2, @object.state_id
217
+ end
218
+
219
+ def test_should_rollback
220
+ @object.state_id = 2
221
+ @transition.rollback
222
+
223
+ assert_equal 1, @object.state_id
224
+ end
225
+ end
226
+
227
+ class TransitionWithoutReadingStateTest < Test::Unit::TestCase
228
+ def setup
229
+ @klass = Class.new
230
+ @machine = StateMachine::Machine.new(@klass)
231
+ @machine.state :parked, :idling
232
+ @machine.event :ignite
233
+
234
+ @object = @klass.new
235
+ @object.state = 'idling'
236
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling, false)
237
+ end
238
+
239
+ def test_should_not_read_from_value_from_object
240
+ assert_equal 'parked', @transition.from
241
+ end
242
+
243
+ def test_should_have_to_value
244
+ assert_equal 'idling', @transition.to
245
+ end
246
+ end
247
+
248
+ class TransitionWithActionTest < Test::Unit::TestCase
249
+ def setup
250
+ @klass = Class.new do
251
+ def save
252
+ end
253
+ end
254
+
255
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
256
+ @machine.state :parked, :idling
257
+ @machine.event :ignite
258
+
259
+ @object = @klass.new
260
+ @object.state = 'parked'
261
+
262
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
263
+ end
264
+
265
+ def test_should_have_an_action
266
+ assert_equal :save, @transition.action
267
+ end
268
+
269
+ def test_should_not_have_a_result
270
+ assert_nil @transition.result
271
+ end
272
+ end
273
+
274
+ class TransitionAfterBeingPersistedTest < Test::Unit::TestCase
275
+ def setup
276
+ @klass = Class.new
277
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
278
+ @machine.state :parked, :idling
279
+ @machine.event :ignite
280
+
281
+ @object = @klass.new
282
+ @object.state = 'parked'
283
+
284
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
285
+ @transition.persist
286
+ end
287
+
288
+ def test_should_update_state_value
289
+ assert_equal 'idling', @object.state
290
+ end
291
+
292
+ def test_should_not_change_from_state
293
+ assert_equal 'parked', @transition.from
294
+ end
295
+
296
+ def test_should_not_change_to_state
297
+ assert_equal 'idling', @transition.to
298
+ end
299
+
300
+ def test_should_not_be_able_to_persist_twice
301
+ @object.state = 'parked'
302
+ @transition.persist
303
+ assert_equal 'parked', @object.state
304
+ end
305
+
306
+ def test_should_be_able_to_persist_again_after_resetting
307
+ @object.state = 'parked'
308
+ @transition.reset
309
+ @transition.persist
310
+ assert_equal 'idling', @object.state
311
+ end
312
+
313
+ def test_should_revert_to_from_state_on_rollback
314
+ @transition.rollback
315
+ assert_equal 'parked', @object.state
316
+ end
317
+ end
318
+
319
+ class TransitionAfterBeingRolledBackTest < Test::Unit::TestCase
320
+ def setup
321
+ @klass = Class.new
322
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
323
+ @machine.state :parked, :idling
324
+ @machine.event :ignite
325
+
326
+ @object = @klass.new
327
+ @object.state = 'parked'
328
+
329
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
330
+ @object.state = 'idling'
331
+
332
+ @transition.rollback
333
+ end
334
+
335
+ def test_should_update_state_value_to_from_state
336
+ assert_equal 'parked', @object.state
337
+ end
338
+
339
+ def test_should_not_change_from_state
340
+ assert_equal 'parked', @transition.from
341
+ end
342
+
343
+ def test_should_not_change_to_state
344
+ assert_equal 'idling', @transition.to
345
+ end
346
+
347
+ def test_should_still_be_able_to_persist
348
+ @transition.persist
349
+ assert_equal 'idling', @object.state
350
+ end
351
+ end
352
+
353
+ class TransitionWithCallbacksTest < Test::Unit::TestCase
354
+ def setup
355
+ @klass = Class.new do
356
+ attr_reader :saved, :save_state
357
+
358
+ def save
359
+ @save_state = state
360
+ @saved = true
361
+ end
362
+ end
363
+
364
+ @machine = StateMachine::Machine.new(@klass)
365
+ @machine.state :parked, :idling
366
+ @machine.event :ignite
367
+
368
+ @object = @klass.new
369
+ @object.state = 'parked'
370
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
371
+ end
372
+
373
+ def test_should_run_before_callbacks_on_before
374
+ @machine.before_transition(lambda {|object| @run = true})
375
+ result = @transition.before
376
+
377
+ assert_equal true, result
378
+ assert_equal true, @run
379
+ end
380
+
381
+ def test_should_run_before_callbacks_in_the_order_they_were_defined
382
+ @callbacks = []
383
+ @machine.before_transition(lambda {@callbacks << 1})
384
+ @machine.before_transition(lambda {@callbacks << 2})
385
+ @transition.before
386
+
387
+ assert_equal [1, 2], @callbacks
388
+ end
389
+
390
+ def test_should_only_run_before_callbacks_that_match_transition_context
391
+ @count = 0
392
+ callback = lambda {@count += 1}
393
+
394
+ @machine.before_transition :from => :parked, :to => :idling, :on => :park, :do => callback
395
+ @machine.before_transition :from => :parked, :to => :parked, :on => :park, :do => callback
396
+ @machine.before_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
397
+ @machine.before_transition :from => :idling, :to => :idling, :on => :park, :do => callback
398
+ @transition.before
399
+
400
+ assert_equal 1, @count
401
+ end
402
+
403
+ def test_should_pass_transition_to_before_callbacks
404
+ @machine.before_transition(lambda {|*args| @args = args})
405
+ @transition.before
406
+
407
+ assert_equal [@object, @transition], @args
408
+ end
409
+
410
+ def test_should_catch_halted_before_callbacks
411
+ @machine.before_transition(lambda {throw :halt})
412
+
413
+ result = nil
414
+ assert_nothing_thrown { result = @transition.before }
415
+ assert_equal false, result
416
+ end
417
+
418
+ def test_should_run_before_callbacks_on_perform_before_changing_the_state
419
+ @machine.before_transition(lambda {|object| @state = object.state})
420
+ @transition.perform
421
+
422
+ assert_equal 'parked', @state
423
+ end
424
+
425
+ def test_should_not_be_able_to_run_before_callbacks_twice
426
+ @count = 0
427
+ @machine.before_transition(lambda {@count += 1})
428
+ @transition.before
429
+ @transition.before
430
+ assert_equal 1, @count
431
+ end
432
+
433
+ def test_should_be_able_to_run_before_callbacks_again_after_resetting
434
+ @count = 0
435
+ @machine.before_transition(lambda {@count += 1})
436
+ @transition.before
437
+ @transition.reset
438
+ @transition.before
439
+ assert_equal 2, @count
440
+ end
441
+
442
+ def test_should_run_after_callbacks_on_after
443
+ @machine.after_transition(lambda {|object| @run = true})
444
+ result = @transition.after(true)
445
+
446
+ assert_equal true, result
447
+ assert_equal true, @run
448
+ end
449
+
450
+ def test_should_set_result_on_after
451
+ @transition.after
452
+ assert_nil @transition.result
453
+
454
+ @transition.after(1)
455
+ assert_equal 1, @transition.result
456
+ end
457
+
458
+ def test_should_run_after_callbacks_in_the_order_they_were_defined
459
+ @callbacks = []
460
+ @machine.after_transition(lambda {@callbacks << 1})
461
+ @machine.after_transition(lambda {@callbacks << 2})
462
+ @transition.after(true)
463
+
464
+ assert_equal [1, 2], @callbacks
465
+ end
466
+
467
+ def test_should_only_run_after_callbacks_that_match_transition_context
468
+ @count = 0
469
+ callback = lambda {@count += 1}
470
+
471
+ @machine.after_transition :from => :parked, :to => :idling, :on => :park, :do => callback
472
+ @machine.after_transition :from => :parked, :to => :parked, :on => :park, :do => callback
473
+ @machine.after_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
474
+ @machine.after_transition :from => :idling, :to => :idling, :on => :park, :do => callback
475
+ @transition.after(true)
476
+
477
+ assert_equal 1, @count
478
+ end
479
+
480
+ def test_should_not_run_after_callbacks_if_not_successful
481
+ @machine.after_transition(lambda {|object| @run = true})
482
+ @transition.after(nil, false)
483
+ assert !@run
484
+ end
485
+
486
+ def test_should_pass_transition_to_after_callbacks
487
+ @machine.after_transition(lambda {|*args| @args = args})
488
+
489
+ @transition.after(true)
490
+ assert_equal [@object, @transition], @args
491
+ assert_equal true, @transition.result
492
+
493
+ @transition.after(false)
494
+ assert_equal [@object, @transition], @args
495
+ assert_equal false, @transition.result
496
+ end
497
+
498
+ def test_should_catch_halted_after_callbacks
499
+ @machine.after_transition(lambda {throw :halt})
500
+
501
+ result = nil
502
+ assert_nothing_thrown { result = @transition.after(true) }
503
+ assert_equal true, result
504
+ end
505
+
506
+ def test_should_run_after_callbacks_on_perform_after_running_the_action
507
+ @machine.after_transition(lambda {|object| @state = object.state})
508
+ @transition.perform(true)
509
+
510
+ assert_equal 'idling', @state
511
+ end
512
+
513
+ def test_should_not_be_able_to_run_after_callbacks_twice
514
+ @count = 0
515
+ @machine.after_transition(lambda {@count += 1})
516
+ @transition.after
517
+ @transition.after
518
+ assert_equal 1, @count
519
+ end
520
+
521
+ def test_should_be_able_to_run_after_callbacks_again_after_resetting
522
+ @count = 0
523
+ @machine.after_transition(lambda {@count += 1})
524
+ @transition.after
525
+ @transition.reset
526
+ @transition.after
527
+ assert_equal 2, @count
528
+ end
529
+ end
530
+
531
+ class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
532
+ def setup
533
+ @klass = Class.new do
534
+ attr_reader :saved, :save_state
535
+
536
+ def save
537
+ @save_state = state
538
+ @saved = true
539
+ end
540
+ end
541
+
542
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
543
+ @machine.state :parked, :idling
544
+ @machine.event :ignite
545
+
546
+ @object = @klass.new
547
+ @object.state = 'parked'
548
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
549
+ @result = @transition.perform
550
+ end
551
+
552
+ def test_should_have_empty_args
553
+ assert_equal [], @transition.args
554
+ end
555
+
556
+ def test_should_have_a_result
557
+ assert_equal true, @transition.result
558
+ end
559
+
560
+ def test_should_be_successful
561
+ assert_equal true, @result
562
+ end
563
+
564
+ def test_should_change_the_current_state
565
+ assert_equal 'idling', @object.state
566
+ end
567
+
568
+ def test_should_run_the_action
569
+ assert @object.saved
570
+ end
571
+
572
+ def test_should_run_the_action_after_saving_the_state
573
+ assert_equal 'idling', @object.save_state
574
+ end
575
+ end
576
+
577
+ class TransitionWithPerformArgumentsTest < Test::Unit::TestCase
578
+ def setup
579
+ @klass = Class.new do
580
+ attr_reader :saved
581
+
582
+ def save
583
+ @saved = true
584
+ end
585
+ end
586
+
587
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
588
+ @machine.state :parked, :idling
589
+ @machine.event :ignite
590
+
591
+ @object = @klass.new
592
+ @object.state = 'parked'
593
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
594
+ end
595
+
596
+ def test_should_have_arguments
597
+ @transition.perform(1, 2)
598
+
599
+ assert_equal [1, 2], @transition.args
600
+ assert @object.saved
601
+ end
602
+
603
+ def test_should_not_include_run_action_in_arguments
604
+ @transition.perform(1, 2, false)
605
+
606
+ assert_equal [1, 2], @transition.args
607
+ assert !@object.saved
608
+ end
609
+ end
610
+
611
+ class TransitionWithoutRunningActionTest < Test::Unit::TestCase
612
+ def setup
613
+ @klass = Class.new do
614
+ attr_reader :saved
615
+
616
+ def save
617
+ @saved = true
618
+ end
619
+ end
620
+
621
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
622
+ @machine.state :parked, :idling
623
+ @machine.event :ignite
624
+ @machine.after_transition(lambda {|object| @run_after = true})
625
+
626
+ @object = @klass.new
627
+ @object.state = 'parked'
628
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
629
+ @result = @transition.perform(false)
630
+ end
631
+
632
+ def test_should_have_empty_args
633
+ assert_equal [], @transition.args
634
+ end
635
+
636
+ def test_should_not_have_a_result
637
+ assert_nil @transition.result
638
+ end
639
+
640
+ def test_should_be_successful
641
+ assert_equal true, @result
642
+ end
643
+
644
+ def test_should_change_the_current_state
645
+ assert_equal 'idling', @object.state
646
+ end
647
+
648
+ def test_should_not_run_the_action
649
+ assert !@object.saved
650
+ end
651
+
652
+ def test_should_run_after_callbacks
653
+ assert @run_after
654
+ end
655
+ end
656
+
657
+ class TransitionWithTransactionsTest < Test::Unit::TestCase
658
+ def setup
659
+ @klass = Class.new do
660
+ class << self
661
+ attr_accessor :running_transaction
662
+ end
663
+
664
+ attr_accessor :result
665
+
666
+ def save
667
+ @result = self.class.running_transaction
668
+ true
669
+ end
670
+ end
671
+
672
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
673
+ @machine.state :parked, :idling
674
+ @machine.event :ignite
675
+
676
+ @object = @klass.new
677
+ @object.state = 'parked'
678
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
679
+
680
+ class << @machine
681
+ def within_transaction(object)
682
+ owner_class.running_transaction = object
683
+ yield
684
+ owner_class.running_transaction = false
685
+ end
686
+ end
687
+ end
688
+
689
+ def test_should_run_blocks_within_transaction_for_object
690
+ @transition.within_transaction do
691
+ @result = @klass.running_transaction
692
+ end
693
+
694
+ assert_equal @object, @result
695
+ end
696
+
697
+ def test_should_run_before_callbacks_within_transaction
698
+ @machine.before_transition(lambda {|object| @result = @klass.running_transaction})
699
+ @transition.perform
700
+
701
+ assert_equal @object, @result
702
+ end
703
+
704
+ def test_should_run_action_within_transaction
705
+ @transition.perform
706
+
707
+ assert_equal @object, @object.result
708
+ end
709
+
710
+ def test_should_run_after_callbacks_within_transaction
711
+ @machine.after_transition(lambda {|object| @result = @klass.running_transaction})
712
+ @transition.perform
713
+
714
+ assert_equal @object, @result
715
+ end
716
+ end
717
+
718
+ class TransitionHaltedDuringBeforeCallbacksTest < Test::Unit::TestCase
719
+ def setup
720
+ @klass = Class.new do
721
+ class << self; attr_accessor :cancelled_transaction; end
722
+ attr_reader :saved
723
+
724
+ def save
725
+ @saved = true
726
+ end
727
+ end
728
+ @before_count = 0
729
+ @after_count = 0
730
+
731
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
732
+ @machine.state :parked, :idling
733
+ @machine.event :ignite
734
+ class << @machine
735
+ def within_transaction(object)
736
+ owner_class.cancelled_transaction = yield == false
737
+ end
738
+ end
739
+
740
+ @machine.before_transition lambda {@before_count += 1; throw :halt}
741
+ @machine.before_transition lambda {@before_count += 1}
742
+ @machine.after_transition lambda {@after_count += 1}
743
+
744
+ @object = @klass.new
745
+ @object.state = 'parked'
746
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
747
+ @result = @transition.perform
748
+ end
749
+
750
+ def test_should_not_be_successful
751
+ assert_equal false, @result
752
+ end
753
+
754
+ def test_should_not_change_current_state
755
+ assert_equal 'parked', @object.state
756
+ end
757
+
758
+ def test_should_not_run_action
759
+ assert !@object.saved
760
+ end
761
+
762
+ def test_should_not_run_further_before_callbacks
763
+ assert_equal 1, @before_count
764
+ end
765
+
766
+ def test_should_not_run_after_callbacks
767
+ assert_equal 0, @after_count
768
+ end
769
+
770
+ def test_should_cancel_the_transaction
771
+ assert @klass.cancelled_transaction
772
+ end
773
+ end
774
+
775
+ class TransitionHaltedAfterCallbackTest < Test::Unit::TestCase
776
+ def setup
777
+ @klass = Class.new do
778
+ class << self; attr_accessor :cancelled_transaction; end
779
+ attr_reader :saved
780
+
781
+ def save
782
+ @saved = true
783
+ end
784
+ end
785
+ @before_count = 0
786
+ @after_count = 0
787
+
788
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
789
+ @machine.state :parked, :idling
790
+ @machine.event :ignite
791
+ class << @machine
792
+ def within_transaction(object)
793
+ owner_class.cancelled_transaction = yield == false
794
+ end
795
+ end
796
+
797
+ @machine.before_transition lambda {@before_count += 1}
798
+ @machine.after_transition lambda {@after_count += 1; throw :halt}
799
+ @machine.after_transition lambda {@after_count += 1}
800
+
801
+ @object = @klass.new
802
+ @object.state = 'parked'
803
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
804
+ @result = @transition.perform
805
+ end
806
+
807
+ def test_should_be_successful
808
+ assert_equal true, @result
809
+ end
810
+
811
+ def test_should_change_current_state
812
+ assert_equal 'idling', @object.state
813
+ end
814
+
815
+ def test_should_run_before_callbacks
816
+ assert_equal 1, @before_count
817
+ end
818
+
819
+ def test_should_not_run_further_after_callbacks
820
+ assert_equal 1, @after_count
821
+ end
822
+
823
+ def test_should_not_cancel_the_transaction
824
+ assert !@klass.cancelled_transaction
825
+ end
826
+ end
827
+
828
+ class TransitionWithActionFailedTest < Test::Unit::TestCase
829
+ def setup
830
+ @klass = Class.new do
831
+ class << self; attr_accessor :cancelled_transaction; end
832
+ attr_reader :saved
833
+
834
+ def save
835
+ false
836
+ end
837
+ end
838
+ @before_count = 0
839
+ @after_count = 0
840
+
841
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
842
+ @machine.state :parked, :idling
843
+ @machine.event :ignite
844
+ class << @machine
845
+ def within_transaction(object)
846
+ owner_class.cancelled_transaction = yield == false
847
+ end
848
+ end
849
+
850
+ @machine.before_transition lambda {@before_count += 1}
851
+ @machine.after_transition lambda {@after_count += 1}
852
+ @machine.after_transition lambda {@after_count += 1}, :include_failures => true
853
+
854
+ @object = @klass.new
855
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
856
+ end
857
+
858
+ def test_should_not_be_successful
859
+ assert_equal false, @transition.perform
860
+ end
861
+
862
+ def test_should_not_change_current_state
863
+ @transition.perform
864
+ assert_nil @object.state
865
+ end
866
+
867
+ def test_should_run_before_callbacks
868
+ @transition.perform
869
+ assert_equal 1, @before_count
870
+ end
871
+
872
+ def test_should_only_run_after_callbacks_that_include_failures
873
+ @transition.perform
874
+ assert_equal 1, @after_count
875
+ end
876
+
877
+ def test_should_cancel_the_transaction
878
+ @transition.perform
879
+ assert @klass.cancelled_transaction
880
+ end
881
+
882
+ def test_should_interpret_nil_as_failure
883
+ @klass.class_eval do
884
+ def save
885
+ nil
886
+ end
887
+ end
888
+
889
+ assert_equal false, @transition.perform
890
+ end
891
+ end
892
+
893
+ class TransitionWithActionErrorTest < Test::Unit::TestCase
894
+ def setup
895
+ @klass = Class.new do
896
+ def save
897
+ raise ArgumentError
898
+ end
899
+ end
900
+
901
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
902
+ @machine.state :parked, :idling
903
+ @machine.event :ignite
904
+
905
+ @object = @klass.new
906
+ @object.state = 'parked'
907
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
908
+
909
+ @raised = true
910
+ begin
911
+ @transition.perform
912
+ @raised = false
913
+ rescue ArgumentError
914
+ end
915
+ end
916
+
917
+ def test_should_not_catch_exception
918
+ assert @raised
919
+ end
920
+
921
+ def test_should_not_change_current_state
922
+ assert_equal 'parked', @object.state
923
+ end
924
+ end
925
+
926
+ class TransitionsInParallelTest < Test::Unit::TestCase
927
+ def setup
928
+ @klass = Class.new do
929
+ attr_reader :persisted
930
+
931
+ def initialize
932
+ @persisted = []
933
+ @state = 'parked'
934
+ @status = 'first_gear'
935
+ super
936
+ end
937
+
938
+ def state=(value)
939
+ @persisted << value
940
+ @state = value
941
+ end
942
+
943
+ def status=(value)
944
+ @persisted << value
945
+ @status = value
946
+ end
947
+ end
948
+
949
+ @state = StateMachine::Machine.new(@klass, :state)
950
+ @state.event :ignite
951
+ @state.state :parked, :idling
952
+
953
+ @status = StateMachine::Machine.new(@klass, :status)
954
+ @status.event :shift_up
955
+ @status.state :first_gear, :second_gear
956
+
957
+ @object = @klass.new
958
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
959
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
960
+
961
+ @result = StateMachine::Transition.perform([@state_transition, @status_transition])
962
+ end
963
+
964
+ def test_should_raise_exception_if_attempted_on_the_same_state_machine
965
+ exception = assert_raise(ArgumentError) { StateMachine::Transition.perform([@state_transition, @state_transition]) }
966
+ assert_equal 'Cannot perform multiple transitions in parallel for the same state machine attribute', exception.message
967
+ end
968
+
969
+ def test_should_perform
970
+ assert_equal true, @result
971
+ end
972
+
973
+ def test_should_persist_first_state
974
+ assert_equal 'idling', @object.state
975
+ end
976
+
977
+ def test_should_persist_second_state
978
+ assert_equal 'second_gear', @object.status
979
+ end
980
+
981
+ def test_should_persist_in_order
982
+ assert_equal ['idling', 'second_gear'], @object.persisted
983
+ end
984
+
985
+ def test_should_have_args_in_transitions
986
+ assert_equal [], @state_transition.args
987
+ assert_equal [], @status_transition.args
988
+ end
989
+ end
990
+
991
+ class TransitionsInParallelWithCallbacksTest < Test::Unit::TestCase
992
+ def setup
993
+ @klass = Class.new
994
+
995
+ @before_callbacks = []
996
+ @after_callbacks = []
997
+
998
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
999
+ @state.event :ignite
1000
+ @state.state :parked, :idling
1001
+ @state.before_transition lambda {@before_callbacks << :state}
1002
+ @state.after_transition lambda {@after_callbacks << :state}
1003
+
1004
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
1005
+ @status.event :shift_up
1006
+ @status.state :first_gear, :second_gear
1007
+ @status.before_transition lambda {@before_callbacks << :status}
1008
+ @status.after_transition lambda {@after_callbacks << :status}
1009
+
1010
+ @object = @klass.new
1011
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1012
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1013
+ end
1014
+
1015
+ def test_should_run_before_callbacks_in_order
1016
+ perform
1017
+ assert_equal [:state, :status], @before_callbacks
1018
+ end
1019
+
1020
+ def test_should_run_after_callbacks_in_order
1021
+ perform
1022
+ assert_equal [:state, :status], @after_callbacks
1023
+ end
1024
+
1025
+ def test_should_not_run_after_callbacks_if_disabled
1026
+ perform(:after => false)
1027
+ assert_equal [], @after_callbacks
1028
+ end
1029
+
1030
+ def test_should_halt_if_before_callback_halted_for_first_transition
1031
+ @state.before_transition lambda {throw :halt}
1032
+
1033
+ assert_equal false, perform
1034
+ assert_equal [:state], @before_callbacks
1035
+ assert_equal 'parked', @object.state
1036
+ assert_equal 'first_gear', @object.status
1037
+ assert_equal [], @after_callbacks
1038
+ end
1039
+
1040
+ def test_should_halt_if_before_callback_halted_for_second_transition
1041
+ @status.before_transition lambda {throw :halt}
1042
+
1043
+ assert_equal false, perform
1044
+ assert_equal [:state, :status], @before_callbacks
1045
+ assert_equal 'parked', @object.state
1046
+ assert_equal 'first_gear', @object.status
1047
+ assert_equal [], @after_callbacks
1048
+ end
1049
+
1050
+ def test_should_perform_if_after_callback_halted_for_first_transition
1051
+ @state.after_transition lambda {throw :halt}
1052
+ @state.after_transition lambda {@after_callbacks << :invalid}
1053
+
1054
+ assert_equal true, perform
1055
+ assert_equal [:state, :status], @before_callbacks
1056
+ assert_equal 'idling', @object.state
1057
+ assert_equal 'second_gear', @object.status
1058
+ assert_equal [:state, :status], @after_callbacks
1059
+ end
1060
+
1061
+ def test_should_perform_if_after_callback_halted_for_second_transition
1062
+ @status.after_transition lambda {throw :halt}
1063
+ @status.after_transition lambda {@after_callbacks << :invalid}
1064
+
1065
+ assert_equal true, perform
1066
+ assert_equal [:state, :status], @before_callbacks
1067
+ assert_equal 'idling', @object.state
1068
+ assert_equal 'second_gear', @object.status
1069
+ assert_equal [:state, :status], @after_callbacks
1070
+ end
1071
+
1072
+ private
1073
+ def perform(options = {})
1074
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
1075
+ end
1076
+ end
1077
+
1078
+ class TransitionsInParallelWithActionsTest < Test::Unit::TestCase
1079
+ def setup
1080
+ @klass = Class.new do
1081
+ attr_reader :actions
1082
+
1083
+ def save_state
1084
+ (@actions ||= []) << :save_state
1085
+ :save_state
1086
+ end
1087
+
1088
+ def save_status
1089
+ (@actions ||= []) << :save_status
1090
+ :save_status
1091
+ end
1092
+ end
1093
+
1094
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save_state)
1095
+ @state.event :ignite
1096
+ @state.state :parked, :idling
1097
+
1098
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save_status)
1099
+ @status.event :shift_up
1100
+ @status.state :first_gear, :second_gear
1101
+
1102
+ @object = @klass.new
1103
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1104
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1105
+ end
1106
+
1107
+ def test_should_run_actions_in_order
1108
+ perform
1109
+ assert_equal [:save_state, :save_status], @object.actions
1110
+ end
1111
+
1112
+ def test_should_store_action_specific_results
1113
+ perform
1114
+ assert_equal :save_state, @state_transition.result
1115
+ assert_equal :save_status, @status_transition.result
1116
+ end
1117
+
1118
+ def test_should_not_perform_if_action_fails_for_first_transition
1119
+ @klass.class_eval do
1120
+ def save_state
1121
+ false
1122
+ end
1123
+ end
1124
+
1125
+ assert_equal false, perform
1126
+ assert_equal 'parked', @object.state
1127
+ assert_equal 'first_gear', @object.status
1128
+ end
1129
+
1130
+ def test_should_not_perform_if_action_fails_for_second_transition
1131
+ @klass.class_eval do
1132
+ def save_status
1133
+ false
1134
+ end
1135
+ end
1136
+
1137
+ assert_equal false, perform
1138
+ assert_equal 'parked', @object.state
1139
+ assert_equal 'first_gear', @object.status
1140
+ end
1141
+
1142
+ def test_should_rollback_if_action_errors_for_first_transition
1143
+ @klass.class_eval do
1144
+ def save_state
1145
+ raise ArgumentError
1146
+ end
1147
+ end
1148
+
1149
+ begin; perform; rescue; end
1150
+ assert_equal 'parked', @object.state
1151
+ assert_equal 'first_gear', @object.status
1152
+ end
1153
+
1154
+ def test_should_rollback_if_action_errors_for_second_transition
1155
+ @klass.class_eval do
1156
+ def save_status
1157
+ raise ArgumentError
1158
+ end
1159
+ end
1160
+
1161
+ begin; perform; rescue; end
1162
+ assert_equal 'parked', @object.state
1163
+ assert_equal 'first_gear', @object.status
1164
+ end
1165
+
1166
+ private
1167
+ def perform(options = {})
1168
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
1169
+ end
1170
+ end
1171
+
1172
+ class TransitionsWithPerformBlockTest < Test::Unit::TestCase
1173
+ def setup
1174
+ @klass = Class.new
1175
+
1176
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
1177
+ @state.event :ignite
1178
+ @state.state :parked, :idling
1179
+
1180
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
1181
+ @status.event :shift_up
1182
+ @status.state :first_gear, :second_gear
1183
+
1184
+ @object = @klass.new
1185
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1186
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1187
+ end
1188
+
1189
+ def test_should_be_perform_if_result_is_not_false
1190
+ assert_equal true, StateMachine::Transition.perform([@state_transition, @status_transition]) { true }
1191
+ assert_equal 'idling', @object.state
1192
+ assert_equal 'second_gear', @object.status
1193
+ end
1194
+
1195
+ def test_should_not_perform_if_result_is_false
1196
+ assert_equal false, StateMachine::Transition.perform([@state_transition, @status_transition]) { false }
1197
+ assert_equal 'parked', @object.state
1198
+ assert_equal 'first_gear', @object.status
1199
+ end
1200
+
1201
+ def test_should_not_perform_if_result_is_nil
1202
+ assert_equal false, StateMachine::Transition.perform([@state_transition, @status_transition]) { nil }
1203
+ assert_equal 'parked', @object.state
1204
+ assert_equal 'first_gear', @object.status
1205
+ end
1206
+
1207
+ def test_should_use_result_as_transition_result
1208
+ StateMachine::Transition.perform([@state_transition, @status_transition]) { 1 }
1209
+ assert_equal 1, @state_transition.result
1210
+ assert_equal 1, @status_transition.result
1211
+ end
1212
+ end