verborghs-state_machine 0.9.4

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 (89) hide show
  1. data/CHANGELOG.rdoc +360 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +635 -0
  4. data/Rakefile +77 -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/assertions.rb +36 -0
  28. data/lib/state_machine/callback.rb +241 -0
  29. data/lib/state_machine/condition_proxy.rb +106 -0
  30. data/lib/state_machine/eval_helpers.rb +83 -0
  31. data/lib/state_machine/event.rb +267 -0
  32. data/lib/state_machine/event_collection.rb +122 -0
  33. data/lib/state_machine/extensions.rb +149 -0
  34. data/lib/state_machine/guard.rb +230 -0
  35. data/lib/state_machine/initializers/merb.rb +1 -0
  36. data/lib/state_machine/initializers/rails.rb +5 -0
  37. data/lib/state_machine/initializers.rb +4 -0
  38. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  40. data/lib/state_machine/integrations/active_model.rb +445 -0
  41. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  42. data/lib/state_machine/integrations/active_record.rb +522 -0
  43. data/lib/state_machine/integrations/data_mapper/observer.rb +175 -0
  44. data/lib/state_machine/integrations/data_mapper.rb +379 -0
  45. data/lib/state_machine/integrations/mongo_mapper.rb +309 -0
  46. data/lib/state_machine/integrations/sequel.rb +356 -0
  47. data/lib/state_machine/integrations.rb +83 -0
  48. data/lib/state_machine/machine.rb +1645 -0
  49. data/lib/state_machine/machine_collection.rb +64 -0
  50. data/lib/state_machine/matcher.rb +123 -0
  51. data/lib/state_machine/matcher_helpers.rb +54 -0
  52. data/lib/state_machine/node_collection.rb +152 -0
  53. data/lib/state_machine/state.rb +260 -0
  54. data/lib/state_machine/state_collection.rb +112 -0
  55. data/lib/state_machine/transition.rb +399 -0
  56. data/lib/state_machine/transition_collection.rb +244 -0
  57. data/lib/state_machine.rb +421 -0
  58. data/lib/tasks/state_machine.rake +1 -0
  59. data/lib/tasks/state_machine.rb +27 -0
  60. data/test/files/en.yml +9 -0
  61. data/test/files/switch.rb +11 -0
  62. data/test/functional/state_machine_test.rb +980 -0
  63. data/test/test_helper.rb +4 -0
  64. data/test/unit/assertions_test.rb +40 -0
  65. data/test/unit/callback_test.rb +728 -0
  66. data/test/unit/condition_proxy_test.rb +328 -0
  67. data/test/unit/eval_helpers_test.rb +222 -0
  68. data/test/unit/event_collection_test.rb +324 -0
  69. data/test/unit/event_test.rb +795 -0
  70. data/test/unit/guard_test.rb +909 -0
  71. data/test/unit/integrations/active_model_test.rb +956 -0
  72. data/test/unit/integrations/active_record_test.rb +1918 -0
  73. data/test/unit/integrations/data_mapper_test.rb +1814 -0
  74. data/test/unit/integrations/mongo_mapper_test.rb +1382 -0
  75. data/test/unit/integrations/sequel_test.rb +1492 -0
  76. data/test/unit/integrations_test.rb +50 -0
  77. data/test/unit/invalid_event_test.rb +7 -0
  78. data/test/unit/invalid_transition_test.rb +7 -0
  79. data/test/unit/machine_collection_test.rb +565 -0
  80. data/test/unit/machine_test.rb +2349 -0
  81. data/test/unit/matcher_helpers_test.rb +37 -0
  82. data/test/unit/matcher_test.rb +155 -0
  83. data/test/unit/node_collection_test.rb +207 -0
  84. data/test/unit/state_collection_test.rb +280 -0
  85. data/test/unit/state_machine_test.rb +31 -0
  86. data/test/unit/state_test.rb +848 -0
  87. data/test/unit/transition_collection_test.rb +2098 -0
  88. data/test/unit/transition_test.rb +1384 -0
  89. metadata +176 -0
@@ -0,0 +1,2349 @@
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_model_integration
73
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveModel)
74
+ end
75
+
76
+ def test_should_not_be_extended_by_the_active_record_integration
77
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveRecord)
78
+ end
79
+
80
+ def test_should_not_be_extended_by_the_datamapper_integration
81
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::DataMapper)
82
+ end
83
+
84
+ def test_should_not_be_extended_by_the_mongo_mapper_integration
85
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::MongoMapper)
86
+ end
87
+
88
+ def test_should_not_be_extended_by_the_sequel_integration
89
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Sequel)
90
+ end
91
+
92
+ def test_should_define_a_reader_attribute_for_the_attribute
93
+ assert @object.respond_to?(:state)
94
+ end
95
+
96
+ def test_should_define_a_writer_attribute_for_the_attribute
97
+ assert @object.respond_to?(:state=)
98
+ end
99
+
100
+ def test_should_define_a_predicate_for_the_attribute
101
+ assert @object.respond_to?(:state?)
102
+ end
103
+
104
+ def test_should_define_a_name_reader_for_the_attribute
105
+ assert @object.respond_to?(:state_name)
106
+ end
107
+
108
+ def test_should_define_an_event_reader_for_the_attribute
109
+ assert @object.respond_to?(:state_events)
110
+ end
111
+
112
+ def test_should_define_a_transition_reader_for_the_attribute
113
+ assert @object.respond_to?(:state_transitions)
114
+ end
115
+
116
+ def test_should_not_define_an_event_attribute_reader
117
+ assert !@object.respond_to?(:state_event)
118
+ end
119
+
120
+ def test_should_not_define_an_event_attribute_writer
121
+ assert !@object.respond_to?(:state_event=)
122
+ end
123
+
124
+ def test_should_not_define_an_event_transition_attribute_reader
125
+ assert !@object.respond_to?(:state_event_transition)
126
+ end
127
+
128
+ def test_should_not_define_an_event_transition_attribute_writer
129
+ assert !@object.respond_to?(:state_event_transition=)
130
+ end
131
+
132
+ def test_should_define_a_human_attribute_name_reader_for_the_attribute
133
+ assert @klass.respond_to?(:human_state_name)
134
+ end
135
+
136
+ def test_should_define_a_human_event_name_reader_for_the_attribute
137
+ assert @klass.respond_to?(:human_state_event_name)
138
+ end
139
+
140
+ def test_should_not_define_singular_with_scope
141
+ assert !@klass.respond_to?(:with_state)
142
+ end
143
+
144
+ def test_should_not_define_singular_without_scope
145
+ assert !@klass.respond_to?(:without_state)
146
+ end
147
+
148
+ def test_should_not_define_plural_with_scope
149
+ assert !@klass.respond_to?(:with_states)
150
+ end
151
+
152
+ def test_should_not_define_plural_without_scope
153
+ assert !@klass.respond_to?(:without_states)
154
+ end
155
+
156
+ def test_should_extend_owner_class_with_class_methods
157
+ assert (class << @klass; ancestors; end).include?(StateMachine::ClassMethods)
158
+ end
159
+
160
+ def test_should_include_instance_methods_in_owner_class
161
+ assert @klass.included_modules.include?(StateMachine::InstanceMethods)
162
+ end
163
+
164
+ def test_should_define_state_machines_reader
165
+ expected = {:state => @machine}
166
+ assert_equal expected, @klass.state_machines
167
+ end
168
+ end
169
+
170
+ class MachineWithCustomNameTest < Test::Unit::TestCase
171
+ def setup
172
+ @klass = Class.new
173
+ @machine = StateMachine::Machine.new(@klass, :status)
174
+ @object = @klass.new
175
+ end
176
+
177
+ def test_should_use_custom_name
178
+ assert_equal :status, @machine.name
179
+ end
180
+
181
+ def test_should_use_custom_name_for_attribute
182
+ assert_equal :status, @machine.attribute
183
+ end
184
+
185
+ def test_should_prefix_custom_attributes_with_custom_name
186
+ assert_equal :status_event, @machine.attribute(:event)
187
+ end
188
+
189
+ def test_should_define_a_reader_attribute_for_the_attribute
190
+ assert @object.respond_to?(:status)
191
+ end
192
+
193
+ def test_should_define_a_writer_attribute_for_the_attribute
194
+ assert @object.respond_to?(:status=)
195
+ end
196
+
197
+ def test_should_define_a_predicate_for_the_attribute
198
+ assert @object.respond_to?(:status?)
199
+ end
200
+
201
+ def test_should_define_a_name_reader_for_the_attribute
202
+ assert @object.respond_to?(:status_name)
203
+ end
204
+
205
+ def test_should_define_an_event_reader_for_the_attribute
206
+ assert @object.respond_to?(:status_events)
207
+ end
208
+
209
+ def test_should_define_a_transition_reader_for_the_attribute
210
+ assert @object.respond_to?(:status_transitions)
211
+ end
212
+
213
+ def test_should_define_a_human_attribute_name_reader_for_the_attribute
214
+ assert @klass.respond_to?(:human_status_name)
215
+ end
216
+
217
+ def test_should_define_a_human_event_name_reader_for_the_attribute
218
+ assert @klass.respond_to?(:human_status_event_name)
219
+ end
220
+ end
221
+
222
+ class MachineWithStaticInitialStateTest < Test::Unit::TestCase
223
+ def setup
224
+ @klass = Class.new do
225
+ def initialize(attributes = {})
226
+ attributes.each {|attr, value| send("#{attr}=", value)}
227
+ super()
228
+ end
229
+ end
230
+
231
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
232
+ end
233
+
234
+ def test_should_not_have_dynamic_initial_state
235
+ assert !@machine.dynamic_initial_state?
236
+ end
237
+
238
+ def test_should_have_an_initial_state
239
+ object = @klass.new
240
+ assert_equal 'parked', @machine.initial_state(object).value
241
+ end
242
+
243
+ def test_should_write_to_attribute_when_initializing_state
244
+ object = @klass.allocate
245
+ @machine.initialize_state(object)
246
+ assert_equal 'parked', object.state
247
+ end
248
+
249
+ def test_should_set_initial_on_state_object
250
+ assert @machine.state(:parked).initial
251
+ end
252
+
253
+ def test_should_set_initial_state_if_existing_is_nil
254
+ object = @klass.new(:state => nil)
255
+ assert_equal 'parked', object.state
256
+ end
257
+
258
+ def test_should_set_initial_state_if_existing_is_empty
259
+ object = @klass.new(:state => '')
260
+ assert_equal 'parked', object.state
261
+ end
262
+
263
+ def test_should_not_set_initial_state_if_existing_is_not_empty
264
+ object = @klass.new(:state => 'idling')
265
+ assert_equal 'idling', object.state
266
+ end
267
+
268
+ def test_should_set_initial_state_prior_to_initialization
269
+ base = Class.new do
270
+ attr_accessor :state_on_init
271
+
272
+ def initialize
273
+ self.state_on_init = state
274
+ end
275
+ end
276
+ klass = Class.new(base)
277
+ machine = StateMachine::Machine.new(klass, :initial => :parked)
278
+
279
+ assert_equal 'parked', klass.new.state_on_init
280
+ end
281
+
282
+ def test_should_be_included_in_known_states
283
+ assert_equal [:parked], @machine.states.keys
284
+ end
285
+ end
286
+
287
+ class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
288
+ def setup
289
+ @klass = Class.new do
290
+ attr_accessor :initial_state
291
+ end
292
+ @machine = StateMachine::Machine.new(@klass, :initial => lambda {|object| object.initial_state || :default})
293
+ @machine.state :parked, :idling, :default
294
+ @object = @klass.new
295
+ end
296
+
297
+ def test_should_have_dynamic_initial_state
298
+ assert @machine.dynamic_initial_state?
299
+ end
300
+
301
+ def test_should_use_the_record_for_determining_the_initial_state
302
+ @object.initial_state = :parked
303
+ assert_equal :parked, @machine.initial_state(@object).name
304
+
305
+ @object.initial_state = :idling
306
+ assert_equal :idling, @machine.initial_state(@object).name
307
+ end
308
+
309
+ def test_should_write_to_attribute_when_initializing_state
310
+ object = @klass.allocate
311
+ object.initial_state = :parked
312
+ @machine.initialize_state(object)
313
+ assert_equal 'parked', object.state
314
+ end
315
+
316
+ def test_should_set_initial_state_on_created_object
317
+ assert_equal 'default', @object.state
318
+ end
319
+
320
+ def test_should_set_initial_state_after_initialization
321
+ base = Class.new do
322
+ attr_accessor :state_on_init
323
+
324
+ def initialize
325
+ self.state_on_init = state
326
+ end
327
+ end
328
+ klass = Class.new(base)
329
+ machine = StateMachine::Machine.new(klass, :initial => lambda {|object| :parked})
330
+ machine.state :parked
331
+
332
+ assert_nil klass.new.state_on_init
333
+ end
334
+
335
+ def test_should_not_be_included_in_known_states
336
+ assert_equal [:parked, :idling, :default], @machine.states.map {|state| state.name}
337
+ end
338
+ end
339
+
340
+ class MachineWithCustomActionTest < Test::Unit::TestCase
341
+ def setup
342
+ @machine = StateMachine::Machine.new(Class.new, :action => :save)
343
+ end
344
+
345
+ def test_should_use_the_custom_action
346
+ assert_equal :save, @machine.action
347
+ end
348
+ end
349
+
350
+ class MachineWithNilActionTest < Test::Unit::TestCase
351
+ def setup
352
+ integration = Module.new do
353
+ class << self; attr_reader :defaults; end
354
+ @defaults = {:action => :save}
355
+ end
356
+ StateMachine::Integrations.const_set('Custom', integration)
357
+ @machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom)
358
+ end
359
+
360
+ def test_should_have_a_nil_action
361
+ assert_nil @machine.action
362
+ end
363
+
364
+ def teardown
365
+ StateMachine::Integrations.send(:remove_const, 'Custom')
366
+ end
367
+ end
368
+
369
+ class MachineWithoutIntegrationTest < Test::Unit::TestCase
370
+ def setup
371
+ @klass = Class.new
372
+ @machine = StateMachine::Machine.new(@klass)
373
+ @object = @klass.new
374
+ end
375
+
376
+ def test_transaction_should_yield
377
+ @yielded = false
378
+ @machine.within_transaction(@object) do
379
+ @yielded = true
380
+ end
381
+
382
+ assert @yielded
383
+ end
384
+
385
+ def test_invalidation_should_do_nothing
386
+ assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
387
+ end
388
+
389
+ def test_reset_should_do_nothing
390
+ assert_nil @machine.reset(@object)
391
+ end
392
+ end
393
+
394
+ class MachineWithCustomIntegrationTest < Test::Unit::TestCase
395
+ def setup
396
+ integration = Module.new do
397
+ def self.matches?(klass)
398
+ true
399
+ end
400
+ end
401
+
402
+ StateMachine::Integrations.const_set('Custom', integration)
403
+ end
404
+
405
+ def test_should_be_extended_by_the_integration_if_explicit
406
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom)
407
+ assert (class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
408
+ end
409
+
410
+ def test_should_be_extended_by_the_integration_if_implicit
411
+ machine = StateMachine::Machine.new(Class.new)
412
+ assert (class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
413
+ end
414
+
415
+ def test_should_not_be_extended_by_the_integration_if_nil
416
+ machine = StateMachine::Machine.new(Class.new, :integration => nil)
417
+ assert !(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
418
+ end
419
+
420
+ def test_should_not_be_extended_by_the_integration_if_false
421
+ machine = StateMachine::Machine.new(Class.new, :integration => false)
422
+ assert !(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
423
+ end
424
+
425
+ def teardown
426
+ StateMachine::Integrations.send(:remove_const, 'Custom')
427
+ end
428
+ end
429
+
430
+ class MachineWithIntegrationTest < Test::Unit::TestCase
431
+ def setup
432
+ StateMachine::Integrations.const_set('Custom', Module.new do
433
+ class << self; attr_reader :defaults; end
434
+ @defaults = {:action => :save, :use_transactions => false}
435
+
436
+ attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
437
+
438
+ def after_initialize
439
+ @initialized = true
440
+ end
441
+
442
+ def create_with_scope(name)
443
+ (@with_scopes ||= []) << name
444
+ lambda {}
445
+ end
446
+
447
+ def create_without_scope(name)
448
+ (@without_scopes ||= []) << name
449
+ lambda {}
450
+ end
451
+
452
+ def transaction(object)
453
+ @ran_transaction = true
454
+ yield
455
+ end
456
+ end)
457
+
458
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom)
459
+ end
460
+
461
+ def test_should_call_after_initialize_hook
462
+ assert @machine.initialized
463
+ end
464
+
465
+ def test_should_use_the_default_action
466
+ assert_equal :save, @machine.action
467
+ end
468
+
469
+ def test_should_use_the_custom_action_if_specified
470
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :action => :save!)
471
+ assert_equal :save!, machine.action
472
+ end
473
+
474
+ def test_should_use_the_default_use_transactions
475
+ assert_equal false, @machine.use_transactions
476
+ end
477
+
478
+ def test_should_use_the_custom_use_transactions_if_specified
479
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true)
480
+ assert_equal true, machine.use_transactions
481
+ end
482
+
483
+ def test_should_define_a_singular_and_plural_with_scope
484
+ assert_equal %w(with_state with_states), @machine.with_scopes
485
+ end
486
+
487
+ def test_should_define_a_singular_and_plural_without_scope
488
+ assert_equal %w(without_state without_states), @machine.without_scopes
489
+ end
490
+
491
+ def teardown
492
+ StateMachine::Integrations.send(:remove_const, 'Custom')
493
+ end
494
+ end
495
+
496
+ class MachineWithActionUndefinedTest < Test::Unit::TestCase
497
+ def setup
498
+ @klass = Class.new
499
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
500
+ @object = @klass.new
501
+ end
502
+
503
+ def test_should_define_an_event_attribute_reader
504
+ assert @object.respond_to?(:state_event)
505
+ end
506
+
507
+ def test_should_define_an_event_attribute_writer
508
+ assert @object.respond_to?(:state_event=)
509
+ end
510
+
511
+ def test_should_define_an_event_transition_attribute_reader
512
+ assert @object.respond_to?(:state_event_transition)
513
+ end
514
+
515
+ def test_should_define_an_event_transition_attribute_writer
516
+ assert @object.respond_to?(:state_event_transition=)
517
+ end
518
+
519
+ def test_should_not_define_action
520
+ assert !@object.respond_to?(:save)
521
+ end
522
+
523
+ def test_should_not_mark_action_helper_as_defined
524
+ assert !@machine.action_helper_defined?
525
+ end
526
+ end
527
+
528
+ class MachineWithActionDefinedInClassTest < Test::Unit::TestCase
529
+ def setup
530
+ @klass = Class.new do
531
+ def save
532
+ end
533
+ end
534
+
535
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
536
+ @object = @klass.new
537
+ end
538
+
539
+ def test_should_define_an_event_attribute_reader
540
+ assert @object.respond_to?(:state_event)
541
+ end
542
+
543
+ def test_should_define_an_event_attribute_writer
544
+ assert @object.respond_to?(:state_event=)
545
+ end
546
+
547
+ def test_should_define_an_event_transition_attribute_reader
548
+ assert @object.respond_to?(:state_event_transition)
549
+ end
550
+
551
+ def test_should_define_an_event_transition_attribute_writer
552
+ assert @object.respond_to?(:state_event_transition=)
553
+ end
554
+
555
+ def test_should_not_define_action
556
+ assert !@klass.ancestors.any? {|ancestor| ancestor != @klass && ancestor.method_defined?(:save)}
557
+ end
558
+
559
+ def test_should_not_mark_action_helper_as_defined
560
+ assert !@machine.action_helper_defined?
561
+ end
562
+ end
563
+
564
+ class MachineWithActionDefinedInIncludedModuleTest < Test::Unit::TestCase
565
+ def setup
566
+ @mod = mod = Module.new do
567
+ def save
568
+ end
569
+ end
570
+
571
+ @klass = Class.new do
572
+ include mod
573
+ end
574
+
575
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
576
+ @object = @klass.new
577
+ end
578
+
579
+ def test_should_define_an_event_attribute_reader
580
+ assert @object.respond_to?(:state_event)
581
+ end
582
+
583
+ def test_should_define_an_event_attribute_writer
584
+ assert @object.respond_to?(:state_event=)
585
+ end
586
+
587
+ def test_should_define_an_event_transition_attribute_reader
588
+ assert @object.respond_to?(:state_event_transition)
589
+ end
590
+
591
+ def test_should_define_an_event_transition_attribute_writer
592
+ assert @object.respond_to?(:state_event_transition=)
593
+ end
594
+
595
+ def test_should_define_action
596
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @mod].include?(ancestor) && ancestor.method_defined?(:save)}
597
+ end
598
+
599
+ def test_should_keep_action_public
600
+ assert @klass.public_method_defined?(:save)
601
+ end
602
+
603
+ def test_should_mark_action_helper_as_defined
604
+ assert @machine.action_helper_defined?
605
+ end
606
+ end
607
+
608
+ class MachineWithActionDefinedInSuperclassTest < Test::Unit::TestCase
609
+ def setup
610
+ @superclass = Class.new do
611
+ def save
612
+ end
613
+ end
614
+ @klass = Class.new(@superclass)
615
+
616
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
617
+ @object = @klass.new
618
+ end
619
+
620
+ def test_should_define_an_event_attribute_reader
621
+ assert @object.respond_to?(:state_event)
622
+ end
623
+
624
+ def test_should_define_an_event_attribute_writer
625
+ assert @object.respond_to?(:state_event=)
626
+ end
627
+
628
+ def test_should_define_an_event_transition_attribute_reader
629
+ assert @object.respond_to?(:state_event_transition)
630
+ end
631
+
632
+ def test_should_define_an_event_transition_attribute_writer
633
+ assert @object.respond_to?(:state_event_transition=)
634
+ end
635
+
636
+ def test_should_define_action
637
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}
638
+ end
639
+
640
+ def test_should_keep_action_public
641
+ assert @klass.public_method_defined?(:save)
642
+ end
643
+
644
+ def test_should_mark_action_helper_as_defined
645
+ assert @machine.action_helper_defined?
646
+ end
647
+ end
648
+
649
+ class MachineWithPrivateActionTest < Test::Unit::TestCase
650
+ def setup
651
+ @superclass = Class.new do
652
+ private
653
+ def save
654
+ end
655
+ end
656
+ @klass = Class.new(@superclass)
657
+
658
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
659
+ @object = @klass.new
660
+ end
661
+
662
+ def test_should_define_an_event_attribute_reader
663
+ assert @object.respond_to?(:state_event)
664
+ end
665
+
666
+ def test_should_define_an_event_attribute_writer
667
+ assert @object.respond_to?(:state_event=)
668
+ end
669
+
670
+ def test_should_define_an_event_transition_attribute_reader
671
+ assert @object.respond_to?(:state_event_transition)
672
+ end
673
+
674
+ def test_should_define_an_event_transition_attribute_writer
675
+ assert @object.respond_to?(:state_event_transition=)
676
+ end
677
+
678
+ def test_should_define_action
679
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.private_method_defined?(:save)}
680
+ end
681
+
682
+ def test_should_keep_action_private
683
+ assert @klass.private_method_defined?(:save)
684
+ end
685
+
686
+ def test_should_mark_action_helper_as_defined
687
+ assert @machine.action_helper_defined?
688
+ end
689
+ end
690
+
691
+ class MachineWithActionAlreadyOverriddenTest < Test::Unit::TestCase
692
+ def setup
693
+ @superclass = Class.new do
694
+ def save
695
+ end
696
+ end
697
+ @klass = Class.new(@superclass)
698
+
699
+ StateMachine::Machine.new(@klass, :action => :save)
700
+ @machine = StateMachine::Machine.new(@klass, :status, :action => :save)
701
+ @object = @klass.new
702
+ end
703
+
704
+ def test_should_not_redefine_action
705
+ assert_equal 1, @klass.ancestors.select {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}.length
706
+ end
707
+
708
+ def test_should_mark_action_helper_as_defined
709
+ assert @machine.action_helper_defined?
710
+ end
711
+ end
712
+
713
+ class MachineWithCustomPluralTest < Test::Unit::TestCase
714
+ def setup
715
+ @integration = Module.new do
716
+ class << self; attr_accessor :with_scopes, :without_scopes; end
717
+ @with_scopes = []
718
+ @without_scopes = []
719
+
720
+ def create_with_scope(name)
721
+ StateMachine::Integrations::Custom.with_scopes << name
722
+ lambda {}
723
+ end
724
+
725
+ def create_without_scope(name)
726
+ StateMachine::Integrations::Custom.without_scopes << name
727
+ lambda {}
728
+ end
729
+ end
730
+
731
+ StateMachine::Integrations.const_set('Custom', @integration)
732
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
733
+ end
734
+
735
+ def test_should_define_a_singular_and_plural_with_scope
736
+ assert_equal %w(with_state with_staties), @integration.with_scopes
737
+ end
738
+
739
+ def test_should_define_a_singular_and_plural_without_scope
740
+ assert_equal %w(without_state without_staties), @integration.without_scopes
741
+ end
742
+
743
+ def teardown
744
+ StateMachine::Integrations.send(:remove_const, 'Custom')
745
+ end
746
+ end
747
+
748
+ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
749
+ def setup
750
+ @integration = Module.new do
751
+ def invalidate(object, attribute, message, values = [])
752
+ object.error = generate_message(message, values)
753
+ end
754
+ end
755
+ StateMachine::Integrations.const_set('Custom', @integration)
756
+
757
+ @klass = Class.new do
758
+ attr_accessor :error
759
+ end
760
+
761
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'})
762
+ @machine.state :parked
763
+
764
+ @object = @klass.new
765
+ @object.state = 'parked'
766
+ end
767
+
768
+ def test_generate_custom_message
769
+ assert_equal 'cannot park', @machine.generate_message(:invalid_transition, [[:event, :park]])
770
+ end
771
+
772
+ def test_use_custom_message
773
+ @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
774
+ assert_equal 'cannot park', @object.error
775
+ end
776
+
777
+ def teardown
778
+ StateMachine::Integrations.send(:remove_const, 'Custom')
779
+ end
780
+ end
781
+
782
+ class MachineTest < Test::Unit::TestCase
783
+ def test_should_raise_exception_if_invalid_option_specified
784
+ assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)}
785
+ end
786
+
787
+ def test_should_not_raise_exception_if_custom_messages_specified
788
+ assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})}
789
+ end
790
+
791
+ def test_should_evaluate_a_block_during_initialization
792
+ called = true
793
+ StateMachine::Machine.new(Class.new) do
794
+ called = respond_to?(:event)
795
+ end
796
+
797
+ assert called
798
+ end
799
+
800
+ def test_should_provide_matcher_helpers_during_initialization
801
+ matchers = []
802
+
803
+ StateMachine::Machine.new(Class.new) do
804
+ matchers = [all, any, same]
805
+ end
806
+
807
+ assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
808
+ end
809
+ end
810
+
811
+ class MachineAfterBeingCopiedTest < Test::Unit::TestCase
812
+ def setup
813
+ @machine = StateMachine::Machine.new(Class.new, :state, :initial => :parked)
814
+ @machine.event(:ignite) {}
815
+ @machine.before_transition(lambda {})
816
+ @machine.after_transition(lambda {})
817
+ @machine.around_transition(lambda {})
818
+
819
+ @copied_machine = @machine.clone
820
+ end
821
+
822
+ def test_should_not_have_the_same_collection_of_states
823
+ assert_not_same @copied_machine.states, @machine.states
824
+ end
825
+
826
+ def test_should_copy_each_state
827
+ assert_not_same @copied_machine.states[:parked], @machine.states[:parked]
828
+ end
829
+
830
+ def test_should_update_machine_for_each_state
831
+ assert_equal @copied_machine, @copied_machine.states[:parked].machine
832
+ end
833
+
834
+ def test_should_not_update_machine_for_original_state
835
+ assert_equal @machine, @machine.states[:parked].machine
836
+ end
837
+
838
+ def test_should_not_have_the_same_collection_of_events
839
+ assert_not_same @copied_machine.events, @machine.events
840
+ end
841
+
842
+ def test_should_copy_each_event
843
+ assert_not_same @copied_machine.events[:ignite], @machine.events[:ignite]
844
+ end
845
+
846
+ def test_should_update_machine_for_each_event
847
+ assert_equal @copied_machine, @copied_machine.events[:ignite].machine
848
+ end
849
+
850
+ def test_should_not_update_machine_for_original_event
851
+ assert_equal @machine, @machine.events[:ignite].machine
852
+ end
853
+
854
+ def test_should_not_have_the_same_callbacks
855
+ assert_not_same @copied_machine.callbacks, @machine.callbacks
856
+ end
857
+
858
+ def test_should_not_have_the_same_before_callbacks
859
+ assert_not_same @copied_machine.callbacks[:before], @machine.callbacks[:before]
860
+ end
861
+
862
+ def test_should_not_have_the_same_after_callbacks
863
+ assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
864
+ end
865
+ end
866
+
867
+ class MachineAfterChangingOwnerClassTest < Test::Unit::TestCase
868
+ def setup
869
+ @original_class = Class.new
870
+ @machine = StateMachine::Machine.new(@original_class)
871
+
872
+ @new_class = Class.new(@original_class)
873
+ @new_machine = @machine.clone
874
+ @new_machine.owner_class = @new_class
875
+
876
+ @object = @new_class.new
877
+ end
878
+
879
+ def test_should_update_owner_class
880
+ assert_equal @new_class, @new_machine.owner_class
881
+ end
882
+
883
+ def test_should_not_change_original_owner_class
884
+ assert_equal @original_class, @machine.owner_class
885
+ end
886
+
887
+ def test_should_change_the_associated_machine_in_the_new_class
888
+ assert_equal @new_machine, @new_class.state_machines[:state]
889
+ end
890
+
891
+ def test_should_not_change_the_associated_machine_in_the_original_class
892
+ assert_equal @machine, @original_class.state_machines[:state]
893
+ end
894
+ end
895
+
896
+ class MachineAfterChangingInitialState < Test::Unit::TestCase
897
+ def setup
898
+ @klass = Class.new
899
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
900
+ @machine.initial_state = :idling
901
+
902
+ @object = @klass.new
903
+ end
904
+
905
+ def test_should_change_the_initial_state
906
+ assert_equal :idling, @machine.initial_state(@object).name
907
+ end
908
+
909
+ def test_should_include_in_known_states
910
+ assert_equal [:parked, :idling], @machine.states.map {|state| state.name}
911
+ end
912
+
913
+ def test_should_reset_original_initial_state
914
+ assert !@machine.state(:parked).initial
915
+ end
916
+
917
+ def test_should_set_new_state_to_initial
918
+ assert @machine.state(:idling).initial
919
+ end
920
+ end
921
+
922
+ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
923
+ def setup
924
+ @klass = Class.new
925
+ @machine = StateMachine::Machine.new(@klass)
926
+ @object = @klass.new
927
+ end
928
+
929
+ def test_should_not_redefine_existing_public_methods
930
+ @klass.class_eval do
931
+ def state
932
+ 'parked'
933
+ end
934
+ end
935
+
936
+ @machine.define_instance_method(:state) {}
937
+ assert_equal 'parked', @object.state
938
+ end
939
+
940
+ def test_should_not_redefine_existing_protected_methods
941
+ @klass.class_eval do
942
+ protected
943
+ def state
944
+ 'parked'
945
+ end
946
+ end
947
+
948
+ @machine.define_instance_method(:state) {}
949
+ assert_equal 'parked', @object.send(:state)
950
+ end
951
+
952
+ def test_should_not_redefine_existing_private_methods
953
+ @klass.class_eval do
954
+ private
955
+ def state
956
+ 'parked'
957
+ end
958
+ end
959
+
960
+ @machine.define_instance_method(:state) {}
961
+ assert_equal 'parked', @object.send(:state)
962
+ end
963
+
964
+ def test_should_define_nonexistent_methods
965
+ @machine.define_instance_method(:state) {'parked'}
966
+ assert_equal 'parked', @object.state
967
+ end
968
+ end
969
+
970
+ class MachineWithClassHelpersTest < Test::Unit::TestCase
971
+ def setup
972
+ @klass = Class.new
973
+ @machine = StateMachine::Machine.new(@klass)
974
+ end
975
+
976
+ def test_should_not_redefine_existing_public_methods
977
+ class << @klass
978
+ def states
979
+ []
980
+ end
981
+ end
982
+
983
+ @machine.define_class_method(:states) {}
984
+ assert_equal [], @klass.states
985
+ end
986
+
987
+ def test_should_not_redefine_existing_protected_methods
988
+ class << @klass
989
+ protected
990
+ def states
991
+ []
992
+ end
993
+ end
994
+
995
+ @machine.define_class_method(:states) {}
996
+ assert_equal [], @klass.send(:states)
997
+ end
998
+
999
+ def test_should_not_redefine_existing_private_methods
1000
+ class << @klass
1001
+ private
1002
+ def states
1003
+ []
1004
+ end
1005
+ end
1006
+
1007
+ @machine.define_class_method(:states) {}
1008
+ assert_equal [], @klass.send(:states)
1009
+ end
1010
+
1011
+ def test_should_define_nonexistent_methods
1012
+ @machine.define_class_method(:states) {[]}
1013
+ assert_equal [], @klass.states
1014
+ end
1015
+ end
1016
+
1017
+ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
1018
+ def setup
1019
+ @klass = Class.new do
1020
+ def self.with_state
1021
+ :with_state
1022
+ end
1023
+
1024
+ def self.with_states
1025
+ :with_states
1026
+ end
1027
+
1028
+ def self.without_state
1029
+ :without_state
1030
+ end
1031
+
1032
+ def self.without_states
1033
+ :without_states
1034
+ end
1035
+
1036
+ def self.human_state_name
1037
+ :human_state_name
1038
+ end
1039
+
1040
+ def self.human_state_event_name
1041
+ :human_state_event_name
1042
+ end
1043
+
1044
+ attr_accessor :status
1045
+
1046
+ def state
1047
+ 'parked'
1048
+ end
1049
+
1050
+ def state=(value)
1051
+ self.status = value
1052
+ end
1053
+
1054
+ def state?
1055
+ true
1056
+ end
1057
+
1058
+ def state_name
1059
+ :parked
1060
+ end
1061
+
1062
+ def human_state_name
1063
+ 'parked'
1064
+ end
1065
+
1066
+ def state_events
1067
+ [:ignite]
1068
+ end
1069
+
1070
+ def state_transitions
1071
+ [{:parked => :idling}]
1072
+ end
1073
+ end
1074
+
1075
+ StateMachine::Integrations.const_set('Custom', Module.new do
1076
+ def create_with_scope(name)
1077
+ lambda {|klass, values| []}
1078
+ end
1079
+
1080
+ def create_without_scope(name)
1081
+ lambda {|klass, values| []}
1082
+ end
1083
+ end)
1084
+
1085
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
1086
+ @machine.state :parked, :idling
1087
+ @machine.event :ignite
1088
+ @object = @klass.new
1089
+ end
1090
+
1091
+ def test_should_not_redefine_singular_with_scope
1092
+ assert_equal :with_state, @klass.with_state
1093
+ end
1094
+
1095
+ def test_should_not_redefine_plural_with_scope
1096
+ assert_equal :with_states, @klass.with_states
1097
+ end
1098
+
1099
+ def test_should_not_redefine_singular_without_scope
1100
+ assert_equal :without_state, @klass.without_state
1101
+ end
1102
+
1103
+ def test_should_not_redefine_plural_without_scope
1104
+ assert_equal :without_states, @klass.without_states
1105
+ end
1106
+
1107
+ def test_should_not_redefine_human_attribute_name_reader
1108
+ assert_equal :human_state_name, @klass.human_state_name
1109
+ end
1110
+
1111
+ def test_should_not_redefine_human_event_name_reader
1112
+ assert_equal :human_state_event_name, @klass.human_state_event_name
1113
+ end
1114
+
1115
+ def test_should_not_redefine_attribute_writer
1116
+ assert_equal 'parked', @object.state
1117
+ end
1118
+
1119
+ def test_should_not_redefine_attribute_writer
1120
+ @object.state = 'parked'
1121
+ assert_equal 'parked', @object.status
1122
+ end
1123
+
1124
+ def test_should_not_define_attribute_predicate
1125
+ assert @object.state?
1126
+ end
1127
+
1128
+ def test_should_not_redefine_attribute_name_reader
1129
+ assert_equal :parked, @object.state_name
1130
+ end
1131
+
1132
+ def test_should_not_redefine_attribute_human_name_reader
1133
+ assert_equal 'parked', @object.human_state_name
1134
+ end
1135
+
1136
+ def test_should_not_redefine_attribute_events_reader
1137
+ assert_equal [:ignite], @object.state_events
1138
+ end
1139
+
1140
+ def test_should_not_redefine_attribute_transitions_reader
1141
+ assert_equal [{:parked => :idling}], @object.state_transitions
1142
+ end
1143
+
1144
+ def test_should_allow_super_chaining
1145
+ @klass.class_eval do
1146
+ def self.with_state(*states)
1147
+ super == []
1148
+ end
1149
+
1150
+ def self.with_states(*states)
1151
+ super == []
1152
+ end
1153
+
1154
+ def self.without_state(*states)
1155
+ super == []
1156
+ end
1157
+
1158
+ def self.without_states(*states)
1159
+ super == []
1160
+ end
1161
+
1162
+ def self.human_state_name(state)
1163
+ super == 'parked'
1164
+ end
1165
+
1166
+ def self.human_state_event_name(event)
1167
+ super == 'ignite'
1168
+ end
1169
+
1170
+ attr_accessor :status
1171
+
1172
+ def state
1173
+ super || 'parked'
1174
+ end
1175
+
1176
+ def state=(value)
1177
+ super
1178
+ self.status = value
1179
+ end
1180
+
1181
+ def state?(state)
1182
+ super ? 1 : 0
1183
+ end
1184
+
1185
+ def state_name
1186
+ super == :parked ? 1 : 0
1187
+ end
1188
+
1189
+ def human_state_name
1190
+ super == 'parked' ? 1 : 0
1191
+ end
1192
+
1193
+ def state_events
1194
+ super == []
1195
+ end
1196
+
1197
+ def state_transitions
1198
+ super == []
1199
+ end
1200
+ end
1201
+
1202
+ assert_equal true, @klass.with_state
1203
+ assert_equal true, @klass.with_states
1204
+ assert_equal true, @klass.without_state
1205
+ assert_equal true, @klass.without_states
1206
+ assert_equal true, @klass.human_state_name(:parked)
1207
+ assert_equal true, @klass.human_state_event_name(:ignite)
1208
+
1209
+ assert_equal 'parked', @object.state
1210
+ @object.state = 'idling'
1211
+ assert_equal 'idling', @object.status
1212
+ assert_equal 0, @object.state?(:parked)
1213
+ assert_equal 0, @object.state_name
1214
+ assert_equal 0, @object.human_state_name
1215
+ assert_equal true, @object.state_events
1216
+ assert_equal true, @object.state_transitions
1217
+ end
1218
+
1219
+ def teardown
1220
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1221
+ end
1222
+ end
1223
+
1224
+ class MachineWithoutInitializeTest < Test::Unit::TestCase
1225
+ def setup
1226
+ @klass = Class.new
1227
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1228
+ @object = @klass.new
1229
+ end
1230
+
1231
+ def test_should_initialize_state
1232
+ assert_equal 'parked', @object.state
1233
+ end
1234
+ end
1235
+
1236
+ class MachineWithInitializeWithoutSuperTest < Test::Unit::TestCase
1237
+ def setup
1238
+ @klass = Class.new do
1239
+ def initialize
1240
+ end
1241
+ end
1242
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1243
+ @object = @klass.new
1244
+ end
1245
+
1246
+ def test_should_not_initialize_state
1247
+ assert_nil @object.state
1248
+ end
1249
+ end
1250
+
1251
+ class MachineWithInitializeAndSuperTest < Test::Unit::TestCase
1252
+ def setup
1253
+ @klass = Class.new do
1254
+ def initialize
1255
+ super()
1256
+ end
1257
+ end
1258
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1259
+ @object = @klass.new
1260
+ end
1261
+
1262
+ def test_should_initialize_state
1263
+ assert_equal 'parked', @object.state
1264
+ end
1265
+ end
1266
+
1267
+ class MachineWithInitializeArgumentsAndBlockTest < Test::Unit::TestCase
1268
+ def setup
1269
+ @superclass = Class.new do
1270
+ attr_reader :args
1271
+ attr_reader :block_given
1272
+
1273
+ def initialize(*args)
1274
+ @args = args
1275
+ @block_given = block_given?
1276
+ end
1277
+ end
1278
+ @klass = Class.new(@superclass)
1279
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1280
+ @object = @klass.new(1, 2, 3) {}
1281
+ end
1282
+
1283
+ def test_should_initialize_state
1284
+ assert_equal 'parked', @object.state
1285
+ end
1286
+
1287
+ def test_should_preserve_arguments
1288
+ assert_equal [1, 2, 3], @object.args
1289
+ end
1290
+
1291
+ def test_should_preserve_block
1292
+ assert @object.block_given
1293
+ end
1294
+ end
1295
+
1296
+ class MachineWithCustomInitializeTest < Test::Unit::TestCase
1297
+ def setup
1298
+ @klass = Class.new do
1299
+ def initialize
1300
+ initialize_state_machines
1301
+ end
1302
+ end
1303
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1304
+ @object = @klass.new
1305
+ end
1306
+
1307
+ def test_should_initialize_state
1308
+ assert_equal 'parked', @object.state
1309
+ end
1310
+ end
1311
+
1312
+ class MachinePersistenceTest < Test::Unit::TestCase
1313
+ def setup
1314
+ @klass = Class.new do
1315
+ attr_accessor :state_event
1316
+ end
1317
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1318
+ @object = @klass.new
1319
+ end
1320
+
1321
+ def test_should_allow_reading_state
1322
+ assert_equal 'parked', @machine.read(@object, :state)
1323
+ end
1324
+
1325
+ def test_should_allow_reading_custom_attributes
1326
+ assert_nil @machine.read(@object, :event)
1327
+
1328
+ @object.state_event = 'ignite'
1329
+ assert_equal 'ignite', @machine.read(@object, :event)
1330
+ end
1331
+
1332
+ def test_should_allow_reading_custom_instance_variables
1333
+ @klass.class_eval do
1334
+ attr_writer :state_value
1335
+ end
1336
+
1337
+ @object.state_value = 1
1338
+ assert_raise(NoMethodError) { @machine.read(@object, :value) }
1339
+ assert_equal 1, @machine.read(@object, :value, true)
1340
+ end
1341
+
1342
+ def test_should_allow_writing_state
1343
+ @machine.write(@object, :state, 'idling')
1344
+ assert_equal 'idling', @object.state
1345
+ end
1346
+
1347
+ def test_should_allow_writing_custom_attributes
1348
+ @machine.write(@object, :event, 'ignite')
1349
+ assert_equal 'ignite', @object.state_event
1350
+ end
1351
+ end
1352
+
1353
+
1354
+ class MachineWithStatesTest < Test::Unit::TestCase
1355
+ def setup
1356
+ @klass = Class.new
1357
+ @machine = StateMachine::Machine.new(@klass)
1358
+ @parked, @idling = @machine.state :parked, :idling
1359
+
1360
+ @object = @klass.new
1361
+ end
1362
+
1363
+ def test_should_have_states
1364
+ assert_equal [nil, :parked, :idling], @machine.states.map {|state| state.name}
1365
+ end
1366
+
1367
+ def test_should_allow_state_lookup_by_name
1368
+ assert_equal @parked, @machine.states[:parked]
1369
+ end
1370
+
1371
+ def test_should_allow_state_lookup_by_value
1372
+ assert_equal @parked, @machine.states['parked', :value]
1373
+ end
1374
+
1375
+ def test_should_allow_human_state_name_lookup
1376
+ assert_equal 'parked', @klass.human_state_name(:parked)
1377
+ end
1378
+
1379
+ def test_should_raise_exception_on_invalid_human_state_name_lookup
1380
+ exception = assert_raise(IndexError) {@klass.human_state_name(:invalid)}
1381
+ assert_equal ':invalid is an invalid name', exception.message
1382
+ end
1383
+
1384
+ def test_should_use_stringified_name_for_value
1385
+ assert_equal 'parked', @parked.value
1386
+ end
1387
+
1388
+ def test_should_not_use_custom_matcher
1389
+ assert_nil @parked.matcher
1390
+ end
1391
+
1392
+ def test_should_raise_exception_if_invalid_option_specified
1393
+ exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)}
1394
+ assert_equal 'Invalid key(s): invalid', exception.message
1395
+ end
1396
+ end
1397
+
1398
+ class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
1399
+ def setup
1400
+ @klass = Class.new
1401
+ @machine = StateMachine::Machine.new(@klass)
1402
+ @state = @machine.state :parked, :value => 1
1403
+
1404
+ @object = @klass.new
1405
+ @object.state = 1
1406
+ end
1407
+
1408
+ def test_should_use_custom_value
1409
+ assert_equal 1, @state.value
1410
+ end
1411
+
1412
+ def test_should_allow_lookup_by_custom_value
1413
+ assert_equal @state, @machine.states[1, :value]
1414
+ end
1415
+ end
1416
+
1417
+ class MachineWithStatesWithCustomHumanNamesTest < Test::Unit::TestCase
1418
+ def setup
1419
+ @klass = Class.new
1420
+ @machine = StateMachine::Machine.new(@klass)
1421
+ @state = @machine.state :parked, :human_name => 'stopped'
1422
+ end
1423
+
1424
+ def test_should_use_custom_human_name
1425
+ assert_equal 'stopped', @state.human_name
1426
+ end
1427
+
1428
+ def test_should_allow_human_state_name_lookup
1429
+ assert_equal 'stopped', @klass.human_state_name(:parked)
1430
+ end
1431
+ end
1432
+
1433
+ class MachineWithStatesWithRuntimeDependenciesTest < Test::Unit::TestCase
1434
+ def setup
1435
+ @klass = Class.new
1436
+ @machine = StateMachine::Machine.new(@klass)
1437
+ @machine.state :parked
1438
+ end
1439
+
1440
+ def test_should_not_evaluate_value_during_definition
1441
+ assert_nothing_raised { @machine.state :parked, :value => lambda {raise ArgumentError} }
1442
+ end
1443
+
1444
+ def test_should_not_evaluate_if_not_initial_state
1445
+ @machine.state :parked, :value => lambda {raise ArgumentError}
1446
+ assert_nothing_raised { @klass.new }
1447
+ end
1448
+ end
1449
+
1450
+ class MachineWithStateWithMatchersTest < Test::Unit::TestCase
1451
+ def setup
1452
+ @klass = Class.new
1453
+ @machine = StateMachine::Machine.new(@klass)
1454
+ @state = @machine.state :parked, :if => lambda {|value| !value.nil?}
1455
+
1456
+ @object = @klass.new
1457
+ @object.state = 1
1458
+ end
1459
+
1460
+ def test_should_use_custom_matcher
1461
+ assert_not_nil @state.matcher
1462
+ assert @state.matches?(1)
1463
+ assert !@state.matches?(nil)
1464
+ end
1465
+ end
1466
+
1467
+ class MachineWithCachedStateTest < Test::Unit::TestCase
1468
+ def setup
1469
+ @klass = Class.new
1470
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1471
+ @state = @machine.state :parked, :value => lambda {Object.new}, :cache => true
1472
+
1473
+ @object = @klass.new
1474
+ end
1475
+
1476
+ def test_should_use_evaluated_value
1477
+ assert_instance_of Object, @object.state
1478
+ end
1479
+
1480
+ def test_use_same_value_across_multiple_objects
1481
+ assert_equal @object.state, @klass.new.state
1482
+ end
1483
+ end
1484
+
1485
+ class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
1486
+ def setup
1487
+ @klass = Class.new
1488
+ @machine = StateMachine::Machine.new(@klass)
1489
+
1490
+ @parked, @idling = @machine.state :parked, :idling do
1491
+ def speed
1492
+ 0
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ def test_should_define_behaviors_for_each_state
1498
+ assert_not_nil @parked.methods[:speed]
1499
+ assert_not_nil @idling.methods[:speed]
1500
+ end
1501
+
1502
+ def test_should_define_different_behaviors_for_each_state
1503
+ assert_not_equal @parked.methods[:speed], @idling.methods[:speed]
1504
+ end
1505
+ end
1506
+
1507
+ class MachineWithExistingStateTest < Test::Unit::TestCase
1508
+ def setup
1509
+ @klass = Class.new
1510
+ @machine = StateMachine::Machine.new(@klass)
1511
+ @state = @machine.state :parked
1512
+ @same_state = @machine.state :parked, :value => 1
1513
+ end
1514
+
1515
+ def test_should_not_create_a_new_state
1516
+ assert_same @state, @same_state
1517
+ end
1518
+
1519
+ def test_should_update_attributes
1520
+ assert_equal 1, @state.value
1521
+ end
1522
+
1523
+ def test_should_no_longer_be_able_to_look_up_state_by_original_value
1524
+ assert_nil @machine.states['parked', :value]
1525
+ end
1526
+
1527
+ def test_should_be_able_to_look_up_state_by_new_value
1528
+ assert_equal @state, @machine.states[1, :value]
1529
+ end
1530
+ end
1531
+
1532
+ class MachineWithOtherStates < Test::Unit::TestCase
1533
+ def setup
1534
+ @klass = Class.new
1535
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1536
+ @parked, @idling = @machine.other_states(:parked, :idling)
1537
+ end
1538
+
1539
+ def test_should_include_other_states_in_known_states
1540
+ assert_equal [@parked, @idling], @machine.states.to_a
1541
+ end
1542
+
1543
+ def test_should_use_default_value
1544
+ assert_equal 'idling', @idling.value
1545
+ end
1546
+
1547
+ def test_should_not_create_matcher
1548
+ assert_nil @idling.matcher
1549
+ end
1550
+ end
1551
+
1552
+ class MachineWithEventsTest < Test::Unit::TestCase
1553
+ def setup
1554
+ @klass = Class.new
1555
+ @machine = StateMachine::Machine.new(@klass)
1556
+ end
1557
+
1558
+ def test_should_return_the_created_event
1559
+ assert_instance_of StateMachine::Event, @machine.event(:ignite)
1560
+ end
1561
+
1562
+ def test_should_create_event_with_given_name
1563
+ event = @machine.event(:ignite) {}
1564
+ assert_equal :ignite, event.name
1565
+ end
1566
+
1567
+ def test_should_evaluate_block_within_event_context
1568
+ responded = false
1569
+ @machine.event :ignite do
1570
+ responded = respond_to?(:transition)
1571
+ end
1572
+
1573
+ assert responded
1574
+ end
1575
+
1576
+ def test_should_be_aliased_as_on
1577
+ event = @machine.on(:ignite) {}
1578
+ assert_equal :ignite, event.name
1579
+ end
1580
+
1581
+ def test_should_have_events
1582
+ event = @machine.event(:ignite)
1583
+ assert_equal [event], @machine.events.to_a
1584
+ end
1585
+
1586
+ def test_should_allow_human_state_name_lookup
1587
+ @machine.event(:ignite)
1588
+ assert_equal 'ignite', @klass.human_state_event_name(:ignite)
1589
+ end
1590
+
1591
+ def test_should_raise_exception_on_invalid_human_state_event_name_lookup
1592
+ exception = assert_raise(IndexError) {@klass.human_state_event_name(:invalid)}
1593
+ assert_equal ':invalid is an invalid name', exception.message
1594
+ end
1595
+ end
1596
+
1597
+ class MachineWithExistingEventTest < Test::Unit::TestCase
1598
+ def setup
1599
+ @machine = StateMachine::Machine.new(Class.new)
1600
+ @event = @machine.event(:ignite)
1601
+ @same_event = @machine.event(:ignite)
1602
+ end
1603
+
1604
+ def test_should_not_create_new_event
1605
+ assert_same @event, @same_event
1606
+ end
1607
+
1608
+ def test_should_allow_accessing_event_without_block
1609
+ assert_equal @event, @machine.event(:ignite)
1610
+ end
1611
+ end
1612
+
1613
+ class MachineWithEventsWithCustomHumanNamesTest < Test::Unit::TestCase
1614
+ def setup
1615
+ @klass = Class.new
1616
+ @machine = StateMachine::Machine.new(@klass)
1617
+ @event = @machine.event(:ignite, :human_name => 'start')
1618
+ end
1619
+
1620
+ def test_should_use_custom_human_name
1621
+ assert_equal 'start', @event.human_name
1622
+ end
1623
+
1624
+ def test_should_allow_human_state_name_lookup
1625
+ assert_equal 'start', @klass.human_state_event_name(:ignite)
1626
+ end
1627
+ end
1628
+
1629
+ class MachineWithEventsWithTransitionsTest < Test::Unit::TestCase
1630
+ def setup
1631
+ @klass = Class.new
1632
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1633
+ @event = @machine.event(:ignite) do
1634
+ transition :parked => :idling
1635
+ transition :stalled => :idling
1636
+ end
1637
+ end
1638
+
1639
+ def test_should_have_events
1640
+ assert_equal [@event], @machine.events.to_a
1641
+ end
1642
+
1643
+ def test_should_track_states_defined_in_event_transitions
1644
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
1645
+ end
1646
+
1647
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
1648
+ @machine.event :park do
1649
+ transition :idling => :parked
1650
+ end
1651
+
1652
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
1653
+ end
1654
+
1655
+ def test_should_track_state_from_new_events
1656
+ @machine.event :shift_up do
1657
+ transition :idling => :first_gear
1658
+ end
1659
+
1660
+ assert_equal [:parked, :idling, :stalled, :first_gear], @machine.states.map {|state| state.name}
1661
+ end
1662
+ end
1663
+
1664
+ class MachineWithMultipleEventsTest < Test::Unit::TestCase
1665
+ def setup
1666
+ @klass = Class.new
1667
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1668
+ @park, @shift_down = @machine.event(:park, :shift_down) do
1669
+ transition :first_gear => :parked
1670
+ end
1671
+ end
1672
+
1673
+ def test_should_have_events
1674
+ assert_equal [@park, @shift_down], @machine.events.to_a
1675
+ end
1676
+
1677
+ def test_should_define_transitions_for_each_event
1678
+ [@park, @shift_down].each {|event| assert_equal 1, event.guards.size}
1679
+ end
1680
+
1681
+ def test_should_transition_the_same_for_each_event
1682
+ object = @klass.new
1683
+ object.state = 'first_gear'
1684
+ object.park
1685
+ assert_equal 'parked', object.state
1686
+
1687
+ object = @klass.new
1688
+ object.state = 'first_gear'
1689
+ object.shift_down
1690
+ assert_equal 'parked', object.state
1691
+ end
1692
+ end
1693
+
1694
+ class MachineWithTransitionCallbacksTest < Test::Unit::TestCase
1695
+ def setup
1696
+ @klass = Class.new do
1697
+ attr_accessor :callbacks
1698
+ end
1699
+
1700
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1701
+ @event = @machine.event :ignite do
1702
+ transition :parked => :idling
1703
+ end
1704
+
1705
+ @object = @klass.new
1706
+ @object.callbacks = []
1707
+ end
1708
+
1709
+ def test_should_not_raise_exception_if_implicit_option_specified
1710
+ assert_nothing_raised {@machine.before_transition :invalid => :valid, :do => lambda {}}
1711
+ end
1712
+
1713
+ def test_should_raise_exception_if_method_not_specified
1714
+ exception = assert_raise(ArgumentError) {@machine.before_transition :to => :idling}
1715
+ assert_equal 'Method(s) for callback must be specified', exception.message
1716
+ end
1717
+
1718
+ def test_should_invoke_callbacks_during_transition
1719
+ @machine.before_transition lambda {|object| object.callbacks << 'before'}
1720
+ @machine.after_transition lambda {|object| object.callbacks << 'after'}
1721
+ @machine.around_transition lambda {|object, transition, block| object.callbacks << 'before_around'; block.call; object.callbacks << 'after_around'}
1722
+
1723
+ @event.fire(@object)
1724
+ assert_equal %w(before before_around after_around after), @object.callbacks
1725
+ end
1726
+
1727
+ def test_should_allow_multiple_callbacks
1728
+ @machine.before_transition lambda {|object| object.callbacks << 'before1'}, lambda {|object| object.callbacks << 'before2'}
1729
+ @machine.after_transition lambda {|object| object.callbacks << 'after1'}, lambda {|object| object.callbacks << 'after2'}
1730
+ @machine.around_transition(
1731
+ lambda {|object, transition, block| object.callbacks << 'before_around1'; block.call; object.callbacks << 'after_around1'},
1732
+ lambda {|object, transition, block| object.callbacks << 'before_around2'; block.call; object.callbacks << 'after_around2'}
1733
+ )
1734
+
1735
+ @event.fire(@object)
1736
+ assert_equal %w(before1 before2 before_around1 before_around2 after_around2 after_around1 after1 after2), @object.callbacks
1737
+ end
1738
+
1739
+ def test_should_allow_multiple_callbacks_with_requirements
1740
+ @machine.before_transition lambda {|object| object.callbacks << 'before_parked1'}, lambda {|object| object.callbacks << 'before_parked2'}, :from => :parked
1741
+ @machine.before_transition lambda {|object| object.callbacks << 'before_idling1'}, lambda {|object| object.callbacks << 'before_idling2'}, :from => :idling
1742
+ @machine.after_transition lambda {|object| object.callbacks << 'after_parked1'}, lambda {|object| object.callbacks << 'after_parked2'}, :from => :parked
1743
+ @machine.after_transition lambda {|object| object.callbacks << 'after_idling1'}, lambda {|object| object.callbacks << 'after_idling2'}, :from => :idling
1744
+ @machine.around_transition(
1745
+ lambda {|object, transition, block| object.callbacks << 'before_around_parked1'; block.call; object.callbacks << 'after_around_parked1'},
1746
+ lambda {|object, transition, block| object.callbacks << 'before_around_parked2'; block.call; object.callbacks << 'after_around_parked2'},
1747
+ :from => :parked
1748
+ )
1749
+ @machine.around_transition(
1750
+ lambda {|object, transition, block| object.callbacks << 'before_around_idling1'; block.call; object.callbacks << 'after_around_idling1'},
1751
+ lambda {|object, transition, block| object.callbacks << 'before_around_idling2'; block.call; object.callbacks << 'after_around_idling2'},
1752
+ :from => :idling
1753
+ )
1754
+
1755
+ @event.fire(@object)
1756
+ assert_equal %w(before_parked1 before_parked2 before_around_parked1 before_around_parked2 after_around_parked2 after_around_parked1 after_parked1 after_parked2), @object.callbacks
1757
+ end
1758
+
1759
+ def test_should_support_from_requirement
1760
+ @machine.before_transition :from => :parked, :do => lambda {|object| object.callbacks << :parked}
1761
+ @machine.before_transition :from => :idling, :do => lambda {|object| object.callbacks << :idling}
1762
+
1763
+ @event.fire(@object)
1764
+ assert_equal [:parked], @object.callbacks
1765
+ end
1766
+
1767
+ def test_should_support_except_from_requirement
1768
+ @machine.before_transition :except_from => :parked, :do => lambda {|object| object.callbacks << :parked}
1769
+ @machine.before_transition :except_from => :idling, :do => lambda {|object| object.callbacks << :idling}
1770
+
1771
+ @event.fire(@object)
1772
+ assert_equal [:idling], @object.callbacks
1773
+ end
1774
+
1775
+ def test_should_support_to_requirement
1776
+ @machine.before_transition :to => :parked, :do => lambda {|object| object.callbacks << :parked}
1777
+ @machine.before_transition :to => :idling, :do => lambda {|object| object.callbacks << :idling}
1778
+
1779
+ @event.fire(@object)
1780
+ assert_equal [:idling], @object.callbacks
1781
+ end
1782
+
1783
+ def test_should_support_except_to_requirement
1784
+ @machine.before_transition :except_to => :parked, :do => lambda {|object| object.callbacks << :parked}
1785
+ @machine.before_transition :except_to => :idling, :do => lambda {|object| object.callbacks << :idling}
1786
+
1787
+ @event.fire(@object)
1788
+ assert_equal [:parked], @object.callbacks
1789
+ end
1790
+
1791
+ def test_should_support_on_requirement
1792
+ @machine.before_transition :on => :park, :do => lambda {|object| object.callbacks << :park}
1793
+ @machine.before_transition :on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
1794
+
1795
+ @event.fire(@object)
1796
+ assert_equal [:ignite], @object.callbacks
1797
+ end
1798
+
1799
+ def test_should_support_except_on_requirement
1800
+ @machine.before_transition :except_on => :park, :do => lambda {|object| object.callbacks << :park}
1801
+ @machine.before_transition :except_on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
1802
+
1803
+ @event.fire(@object)
1804
+ assert_equal [:park], @object.callbacks
1805
+ end
1806
+
1807
+ def test_should_support_implicit_requirement
1808
+ @machine.before_transition :parked => :idling, :do => lambda {|object| object.callbacks << :parked}
1809
+ @machine.before_transition :idling => :parked, :do => lambda {|object| object.callbacks << :idling}
1810
+
1811
+ @event.fire(@object)
1812
+ assert_equal [:parked], @object.callbacks
1813
+ end
1814
+
1815
+ def test_should_track_states_defined_in_transition_callbacks
1816
+ @machine.before_transition :parked => :idling, :do => lambda {}
1817
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
1818
+ @machine.around_transition :third_gear => :fourth_gear, :do => lambda {}
1819
+
1820
+ assert_equal [:parked, :idling, :first_gear, :second_gear, :third_gear, :fourth_gear], @machine.states.map {|state| state.name}
1821
+ end
1822
+
1823
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
1824
+ @machine.before_transition :parked => :idling, :do => lambda {}
1825
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
1826
+ @machine.after_transition :parked => :idling, :do => lambda {}
1827
+ @machine.around_transition :parked => :idling, :do => lambda {}
1828
+
1829
+ assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name}
1830
+ end
1831
+
1832
+ def test_should_define_predicates_for_each_state
1833
+ [:parked?, :idling?].each {|predicate| assert @object.respond_to?(predicate)}
1834
+ end
1835
+ end
1836
+
1837
+ class MachineWithOwnerSubclassTest < Test::Unit::TestCase
1838
+ def setup
1839
+ @klass = Class.new
1840
+ @machine = StateMachine::Machine.new(@klass)
1841
+ @subclass = Class.new(@klass)
1842
+ end
1843
+
1844
+ def test_should_have_a_different_collection_of_state_machines
1845
+ assert_not_same @klass.state_machines, @subclass.state_machines
1846
+ end
1847
+
1848
+ def test_should_have_the_same_attribute_associated_state_machines
1849
+ assert_equal @klass.state_machines, @subclass.state_machines
1850
+ end
1851
+ end
1852
+
1853
+ class MachineWithExistingMachinesOnOwnerClassTest < Test::Unit::TestCase
1854
+ def setup
1855
+ @klass = Class.new
1856
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1857
+ @second_machine = StateMachine::Machine.new(@klass, :status, :initial => :idling)
1858
+ @object = @klass.new
1859
+ end
1860
+
1861
+ def test_should_track_each_state_machine
1862
+ expected = {:state => @machine, :status => @second_machine}
1863
+ assert_equal expected, @klass.state_machines
1864
+ end
1865
+
1866
+ def test_should_initialize_state_for_both_machines
1867
+ assert_equal 'parked', @object.state
1868
+ assert_equal 'idling', @object.status
1869
+ end
1870
+ end
1871
+
1872
+ class MachineWithExistingMachinesWithSameAttributesOnOwnerClassTest < Test::Unit::TestCase
1873
+ def setup
1874
+ @klass = Class.new
1875
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1876
+ @second_machine = StateMachine::Machine.new(@klass, :public_state, :attribute => :state)
1877
+ @object = @klass.new
1878
+ end
1879
+
1880
+ def test_should_track_each_state_machine
1881
+ expected = {:state => @machine, :public_state => @second_machine}
1882
+ assert_equal expected, @klass.state_machines
1883
+ end
1884
+
1885
+ def test_should_initialize_based_on_first_available_initial_state
1886
+ assert_equal 'parked', @object.state
1887
+ end
1888
+
1889
+ def test_should_allow_transitions_on_both_machines
1890
+ @machine.event :ignite do
1891
+ transition :parked => :idling
1892
+ end
1893
+
1894
+ @second_machine.event :park do
1895
+ transition :idling => :parked
1896
+ end
1897
+
1898
+ @object.ignite
1899
+ assert_equal 'idling', @object.state
1900
+
1901
+ @object.park
1902
+ assert_equal 'parked', @object.state
1903
+ end
1904
+ end
1905
+
1906
+ class MachineWithNamespaceTest < Test::Unit::TestCase
1907
+ def setup
1908
+ @klass = Class.new
1909
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do
1910
+ event :enable do
1911
+ transition :off => :active
1912
+ end
1913
+
1914
+ event :disable do
1915
+ transition :active => :off
1916
+ end
1917
+ end
1918
+ @object = @klass.new
1919
+ end
1920
+
1921
+ def test_should_namespace_state_predicates
1922
+ [:alarm_active?, :alarm_off?].each do |name|
1923
+ assert @object.respond_to?(name)
1924
+ end
1925
+ end
1926
+
1927
+ def test_should_namespace_event_checks
1928
+ [:can_enable_alarm?, :can_disable_alarm?].each do |name|
1929
+ assert @object.respond_to?(name)
1930
+ end
1931
+ end
1932
+
1933
+ def test_should_namespace_event_transition_readers
1934
+ [:enable_alarm_transition, :disable_alarm_transition].each do |name|
1935
+ assert @object.respond_to?(name)
1936
+ end
1937
+ end
1938
+
1939
+ def test_should_namespace_events
1940
+ [:enable_alarm, :disable_alarm].each do |name|
1941
+ assert @object.respond_to?(name)
1942
+ end
1943
+ end
1944
+
1945
+ def test_should_namespace_bang_events
1946
+ [:enable_alarm!, :disable_alarm!].each do |name|
1947
+ assert @object.respond_to?(name)
1948
+ end
1949
+ end
1950
+ end
1951
+
1952
+ class MachineWithCustomAttributeTest < Test::Unit::TestCase
1953
+ def setup
1954
+ StateMachine::Integrations.const_set('Custom', Module.new do
1955
+ class << self; attr_reader :defaults; end
1956
+ @defaults = {:action => :save, :use_transactions => false}
1957
+
1958
+ def create_with_scope(name)
1959
+ lambda {}
1960
+ end
1961
+
1962
+ def create_without_scope(name)
1963
+ lambda {}
1964
+ end
1965
+ end)
1966
+
1967
+ @klass = Class.new
1968
+ @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :active, :integration => :custom) do
1969
+ event :ignite do
1970
+ transition :parked => :idling
1971
+ end
1972
+ end
1973
+ @object = @klass.new
1974
+ end
1975
+
1976
+ def test_should_define_a_reader_attribute_for_the_attribute
1977
+ assert @object.respond_to?(:state_id)
1978
+ end
1979
+
1980
+ def test_should_define_a_writer_attribute_for_the_attribute
1981
+ assert @object.respond_to?(:state_id=)
1982
+ end
1983
+
1984
+ def test_should_define_a_predicate_for_the_attribute
1985
+ assert @object.respond_to?(:state?)
1986
+ end
1987
+
1988
+ def test_should_define_a_name_reader_for_the_attribute
1989
+ assert @object.respond_to?(:state_name)
1990
+ end
1991
+
1992
+ def test_should_define_a_human_name_reader_for_the_attribute
1993
+ assert @object.respond_to?(:state_name)
1994
+ end
1995
+
1996
+ def test_should_define_an_event_reader_for_the_attribute
1997
+ assert @object.respond_to?(:state_events)
1998
+ end
1999
+
2000
+ def test_should_define_a_transition_reader_for_the_attribute
2001
+ assert @object.respond_to?(:state_transitions)
2002
+ end
2003
+
2004
+ def test_should_define_a_human_attribute_name_reader
2005
+ assert @klass.respond_to?(:human_state_name)
2006
+ end
2007
+
2008
+ def test_should_define_a_human_event_name_reader
2009
+ assert @klass.respond_to?(:human_state_event_name)
2010
+ end
2011
+
2012
+ def test_should_define_singular_with_scope
2013
+ assert @klass.respond_to?(:with_state)
2014
+ end
2015
+
2016
+ def test_should_define_singular_without_scope
2017
+ assert @klass.respond_to?(:without_state)
2018
+ end
2019
+
2020
+ def test_should_define_plural_with_scope
2021
+ assert @klass.respond_to?(:with_states)
2022
+ end
2023
+
2024
+ def test_should_define_plural_without_scope
2025
+ assert @klass.respond_to?(:without_states)
2026
+ end
2027
+
2028
+ def test_should_define_state_machines_reader
2029
+ expected = {:state => @machine}
2030
+ assert_equal expected, @klass.state_machines
2031
+ end
2032
+
2033
+ def teardown
2034
+ StateMachine::Integrations.send(:remove_const, 'Custom')
2035
+ end
2036
+ end
2037
+
2038
+ class MachineFinderWithoutExistingMachineTest < Test::Unit::TestCase
2039
+ def setup
2040
+ @klass = Class.new
2041
+ @machine = StateMachine::Machine.find_or_create(@klass)
2042
+ end
2043
+
2044
+ def test_should_accept_a_block
2045
+ called = false
2046
+ StateMachine::Machine.find_or_create(Class.new) do
2047
+ called = respond_to?(:event)
2048
+ end
2049
+
2050
+ assert called
2051
+ end
2052
+
2053
+ def test_should_create_a_new_machine
2054
+ assert_not_nil @machine
2055
+ end
2056
+
2057
+ def test_should_use_default_state
2058
+ assert_equal :state, @machine.attribute
2059
+ end
2060
+ end
2061
+
2062
+ class MachineFinderWithExistingOnSameClassTest < Test::Unit::TestCase
2063
+ def setup
2064
+ @klass = Class.new
2065
+ @existing_machine = StateMachine::Machine.new(@klass)
2066
+ @machine = StateMachine::Machine.find_or_create(@klass)
2067
+ end
2068
+
2069
+ def test_should_accept_a_block
2070
+ called = false
2071
+ StateMachine::Machine.find_or_create(@klass) do
2072
+ called = respond_to?(:event)
2073
+ end
2074
+
2075
+ assert called
2076
+ end
2077
+
2078
+ def test_should_not_create_a_new_machine
2079
+ assert_same @machine, @existing_machine
2080
+ end
2081
+ end
2082
+
2083
+ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
2084
+ def setup
2085
+ integration = Module.new do
2086
+ def self.matches?(klass)
2087
+ false
2088
+ end
2089
+ end
2090
+ StateMachine::Integrations.const_set('Custom', integration)
2091
+
2092
+ @base_class = Class.new
2093
+ @base_machine = StateMachine::Machine.new(@base_class, :status, :action => :save, :integration => :custom)
2094
+ @base_machine.event(:ignite) {}
2095
+ @base_machine.before_transition(lambda {})
2096
+ @base_machine.after_transition(lambda {})
2097
+ @base_machine.around_transition(lambda {})
2098
+
2099
+ @klass = Class.new(@base_class)
2100
+ @machine = StateMachine::Machine.find_or_create(@klass, :status) {}
2101
+ end
2102
+
2103
+ def test_should_accept_a_block
2104
+ called = false
2105
+ StateMachine::Machine.find_or_create(Class.new(@base_class)) do
2106
+ called = respond_to?(:event)
2107
+ end
2108
+
2109
+ assert called
2110
+ end
2111
+
2112
+ def test_should_not_create_a_new_machine_if_no_block_or_options
2113
+ machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status)
2114
+
2115
+ assert_same machine, @base_machine
2116
+ end
2117
+
2118
+ def test_should_create_a_new_machine_if_given_options
2119
+ machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
2120
+
2121
+ assert_not_nil machine
2122
+ assert_not_same machine, @base_machine
2123
+ end
2124
+
2125
+ def test_should_create_a_new_machine_if_given_block
2126
+ assert_not_nil @machine
2127
+ assert_not_same @machine, @base_machine
2128
+ end
2129
+
2130
+ def test_should_copy_the_base_attribute
2131
+ assert_equal :status, @machine.attribute
2132
+ end
2133
+
2134
+ def test_should_copy_the_base_configuration
2135
+ assert_equal :save, @machine.action
2136
+ end
2137
+
2138
+ def test_should_copy_events
2139
+ # Can't assert equal arrays since their machines change
2140
+ assert_equal 1, @machine.events.length
2141
+ end
2142
+
2143
+ def test_should_copy_before_callbacks
2144
+ assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before]
2145
+ end
2146
+
2147
+ def test_should_copy_after_transitions
2148
+ assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after]
2149
+ end
2150
+
2151
+ def test_should_use_the_same_integration
2152
+ assert (class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom)
2153
+ end
2154
+
2155
+ def teardown
2156
+ StateMachine::Integrations.send(:remove_const, 'Custom')
2157
+ end
2158
+ end
2159
+
2160
+ class MachineFinderCustomOptionsTest < Test::Unit::TestCase
2161
+ def setup
2162
+ @klass = Class.new
2163
+ @machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
2164
+ @object = @klass.new
2165
+ end
2166
+
2167
+ def test_should_use_custom_attribute
2168
+ assert_equal :status, @machine.attribute
2169
+ end
2170
+
2171
+ def test_should_set_custom_initial_state
2172
+ assert_equal :parked, @machine.initial_state(@object).name
2173
+ end
2174
+ end
2175
+
2176
+ begin
2177
+ # Load library
2178
+ require 'rubygems'
2179
+ gem 'ruby-graphviz', '>=0.9.0'
2180
+ require 'graphviz'
2181
+
2182
+ class MachineDrawingTest < Test::Unit::TestCase
2183
+ def setup
2184
+ @klass = Class.new do
2185
+ def self.name; 'Vehicle'; end
2186
+ end
2187
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2188
+ @machine.event :ignite do
2189
+ transition :parked => :idling
2190
+ end
2191
+ end
2192
+
2193
+ def test_should_raise_exception_if_invalid_option_specified
2194
+ assert_raise(ArgumentError) {@machine.draw(:invalid => true)}
2195
+ end
2196
+
2197
+ def test_should_save_file_with_class_name_by_default
2198
+ graph = @machine.draw
2199
+ assert File.exists?('./Vehicle_state.png')
2200
+ end
2201
+
2202
+ def test_should_allow_base_name_to_be_customized
2203
+ graph = @machine.draw(:name => 'machine')
2204
+ assert File.exists?('./machine.png')
2205
+ end
2206
+
2207
+ def test_should_allow_format_to_be_customized
2208
+ graph = @machine.draw(:format => 'jpg')
2209
+ assert File.exists?('./Vehicle_state.jpg')
2210
+ end
2211
+
2212
+ def test_should_allow_path_to_be_customized
2213
+ graph = @machine.draw(:path => "#{File.dirname(__FILE__)}/")
2214
+ assert File.exists?("#{File.dirname(__FILE__)}/Vehicle_state.png")
2215
+ end
2216
+
2217
+ def test_should_allow_orientation_to_be_landscape
2218
+ graph = @machine.draw(:orientation => 'landscape')
2219
+ assert_equal 'LR', graph['rankdir'].to_s.gsub('"', '')
2220
+ end
2221
+
2222
+ def test_should_allow_orientation_to_be_portrait
2223
+ graph = @machine.draw(:orientation => 'portrait')
2224
+ assert_equal 'TB', graph['rankdir'].to_s.gsub('"', '')
2225
+ end
2226
+
2227
+ def teardown
2228
+ FileUtils.rm Dir["{.,#{File.dirname(__FILE__)}}/*.{png,jpg}"]
2229
+ end
2230
+ end
2231
+
2232
+ class MachineDrawingWithIntegerStatesTest < Test::Unit::TestCase
2233
+ def setup
2234
+ @klass = Class.new do
2235
+ def self.name; 'Vehicle'; end
2236
+ end
2237
+ @machine = StateMachine::Machine.new(@klass, :state_id, :initial => :parked)
2238
+ @machine.event :ignite do
2239
+ transition :parked => :idling
2240
+ end
2241
+ @machine.state :parked, :value => 1
2242
+ @machine.state :idling, :value => 2
2243
+ @graph = @machine.draw
2244
+ end
2245
+
2246
+ def test_should_draw_all_states
2247
+ assert_equal 3, @graph.node_count
2248
+ end
2249
+
2250
+ def test_should_draw_all_events
2251
+ assert_equal 2, @graph.edge_count
2252
+ end
2253
+
2254
+ def test_should_draw_machine
2255
+ assert File.exist?('./Vehicle_state_id.png')
2256
+ ensure
2257
+ FileUtils.rm('./Vehicle_state_id.png')
2258
+ end
2259
+ end
2260
+
2261
+ class MachineDrawingWithNilStatesTest < Test::Unit::TestCase
2262
+ def setup
2263
+ @klass = Class.new do
2264
+ def self.name; 'Vehicle'; end
2265
+ end
2266
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2267
+ @machine.event :ignite do
2268
+ transition :parked => :idling
2269
+ end
2270
+ @machine.state :parked, :value => nil
2271
+ @graph = @machine.draw
2272
+ end
2273
+
2274
+ def test_should_draw_all_states
2275
+ assert_equal 3, @graph.node_count
2276
+ end
2277
+
2278
+ def test_should_draw_all_events
2279
+ assert_equal 2, @graph.edge_count
2280
+ end
2281
+
2282
+ def test_should_draw_machine
2283
+ assert File.exist?('./Vehicle_state.png')
2284
+ ensure
2285
+ FileUtils.rm('./Vehicle_state.png')
2286
+ end
2287
+ end
2288
+
2289
+ class MachineDrawingWithDynamicStatesTest < Test::Unit::TestCase
2290
+ def setup
2291
+ @klass = Class.new do
2292
+ def self.name; 'Vehicle'; end
2293
+ end
2294
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2295
+ @machine.event :activate do
2296
+ transition :parked => :idling
2297
+ end
2298
+ @machine.state :idling, :value => lambda {Time.now}
2299
+ @graph = @machine.draw
2300
+ end
2301
+
2302
+ def test_should_draw_all_states
2303
+ assert_equal 3, @graph.node_count
2304
+ end
2305
+
2306
+ def test_should_draw_all_events
2307
+ assert_equal 2, @graph.edge_count
2308
+ end
2309
+
2310
+ def test_should_draw_machine
2311
+ assert File.exist?('./Vehicle_state.png')
2312
+ ensure
2313
+ FileUtils.rm('./Vehicle_state.png')
2314
+ end
2315
+ end
2316
+
2317
+ class MachineClassDrawingTest < Test::Unit::TestCase
2318
+ def setup
2319
+ @klass = Class.new do
2320
+ def self.name; 'Vehicle'; end
2321
+ end
2322
+ @machine = StateMachine::Machine.new(@klass)
2323
+ @machine.event :ignite do
2324
+ transition :parked => :idling
2325
+ end
2326
+ end
2327
+
2328
+ def test_should_raise_exception_if_no_class_names_specified
2329
+ exception = assert_raise(ArgumentError) {StateMachine::Machine.draw(nil)}
2330
+ assert_equal 'At least one class must be specified', exception.message
2331
+ end
2332
+
2333
+ def test_should_load_files
2334
+ StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../files/switch.rb")
2335
+ assert defined?(::Switch)
2336
+ ensure
2337
+ FileUtils.rm('./Switch_state.png')
2338
+ end
2339
+
2340
+ def test_should_allow_path_and_format_to_be_customized
2341
+ StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../files/switch.rb", :path => "#{File.dirname(__FILE__)}/", :format => 'jpg')
2342
+ assert File.exist?("#{File.dirname(__FILE__)}/Switch_state.jpg")
2343
+ ensure
2344
+ FileUtils.rm("#{File.dirname(__FILE__)}/Switch_state.jpg")
2345
+ end
2346
+ end
2347
+ rescue LoadError
2348
+ $stderr.puts 'Skipping GraphViz StateMachine::Machine tests. `gem install ruby-graphviz` >= v0.9.0 and try again.'
2349
+ end