pluginaweek-state_machine 0.7.6

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