verborghs-state_machine 0.9.4

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