pluginaweek-state_machine 0.7.6

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 +273 -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 +429 -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 +251 -0
  33. data/lib/state_machine/event_collection.rb +113 -0
  34. data/lib/state_machine/extensions.rb +158 -0
  35. data/lib/state_machine/guard.rb +219 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +444 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +10 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +325 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +292 -0
  43. data/lib/state_machine/machine.rb +1431 -0
  44. data/lib/state_machine/machine_collection.rb +146 -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 +367 -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 +129 -0
  60. data/test/unit/event_collection_test.rb +293 -0
  61. data/test/unit/event_test.rb +605 -0
  62. data/test/unit/guard_test.rb +862 -0
  63. data/test/unit/integrations/active_record_test.rb +1001 -0
  64. data/test/unit/integrations/data_mapper_test.rb +694 -0
  65. data/test/unit/integrations/sequel_test.rb +486 -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 +710 -0
  70. data/test/unit/machine_test.rb +1910 -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 +1113 -0
  78. metadata +161 -0
@@ -0,0 +1,1910 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class MachineByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @klass = Class.new
6
+ @machine = StateMachine::Machine.new(@klass)
7
+ @object = @klass.new
8
+ end
9
+
10
+ def test_should_have_an_owner_class
11
+ assert_equal @klass, @machine.owner_class
12
+ end
13
+
14
+ def test_should_have_a_name
15
+ assert_equal :state, @machine.name
16
+ end
17
+
18
+ def test_should_have_an_attribute
19
+ assert_equal :state, @machine.attribute
20
+ end
21
+
22
+ def test_should_prefix_custom_attributes_with_attribute
23
+ assert_equal :state_event, @machine.attribute(:event)
24
+ end
25
+
26
+ def test_should_have_an_initial_state
27
+ assert_not_nil @machine.initial_state(@object)
28
+ end
29
+
30
+ def test_should_have_a_nil_initial_state
31
+ assert_nil @machine.initial_state(@object).value
32
+ end
33
+
34
+ def test_should_not_have_any_events
35
+ assert !@machine.events.any?
36
+ end
37
+
38
+ def test_should_not_have_any_before_callbacks
39
+ assert @machine.callbacks[:before].empty?
40
+ end
41
+
42
+ def test_should_not_have_any_after_callbacks
43
+ assert @machine.callbacks[:after].empty?
44
+ end
45
+
46
+ def test_should_not_have_an_action
47
+ assert_nil @machine.action
48
+ end
49
+
50
+ def test_should_use_tranactions
51
+ assert_equal true, @machine.use_transactions
52
+ end
53
+
54
+ def test_should_not_have_a_namespace
55
+ assert_nil @machine.namespace
56
+ end
57
+
58
+ def test_should_have_a_nil_state
59
+ assert_equal [nil], @machine.states.keys
60
+ end
61
+
62
+ def test_should_set_initial_on_nil_state
63
+ assert @machine.state(nil).initial
64
+ end
65
+
66
+ def test_should_generate_default_messages
67
+ assert_equal 'is invalid', @machine.generate_message(:invalid)
68
+ assert_equal 'cannot transition when parked', @machine.generate_message(:invalid_event, [[:state, :parked]])
69
+ assert_equal 'cannot transition via "park"', @machine.generate_message(:invalid_transition, [[:event, :park]])
70
+ end
71
+
72
+ def test_should_not_be_extended_by_the_active_record_integration
73
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveRecord)
74
+ end
75
+
76
+ def test_should_not_be_extended_by_the_datamapper_integration
77
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::DataMapper)
78
+ end
79
+
80
+ def test_should_not_be_extended_by_the_sequel_integration
81
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Sequel)
82
+ end
83
+
84
+ def test_should_define_a_reader_attribute_for_the_attribute
85
+ assert @object.respond_to?(:state)
86
+ end
87
+
88
+ def test_should_define_a_writer_attribute_for_the_attribute
89
+ assert @object.respond_to?(:state=)
90
+ end
91
+
92
+ def test_should_define_a_predicate_for_the_attribute
93
+ assert @object.respond_to?(:state?)
94
+ end
95
+
96
+ def test_should_define_a_name_reader_for_the_attribute
97
+ assert @object.respond_to?(:state_name)
98
+ end
99
+
100
+ def test_should_define_an_event_reader_for_the_attribute
101
+ assert @object.respond_to?(:state_events)
102
+ end
103
+
104
+ def test_should_define_a_transition_reader_for_the_attribute
105
+ assert @object.respond_to?(:state_transitions)
106
+ end
107
+
108
+ def test_should_not_define_an_event_attribute_reader
109
+ assert !@object.respond_to?(:state_event)
110
+ end
111
+
112
+ def test_should_not_define_an_event_attribute_writer
113
+ assert !@object.respond_to?(:state_event=)
114
+ end
115
+
116
+ def test_should_not_define_an_event_transition_attribute_reader
117
+ assert !@object.respond_to?(:state_event_transition)
118
+ end
119
+
120
+ def test_should_not_define_an_event_transition_attribute_writer
121
+ assert !@object.respond_to?(:state_event_transition=)
122
+ end
123
+
124
+ def test_should_not_define_singular_with_scope
125
+ assert !@klass.respond_to?(:with_state)
126
+ end
127
+
128
+ def test_should_not_define_singular_without_scope
129
+ assert !@klass.respond_to?(:without_state)
130
+ end
131
+
132
+ def test_should_not_define_plural_with_scope
133
+ assert !@klass.respond_to?(:with_states)
134
+ end
135
+
136
+ def test_should_not_define_plural_without_scope
137
+ assert !@klass.respond_to?(:without_states)
138
+ end
139
+
140
+ def test_should_extend_owner_class_with_class_methods
141
+ assert (class << @klass; ancestors; end).include?(StateMachine::ClassMethods)
142
+ end
143
+
144
+ def test_should_include_instance_methods_in_owner_class
145
+ assert @klass.included_modules.include?(StateMachine::InstanceMethods)
146
+ end
147
+
148
+ def test_should_define_state_machines_reader
149
+ expected = {:state => @machine}
150
+ assert_equal expected, @klass.state_machines
151
+ end
152
+ end
153
+
154
+ class MachineWithCustomAttributeTest < Test::Unit::TestCase
155
+ def setup
156
+ @klass = Class.new
157
+ @machine = StateMachine::Machine.new(@klass, :status)
158
+ @object = @klass.new
159
+ end
160
+
161
+ def test_should_use_custom_attribute_for_name
162
+ assert_equal :status, @machine.name
163
+ end
164
+
165
+ def test_should_use_custom_attribute
166
+ assert_equal :status, @machine.attribute
167
+ end
168
+
169
+ def test_should_prefix_custom_attributes_with_custom_attribute
170
+ assert_equal :status_event, @machine.attribute(:event)
171
+ end
172
+
173
+ def test_should_define_a_reader_attribute_for_the_attribute
174
+ assert @object.respond_to?(:status)
175
+ end
176
+
177
+ def test_should_define_a_writer_attribute_for_the_attribute
178
+ assert @object.respond_to?(:status=)
179
+ end
180
+
181
+ def test_should_define_a_predicate_for_the_attribute
182
+ assert @object.respond_to?(:status?)
183
+ end
184
+
185
+ def test_should_define_a_name_reader_for_the_attribute
186
+ assert @object.respond_to?(:status_name)
187
+ end
188
+
189
+ def test_should_define_an_event_reader_for_the_attribute
190
+ assert @object.respond_to?(:status_events)
191
+ end
192
+
193
+ def test_should_define_a_transition_reader_for_the_attribute
194
+ assert @object.respond_to?(:status_transitions)
195
+ end
196
+ end
197
+
198
+ class MachineWithStaticInitialStateTest < Test::Unit::TestCase
199
+ def setup
200
+ @klass = Class.new do
201
+ def initialize(attributes = {})
202
+ attributes.each {|attr, value| send("#{attr}=", value)}
203
+ super()
204
+ end
205
+ end
206
+
207
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
208
+ end
209
+
210
+ def test_should_have_an_initial_state
211
+ object = @klass.new
212
+ assert_equal 'parked', @machine.initial_state(object).value
213
+ end
214
+
215
+ def test_should_set_initial_on_state_object
216
+ assert @machine.state(:parked).initial
217
+ end
218
+
219
+ def test_should_set_initial_state_if_existing_is_nil
220
+ object = @klass.new(:state => nil)
221
+ assert_equal 'parked', object.state
222
+ end
223
+
224
+ def test_should_set_initial_state_if_existing_is_empty
225
+ object = @klass.new(:state => '')
226
+ assert_equal 'parked', object.state
227
+ end
228
+
229
+ def test_should_not_set_initial_state_if_existing_is_not_empty
230
+ object = @klass.new(:state => 'idling')
231
+ assert_equal 'idling', object.state
232
+ end
233
+
234
+ def test_should_be_included_in_known_states
235
+ assert_equal [:parked], @machine.states.keys
236
+ end
237
+ end
238
+
239
+ class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
240
+ def setup
241
+ @klass = Class.new do
242
+ attr_accessor :initial_state
243
+ end
244
+ @machine = StateMachine::Machine.new(@klass, :initial => lambda {|object| object.initial_state || :default})
245
+ @machine.state :parked, :idling, :default
246
+ @object = @klass.new
247
+ end
248
+
249
+ def test_should_use_the_record_for_determining_the_initial_state
250
+ @object.initial_state = :parked
251
+ assert_equal :parked, @machine.initial_state(@object).name
252
+
253
+ @object.initial_state = :idling
254
+ assert_equal :idling, @machine.initial_state(@object).name
255
+ end
256
+
257
+ def test_should_set_initial_state_on_created_object
258
+ assert_equal 'default', @object.state
259
+ end
260
+
261
+ def test_should_not_be_included_in_known_states
262
+ assert_equal [:parked, :idling, :default], @machine.states.map {|state| state.name}
263
+ end
264
+ end
265
+
266
+ class MachineWithCustomActionTest < Test::Unit::TestCase
267
+ def setup
268
+ @machine = StateMachine::Machine.new(Class.new, :action => :save)
269
+ end
270
+
271
+ def test_should_use_the_custom_action
272
+ assert_equal :save, @machine.action
273
+ end
274
+ end
275
+
276
+ class MachineWithNilActionTest < Test::Unit::TestCase
277
+ def setup
278
+ integration = Module.new do
279
+ class << self; attr_reader :defaults; end
280
+ @defaults = {:action => :save}
281
+ end
282
+ StateMachine::Integrations.const_set('Custom', integration)
283
+ @machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom)
284
+ end
285
+
286
+ def test_should_have_a_nil_action
287
+ assert_nil @machine.action
288
+ end
289
+
290
+ def teardown
291
+ StateMachine::Integrations.send(:remove_const, 'Custom')
292
+ end
293
+ end
294
+
295
+ class MachineWithoutIntegrationTest < Test::Unit::TestCase
296
+ def setup
297
+ @klass = Class.new
298
+ @machine = StateMachine::Machine.new(@klass)
299
+ @object = @klass.new
300
+ end
301
+
302
+ def test_transaction_should_yield
303
+ @yielded = false
304
+ @machine.within_transaction(@object) do
305
+ @yielded = true
306
+ end
307
+
308
+ assert @yielded
309
+ end
310
+
311
+ def test_invalidation_should_do_nothing
312
+ assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
313
+ end
314
+
315
+ def test_reset_should_do_nothing
316
+ assert_nil @machine.reset(@object)
317
+ end
318
+ end
319
+
320
+ class MachineWithCustomIntegrationTest < Test::Unit::TestCase
321
+ def setup
322
+ StateMachine::Integrations.const_set('Custom', Module.new)
323
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom)
324
+ end
325
+
326
+ def test_should_be_extended_by_the_integration
327
+ assert (class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom)
328
+ end
329
+
330
+ def teardown
331
+ StateMachine::Integrations.send(:remove_const, 'Custom')
332
+ end
333
+ end
334
+
335
+ class MachineWithIntegrationTest < Test::Unit::TestCase
336
+ def setup
337
+ StateMachine::Integrations.const_set('Custom', Module.new do
338
+ class << self; attr_reader :defaults; end
339
+ @defaults = {:action => :save, :use_transactions => false}
340
+
341
+ attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
342
+
343
+ def after_initialize
344
+ @initialized = true
345
+ end
346
+
347
+ def create_with_scope(name)
348
+ (@with_scopes ||= []) << name
349
+ lambda {}
350
+ end
351
+
352
+ def create_without_scope(name)
353
+ (@without_scopes ||= []) << name
354
+ lambda {}
355
+ end
356
+
357
+ def transaction(object)
358
+ @ran_transaction = true
359
+ yield
360
+ end
361
+ end)
362
+
363
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom)
364
+ end
365
+
366
+ def test_should_call_after_initialize_hook
367
+ assert @machine.initialized
368
+ end
369
+
370
+ def test_should_use_the_default_action
371
+ assert_equal :save, @machine.action
372
+ end
373
+
374
+ def test_should_use_the_custom_action_if_specified
375
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :action => :save!)
376
+ assert_equal :save!, machine.action
377
+ end
378
+
379
+ def test_should_use_the_default_use_transactions
380
+ assert_equal false, @machine.use_transactions
381
+ end
382
+
383
+ def test_should_use_the_custom_use_transactions_if_specified
384
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true)
385
+ assert_equal true, machine.use_transactions
386
+ end
387
+
388
+ def test_should_define_a_singular_and_plural_with_scope
389
+ assert_equal %w(with_state with_states), @machine.with_scopes
390
+ end
391
+
392
+ def test_should_define_a_singular_and_plural_without_scope
393
+ assert_equal %w(without_state without_states), @machine.without_scopes
394
+ end
395
+
396
+ def teardown
397
+ StateMachine::Integrations.send(:remove_const, 'Custom')
398
+ end
399
+ end
400
+
401
+ class MachineWithActionTest < Test::Unit::TestCase
402
+ def setup
403
+ @klass = Class.new do
404
+ def save
405
+ end
406
+ end
407
+
408
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
409
+ @object = @klass.new
410
+ end
411
+
412
+ def test_should_define_an_event_attribute_reader
413
+ assert @object.respond_to?(:state_event)
414
+ end
415
+
416
+ def test_should_define_an_event_attribute_writer
417
+ assert @object.respond_to?(:state_event=)
418
+ end
419
+
420
+ def test_should_define_an_event_transition_attribute_reader
421
+ assert @object.respond_to?(:state_event_transition)
422
+ end
423
+
424
+ def test_should_define_an_event_transition_attribute_writer
425
+ assert @object.respond_to?(:state_event_transition=)
426
+ end
427
+ end
428
+
429
+ class MachineWithActionUndefinedTest < Test::Unit::TestCase
430
+ def setup
431
+ @klass = Class.new
432
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
433
+ @object = @klass.new
434
+ end
435
+
436
+ def test_should_define_an_event_attribute_reader
437
+ assert @object.respond_to?(:state_event)
438
+ end
439
+
440
+ def test_should_define_an_event_attribute_writer
441
+ assert @object.respond_to?(:state_event=)
442
+ end
443
+
444
+ def test_should_define_an_event_transition_attribute_reader
445
+ assert @object.respond_to?(:state_event_transition)
446
+ end
447
+
448
+ def test_should_define_an_event_transition_attribute_writer
449
+ assert @object.respond_to?(:state_event_transition=)
450
+ end
451
+
452
+ def test_should_not_define_action
453
+ assert !@object.respond_to?(:save)
454
+ end
455
+ end
456
+
457
+ class MachineWithCustomPluralTest < Test::Unit::TestCase
458
+ def setup
459
+ @integration = Module.new do
460
+ class << self; attr_accessor :with_scopes, :without_scopes; end
461
+ @with_scopes = []
462
+ @without_scopes = []
463
+
464
+ def create_with_scope(name)
465
+ StateMachine::Integrations::Custom.with_scopes << name
466
+ lambda {}
467
+ end
468
+
469
+ def create_without_scope(name)
470
+ StateMachine::Integrations::Custom.without_scopes << name
471
+ lambda {}
472
+ end
473
+ end
474
+
475
+ StateMachine::Integrations.const_set('Custom', @integration)
476
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
477
+ end
478
+
479
+ def test_should_define_a_singular_and_plural_with_scope
480
+ assert_equal %w(with_state with_staties), @integration.with_scopes
481
+ end
482
+
483
+ def test_should_define_a_singular_and_plural_without_scope
484
+ assert_equal %w(without_state without_staties), @integration.without_scopes
485
+ end
486
+
487
+ def teardown
488
+ StateMachine::Integrations.send(:remove_const, 'Custom')
489
+ end
490
+ end
491
+
492
+ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
493
+ def setup
494
+ @integration = Module.new do
495
+ def invalidate(object, attribute, message, values = [])
496
+ object.error = generate_message(message, values)
497
+ end
498
+ end
499
+ StateMachine::Integrations.const_set('Custom', @integration)
500
+
501
+ @klass = Class.new do
502
+ attr_accessor :error
503
+ end
504
+
505
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'})
506
+ @machine.state :parked
507
+
508
+ @object = @klass.new
509
+ @object.state = 'parked'
510
+ end
511
+
512
+ def test_generate_custom_message
513
+ assert_equal 'cannot park', @machine.generate_message(:invalid_transition, [[:event, :park]])
514
+ end
515
+
516
+ def test_use_custom_message
517
+ @machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
518
+ assert_equal 'cannot park', @object.error
519
+ end
520
+
521
+ def teardown
522
+ StateMachine::Integrations.send(:remove_const, 'Custom')
523
+ end
524
+ end
525
+
526
+ class MachineTest < Test::Unit::TestCase
527
+ def test_should_raise_exception_if_invalid_option_specified
528
+ assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)}
529
+ end
530
+
531
+ def test_should_not_raise_exception_if_custom_messages_specified
532
+ assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})}
533
+ end
534
+
535
+ def test_should_evaluate_a_block_during_initialization
536
+ called = true
537
+ StateMachine::Machine.new(Class.new) do
538
+ called = respond_to?(:event)
539
+ end
540
+
541
+ assert called
542
+ end
543
+
544
+ def test_should_provide_matcher_helpers_during_initialization
545
+ matchers = []
546
+
547
+ StateMachine::Machine.new(Class.new) do
548
+ matchers = [all, any, same]
549
+ end
550
+
551
+ assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
552
+ end
553
+ end
554
+
555
+ class MachineAfterBeingCopiedTest < Test::Unit::TestCase
556
+ def setup
557
+ @machine = StateMachine::Machine.new(Class.new, :state, :initial => :parked)
558
+ @machine.event(:ignite) {}
559
+ @machine.before_transition(lambda {})
560
+ @machine.after_transition(lambda {})
561
+
562
+ @copied_machine = @machine.clone
563
+ end
564
+
565
+ def test_should_not_have_the_same_collection_of_states
566
+ assert_not_same @copied_machine.states, @machine.states
567
+ end
568
+
569
+ def test_should_copy_each_state
570
+ assert_not_same @copied_machine.states[:parked], @machine.states[:parked]
571
+ end
572
+
573
+ def test_should_update_machine_for_each_state
574
+ assert_equal @copied_machine, @copied_machine.states[:parked].machine
575
+ end
576
+
577
+ def test_should_not_update_machine_for_original_state
578
+ assert_equal @machine, @machine.states[:parked].machine
579
+ end
580
+
581
+ def test_should_not_have_the_same_collection_of_events
582
+ assert_not_same @copied_machine.events, @machine.events
583
+ end
584
+
585
+ def test_should_copy_each_event
586
+ assert_not_same @copied_machine.events[:ignite], @machine.events[:ignite]
587
+ end
588
+
589
+ def test_should_update_machine_for_each_event
590
+ assert_equal @copied_machine, @copied_machine.events[:ignite].machine
591
+ end
592
+
593
+ def test_should_not_update_machine_for_original_event
594
+ assert_equal @machine, @machine.events[:ignite].machine
595
+ end
596
+
597
+ def test_should_not_have_the_same_callbacks
598
+ assert_not_same @copied_machine.callbacks, @machine.callbacks
599
+ end
600
+
601
+ def test_should_not_have_the_same_before_callbacks
602
+ assert_not_same @copied_machine.callbacks[:before], @machine.callbacks[:before]
603
+ end
604
+
605
+ def test_should_not_have_the_same_after_callbacks
606
+ assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
607
+ end
608
+ end
609
+
610
+ class MachineAfterChangingOwnerClassTest < Test::Unit::TestCase
611
+ def setup
612
+ @original_class = Class.new
613
+ @machine = StateMachine::Machine.new(@original_class)
614
+
615
+ @new_class = Class.new(@original_class)
616
+ @new_machine = @machine.clone
617
+ @new_machine.owner_class = @new_class
618
+
619
+ @object = @new_class.new
620
+ end
621
+
622
+ def test_should_update_owner_class
623
+ assert_equal @new_class, @new_machine.owner_class
624
+ end
625
+
626
+ def test_should_not_change_original_owner_class
627
+ assert_equal @original_class, @machine.owner_class
628
+ end
629
+
630
+ def test_should_change_the_associated_machine_in_the_new_class
631
+ assert_equal @new_machine, @new_class.state_machines[:state]
632
+ end
633
+
634
+ def test_should_not_change_the_associated_machine_in_the_original_class
635
+ assert_equal @machine, @original_class.state_machines[:state]
636
+ end
637
+ end
638
+
639
+ class MachineAfterChangingInitialState < Test::Unit::TestCase
640
+ def setup
641
+ @klass = Class.new
642
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
643
+ @machine.initial_state = :idling
644
+
645
+ @object = @klass.new
646
+ end
647
+
648
+ def test_should_change_the_initial_state
649
+ assert_equal :idling, @machine.initial_state(@object).name
650
+ end
651
+
652
+ def test_should_include_in_known_states
653
+ assert_equal [:parked, :idling], @machine.states.map {|state| state.name}
654
+ end
655
+
656
+ def test_should_reset_original_initial_state
657
+ assert !@machine.state(:parked).initial
658
+ end
659
+
660
+ def test_should_set_new_state_to_initial
661
+ assert @machine.state(:idling).initial
662
+ end
663
+ end
664
+
665
+ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
666
+ def setup
667
+ @klass = Class.new
668
+ @machine = StateMachine::Machine.new(@klass)
669
+ @object = @klass.new
670
+ end
671
+
672
+ def test_should_not_redefine_existing_public_methods
673
+ @klass.class_eval do
674
+ def state
675
+ 'parked'
676
+ end
677
+ end
678
+
679
+ @machine.define_instance_method(:state) {}
680
+ assert_equal 'parked', @object.state
681
+ end
682
+
683
+ def test_should_not_redefine_existing_protected_methods
684
+ @klass.class_eval do
685
+ protected
686
+ def state
687
+ 'parked'
688
+ end
689
+ end
690
+
691
+ @machine.define_instance_method(:state) {}
692
+ assert_equal 'parked', @object.send(:state)
693
+ end
694
+
695
+ def test_should_not_redefine_existing_private_methods
696
+ @klass.class_eval do
697
+ private
698
+ def state
699
+ 'parked'
700
+ end
701
+ end
702
+
703
+ @machine.define_instance_method(:state) {}
704
+ assert_equal 'parked', @object.send(:state)
705
+ end
706
+
707
+ def test_should_define_nonexistent_methods
708
+ @machine.define_instance_method(:state) {'parked'}
709
+ assert_equal 'parked', @object.state
710
+ end
711
+ end
712
+
713
+ class MachineWithClassHelpersTest < Test::Unit::TestCase
714
+ def setup
715
+ @klass = Class.new
716
+ @machine = StateMachine::Machine.new(@klass)
717
+ end
718
+
719
+ def test_should_not_redefine_existing_public_methods
720
+ class << @klass
721
+ def states
722
+ []
723
+ end
724
+ end
725
+
726
+ @machine.define_class_method(:states) {}
727
+ assert_equal [], @klass.states
728
+ end
729
+
730
+ def test_should_not_redefine_existing_protected_methods
731
+ class << @klass
732
+ protected
733
+ def states
734
+ []
735
+ end
736
+ end
737
+
738
+ @machine.define_class_method(:states) {}
739
+ assert_equal [], @klass.send(:states)
740
+ end
741
+
742
+ def test_should_not_redefine_existing_private_methods
743
+ class << @klass
744
+ private
745
+ def states
746
+ []
747
+ end
748
+ end
749
+
750
+ @machine.define_class_method(:states) {}
751
+ assert_equal [], @klass.send(:states)
752
+ end
753
+
754
+ def test_should_define_nonexistent_methods
755
+ @machine.define_class_method(:states) {[]}
756
+ assert_equal [], @klass.states
757
+ end
758
+ end
759
+
760
+ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
761
+ def setup
762
+ @klass = Class.new do
763
+ def self.with_state
764
+ :with_state
765
+ end
766
+
767
+ def self.with_states
768
+ :with_states
769
+ end
770
+
771
+ def self.without_state
772
+ :without_state
773
+ end
774
+
775
+ def self.without_states
776
+ :without_states
777
+ end
778
+
779
+ attr_accessor :status
780
+
781
+ def state
782
+ 'parked'
783
+ end
784
+
785
+ def state=(value)
786
+ self.status = value
787
+ end
788
+
789
+ def state?
790
+ true
791
+ end
792
+
793
+ def state_name
794
+ :parked
795
+ end
796
+
797
+ def state_events
798
+ [:ignite]
799
+ end
800
+
801
+ def state_transitions
802
+ [{:parked => :idling}]
803
+ end
804
+ end
805
+
806
+ StateMachine::Integrations.const_set('Custom', Module.new do
807
+ def create_with_scope(name)
808
+ lambda {|klass, values| []}
809
+ end
810
+
811
+ def create_without_scope(name)
812
+ lambda {|klass, values| []}
813
+ end
814
+ end)
815
+
816
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
817
+ @machine.state :parked, :idling
818
+ @object = @klass.new
819
+ end
820
+
821
+ def test_should_not_redefine_singular_with_scope
822
+ assert_equal :with_state, @klass.with_state
823
+ end
824
+
825
+ def test_should_not_redefine_plural_with_scope
826
+ assert_equal :with_states, @klass.with_states
827
+ end
828
+
829
+ def test_should_not_redefine_singular_without_scope
830
+ assert_equal :without_state, @klass.without_state
831
+ end
832
+
833
+ def test_should_not_redefine_plural_without_scope
834
+ assert_equal :without_states, @klass.without_states
835
+ end
836
+
837
+ def test_should_not_redefine_attribute_writer
838
+ assert_equal 'parked', @object.state
839
+ end
840
+
841
+ def test_should_not_redefine_attribute_writer
842
+ @object.state = 'parked'
843
+ assert_equal 'parked', @object.status
844
+ end
845
+
846
+ def test_should_not_define_attribute_predicate
847
+ assert @object.state?
848
+ end
849
+
850
+ def test_should_not_redefine_attribute_name_reader
851
+ assert_equal :parked, @object.state_name
852
+ end
853
+
854
+ def test_should_not_redefine_attribute_events_reader
855
+ assert_equal [:ignite], @object.state_events
856
+ end
857
+
858
+ def test_should_not_redefine_attribute_transitions_reader
859
+ assert_equal [{:parked => :idling}], @object.state_transitions
860
+ end
861
+
862
+ def test_should_allow_super_chaining
863
+ @klass.class_eval do
864
+ def self.with_state(*states)
865
+ super == []
866
+ end
867
+
868
+ def self.with_states(*states)
869
+ super == []
870
+ end
871
+
872
+ def self.without_state(*states)
873
+ super == []
874
+ end
875
+
876
+ def self.without_states(*states)
877
+ super == []
878
+ end
879
+
880
+ attr_accessor :status
881
+
882
+ def state
883
+ super || 'parked'
884
+ end
885
+
886
+ def state=(value)
887
+ super
888
+ self.status = value
889
+ end
890
+
891
+ def state?(state)
892
+ super ? 1 : 0
893
+ end
894
+
895
+ def state_name
896
+ super == :parked ? 1 : 0
897
+ end
898
+
899
+ def state_events
900
+ super == []
901
+ end
902
+
903
+ def state_transitions
904
+ super == []
905
+ end
906
+ end
907
+
908
+ assert_equal true, @klass.with_state
909
+ assert_equal true, @klass.with_states
910
+ assert_equal true, @klass.without_state
911
+ assert_equal true, @klass.without_states
912
+
913
+ assert_equal 'parked', @object.state
914
+ @object.state = 'idling'
915
+ assert_equal 'idling', @object.status
916
+ assert_equal 0, @object.state?(:parked)
917
+ assert_equal 0, @object.state_name
918
+ assert_equal true, @object.state_events
919
+ assert_equal true, @object.state_transitions
920
+ end
921
+
922
+ def teardown
923
+ StateMachine::Integrations.send(:remove_const, 'Custom')
924
+ end
925
+ end
926
+
927
+ class MachineWithoutInitializeTest < Test::Unit::TestCase
928
+ def setup
929
+ @klass = Class.new
930
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
931
+ @object = @klass.new
932
+ end
933
+
934
+ def test_should_initialize_state
935
+ assert_equal 'parked', @object.state
936
+ end
937
+ end
938
+
939
+ class MachineWithInitializeWithoutSuperTest < Test::Unit::TestCase
940
+ def setup
941
+ @klass = Class.new do
942
+ def initialize
943
+ end
944
+ end
945
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
946
+ @object = @klass.new
947
+ end
948
+
949
+ def test_should_not_initialize_state
950
+ assert_nil @object.state
951
+ end
952
+ end
953
+
954
+ class MachineWithInitializeAndSuperTest < Test::Unit::TestCase
955
+ def setup
956
+ @klass = Class.new do
957
+ def initialize
958
+ super()
959
+ end
960
+ end
961
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
962
+ @object = @klass.new
963
+ end
964
+
965
+ def test_should_initialize_state
966
+ assert_equal 'parked', @object.state
967
+ end
968
+ end
969
+
970
+ class MachineWithInitializeArgumentsAndBlockTest < Test::Unit::TestCase
971
+ def setup
972
+ @superclass = Class.new do
973
+ attr_reader :args
974
+ attr_reader :block_given
975
+
976
+ def initialize(*args)
977
+ @args = args
978
+ @block_given = block_given?
979
+ end
980
+ end
981
+ @klass = Class.new(@superclass)
982
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
983
+ @object = @klass.new(1, 2, 3) {}
984
+ end
985
+
986
+ def test_should_initialize_state
987
+ assert_equal 'parked', @object.state
988
+ end
989
+
990
+ def test_should_preserve_arguments
991
+ assert_equal [1, 2, 3], @object.args
992
+ end
993
+
994
+ def test_should_preserve_block
995
+ assert @object.block_given
996
+ end
997
+ end
998
+
999
+ class MachineWithCustomInitializeTest < Test::Unit::TestCase
1000
+ def setup
1001
+ @klass = Class.new do
1002
+ def initialize
1003
+ initialize_state_machines
1004
+ end
1005
+ end
1006
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1007
+ @object = @klass.new
1008
+ end
1009
+
1010
+ def test_should_initialize_state
1011
+ assert_equal 'parked', @object.state
1012
+ end
1013
+ end
1014
+
1015
+ class MachinePersistenceTest < Test::Unit::TestCase
1016
+ def setup
1017
+ @klass = Class.new do
1018
+ attr_accessor :state_event
1019
+ end
1020
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1021
+ @object = @klass.new
1022
+ end
1023
+
1024
+ def test_should_allow_reading_state
1025
+ assert_equal 'parked', @machine.read(@object, :state)
1026
+ end
1027
+
1028
+ def test_should_allow_reading_custom_attributes
1029
+ assert_nil @machine.read(@object, :event)
1030
+
1031
+ @object.state_event = 'ignite'
1032
+ assert_equal 'ignite', @machine.read(@object, :event)
1033
+ end
1034
+
1035
+ def test_should_allow_reading_custom_instance_variables
1036
+ @klass.class_eval do
1037
+ attr_writer :state_value
1038
+ end
1039
+
1040
+ @object.state_value = 1
1041
+ assert_raise(NoMethodError) { @machine.read(@object, :value) }
1042
+ assert_equal 1, @machine.read(@object, :value, true)
1043
+ end
1044
+
1045
+ def test_should_allow_writing_state
1046
+ @machine.write(@object, :state, 'idling')
1047
+ assert_equal 'idling', @object.state
1048
+ end
1049
+
1050
+ def test_should_allow_writing_custom_attributes
1051
+ @machine.write(@object, :event, 'ignite')
1052
+ assert_equal 'ignite', @object.state_event
1053
+ end
1054
+ end
1055
+
1056
+
1057
+ class MachineWithStatesTest < Test::Unit::TestCase
1058
+ def setup
1059
+ @klass = Class.new
1060
+ @machine = StateMachine::Machine.new(@klass)
1061
+ @parked, @idling = @machine.state :parked, :idling
1062
+
1063
+ @object = @klass.new
1064
+ end
1065
+
1066
+ def test_should_have_states
1067
+ assert_equal [nil, :parked, :idling], @machine.states.map {|state| state.name}
1068
+ end
1069
+
1070
+ def test_should_allow_state_lookup_by_name
1071
+ assert_equal @parked, @machine.states[:parked]
1072
+ end
1073
+
1074
+ def test_should_allow_state_lookup_by_value
1075
+ assert_equal @parked, @machine.states['parked', :value]
1076
+ end
1077
+
1078
+ def test_should_use_stringified_name_for_value
1079
+ assert_equal 'parked', @parked.value
1080
+ end
1081
+
1082
+ def test_should_not_use_custom_matcher
1083
+ assert_nil @parked.matcher
1084
+ end
1085
+
1086
+ def test_should_raise_exception_if_invalid_option_specified
1087
+ exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)}
1088
+ assert_equal 'Invalid key(s): invalid', exception.message
1089
+ end
1090
+ end
1091
+
1092
+ class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
1093
+ def setup
1094
+ @klass = Class.new
1095
+ @machine = StateMachine::Machine.new(@klass)
1096
+ @state = @machine.state :parked, :value => 1
1097
+
1098
+ @object = @klass.new
1099
+ @object.state = 1
1100
+ end
1101
+
1102
+ def test_should_use_custom_value
1103
+ assert_equal 1, @state.value
1104
+ end
1105
+
1106
+ def test_should_allow_lookup_by_custom_value
1107
+ assert_equal @state, @machine.states[1, :value]
1108
+ end
1109
+ end
1110
+
1111
+ class MachineWithStatesWithRuntimeDependenciesTest < Test::Unit::TestCase
1112
+ def setup
1113
+ @klass = Class.new
1114
+ @machine = StateMachine::Machine.new(@klass)
1115
+ @machine.state :parked
1116
+ end
1117
+
1118
+ def test_should_not_evaluate_value_during_definition
1119
+ assert_nothing_raised { @machine.state :parked, :value => lambda {raise ArgumentError} }
1120
+ end
1121
+
1122
+ def test_should_not_evaluate_if_not_initial_state
1123
+ @machine.state :parked, :value => lambda {raise ArgumentError}
1124
+ assert_nothing_raised { @klass.new }
1125
+ end
1126
+ end
1127
+
1128
+ class MachineWithStateWithMatchersTest < Test::Unit::TestCase
1129
+ def setup
1130
+ @klass = Class.new
1131
+ @machine = StateMachine::Machine.new(@klass)
1132
+ @state = @machine.state :parked, :if => lambda {|value| !value.nil?}
1133
+
1134
+ @object = @klass.new
1135
+ @object.state = 1
1136
+ end
1137
+
1138
+ def test_should_use_custom_matcher
1139
+ assert_not_nil @state.matcher
1140
+ assert @state.matches?(1)
1141
+ assert !@state.matches?(nil)
1142
+ end
1143
+ end
1144
+
1145
+ class MachineWithCachedStateTest < Test::Unit::TestCase
1146
+ def setup
1147
+ @klass = Class.new
1148
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1149
+ @state = @machine.state :parked, :value => lambda {Object.new}, :cache => true
1150
+
1151
+ @object = @klass.new
1152
+ end
1153
+
1154
+ def test_should_use_evaluated_value
1155
+ assert_instance_of Object, @object.state
1156
+ end
1157
+
1158
+ def test_use_same_value_across_multiple_objects
1159
+ assert_equal @object.state, @klass.new.state
1160
+ end
1161
+ end
1162
+
1163
+ class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
1164
+ def setup
1165
+ @klass = Class.new
1166
+ @machine = StateMachine::Machine.new(@klass)
1167
+
1168
+ @parked, @idling = @machine.state :parked, :idling do
1169
+ def speed
1170
+ 0
1171
+ end
1172
+ end
1173
+ end
1174
+
1175
+ def test_should_define_behaviors_for_each_state
1176
+ assert_not_nil @parked.methods[:speed]
1177
+ assert_not_nil @idling.methods[:speed]
1178
+ end
1179
+
1180
+ def test_should_define_different_behaviors_for_each_state
1181
+ assert_not_equal @parked.methods[:speed], @idling.methods[:speed]
1182
+ end
1183
+ end
1184
+
1185
+ class MachineWithExistingStateTest < Test::Unit::TestCase
1186
+ def setup
1187
+ @klass = Class.new
1188
+ @machine = StateMachine::Machine.new(@klass)
1189
+ @state = @machine.state :parked
1190
+ @same_state = @machine.state :parked, :value => 1
1191
+ end
1192
+
1193
+ def test_should_not_create_a_new_state
1194
+ assert_same @state, @same_state
1195
+ end
1196
+
1197
+ def test_should_update_attributes
1198
+ assert_equal 1, @state.value
1199
+ end
1200
+
1201
+ def test_should_no_longer_be_able_to_look_up_state_by_original_value
1202
+ assert_nil @machine.states['parked', :value]
1203
+ end
1204
+
1205
+ def test_should_be_able_to_look_up_state_by_new_value
1206
+ assert_equal @state, @machine.states[1, :value]
1207
+ end
1208
+ end
1209
+
1210
+ class MachineWithOtherStates < Test::Unit::TestCase
1211
+ def setup
1212
+ @klass = Class.new
1213
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1214
+ @parked, @idling = @machine.other_states(:parked, :idling)
1215
+ end
1216
+
1217
+ def test_should_include_other_states_in_known_states
1218
+ assert_equal [@parked, @idling], @machine.states.to_a
1219
+ end
1220
+
1221
+ def test_should_use_default_value
1222
+ assert_equal 'idling', @idling.value
1223
+ end
1224
+
1225
+ def test_should_not_create_matcher
1226
+ assert_nil @idling.matcher
1227
+ end
1228
+ end
1229
+
1230
+ class MachineWithEventsTest < Test::Unit::TestCase
1231
+ def setup
1232
+ @klass = Class.new
1233
+ @machine = StateMachine::Machine.new(@klass)
1234
+ end
1235
+
1236
+ def test_should_return_the_created_event
1237
+ assert_instance_of StateMachine::Event, @machine.event(:ignite)
1238
+ end
1239
+
1240
+ def test_should_create_event_with_given_name
1241
+ event = @machine.event(:ignite) {}
1242
+ assert_equal :ignite, event.name
1243
+ end
1244
+
1245
+ def test_should_evaluate_block_within_event_context
1246
+ responded = false
1247
+ @machine.event :ignite do
1248
+ responded = respond_to?(:transition)
1249
+ end
1250
+
1251
+ assert responded
1252
+ end
1253
+
1254
+ def test_should_be_aliased_as_on
1255
+ event = @machine.on(:ignite) {}
1256
+ assert_equal :ignite, event.name
1257
+ end
1258
+
1259
+ def test_should_have_events
1260
+ event = @machine.event(:ignite)
1261
+ assert_equal [event], @machine.events.to_a
1262
+ end
1263
+ end
1264
+
1265
+ class MachineWithExistingEventTest < Test::Unit::TestCase
1266
+ def setup
1267
+ @machine = StateMachine::Machine.new(Class.new)
1268
+ @event = @machine.event(:ignite)
1269
+ @same_event = @machine.event(:ignite)
1270
+ end
1271
+
1272
+ def test_should_not_create_new_event
1273
+ assert_same @event, @same_event
1274
+ end
1275
+
1276
+ def test_should_allow_accessing_event_without_block
1277
+ assert_equal @event, @machine.event(:ignite)
1278
+ end
1279
+ end
1280
+
1281
+ class MachineWithEventsWithTransitionsTest < Test::Unit::TestCase
1282
+ def setup
1283
+ @klass = Class.new
1284
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1285
+ @event = @machine.event(:ignite) do
1286
+ transition :parked => :idling
1287
+ transition :stalled => :idling
1288
+ end
1289
+ end
1290
+
1291
+ def test_should_have_events
1292
+ assert_equal [@event], @machine.events.to_a
1293
+ end
1294
+
1295
+ def test_should_track_states_defined_in_event_transitions
1296
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
1297
+ end
1298
+
1299
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
1300
+ @machine.event :park do
1301
+ transition :idling => :parked
1302
+ end
1303
+
1304
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
1305
+ end
1306
+
1307
+ def test_should_track_state_from_new_events
1308
+ @machine.event :shift_up do
1309
+ transition :idling => :first_gear
1310
+ end
1311
+
1312
+ assert_equal [:parked, :idling, :stalled, :first_gear], @machine.states.map {|state| state.name}
1313
+ end
1314
+ end
1315
+
1316
+ class MachineWithMultipleEventsTest < Test::Unit::TestCase
1317
+ def setup
1318
+ @klass = Class.new
1319
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1320
+ @park, @shift_down = @machine.event(:park, :shift_down) do
1321
+ transition :first_gear => :parked
1322
+ end
1323
+ end
1324
+
1325
+ def test_should_have_events
1326
+ assert_equal [@park, @shift_down], @machine.events.to_a
1327
+ end
1328
+
1329
+ def test_should_define_transitions_for_each_event
1330
+ [@park, @shift_down].each {|event| assert_equal 1, event.guards.size}
1331
+ end
1332
+
1333
+ def test_should_transition_the_same_for_each_event
1334
+ object = @klass.new
1335
+ object.state = 'first_gear'
1336
+ object.park
1337
+ assert_equal 'parked', object.state
1338
+
1339
+ object = @klass.new
1340
+ object.state = 'first_gear'
1341
+ object.shift_down
1342
+ assert_equal 'parked', object.state
1343
+ end
1344
+ end
1345
+
1346
+ class MachineWithTransitionCallbacksTest < Test::Unit::TestCase
1347
+ def setup
1348
+ @klass = Class.new do
1349
+ attr_accessor :callbacks
1350
+ end
1351
+
1352
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1353
+ @event = @machine.event :ignite do
1354
+ transition :parked => :idling
1355
+ end
1356
+
1357
+ @object = @klass.new
1358
+ @object.callbacks = []
1359
+ end
1360
+
1361
+ def test_should_not_raise_exception_if_implicit_option_specified
1362
+ assert_nothing_raised {@machine.before_transition :invalid => :valid, :do => lambda {}}
1363
+ end
1364
+
1365
+ def test_should_raise_exception_if_method_not_specified
1366
+ exception = assert_raise(ArgumentError) {@machine.before_transition :to => :idling}
1367
+ assert_equal 'Method(s) for callback must be specified', exception.message
1368
+ end
1369
+
1370
+ def test_should_invoke_callbacks_during_transition
1371
+ @machine.before_transition lambda {|object| object.callbacks << 'before'}
1372
+ @machine.after_transition lambda {|object| object.callbacks << 'after'}
1373
+
1374
+ @event.fire(@object)
1375
+ assert_equal %w(before after), @object.callbacks
1376
+ end
1377
+
1378
+ def test_should_support_from_requirement
1379
+ @machine.before_transition :from => :parked, :do => lambda {|object| object.callbacks << :parked}
1380
+ @machine.before_transition :from => :idling, :do => lambda {|object| object.callbacks << :idling}
1381
+
1382
+ @event.fire(@object)
1383
+ assert_equal [:parked], @object.callbacks
1384
+ end
1385
+
1386
+ def test_should_support_except_from_requirement
1387
+ @machine.before_transition :except_from => :parked, :do => lambda {|object| object.callbacks << :parked}
1388
+ @machine.before_transition :except_from => :idling, :do => lambda {|object| object.callbacks << :idling}
1389
+
1390
+ @event.fire(@object)
1391
+ assert_equal [:idling], @object.callbacks
1392
+ end
1393
+
1394
+ def test_should_support_to_requirement
1395
+ @machine.before_transition :to => :parked, :do => lambda {|object| object.callbacks << :parked}
1396
+ @machine.before_transition :to => :idling, :do => lambda {|object| object.callbacks << :idling}
1397
+
1398
+ @event.fire(@object)
1399
+ assert_equal [:idling], @object.callbacks
1400
+ end
1401
+
1402
+ def test_should_support_except_to_requirement
1403
+ @machine.before_transition :except_to => :parked, :do => lambda {|object| object.callbacks << :parked}
1404
+ @machine.before_transition :except_to => :idling, :do => lambda {|object| object.callbacks << :idling}
1405
+
1406
+ @event.fire(@object)
1407
+ assert_equal [:parked], @object.callbacks
1408
+ end
1409
+
1410
+ def test_should_support_on_requirement
1411
+ @machine.before_transition :on => :park, :do => lambda {|object| object.callbacks << :park}
1412
+ @machine.before_transition :on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
1413
+
1414
+ @event.fire(@object)
1415
+ assert_equal [:ignite], @object.callbacks
1416
+ end
1417
+
1418
+ def test_should_support_except_on_requirement
1419
+ @machine.before_transition :except_on => :park, :do => lambda {|object| object.callbacks << :park}
1420
+ @machine.before_transition :except_on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
1421
+
1422
+ @event.fire(@object)
1423
+ assert_equal [:park], @object.callbacks
1424
+ end
1425
+
1426
+ def test_should_support_implicit_requirement
1427
+ @machine.before_transition :parked => :idling, :do => lambda {|object| object.callbacks << :parked}
1428
+ @machine.before_transition :idling => :parked, :do => lambda {|object| object.callbacks << :idling}
1429
+
1430
+ @event.fire(@object)
1431
+ assert_equal [:parked], @object.callbacks
1432
+ end
1433
+
1434
+ def test_should_track_states_defined_in_transition_callbacks
1435
+ @machine.before_transition :parked => :idling, :do => lambda {}
1436
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
1437
+
1438
+ assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name}
1439
+ end
1440
+
1441
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
1442
+ @machine.before_transition :parked => :idling, :do => lambda {}
1443
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
1444
+ @machine.after_transition :parked => :idling, :do => lambda {}
1445
+
1446
+ assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name}
1447
+ end
1448
+
1449
+ def test_should_define_predicates_for_each_state
1450
+ [:parked?, :idling?].each {|predicate| assert @object.respond_to?(predicate)}
1451
+ end
1452
+ end
1453
+
1454
+ class MachineWithOwnerSubclassTest < Test::Unit::TestCase
1455
+ def setup
1456
+ @klass = Class.new
1457
+ @machine = StateMachine::Machine.new(@klass)
1458
+ @subclass = Class.new(@klass)
1459
+ end
1460
+
1461
+ def test_should_have_a_different_collection_of_state_machines
1462
+ assert_not_same @klass.state_machines, @subclass.state_machines
1463
+ end
1464
+
1465
+ def test_should_have_the_same_attribute_associated_state_machines
1466
+ assert_equal @klass.state_machines, @subclass.state_machines
1467
+ end
1468
+ end
1469
+
1470
+ class MachineWithExistingMachinesOnOwnerClassTest < Test::Unit::TestCase
1471
+ def setup
1472
+ @klass = Class.new
1473
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1474
+ @second_machine = StateMachine::Machine.new(@klass, :status, :initial => :idling)
1475
+ @object = @klass.new
1476
+ end
1477
+
1478
+ def test_should_track_each_state_machine
1479
+ expected = {:state => @machine, :status => @second_machine}
1480
+ assert_equal expected, @klass.state_machines
1481
+ end
1482
+
1483
+ def test_should_initialize_state_for_both_machines
1484
+ assert_equal 'parked', @object.state
1485
+ assert_equal 'idling', @object.status
1486
+ end
1487
+ end
1488
+
1489
+ class MachineWithNamespaceTest < Test::Unit::TestCase
1490
+ def setup
1491
+ @klass = Class.new
1492
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do
1493
+ event :enable do
1494
+ transition :off => :active
1495
+ end
1496
+
1497
+ event :disable do
1498
+ transition :active => :off
1499
+ end
1500
+ end
1501
+ @object = @klass.new
1502
+ end
1503
+
1504
+ def test_should_namespace_state_predicates
1505
+ [:alarm_active?, :alarm_off?].each do |name|
1506
+ assert @object.respond_to?(name)
1507
+ end
1508
+ end
1509
+
1510
+ def test_should_namespace_event_checks
1511
+ [:can_enable_alarm?, :can_disable_alarm?].each do |name|
1512
+ assert @object.respond_to?(name)
1513
+ end
1514
+ end
1515
+
1516
+ def test_should_namespace_event_transition_readers
1517
+ [:enable_alarm_transition, :disable_alarm_transition].each do |name|
1518
+ assert @object.respond_to?(name)
1519
+ end
1520
+ end
1521
+
1522
+ def test_should_namespace_events
1523
+ [:enable_alarm, :disable_alarm].each do |name|
1524
+ assert @object.respond_to?(name)
1525
+ end
1526
+ end
1527
+
1528
+ def test_should_namespace_bang_events
1529
+ [:enable_alarm!, :disable_alarm!].each do |name|
1530
+ assert @object.respond_to?(name)
1531
+ end
1532
+ end
1533
+ end
1534
+
1535
+ class MachineWithCustomNameTest < Test::Unit::TestCase
1536
+ def setup
1537
+ StateMachine::Integrations.const_set('Custom', Module.new do
1538
+ class << self; attr_reader :defaults; end
1539
+ @defaults = {:action => :save, :use_transactions => false}
1540
+
1541
+ def create_with_scope(name)
1542
+ lambda {}
1543
+ end
1544
+
1545
+ def create_without_scope(name)
1546
+ lambda {}
1547
+ end
1548
+ end)
1549
+
1550
+ @klass = Class.new
1551
+ @machine = StateMachine::Machine.new(@klass, :state_id, :as => 'state', :initial => :active, :integration => :custom) do
1552
+ event :ignite do
1553
+ transition :parked => :idling
1554
+ end
1555
+ end
1556
+ @object = @klass.new
1557
+ end
1558
+
1559
+ def test_should_not_define_a_reader_attribute_for_the_attribute
1560
+ assert !@object.respond_to?(:state)
1561
+ end
1562
+
1563
+ def test_should_not_define_a_writer_attribute_for_the_attribute
1564
+ assert !@object.respond_to?(:state=)
1565
+ end
1566
+
1567
+ def test_should_define_a_predicate_for_the_attribute
1568
+ assert @object.respond_to?(:state?)
1569
+ end
1570
+
1571
+ def test_should_define_a_name_reader_for_the_attribute
1572
+ assert @object.respond_to?(:state_name)
1573
+ end
1574
+
1575
+ def test_should_define_an_event_reader_for_the_attribute
1576
+ assert @object.respond_to?(:state_events)
1577
+ end
1578
+
1579
+ def test_should_define_a_transition_reader_for_the_attribute
1580
+ assert @object.respond_to?(:state_transitions)
1581
+ end
1582
+
1583
+ def test_should_define_singular_with_scope
1584
+ assert @klass.respond_to?(:with_state)
1585
+ end
1586
+
1587
+ def test_should_define_singular_without_scope
1588
+ assert @klass.respond_to?(:without_state)
1589
+ end
1590
+
1591
+ def test_should_define_plural_with_scope
1592
+ assert @klass.respond_to?(:with_states)
1593
+ end
1594
+
1595
+ def test_should_define_plural_without_scope
1596
+ assert @klass.respond_to?(:without_states)
1597
+ end
1598
+
1599
+ def teardown
1600
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1601
+ end
1602
+ end
1603
+
1604
+ class MachineFinderWithoutExistingMachineTest < Test::Unit::TestCase
1605
+ def setup
1606
+ @klass = Class.new
1607
+ @machine = StateMachine::Machine.find_or_create(@klass)
1608
+ end
1609
+
1610
+ def test_should_accept_a_block
1611
+ called = false
1612
+ StateMachine::Machine.find_or_create(Class.new) do
1613
+ called = respond_to?(:event)
1614
+ end
1615
+
1616
+ assert called
1617
+ end
1618
+
1619
+ def test_should_create_a_new_machine
1620
+ assert_not_nil @machine
1621
+ end
1622
+
1623
+ def test_should_use_default_state
1624
+ assert_equal :state, @machine.attribute
1625
+ end
1626
+ end
1627
+
1628
+ class MachineFinderWithExistingOnSameClassTest < Test::Unit::TestCase
1629
+ def setup
1630
+ @klass = Class.new
1631
+ @existing_machine = StateMachine::Machine.new(@klass)
1632
+ @machine = StateMachine::Machine.find_or_create(@klass)
1633
+ end
1634
+
1635
+ def test_should_accept_a_block
1636
+ called = false
1637
+ StateMachine::Machine.find_or_create(@klass) do
1638
+ called = respond_to?(:event)
1639
+ end
1640
+
1641
+ assert called
1642
+ end
1643
+
1644
+ def test_should_not_create_a_new_machine
1645
+ assert_same @machine, @existing_machine
1646
+ end
1647
+ end
1648
+
1649
+ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
1650
+ def setup
1651
+ integration = Module.new do
1652
+ def self.matches?(klass)
1653
+ false
1654
+ end
1655
+ end
1656
+ StateMachine::Integrations.const_set('Custom', integration)
1657
+
1658
+ @base_class = Class.new
1659
+ @base_machine = StateMachine::Machine.new(@base_class, :status, :action => :save, :integration => :custom)
1660
+ @base_machine.event(:ignite) {}
1661
+ @base_machine.before_transition(lambda {})
1662
+ @base_machine.after_transition(lambda {})
1663
+
1664
+ @klass = Class.new(@base_class)
1665
+ @machine = StateMachine::Machine.find_or_create(@klass, :status) {}
1666
+ end
1667
+
1668
+ def test_should_accept_a_block
1669
+ called = false
1670
+ StateMachine::Machine.find_or_create(Class.new(@base_class)) do
1671
+ called = respond_to?(:event)
1672
+ end
1673
+
1674
+ assert called
1675
+ end
1676
+
1677
+ def test_should_not_create_a_new_machine_if_no_block_or_options
1678
+ machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status)
1679
+
1680
+ assert_same machine, @base_machine
1681
+ end
1682
+
1683
+ def test_should_create_a_new_machine_if_given_options
1684
+ machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
1685
+
1686
+ assert_not_nil machine
1687
+ assert_not_same machine, @base_machine
1688
+ end
1689
+
1690
+ def test_should_create_a_new_machine_if_given_block
1691
+ assert_not_nil @machine
1692
+ assert_not_same @machine, @base_machine
1693
+ end
1694
+
1695
+ def test_should_copy_the_base_attribute
1696
+ assert_equal :status, @machine.attribute
1697
+ end
1698
+
1699
+ def test_should_copy_the_base_configuration
1700
+ assert_equal :save, @machine.action
1701
+ end
1702
+
1703
+ def test_should_copy_events
1704
+ # Can't assert equal arrays since their machines change
1705
+ assert_equal 1, @machine.events.length
1706
+ end
1707
+
1708
+ def test_should_copy_before_callbacks
1709
+ assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before]
1710
+ end
1711
+
1712
+ def test_should_copy_after_transitions
1713
+ assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after]
1714
+ end
1715
+
1716
+ def test_should_use_the_same_integration
1717
+ assert (class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom)
1718
+ end
1719
+
1720
+ def teardown
1721
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1722
+ end
1723
+ end
1724
+
1725
+ class MachineFinderCustomOptionsTest < Test::Unit::TestCase
1726
+ def setup
1727
+ @klass = Class.new
1728
+ @machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
1729
+ @object = @klass.new
1730
+ end
1731
+
1732
+ def test_should_use_custom_attribute
1733
+ assert_equal :status, @machine.attribute
1734
+ end
1735
+
1736
+ def test_should_set_custom_initial_state
1737
+ assert_equal :parked, @machine.initial_state(@object).name
1738
+ end
1739
+ end
1740
+
1741
+ begin
1742
+ # Load library
1743
+ require 'rubygems'
1744
+ require 'graphviz'
1745
+
1746
+ class MachineDrawingTest < Test::Unit::TestCase
1747
+ def setup
1748
+ @klass = Class.new do
1749
+ def self.name; 'Vehicle'; end
1750
+ end
1751
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1752
+ @machine.event :ignite do
1753
+ transition :parked => :idling
1754
+ end
1755
+ end
1756
+
1757
+ def test_should_raise_exception_if_invalid_option_specified
1758
+ assert_raise(ArgumentError) {@machine.draw(:invalid => true)}
1759
+ end
1760
+
1761
+ def test_should_save_file_with_class_name_by_default
1762
+ graph = @machine.draw(:output => false)
1763
+ assert_equal './Vehicle_state.png', graph.instance_variable_get('@filename')
1764
+ end
1765
+
1766
+ def test_should_allow_base_name_to_be_customized
1767
+ graph = @machine.draw(:name => 'machine', :output => false)
1768
+ assert_equal './machine.png', graph.instance_variable_get('@filename')
1769
+ end
1770
+
1771
+ def test_should_allow_format_to_be_customized
1772
+ graph = @machine.draw(:format => 'jpg', :output => false)
1773
+ assert_equal './Vehicle_state.jpg', graph.instance_variable_get('@filename')
1774
+ assert_equal 'jpg', graph.instance_variable_get('@format')
1775
+ end
1776
+
1777
+ def test_should_allow_path_to_be_customized
1778
+ graph = @machine.draw(:path => "#{File.dirname(__FILE__)}/", :output => false)
1779
+ assert_equal "#{File.dirname(__FILE__)}/Vehicle_state.png", graph.instance_variable_get('@filename')
1780
+ end
1781
+
1782
+ def test_should_allow_orientation_to_be_landscape
1783
+ graph = @machine.draw(:orientation => 'landscape', :output => false)
1784
+ assert_equal 'LR', graph['rankdir']
1785
+ end
1786
+
1787
+ def test_should_allow_orientation_to_be_portrait
1788
+ graph = @machine.draw(:orientation => 'portrait', :output => false)
1789
+ assert_equal 'TB', graph['rankdir']
1790
+ end
1791
+ end
1792
+
1793
+ class MachineDrawingWithIntegerStatesTest < Test::Unit::TestCase
1794
+ def setup
1795
+ @klass = Class.new do
1796
+ def self.name; 'Vehicle'; end
1797
+ end
1798
+ @machine = StateMachine::Machine.new(@klass, :state_id, :initial => :parked)
1799
+ @machine.event :ignite do
1800
+ transition :parked => :idling
1801
+ end
1802
+ @machine.state :parked, :value => 1
1803
+ @machine.state :idling, :value => 2
1804
+ @graph = @machine.draw
1805
+ end
1806
+
1807
+ def test_should_draw_all_states
1808
+ assert_equal 3, @graph.node_count
1809
+ end
1810
+
1811
+ def test_should_draw_all_events
1812
+ assert_equal 2, @graph.edge_count
1813
+ end
1814
+
1815
+ def test_should_draw_machine
1816
+ assert File.exist?('./Vehicle_state_id.png')
1817
+ ensure
1818
+ FileUtils.rm('./Vehicle_state_id.png')
1819
+ end
1820
+ end
1821
+
1822
+ class MachineDrawingWithNilStatesTest < Test::Unit::TestCase
1823
+ def setup
1824
+ @klass = Class.new do
1825
+ def self.name; 'Vehicle'; end
1826
+ end
1827
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1828
+ @machine.event :ignite do
1829
+ transition :parked => :idling
1830
+ end
1831
+ @machine.state :parked, :value => nil
1832
+ @graph = @machine.draw
1833
+ end
1834
+
1835
+ def test_should_draw_all_states
1836
+ assert_equal 3, @graph.node_count
1837
+ end
1838
+
1839
+ def test_should_draw_all_events
1840
+ assert_equal 2, @graph.edge_count
1841
+ end
1842
+
1843
+ def test_should_draw_machine
1844
+ assert File.exist?('./Vehicle_state.png')
1845
+ ensure
1846
+ FileUtils.rm('./Vehicle_state.png')
1847
+ end
1848
+ end
1849
+
1850
+ class MachineDrawingWithDynamicStatesTest < Test::Unit::TestCase
1851
+ def setup
1852
+ @klass = Class.new do
1853
+ def self.name; 'Vehicle'; end
1854
+ end
1855
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1856
+ @machine.event :activate do
1857
+ transition :parked => :idling
1858
+ end
1859
+ @machine.state :idling, :value => lambda {Time.now}
1860
+ @graph = @machine.draw
1861
+ end
1862
+
1863
+ def test_should_draw_all_states
1864
+ assert_equal 3, @graph.node_count
1865
+ end
1866
+
1867
+ def test_should_draw_all_events
1868
+ assert_equal 2, @graph.edge_count
1869
+ end
1870
+
1871
+ def test_should_draw_machine
1872
+ assert File.exist?('./Vehicle_state.png')
1873
+ ensure
1874
+ FileUtils.rm('./Vehicle_state.png')
1875
+ end
1876
+ end
1877
+
1878
+ class MachineClassDrawingTest < Test::Unit::TestCase
1879
+ def setup
1880
+ @klass = Class.new do
1881
+ def self.name; 'Vehicle'; end
1882
+ end
1883
+ @machine = StateMachine::Machine.new(@klass)
1884
+ @machine.event :ignite do
1885
+ transition :parked => :idling
1886
+ end
1887
+ end
1888
+
1889
+ def test_should_raise_exception_if_no_class_names_specified
1890
+ exception = assert_raise(ArgumentError) {StateMachine::Machine.draw(nil)}
1891
+ assert_equal 'At least one class must be specified', exception.message
1892
+ end
1893
+
1894
+ def test_should_load_files
1895
+ StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../classes/switch.rb")
1896
+ assert defined?(::Switch)
1897
+ ensure
1898
+ FileUtils.rm('./Switch_state.png')
1899
+ end
1900
+
1901
+ def test_should_allow_path_and_format_to_be_customized
1902
+ StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../classes/switch.rb", :path => "#{File.dirname(__FILE__)}/", :format => 'jpg')
1903
+ assert File.exist?("#{File.dirname(__FILE__)}/Switch_state.jpg")
1904
+ ensure
1905
+ FileUtils.rm("#{File.dirname(__FILE__)}/Switch_state.jpg")
1906
+ end
1907
+ end
1908
+ rescue LoadError
1909
+ $stderr.puts 'Skipping GraphViz StateMachine::Machine tests. `gem install ruby-graphviz` and try again.'
1910
+ end