hsume2-state_machine 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. data/CHANGELOG.rdoc +413 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +717 -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.rb +448 -0
  28. data/lib/state_machine/alternate_machine.rb +79 -0
  29. data/lib/state_machine/assertions.rb +36 -0
  30. data/lib/state_machine/branch.rb +224 -0
  31. data/lib/state_machine/callback.rb +236 -0
  32. data/lib/state_machine/condition_proxy.rb +94 -0
  33. data/lib/state_machine/error.rb +13 -0
  34. data/lib/state_machine/eval_helpers.rb +86 -0
  35. data/lib/state_machine/event.rb +304 -0
  36. data/lib/state_machine/event_collection.rb +139 -0
  37. data/lib/state_machine/extensions.rb +149 -0
  38. data/lib/state_machine/initializers.rb +4 -0
  39. data/lib/state_machine/initializers/merb.rb +1 -0
  40. data/lib/state_machine/initializers/rails.rb +25 -0
  41. data/lib/state_machine/integrations.rb +110 -0
  42. data/lib/state_machine/integrations/active_model.rb +502 -0
  43. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  44. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  45. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  46. data/lib/state_machine/integrations/active_record.rb +424 -0
  47. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  48. data/lib/state_machine/integrations/active_record/versions.rb +143 -0
  49. data/lib/state_machine/integrations/base.rb +91 -0
  50. data/lib/state_machine/integrations/data_mapper.rb +392 -0
  51. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  52. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  53. data/lib/state_machine/integrations/mongo_mapper.rb +272 -0
  54. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  55. data/lib/state_machine/integrations/mongo_mapper/versions.rb +110 -0
  56. data/lib/state_machine/integrations/mongoid.rb +357 -0
  57. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  58. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  59. data/lib/state_machine/integrations/sequel.rb +428 -0
  60. data/lib/state_machine/integrations/sequel/versions.rb +36 -0
  61. data/lib/state_machine/machine.rb +1873 -0
  62. data/lib/state_machine/machine_collection.rb +87 -0
  63. data/lib/state_machine/matcher.rb +123 -0
  64. data/lib/state_machine/matcher_helpers.rb +54 -0
  65. data/lib/state_machine/node_collection.rb +157 -0
  66. data/lib/state_machine/path.rb +120 -0
  67. data/lib/state_machine/path_collection.rb +90 -0
  68. data/lib/state_machine/state.rb +271 -0
  69. data/lib/state_machine/state_collection.rb +112 -0
  70. data/lib/state_machine/transition.rb +458 -0
  71. data/lib/state_machine/transition_collection.rb +244 -0
  72. data/lib/tasks/state_machine.rake +1 -0
  73. data/lib/tasks/state_machine.rb +27 -0
  74. data/test/files/en.yml +17 -0
  75. data/test/files/switch.rb +11 -0
  76. data/test/functional/alternate_state_machine_test.rb +122 -0
  77. data/test/functional/state_machine_test.rb +993 -0
  78. data/test/test_helper.rb +4 -0
  79. data/test/unit/assertions_test.rb +40 -0
  80. data/test/unit/branch_test.rb +890 -0
  81. data/test/unit/callback_test.rb +701 -0
  82. data/test/unit/condition_proxy_test.rb +328 -0
  83. data/test/unit/error_test.rb +43 -0
  84. data/test/unit/eval_helpers_test.rb +222 -0
  85. data/test/unit/event_collection_test.rb +358 -0
  86. data/test/unit/event_test.rb +985 -0
  87. data/test/unit/integrations/active_model_test.rb +1097 -0
  88. data/test/unit/integrations/active_record_test.rb +2021 -0
  89. data/test/unit/integrations/base_test.rb +99 -0
  90. data/test/unit/integrations/data_mapper_test.rb +1909 -0
  91. data/test/unit/integrations/mongo_mapper_test.rb +1611 -0
  92. data/test/unit/integrations/mongoid_test.rb +1591 -0
  93. data/test/unit/integrations/sequel_test.rb +1523 -0
  94. data/test/unit/integrations_test.rb +61 -0
  95. data/test/unit/invalid_event_test.rb +20 -0
  96. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  97. data/test/unit/invalid_transition_test.rb +77 -0
  98. data/test/unit/machine_collection_test.rb +599 -0
  99. data/test/unit/machine_test.rb +3043 -0
  100. data/test/unit/matcher_helpers_test.rb +37 -0
  101. data/test/unit/matcher_test.rb +155 -0
  102. data/test/unit/node_collection_test.rb +217 -0
  103. data/test/unit/path_collection_test.rb +266 -0
  104. data/test/unit/path_test.rb +485 -0
  105. data/test/unit/state_collection_test.rb +310 -0
  106. data/test/unit/state_machine_test.rb +31 -0
  107. data/test/unit/state_test.rb +924 -0
  108. data/test/unit/transition_collection_test.rb +2102 -0
  109. data/test/unit/transition_test.rb +1541 -0
  110. metadata +207 -0
@@ -0,0 +1,3043 @@
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_any_failure_callbacks
47
+ assert @machine.callbacks[:failure].empty?
48
+ end
49
+
50
+ def test_should_not_have_an_action
51
+ assert_nil @machine.action
52
+ end
53
+
54
+ def test_should_use_tranactions
55
+ assert_equal true, @machine.use_transactions
56
+ end
57
+
58
+ def test_should_not_have_a_namespace
59
+ assert_nil @machine.namespace
60
+ end
61
+
62
+ def test_should_have_a_nil_state
63
+ assert_equal [nil], @machine.states.keys
64
+ end
65
+
66
+ def test_should_set_initial_on_nil_state
67
+ assert @machine.state(nil).initial
68
+ end
69
+
70
+ def test_should_generate_default_messages
71
+ assert_equal 'is invalid', @machine.generate_message(:invalid)
72
+ assert_equal 'cannot transition when parked', @machine.generate_message(:invalid_event, [[:state, :parked]])
73
+ assert_equal 'cannot transition via "park"', @machine.generate_message(:invalid_transition, [[:event, :park]])
74
+ end
75
+
76
+ def test_should_not_be_extended_by_the_base_integration
77
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Base)
78
+ end
79
+
80
+ def test_should_not_be_extended_by_the_active_model_integration
81
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveModel)
82
+ end
83
+
84
+ def test_should_not_be_extended_by_the_active_record_integration
85
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveRecord)
86
+ end
87
+
88
+ def test_should_not_be_extended_by_the_datamapper_integration
89
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::DataMapper)
90
+ end
91
+
92
+ def test_should_not_be_extended_by_the_mongo_mapper_integration
93
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::MongoMapper)
94
+ end
95
+
96
+ def test_should_not_be_extended_by_the_sequel_integration
97
+ assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Sequel)
98
+ end
99
+
100
+ def test_should_define_a_reader_attribute_for_the_attribute
101
+ assert @object.respond_to?(:state)
102
+ end
103
+
104
+ def test_should_define_a_writer_attribute_for_the_attribute
105
+ assert @object.respond_to?(:state=)
106
+ end
107
+
108
+ def test_should_define_a_predicate_for_the_attribute
109
+ assert @object.respond_to?(:state?)
110
+ end
111
+
112
+ def test_should_define_a_name_reader_for_the_attribute
113
+ assert @object.respond_to?(:state_name)
114
+ end
115
+
116
+ def test_should_define_an_event_reader_for_the_attribute
117
+ assert @object.respond_to?(:state_events)
118
+ end
119
+
120
+ def test_should_define_a_transition_reader_for_the_attribute
121
+ assert @object.respond_to?(:state_transitions)
122
+ end
123
+
124
+ def test_should_define_a_path_reader_for_the_attribute
125
+ assert @object.respond_to?(:state_paths)
126
+ end
127
+
128
+ def test_should_not_define_an_event_attribute_reader
129
+ assert !@object.respond_to?(:state_event)
130
+ end
131
+
132
+ def test_should_not_define_an_event_attribute_writer
133
+ assert !@object.respond_to?(:state_event=)
134
+ end
135
+
136
+ def test_should_not_define_an_event_transition_attribute_reader
137
+ assert !@object.respond_to?(:state_event_transition)
138
+ end
139
+
140
+ def test_should_not_define_an_event_transition_attribute_writer
141
+ assert !@object.respond_to?(:state_event_transition=)
142
+ end
143
+
144
+ def test_should_define_a_human_attribute_name_reader_for_the_attribute
145
+ assert @klass.respond_to?(:human_state_name)
146
+ end
147
+
148
+ def test_should_define_a_human_event_name_reader_for_the_attribute
149
+ assert @klass.respond_to?(:human_state_event_name)
150
+ end
151
+
152
+ def test_should_not_define_singular_with_scope
153
+ assert !@klass.respond_to?(:with_state)
154
+ end
155
+
156
+ def test_should_not_define_singular_without_scope
157
+ assert !@klass.respond_to?(:without_state)
158
+ end
159
+
160
+ def test_should_not_define_plural_with_scope
161
+ assert !@klass.respond_to?(:with_states)
162
+ end
163
+
164
+ def test_should_not_define_plural_without_scope
165
+ assert !@klass.respond_to?(:without_states)
166
+ end
167
+
168
+ def test_should_extend_owner_class_with_class_methods
169
+ assert (class << @klass; ancestors; end).include?(StateMachine::ClassMethods)
170
+ end
171
+
172
+ def test_should_include_instance_methods_in_owner_class
173
+ assert @klass.included_modules.include?(StateMachine::InstanceMethods)
174
+ end
175
+
176
+ def test_should_define_state_machines_reader
177
+ expected = {:state => @machine}
178
+ assert_equal expected, @klass.state_machines
179
+ end
180
+ end
181
+
182
+ class MachineWithCustomNameTest < Test::Unit::TestCase
183
+ def setup
184
+ @klass = Class.new
185
+ @machine = StateMachine::Machine.new(@klass, :status)
186
+ @object = @klass.new
187
+ end
188
+
189
+ def test_should_use_custom_name
190
+ assert_equal :status, @machine.name
191
+ end
192
+
193
+ def test_should_use_custom_name_for_attribute
194
+ assert_equal :status, @machine.attribute
195
+ end
196
+
197
+ def test_should_prefix_custom_attributes_with_custom_name
198
+ assert_equal :status_event, @machine.attribute(:event)
199
+ end
200
+
201
+ def test_should_define_a_reader_attribute_for_the_attribute
202
+ assert @object.respond_to?(:status)
203
+ end
204
+
205
+ def test_should_define_a_writer_attribute_for_the_attribute
206
+ assert @object.respond_to?(:status=)
207
+ end
208
+
209
+ def test_should_define_a_predicate_for_the_attribute
210
+ assert @object.respond_to?(:status?)
211
+ end
212
+
213
+ def test_should_define_a_name_reader_for_the_attribute
214
+ assert @object.respond_to?(:status_name)
215
+ end
216
+
217
+ def test_should_define_an_event_reader_for_the_attribute
218
+ assert @object.respond_to?(:status_events)
219
+ end
220
+
221
+ def test_should_define_a_transition_reader_for_the_attribute
222
+ assert @object.respond_to?(:status_transitions)
223
+ end
224
+
225
+ def test_should_define_a_human_attribute_name_reader_for_the_attribute
226
+ assert @klass.respond_to?(:human_status_name)
227
+ end
228
+
229
+ def test_should_define_a_human_event_name_reader_for_the_attribute
230
+ assert @klass.respond_to?(:human_status_event_name)
231
+ end
232
+ end
233
+
234
+ class MachineWithoutInitializationTest < Test::Unit::TestCase
235
+ def setup
236
+ @klass = Class.new do
237
+ def initialize(attributes = {})
238
+ attributes.each {|attr, value| send("#{attr}=", value)}
239
+ super()
240
+ end
241
+ end
242
+
243
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked, :initialize => false)
244
+ end
245
+
246
+ def test_should_not_have_an_initial_state
247
+ object = @klass.new
248
+ assert_nil object.state
249
+ end
250
+
251
+ def test_should_still_allow_manual_initialization
252
+ @klass.class_eval do
253
+ def initialize(attributes = {})
254
+ attributes.each {|attr, value| send("#{attr}=", value)}
255
+ super()
256
+ initialize_state_machines
257
+ end
258
+ end
259
+
260
+ object = @klass.new
261
+ assert_equal 'parked', object.state
262
+ end
263
+ end
264
+
265
+ class MachineWithStaticInitialStateTest < Test::Unit::TestCase
266
+ def setup
267
+ @klass = Class.new
268
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
269
+ end
270
+
271
+ def test_should_not_have_dynamic_initial_state
272
+ assert !@machine.dynamic_initial_state?
273
+ end
274
+
275
+ def test_should_have_an_initial_state
276
+ object = @klass.new
277
+ assert_equal 'parked', @machine.initial_state(object).value
278
+ end
279
+
280
+ def test_should_write_to_attribute_when_initializing_state
281
+ object = @klass.allocate
282
+ @machine.initialize_state(object)
283
+ assert_equal 'parked', object.state
284
+ end
285
+
286
+ def test_should_set_initial_on_state_object
287
+ assert @machine.state(:parked).initial
288
+ end
289
+
290
+ def test_should_set_initial_state_on_created_object
291
+ assert_equal 'parked', @klass.new.state
292
+ end
293
+
294
+ def test_should_still_set_initial_state_even_if_not_empty
295
+ @klass.class_eval do
296
+ def initialize(attributes = {})
297
+ self.state = 'idling'
298
+ super()
299
+ end
300
+ end
301
+ object = @klass.new
302
+ assert_equal 'parked', object.state
303
+ end
304
+
305
+ def test_should_set_initial_state_prior_to_initialization
306
+ base = Class.new do
307
+ attr_accessor :state_on_init
308
+
309
+ def initialize
310
+ self.state_on_init = state
311
+ end
312
+ end
313
+ klass = Class.new(base)
314
+ machine = StateMachine::Machine.new(klass, :initial => :parked)
315
+
316
+ assert_equal 'parked', klass.new.state_on_init
317
+ end
318
+
319
+ def test_should_be_included_in_known_states
320
+ assert_equal [:parked], @machine.states.keys
321
+ end
322
+ end
323
+
324
+ class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
325
+ def setup
326
+ @klass = Class.new do
327
+ attr_accessor :initial_state
328
+ end
329
+ @machine = StateMachine::Machine.new(@klass, :initial => lambda {|object| object.initial_state || :default})
330
+ @machine.state :parked, :idling, :default
331
+ @object = @klass.new
332
+ end
333
+
334
+ def test_should_have_dynamic_initial_state
335
+ assert @machine.dynamic_initial_state?
336
+ end
337
+
338
+ def test_should_use_the_record_for_determining_the_initial_state
339
+ @object.initial_state = :parked
340
+ assert_equal :parked, @machine.initial_state(@object).name
341
+
342
+ @object.initial_state = :idling
343
+ assert_equal :idling, @machine.initial_state(@object).name
344
+ end
345
+
346
+ def test_should_write_to_attribute_when_initializing_state
347
+ object = @klass.allocate
348
+ object.initial_state = :parked
349
+ @machine.initialize_state(object)
350
+ assert_equal 'parked', object.state
351
+ end
352
+
353
+ def test_should_set_initial_state_on_created_object
354
+ assert_equal 'default', @object.state
355
+ end
356
+
357
+ def test_should_not_set_initial_state_even_if_not_empty
358
+ @klass.class_eval do
359
+ def initialize(attributes = {})
360
+ self.state = 'parked'
361
+ super()
362
+ end
363
+ end
364
+ object = @klass.new
365
+ assert_equal 'parked', object.state
366
+ end
367
+
368
+ def test_should_set_initial_state_after_initialization
369
+ base = Class.new do
370
+ attr_accessor :state_on_init
371
+
372
+ def initialize
373
+ self.state_on_init = state
374
+ end
375
+ end
376
+ klass = Class.new(base)
377
+ machine = StateMachine::Machine.new(klass, :initial => lambda {|object| :parked})
378
+ machine.state :parked
379
+
380
+ assert_nil klass.new.state_on_init
381
+ end
382
+
383
+ def test_should_not_be_included_in_known_states
384
+ assert_equal [:parked, :idling, :default], @machine.states.map {|state| state.name}
385
+ end
386
+ end
387
+
388
+ class MachineStateInitializationTest < Test::Unit::TestCase
389
+ def setup
390
+ @klass = Class.new
391
+ @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :initialize => false)
392
+
393
+ @object = @klass.new
394
+ @object.state = nil
395
+ end
396
+
397
+ def test_should_set_states_if_nil
398
+ @machine.initialize_state(@object)
399
+
400
+ assert_equal 'parked', @object.state
401
+ end
402
+
403
+ def test_should_set_states_if_empty
404
+ @object.state = ''
405
+ @machine.initialize_state(@object)
406
+
407
+ assert_equal 'parked', @object.state
408
+ end
409
+
410
+ def test_should_not_set_states_if_not_empty
411
+ @object.state = 'idling'
412
+ @machine.initialize_state(@object)
413
+
414
+ assert_equal 'idling', @object.state
415
+ end
416
+
417
+ def test_should_set_states_if_not_empty_and_forced
418
+ @object.state = 'idling'
419
+ @machine.initialize_state(@object, :force => true)
420
+
421
+ assert_equal 'parked', @object.state
422
+ end
423
+
424
+ def test_should_not_set_state_if_nil_and_nil_is_valid_state
425
+ @machine.state :initial, :value => nil
426
+ @machine.initialize_state(@object)
427
+
428
+ assert_nil @object.state
429
+ end
430
+
431
+ def test_should_write_to_hash_if_specified
432
+ @machine.initialize_state(@object, :to => hash = {})
433
+ assert_equal expected = {'state' => 'parked'}, hash
434
+ end
435
+
436
+ def test_should_not_write_to_object_if_writing_to_hash
437
+ @machine.initialize_state(@object, :to => {})
438
+ assert_nil @object.state
439
+ end
440
+ end
441
+
442
+ class MachineWithCustomActionTest < Test::Unit::TestCase
443
+ def setup
444
+ @machine = StateMachine::Machine.new(Class.new, :action => :save)
445
+ end
446
+
447
+ def test_should_use_the_custom_action
448
+ assert_equal :save, @machine.action
449
+ end
450
+ end
451
+
452
+ class MachineWithNilActionTest < Test::Unit::TestCase
453
+ def setup
454
+ integration = Module.new do
455
+ include StateMachine::Integrations::Base
456
+
457
+ @defaults = {:action => :save}
458
+ end
459
+ StateMachine::Integrations.const_set('Custom', integration)
460
+ @machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom)
461
+ end
462
+
463
+ def test_should_have_a_nil_action
464
+ assert_nil @machine.action
465
+ end
466
+
467
+ def teardown
468
+ StateMachine::Integrations.send(:remove_const, 'Custom')
469
+ end
470
+ end
471
+
472
+ class MachineWithoutIntegrationTest < Test::Unit::TestCase
473
+ def setup
474
+ @klass = Class.new
475
+ @machine = StateMachine::Machine.new(@klass)
476
+ @object = @klass.new
477
+ end
478
+
479
+ def test_transaction_should_yield
480
+ @yielded = false
481
+ @machine.within_transaction(@object) do
482
+ @yielded = true
483
+ end
484
+
485
+ assert @yielded
486
+ end
487
+
488
+ def test_invalidation_should_do_nothing
489
+ assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
490
+ end
491
+
492
+ def test_reset_should_do_nothing
493
+ assert_nil @machine.reset(@object)
494
+ end
495
+ end
496
+
497
+ class MachineWithCustomIntegrationTest < Test::Unit::TestCase
498
+ def setup
499
+ integration = Module.new do
500
+ include StateMachine::Integrations::Base
501
+
502
+ def self.available?
503
+ true
504
+ end
505
+
506
+ def self.matches?(klass)
507
+ true
508
+ end
509
+ end
510
+
511
+ StateMachine::Integrations.const_set('Custom', integration)
512
+ end
513
+
514
+ def test_should_be_extended_by_the_integration_if_explicit
515
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom)
516
+ assert (class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
517
+ end
518
+
519
+ def test_should_not_be_extended_by_the_integration_if_implicit_but_not_available
520
+ StateMachine::Integrations::Custom.class_eval do
521
+ def self.available?
522
+ false
523
+ end
524
+ end
525
+
526
+ machine = StateMachine::Machine.new(Class.new)
527
+ assert !(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
528
+ end
529
+
530
+ def test_should_not_be_extended_by_the_integration_if_implicit_but_not_matched
531
+ StateMachine::Integrations::Custom.class_eval do
532
+ def self.matches?(klass)
533
+ false
534
+ end
535
+ end
536
+
537
+ machine = StateMachine::Machine.new(Class.new)
538
+ assert !(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
539
+ end
540
+
541
+ def test_should_be_extended_by_the_integration_if_implicit_and_available_and_matches
542
+ machine = StateMachine::Machine.new(Class.new)
543
+ assert (class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
544
+ end
545
+
546
+ def test_should_not_be_extended_by_the_integration_if_nil
547
+ machine = StateMachine::Machine.new(Class.new, :integration => nil)
548
+ assert !(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
549
+ end
550
+
551
+ def test_should_not_be_extended_by_the_integration_if_false
552
+ machine = StateMachine::Machine.new(Class.new, :integration => false)
553
+ assert !(class << machine; ancestors; end).include?(StateMachine::Integrations::Custom)
554
+ end
555
+
556
+ def teardown
557
+ StateMachine::Integrations.send(:remove_const, 'Custom')
558
+ end
559
+ end
560
+
561
+ class MachineWithIntegrationTest < Test::Unit::TestCase
562
+ def setup
563
+ StateMachine::Integrations.const_set('Custom', Module.new do
564
+ include StateMachine::Integrations::Base
565
+
566
+ @defaults = {:action => :save, :use_transactions => false}
567
+
568
+ attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
569
+
570
+ def after_initialize
571
+ @initialized = true
572
+ end
573
+
574
+ def create_with_scope(name)
575
+ (@with_scopes ||= []) << name
576
+ lambda {}
577
+ end
578
+
579
+ def create_without_scope(name)
580
+ (@without_scopes ||= []) << name
581
+ lambda {}
582
+ end
583
+
584
+ def transaction(object)
585
+ @ran_transaction = true
586
+ yield
587
+ end
588
+ end)
589
+
590
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom)
591
+ end
592
+
593
+ def test_should_call_after_initialize_hook
594
+ assert @machine.initialized
595
+ end
596
+
597
+ def test_should_use_the_default_action
598
+ assert_equal :save, @machine.action
599
+ end
600
+
601
+ def test_should_use_the_custom_action_if_specified
602
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :action => :save!)
603
+ assert_equal :save!, machine.action
604
+ end
605
+
606
+ def test_should_use_the_default_use_transactions
607
+ assert_equal false, @machine.use_transactions
608
+ end
609
+
610
+ def test_should_use_the_custom_use_transactions_if_specified
611
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true)
612
+ assert_equal true, machine.use_transactions
613
+ end
614
+
615
+ def test_should_define_a_singular_and_plural_with_scope
616
+ assert_equal %w(with_state with_states), @machine.with_scopes
617
+ end
618
+
619
+ def test_should_define_a_singular_and_plural_without_scope
620
+ assert_equal %w(without_state without_states), @machine.without_scopes
621
+ end
622
+
623
+ def teardown
624
+ StateMachine::Integrations.send(:remove_const, 'Custom')
625
+ end
626
+ end
627
+
628
+ class MachineWithActionUndefinedTest < Test::Unit::TestCase
629
+ def setup
630
+ @klass = Class.new
631
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
632
+ @object = @klass.new
633
+ end
634
+
635
+ def test_should_define_an_event_attribute_reader
636
+ assert @object.respond_to?(:state_event)
637
+ end
638
+
639
+ def test_should_define_an_event_attribute_writer
640
+ assert @object.respond_to?(:state_event=)
641
+ end
642
+
643
+ def test_should_define_an_event_transition_attribute_reader
644
+ assert @object.respond_to?(:state_event_transition)
645
+ end
646
+
647
+ def test_should_define_an_event_transition_attribute_writer
648
+ assert @object.respond_to?(:state_event_transition=)
649
+ end
650
+
651
+ def test_should_not_define_action
652
+ assert !@object.respond_to?(:save)
653
+ end
654
+
655
+ def test_should_not_mark_action_hook_as_defined
656
+ assert !@machine.action_hook?
657
+ end
658
+ end
659
+
660
+ class MachineWithActionDefinedInClassTest < Test::Unit::TestCase
661
+ def setup
662
+ @klass = Class.new do
663
+ def save
664
+ end
665
+ end
666
+
667
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
668
+ @object = @klass.new
669
+ end
670
+
671
+ def test_should_define_an_event_attribute_reader
672
+ assert @object.respond_to?(:state_event)
673
+ end
674
+
675
+ def test_should_define_an_event_attribute_writer
676
+ assert @object.respond_to?(:state_event=)
677
+ end
678
+
679
+ def test_should_define_an_event_transition_attribute_reader
680
+ assert @object.respond_to?(:state_event_transition)
681
+ end
682
+
683
+ def test_should_define_an_event_transition_attribute_writer
684
+ assert @object.respond_to?(:state_event_transition=)
685
+ end
686
+
687
+ def test_should_not_define_action
688
+ assert !@klass.ancestors.any? {|ancestor| ancestor != @klass && ancestor.method_defined?(:save)}
689
+ end
690
+
691
+ def test_should_not_mark_action_hook_as_defined
692
+ assert !@machine.action_hook?
693
+ end
694
+ end
695
+
696
+ class MachineWithActionDefinedInIncludedModuleTest < Test::Unit::TestCase
697
+ def setup
698
+ @mod = mod = Module.new do
699
+ def save
700
+ end
701
+ end
702
+
703
+ @klass = Class.new do
704
+ include mod
705
+ end
706
+
707
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
708
+ @object = @klass.new
709
+ end
710
+
711
+ def test_should_define_an_event_attribute_reader
712
+ assert @object.respond_to?(:state_event)
713
+ end
714
+
715
+ def test_should_define_an_event_attribute_writer
716
+ assert @object.respond_to?(:state_event=)
717
+ end
718
+
719
+ def test_should_define_an_event_transition_attribute_reader
720
+ assert @object.respond_to?(:state_event_transition)
721
+ end
722
+
723
+ def test_should_define_an_event_transition_attribute_writer
724
+ assert @object.respond_to?(:state_event_transition=)
725
+ end
726
+
727
+ def test_should_define_action
728
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @mod].include?(ancestor) && ancestor.method_defined?(:save)}
729
+ end
730
+
731
+ def test_should_keep_action_public
732
+ assert @klass.public_method_defined?(:save)
733
+ end
734
+
735
+ def test_should_mark_action_hook_as_defined
736
+ assert @machine.action_hook?
737
+ end
738
+ end
739
+
740
+ class MachineWithActionDefinedInSuperclassTest < Test::Unit::TestCase
741
+ def setup
742
+ @superclass = Class.new do
743
+ def save
744
+ end
745
+ end
746
+ @klass = Class.new(@superclass)
747
+
748
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
749
+ @object = @klass.new
750
+ end
751
+
752
+ def test_should_define_an_event_attribute_reader
753
+ assert @object.respond_to?(:state_event)
754
+ end
755
+
756
+ def test_should_define_an_event_attribute_writer
757
+ assert @object.respond_to?(:state_event=)
758
+ end
759
+
760
+ def test_should_define_an_event_transition_attribute_reader
761
+ assert @object.respond_to?(:state_event_transition)
762
+ end
763
+
764
+ def test_should_define_an_event_transition_attribute_writer
765
+ assert @object.respond_to?(:state_event_transition=)
766
+ end
767
+
768
+ def test_should_define_action
769
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}
770
+ end
771
+
772
+ def test_should_keep_action_public
773
+ assert @klass.public_method_defined?(:save)
774
+ end
775
+
776
+ def test_should_mark_action_hook_as_defined
777
+ assert @machine.action_hook?
778
+ end
779
+ end
780
+
781
+ class MachineWithPrivateActionTest < Test::Unit::TestCase
782
+ def setup
783
+ @superclass = Class.new do
784
+ private
785
+ def save
786
+ end
787
+ end
788
+ @klass = Class.new(@superclass)
789
+
790
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
791
+ @object = @klass.new
792
+ end
793
+
794
+ def test_should_define_an_event_attribute_reader
795
+ assert @object.respond_to?(:state_event)
796
+ end
797
+
798
+ def test_should_define_an_event_attribute_writer
799
+ assert @object.respond_to?(:state_event=)
800
+ end
801
+
802
+ def test_should_define_an_event_transition_attribute_reader
803
+ assert @object.respond_to?(:state_event_transition)
804
+ end
805
+
806
+ def test_should_define_an_event_transition_attribute_writer
807
+ assert @object.respond_to?(:state_event_transition=)
808
+ end
809
+
810
+ def test_should_define_action
811
+ assert @klass.ancestors.any? {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.private_method_defined?(:save)}
812
+ end
813
+
814
+ def test_should_keep_action_private
815
+ assert @klass.private_method_defined?(:save)
816
+ end
817
+
818
+ def test_should_mark_action_hook_as_defined
819
+ assert @machine.action_hook?
820
+ end
821
+ end
822
+
823
+ class MachineWithActionAlreadyOverriddenTest < Test::Unit::TestCase
824
+ def setup
825
+ @superclass = Class.new do
826
+ def save
827
+ end
828
+ end
829
+ @klass = Class.new(@superclass)
830
+
831
+ StateMachine::Machine.new(@klass, :action => :save)
832
+ @machine = StateMachine::Machine.new(@klass, :status, :action => :save)
833
+ @object = @klass.new
834
+ end
835
+
836
+ def test_should_not_redefine_action
837
+ assert_equal 1, @klass.ancestors.select {|ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save)}.length
838
+ end
839
+
840
+ def test_should_mark_action_hook_as_defined
841
+ assert @machine.action_hook?
842
+ end
843
+ end
844
+
845
+ class MachineWithCustomPluralTest < Test::Unit::TestCase
846
+ def setup
847
+ @integration = Module.new do
848
+ include StateMachine::Integrations::Base
849
+
850
+ class << self; attr_accessor :with_scopes, :without_scopes; end
851
+ @with_scopes = []
852
+ @without_scopes = []
853
+
854
+ def create_with_scope(name)
855
+ StateMachine::Integrations::Custom.with_scopes << name
856
+ lambda {}
857
+ end
858
+
859
+ def create_without_scope(name)
860
+ StateMachine::Integrations::Custom.without_scopes << name
861
+ lambda {}
862
+ end
863
+ end
864
+
865
+ StateMachine::Integrations.const_set('Custom', @integration)
866
+ @machine = StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
867
+ end
868
+
869
+ def test_should_define_a_singular_and_plural_with_scope
870
+ assert_equal %w(with_state with_staties), @integration.with_scopes
871
+ end
872
+
873
+ def test_should_define_a_singular_and_plural_without_scope
874
+ assert_equal %w(without_state without_staties), @integration.without_scopes
875
+ end
876
+
877
+ def teardown
878
+ StateMachine::Integrations.send(:remove_const, 'Custom')
879
+ end
880
+ end
881
+
882
+ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
883
+ def setup
884
+ @integration = Module.new do
885
+ include StateMachine::Integrations::Base
886
+
887
+ def invalidate(object, attribute, message, values = [])
888
+ object.error = generate_message(message, values)
889
+ end
890
+ end
891
+ StateMachine::Integrations.const_set('Custom', @integration)
892
+
893
+ @klass = Class.new do
894
+ attr_accessor :error
895
+ end
896
+
897
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'})
898
+ @machine.state :parked
899
+
900
+ @object = @klass.new
901
+ @object.state = 'parked'
902
+ end
903
+
904
+ def test_generate_custom_message
905
+ assert_equal 'cannot park', @machine.generate_message(:invalid_transition, [[:event, :park]])
906
+ end
907
+
908
+ def test_use_custom_message
909
+ @machine.invalidate(@object, :state, :invalid_transition, [[:event, 'park']])
910
+ assert_equal 'cannot park', @object.error
911
+ end
912
+
913
+ def teardown
914
+ StateMachine::Integrations.send(:remove_const, 'Custom')
915
+ end
916
+ end
917
+
918
+ class MachineTest < Test::Unit::TestCase
919
+ def test_should_raise_exception_if_invalid_option_specified
920
+ assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)}
921
+ end
922
+
923
+ def test_should_not_raise_exception_if_custom_messages_specified
924
+ assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})}
925
+ end
926
+
927
+ def test_should_evaluate_a_block_during_initialization
928
+ called = true
929
+ StateMachine::Machine.new(Class.new) do
930
+ called = respond_to?(:event)
931
+ end
932
+
933
+ assert called
934
+ end
935
+
936
+ def test_should_provide_matcher_helpers_during_initialization
937
+ matchers = []
938
+
939
+ StateMachine::Machine.new(Class.new) do
940
+ matchers = [all, any, same]
941
+ end
942
+
943
+ assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
944
+ end
945
+ end
946
+
947
+ class MachineAfterBeingCopiedTest < Test::Unit::TestCase
948
+ def setup
949
+ @machine = StateMachine::Machine.new(Class.new, :state, :initial => :parked)
950
+ @machine.event(:ignite) {}
951
+ @machine.before_transition(lambda {})
952
+ @machine.after_transition(lambda {})
953
+ @machine.around_transition(lambda {})
954
+ @machine.after_failure(lambda {})
955
+
956
+ @copied_machine = @machine.clone
957
+ end
958
+
959
+ def test_should_not_have_the_same_collection_of_states
960
+ assert_not_same @copied_machine.states, @machine.states
961
+ end
962
+
963
+ def test_should_copy_each_state
964
+ assert_not_same @copied_machine.states[:parked], @machine.states[:parked]
965
+ end
966
+
967
+ def test_should_update_machine_for_each_state
968
+ assert_equal @copied_machine, @copied_machine.states[:parked].machine
969
+ end
970
+
971
+ def test_should_not_update_machine_for_original_state
972
+ assert_equal @machine, @machine.states[:parked].machine
973
+ end
974
+
975
+ def test_should_not_have_the_same_collection_of_events
976
+ assert_not_same @copied_machine.events, @machine.events
977
+ end
978
+
979
+ def test_should_copy_each_event
980
+ assert_not_same @copied_machine.events[:ignite], @machine.events[:ignite]
981
+ end
982
+
983
+ def test_should_update_machine_for_each_event
984
+ assert_equal @copied_machine, @copied_machine.events[:ignite].machine
985
+ end
986
+
987
+ def test_should_not_update_machine_for_original_event
988
+ assert_equal @machine, @machine.events[:ignite].machine
989
+ end
990
+
991
+ def test_should_not_have_the_same_callbacks
992
+ assert_not_same @copied_machine.callbacks, @machine.callbacks
993
+ end
994
+
995
+ def test_should_not_have_the_same_before_callbacks
996
+ assert_not_same @copied_machine.callbacks[:before], @machine.callbacks[:before]
997
+ end
998
+
999
+ def test_should_not_have_the_same_after_callbacks
1000
+ assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
1001
+ end
1002
+
1003
+ def test_should_not_have_the_same_failure_callbacks
1004
+ assert_not_same @copied_machine.callbacks[:failure], @machine.callbacks[:failure]
1005
+ end
1006
+ end
1007
+
1008
+ class MachineAfterChangingOwnerClassTest < Test::Unit::TestCase
1009
+ def setup
1010
+ @original_class = Class.new
1011
+ @machine = StateMachine::Machine.new(@original_class)
1012
+
1013
+ @new_class = Class.new(@original_class)
1014
+ @new_machine = @machine.clone
1015
+ @new_machine.owner_class = @new_class
1016
+
1017
+ @object = @new_class.new
1018
+ end
1019
+
1020
+ def test_should_update_owner_class
1021
+ assert_equal @new_class, @new_machine.owner_class
1022
+ end
1023
+
1024
+ def test_should_not_change_original_owner_class
1025
+ assert_equal @original_class, @machine.owner_class
1026
+ end
1027
+
1028
+ def test_should_change_the_associated_machine_in_the_new_class
1029
+ assert_equal @new_machine, @new_class.state_machines[:state]
1030
+ end
1031
+
1032
+ def test_should_not_change_the_associated_machine_in_the_original_class
1033
+ assert_equal @machine, @original_class.state_machines[:state]
1034
+ end
1035
+ end
1036
+
1037
+ class MachineAfterChangingInitialState < Test::Unit::TestCase
1038
+ def setup
1039
+ @klass = Class.new
1040
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1041
+ @machine.initial_state = :idling
1042
+
1043
+ @object = @klass.new
1044
+ end
1045
+
1046
+ def test_should_change_the_initial_state
1047
+ assert_equal :idling, @machine.initial_state(@object).name
1048
+ end
1049
+
1050
+ def test_should_include_in_known_states
1051
+ assert_equal [:parked, :idling], @machine.states.map {|state| state.name}
1052
+ end
1053
+
1054
+ def test_should_reset_original_initial_state
1055
+ assert !@machine.state(:parked).initial
1056
+ end
1057
+
1058
+ def test_should_set_new_state_to_initial
1059
+ assert @machine.state(:idling).initial
1060
+ end
1061
+ end
1062
+
1063
+ class MachineWithHelpersTest < Test::Unit::TestCase
1064
+ def setup
1065
+ @klass = Class.new
1066
+ @machine = StateMachine::Machine.new(@klass)
1067
+ @object = @klass.new
1068
+ end
1069
+
1070
+ def test_should_throw_exception_with_invalid_scope
1071
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { @machine.define_helper(:invalid, :state) {} }
1072
+ end
1073
+ end
1074
+
1075
+ class MachineWithInstanceHelpersTest < Test::Unit::TestCase
1076
+ def setup
1077
+ @klass = Class.new
1078
+ @machine = StateMachine::Machine.new(@klass)
1079
+ @object = @klass.new
1080
+ end
1081
+
1082
+ def test_should_not_redefine_existing_public_methods
1083
+ @klass.class_eval do
1084
+ def state
1085
+ 'parked'
1086
+ end
1087
+ end
1088
+
1089
+ @machine.define_helper(:instance, :state) {}
1090
+ assert_equal 'parked', @object.state
1091
+ end
1092
+
1093
+ def test_should_not_redefine_existing_protected_methods
1094
+ @klass.class_eval do
1095
+ protected
1096
+ def state
1097
+ 'parked'
1098
+ end
1099
+ end
1100
+
1101
+ @machine.define_helper(:instance, :state) {}
1102
+ assert_equal 'parked', @object.send(:state)
1103
+ end
1104
+
1105
+ def test_should_not_redefine_existing_private_methods
1106
+ @klass.class_eval do
1107
+ private
1108
+ def state
1109
+ 'parked'
1110
+ end
1111
+ end
1112
+
1113
+ @machine.define_helper(:instance, :state) {}
1114
+ assert_equal 'parked', @object.send(:state)
1115
+ end
1116
+
1117
+ def test_should_warn_if_defined_in_superclass
1118
+ require 'stringio'
1119
+ @original_stderr, $stderr = $stderr, StringIO.new
1120
+
1121
+ superclass = Class.new do
1122
+ def park
1123
+ end
1124
+ end
1125
+ klass = Class.new(superclass)
1126
+ machine = StateMachine::Machine.new(klass)
1127
+
1128
+ machine.define_helper(:instance, :park) {}
1129
+ assert_equal "Instance method \"park\" is already defined in #{superclass.to_s}, use generic helper instead.\n", $stderr.string
1130
+ ensure
1131
+ $stderr = @original_stderr
1132
+ end
1133
+
1134
+ def test_should_warn_if_defined_in_multiple_superclasses
1135
+ require 'stringio'
1136
+ @original_stderr, $stderr = $stderr, StringIO.new
1137
+
1138
+ superclass1 = Class.new do
1139
+ def park
1140
+ end
1141
+ end
1142
+ superclass2 = Class.new(superclass1) do
1143
+ def park
1144
+ end
1145
+ end
1146
+ klass = Class.new(superclass2)
1147
+ machine = StateMachine::Machine.new(klass)
1148
+
1149
+ machine.define_helper(:instance, :park) {}
1150
+ assert_equal "Instance method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead.\n", $stderr.string
1151
+ ensure
1152
+ $stderr = @original_stderr
1153
+ end
1154
+
1155
+ def test_should_warn_if_defined_in_module_prior_to_helper_module
1156
+ require 'stringio'
1157
+ @original_stderr, $stderr = $stderr, StringIO.new
1158
+
1159
+ mod = Module.new do
1160
+ def park
1161
+ end
1162
+ end
1163
+ klass = Class.new do
1164
+ include mod
1165
+ end
1166
+ machine = StateMachine::Machine.new(klass)
1167
+
1168
+ machine.define_helper(:instance, :park) {}
1169
+ assert_equal "Instance method \"park\" is already defined in #{mod.to_s}, use generic helper instead.\n", $stderr.string
1170
+ ensure
1171
+ $stderr = @original_stderr
1172
+ end
1173
+
1174
+ def test_should_not_warn_if_defined_in_module_after_helper_module
1175
+ require 'stringio'
1176
+ @original_stderr, $stderr = $stderr, StringIO.new
1177
+
1178
+ klass = Class.new
1179
+ machine = StateMachine::Machine.new(klass)
1180
+
1181
+ mod = Module.new do
1182
+ def park
1183
+ end
1184
+ end
1185
+ klass.class_eval do
1186
+ include mod
1187
+ end
1188
+
1189
+ machine.define_helper(:instance, :park) {}
1190
+ assert_equal '', $stderr.string
1191
+ ensure
1192
+ $stderr = @original_stderr
1193
+ end
1194
+
1195
+ def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
1196
+ require 'stringio'
1197
+ @original_stderr, $stderr = $stderr, StringIO.new
1198
+ StateMachine::Machine.ignore_method_conflicts = true
1199
+
1200
+ superclass = Class.new do
1201
+ def park
1202
+ end
1203
+ end
1204
+ klass = Class.new(superclass)
1205
+ machine = StateMachine::Machine.new(klass)
1206
+
1207
+ machine.define_helper(:instance, :park) {true}
1208
+ assert_equal '', $stderr.string
1209
+ assert_equal true, klass.new.park
1210
+ ensure
1211
+ StateMachine::Machine.ignore_method_conflicts = false
1212
+ $stderr = @original_stderr
1213
+ end
1214
+
1215
+ def test_should_define_nonexistent_methods
1216
+ @machine.define_helper(:instance, :state) {'parked'}
1217
+ assert_equal 'parked', @object.state
1218
+ end
1219
+
1220
+ def test_should_pass_context_as_arguments
1221
+ helper_args = nil
1222
+ @machine.define_helper(:instance, :state) {|*args| helper_args = args}
1223
+ @object.state
1224
+ assert_equal 2, helper_args.length
1225
+ assert_equal [@machine, @object], helper_args
1226
+ end
1227
+
1228
+ def test_should_pass_method_arguments_through
1229
+ helper_args = nil
1230
+ @machine.define_helper(:instance, :state) {|*args| helper_args = args}
1231
+ @object.state(1, 2, 3)
1232
+ assert_equal 5, helper_args.length
1233
+ assert_equal [@machine, @object, 1, 2, 3], helper_args
1234
+ end
1235
+
1236
+ def test_should_allow_string_evaluation
1237
+ @machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1238
+ def state
1239
+ 'parked'
1240
+ end
1241
+ end_eval
1242
+ assert_equal 'parked', @object.state
1243
+ end
1244
+ end
1245
+
1246
+ class MachineWithClassHelpersTest < Test::Unit::TestCase
1247
+ def setup
1248
+ @klass = Class.new
1249
+ @machine = StateMachine::Machine.new(@klass)
1250
+ end
1251
+
1252
+ def test_should_not_redefine_existing_public_methods
1253
+ class << @klass
1254
+ def states
1255
+ []
1256
+ end
1257
+ end
1258
+
1259
+ @machine.define_helper(:class, :states) {}
1260
+ assert_equal [], @klass.states
1261
+ end
1262
+
1263
+ def test_should_not_redefine_existing_protected_methods
1264
+ class << @klass
1265
+ protected
1266
+ def states
1267
+ []
1268
+ end
1269
+ end
1270
+
1271
+ @machine.define_helper(:class, :states) {}
1272
+ assert_equal [], @klass.send(:states)
1273
+ end
1274
+
1275
+ def test_should_not_redefine_existing_private_methods
1276
+ class << @klass
1277
+ private
1278
+ def states
1279
+ []
1280
+ end
1281
+ end
1282
+
1283
+ @machine.define_helper(:class, :states) {}
1284
+ assert_equal [], @klass.send(:states)
1285
+ end
1286
+
1287
+ def test_should_warn_if_defined_in_superclass
1288
+ require 'stringio'
1289
+ @original_stderr, $stderr = $stderr, StringIO.new
1290
+
1291
+ superclass = Class.new do
1292
+ def self.park
1293
+ end
1294
+ end
1295
+ klass = Class.new(superclass)
1296
+ machine = StateMachine::Machine.new(klass)
1297
+
1298
+ machine.define_helper(:class, :park) {}
1299
+ assert_equal "Class method \"park\" is already defined in #{superclass.to_s}, use generic helper instead.\n", $stderr.string
1300
+ ensure
1301
+ $stderr = @original_stderr
1302
+ end
1303
+
1304
+ def test_should_warn_if_defined_in_multiple_superclasses
1305
+ require 'stringio'
1306
+ @original_stderr, $stderr = $stderr, StringIO.new
1307
+
1308
+ superclass1 = Class.new do
1309
+ def self.park
1310
+ end
1311
+ end
1312
+ superclass2 = Class.new(superclass1) do
1313
+ def self.park
1314
+ end
1315
+ end
1316
+ klass = Class.new(superclass2)
1317
+ machine = StateMachine::Machine.new(klass)
1318
+
1319
+ machine.define_helper(:class, :park) {}
1320
+ assert_equal "Class method \"park\" is already defined in #{superclass1.to_s}, use generic helper instead.\n", $stderr.string
1321
+ ensure
1322
+ $stderr = @original_stderr
1323
+ end
1324
+
1325
+ def test_should_warn_if_defined_in_module_prior_to_helper_module
1326
+ require 'stringio'
1327
+ @original_stderr, $stderr = $stderr, StringIO.new
1328
+
1329
+ mod = Module.new do
1330
+ def park
1331
+ end
1332
+ end
1333
+ klass = Class.new do
1334
+ extend mod
1335
+ end
1336
+ machine = StateMachine::Machine.new(klass)
1337
+
1338
+ machine.define_helper(:class, :park) {}
1339
+ assert_equal "Class method \"park\" is already defined in #{mod.to_s}, use generic helper instead.\n", $stderr.string
1340
+ ensure
1341
+ $stderr = @original_stderr
1342
+ end
1343
+
1344
+ def test_should_not_warn_if_defined_in_module_after_helper_module
1345
+ require 'stringio'
1346
+ @original_stderr, $stderr = $stderr, StringIO.new
1347
+
1348
+ klass = Class.new
1349
+ machine = StateMachine::Machine.new(klass)
1350
+
1351
+ mod = Module.new do
1352
+ def park
1353
+ end
1354
+ end
1355
+ klass.class_eval do
1356
+ extend mod
1357
+ end
1358
+
1359
+ machine.define_helper(:class, :park) {}
1360
+ assert_equal '', $stderr.string
1361
+ ensure
1362
+ $stderr = @original_stderr
1363
+ end
1364
+
1365
+ def test_should_define_if_ignoring_method_conflicts_and_defined_in_superclass
1366
+ require 'stringio'
1367
+ @original_stderr, $stderr = $stderr, StringIO.new
1368
+ StateMachine::Machine.ignore_method_conflicts = true
1369
+
1370
+ superclass = Class.new do
1371
+ def self.park
1372
+ end
1373
+ end
1374
+ klass = Class.new(superclass)
1375
+ machine = StateMachine::Machine.new(klass)
1376
+
1377
+ machine.define_helper(:class, :park) {true}
1378
+ assert_equal '', $stderr.string
1379
+ assert_equal true, klass.park
1380
+ ensure
1381
+ StateMachine::Machine.ignore_method_conflicts = false
1382
+ $stderr = @original_stderr
1383
+ end
1384
+
1385
+ def test_should_define_nonexistent_methods
1386
+ @machine.define_helper(:class, :states) {[]}
1387
+ assert_equal [], @klass.states
1388
+ end
1389
+
1390
+ def test_should_pass_context_as_arguments
1391
+ helper_args = nil
1392
+ @machine.define_helper(:class, :states) {|*args| helper_args = args}
1393
+ @klass.states
1394
+ assert_equal 2, helper_args.length
1395
+ assert_equal [@machine, @klass], helper_args
1396
+ end
1397
+
1398
+ def test_should_pass_method_arguments_through
1399
+ helper_args = nil
1400
+ @machine.define_helper(:class, :states) {|*args| helper_args = args}
1401
+ @klass.states(1, 2, 3)
1402
+ assert_equal 5, helper_args.length
1403
+ assert_equal [@machine, @klass, 1, 2, 3], helper_args
1404
+ end
1405
+
1406
+ def test_should_allow_string_evaluation
1407
+ @machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
1408
+ def states
1409
+ []
1410
+ end
1411
+ end_eval
1412
+ assert_equal [], @klass.states
1413
+ end
1414
+ end
1415
+
1416
+ class MachineWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
1417
+ def setup
1418
+ require 'stringio'
1419
+ @original_stderr, $stderr = $stderr, StringIO.new
1420
+
1421
+ @superclass = Class.new do
1422
+ def self.with_state
1423
+ :with_state
1424
+ end
1425
+
1426
+ def self.with_states
1427
+ :with_states
1428
+ end
1429
+
1430
+ def self.without_state
1431
+ :without_state
1432
+ end
1433
+
1434
+ def self.without_states
1435
+ :without_states
1436
+ end
1437
+
1438
+ def self.human_state_name
1439
+ :human_state_name
1440
+ end
1441
+
1442
+ def self.human_state_event_name
1443
+ :human_state_event_name
1444
+ end
1445
+
1446
+ attr_accessor :status
1447
+
1448
+ def state
1449
+ 'parked'
1450
+ end
1451
+
1452
+ def state=(value)
1453
+ self.status = value
1454
+ end
1455
+
1456
+ def state?
1457
+ true
1458
+ end
1459
+
1460
+ def state_name
1461
+ :parked
1462
+ end
1463
+
1464
+ def human_state_name
1465
+ 'parked'
1466
+ end
1467
+
1468
+ def state_events
1469
+ [:ignite]
1470
+ end
1471
+
1472
+ def state_transitions
1473
+ [{:parked => :idling}]
1474
+ end
1475
+
1476
+ def state_paths
1477
+ [[{:parked => :idling}]]
1478
+ end
1479
+ end
1480
+ @klass = Class.new(@superclass)
1481
+
1482
+ StateMachine::Integrations.const_set('Custom', Module.new do
1483
+ include StateMachine::Integrations::Base
1484
+
1485
+ def create_with_scope(name)
1486
+ lambda {|klass, values| []}
1487
+ end
1488
+
1489
+ def create_without_scope(name)
1490
+ lambda {|klass, values| []}
1491
+ end
1492
+ end)
1493
+
1494
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
1495
+ @machine.state :parked, :idling
1496
+ @machine.event :ignite
1497
+ @object = @klass.new
1498
+ end
1499
+
1500
+ def test_should_not_redefine_singular_with_scope
1501
+ assert_equal :with_state, @klass.with_state
1502
+ end
1503
+
1504
+ def test_should_not_redefine_plural_with_scope
1505
+ assert_equal :with_states, @klass.with_states
1506
+ end
1507
+
1508
+ def test_should_not_redefine_singular_without_scope
1509
+ assert_equal :without_state, @klass.without_state
1510
+ end
1511
+
1512
+ def test_should_not_redefine_plural_without_scope
1513
+ assert_equal :without_states, @klass.without_states
1514
+ end
1515
+
1516
+ def test_should_not_redefine_human_attribute_name_reader
1517
+ assert_equal :human_state_name, @klass.human_state_name
1518
+ end
1519
+
1520
+ def test_should_not_redefine_human_event_name_reader
1521
+ assert_equal :human_state_event_name, @klass.human_state_event_name
1522
+ end
1523
+
1524
+ def test_should_not_redefine_attribute_writer
1525
+ assert_equal 'parked', @object.state
1526
+ end
1527
+
1528
+ def test_should_not_redefine_attribute_writer
1529
+ @object.state = 'parked'
1530
+ assert_equal 'parked', @object.status
1531
+ end
1532
+
1533
+ def test_should_not_define_attribute_predicate
1534
+ assert @object.state?
1535
+ end
1536
+
1537
+ def test_should_not_redefine_attribute_name_reader
1538
+ assert_equal :parked, @object.state_name
1539
+ end
1540
+
1541
+ def test_should_not_redefine_attribute_human_name_reader
1542
+ assert_equal 'parked', @object.human_state_name
1543
+ end
1544
+
1545
+ def test_should_not_redefine_attribute_events_reader
1546
+ assert_equal [:ignite], @object.state_events
1547
+ end
1548
+
1549
+ def test_should_not_redefine_attribute_transitions_reader
1550
+ assert_equal [{:parked => :idling}], @object.state_transitions
1551
+ end
1552
+
1553
+ def test_should_not_redefine_attribute_paths_reader
1554
+ assert_equal [[{:parked => :idling}]], @object.state_paths
1555
+ end
1556
+
1557
+ def test_should_output_warning
1558
+ expected = [
1559
+ 'Instance method "state_events"',
1560
+ 'Instance method "state_transitions"',
1561
+ 'Instance method "state_paths"',
1562
+ 'Class method "human_state_name"',
1563
+ 'Class method "human_state_event_name"',
1564
+ 'Instance method "state_name"',
1565
+ 'Instance method "human_state_name"',
1566
+ 'Class method "with_state"',
1567
+ 'Class method "without_state"',
1568
+ 'Class method "with_states"',
1569
+ 'Class method "without_states"'
1570
+ ].map {|method| "#{method} is already defined in #{@superclass.to_s}, use generic helper instead.\n"}.join
1571
+
1572
+ assert_equal expected, $stderr.string
1573
+ end
1574
+
1575
+ def teardown
1576
+ $stderr = @original_stderr
1577
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1578
+ end
1579
+ end
1580
+
1581
+ class MachineWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase
1582
+ def setup
1583
+ require 'stringio'
1584
+ @original_stderr, $stderr = $stderr, StringIO.new
1585
+
1586
+ @klass = Class.new do
1587
+ def self.with_state
1588
+ :with_state
1589
+ end
1590
+
1591
+ def self.with_states
1592
+ :with_states
1593
+ end
1594
+
1595
+ def self.without_state
1596
+ :without_state
1597
+ end
1598
+
1599
+ def self.without_states
1600
+ :without_states
1601
+ end
1602
+
1603
+ def self.human_state_name
1604
+ :human_state_name
1605
+ end
1606
+
1607
+ def self.human_state_event_name
1608
+ :human_state_event_name
1609
+ end
1610
+
1611
+ attr_accessor :status
1612
+
1613
+ def state
1614
+ 'parked'
1615
+ end
1616
+
1617
+ def state=(value)
1618
+ self.status = value
1619
+ end
1620
+
1621
+ def state?
1622
+ true
1623
+ end
1624
+
1625
+ def state_name
1626
+ :parked
1627
+ end
1628
+
1629
+ def human_state_name
1630
+ 'parked'
1631
+ end
1632
+
1633
+ def state_events
1634
+ [:ignite]
1635
+ end
1636
+
1637
+ def state_transitions
1638
+ [{:parked => :idling}]
1639
+ end
1640
+
1641
+ def state_paths
1642
+ [[{:parked => :idling}]]
1643
+ end
1644
+ end
1645
+
1646
+ StateMachine::Integrations.const_set('Custom', Module.new do
1647
+ include StateMachine::Integrations::Base
1648
+
1649
+ def create_with_scope(name)
1650
+ lambda {|klass, values| []}
1651
+ end
1652
+
1653
+ def create_without_scope(name)
1654
+ lambda {|klass, values| []}
1655
+ end
1656
+ end)
1657
+
1658
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
1659
+ @machine.state :parked, :idling
1660
+ @machine.event :ignite
1661
+ @object = @klass.new
1662
+ end
1663
+
1664
+ def test_should_not_redefine_singular_with_scope
1665
+ assert_equal :with_state, @klass.with_state
1666
+ end
1667
+
1668
+ def test_should_not_redefine_plural_with_scope
1669
+ assert_equal :with_states, @klass.with_states
1670
+ end
1671
+
1672
+ def test_should_not_redefine_singular_without_scope
1673
+ assert_equal :without_state, @klass.without_state
1674
+ end
1675
+
1676
+ def test_should_not_redefine_plural_without_scope
1677
+ assert_equal :without_states, @klass.without_states
1678
+ end
1679
+
1680
+ def test_should_not_redefine_human_attribute_name_reader
1681
+ assert_equal :human_state_name, @klass.human_state_name
1682
+ end
1683
+
1684
+ def test_should_not_redefine_human_event_name_reader
1685
+ assert_equal :human_state_event_name, @klass.human_state_event_name
1686
+ end
1687
+
1688
+ def test_should_not_redefine_attribute_writer
1689
+ assert_equal 'parked', @object.state
1690
+ end
1691
+
1692
+ def test_should_not_redefine_attribute_writer
1693
+ @object.state = 'parked'
1694
+ assert_equal 'parked', @object.status
1695
+ end
1696
+
1697
+ def test_should_not_define_attribute_predicate
1698
+ assert @object.state?
1699
+ end
1700
+
1701
+ def test_should_not_redefine_attribute_name_reader
1702
+ assert_equal :parked, @object.state_name
1703
+ end
1704
+
1705
+ def test_should_not_redefine_attribute_human_name_reader
1706
+ assert_equal 'parked', @object.human_state_name
1707
+ end
1708
+
1709
+ def test_should_not_redefine_attribute_events_reader
1710
+ assert_equal [:ignite], @object.state_events
1711
+ end
1712
+
1713
+ def test_should_not_redefine_attribute_transitions_reader
1714
+ assert_equal [{:parked => :idling}], @object.state_transitions
1715
+ end
1716
+
1717
+ def test_should_not_redefine_attribute_paths_reader
1718
+ assert_equal [[{:parked => :idling}]], @object.state_paths
1719
+ end
1720
+
1721
+ def test_should_allow_super_chaining
1722
+ @klass.class_eval do
1723
+ def self.with_state(*states)
1724
+ super
1725
+ end
1726
+
1727
+ def self.with_states(*states)
1728
+ super
1729
+ end
1730
+
1731
+ def self.without_state(*states)
1732
+ super
1733
+ end
1734
+
1735
+ def self.without_states(*states)
1736
+ super
1737
+ end
1738
+
1739
+ def self.human_state_name(state)
1740
+ super
1741
+ end
1742
+
1743
+ def self.human_state_event_name(event)
1744
+ super
1745
+ end
1746
+
1747
+ attr_accessor :status
1748
+
1749
+ def state
1750
+ super
1751
+ end
1752
+
1753
+ def state=(value)
1754
+ super
1755
+ end
1756
+
1757
+ def state?(state)
1758
+ super
1759
+ end
1760
+
1761
+ def state_name
1762
+ super
1763
+ end
1764
+
1765
+ def human_state_name
1766
+ super
1767
+ end
1768
+
1769
+ def state_events
1770
+ super
1771
+ end
1772
+
1773
+ def state_transitions
1774
+ super
1775
+ end
1776
+
1777
+ def state_paths
1778
+ super
1779
+ end
1780
+ end
1781
+
1782
+ assert_equal [], @klass.with_state
1783
+ assert_equal [], @klass.with_states
1784
+ assert_equal [], @klass.without_state
1785
+ assert_equal [], @klass.without_states
1786
+ assert_equal 'parked', @klass.human_state_name(:parked)
1787
+ assert_equal 'ignite', @klass.human_state_event_name(:ignite)
1788
+
1789
+ assert_equal nil, @object.state
1790
+ @object.state = 'idling'
1791
+ assert_equal 'idling', @object.state
1792
+ assert_equal nil, @object.status
1793
+ assert_equal false, @object.state?(:parked)
1794
+ assert_equal :idling, @object.state_name
1795
+ assert_equal 'idling', @object.human_state_name
1796
+ assert_equal [], @object.state_events
1797
+ assert_equal [], @object.state_transitions
1798
+ assert_equal [], @object.state_paths
1799
+ end
1800
+
1801
+ def test_should_not_output_warning
1802
+ assert_equal '', $stderr.string
1803
+ end
1804
+
1805
+ def teardown
1806
+ $stderr = @original_stderr
1807
+ StateMachine::Integrations.send(:remove_const, 'Custom')
1808
+ end
1809
+ end
1810
+
1811
+ class MachineWithoutInitializeTest < Test::Unit::TestCase
1812
+ def setup
1813
+ @klass = Class.new
1814
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1815
+ @object = @klass.new
1816
+ end
1817
+
1818
+ def test_should_initialize_state
1819
+ assert_equal 'parked', @object.state
1820
+ end
1821
+ end
1822
+
1823
+ class MachineWithInitializeWithoutSuperTest < Test::Unit::TestCase
1824
+ def setup
1825
+ @klass = Class.new do
1826
+ def initialize
1827
+ end
1828
+ end
1829
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1830
+ @object = @klass.new
1831
+ end
1832
+
1833
+ def test_should_not_initialize_state
1834
+ assert_nil @object.state
1835
+ end
1836
+ end
1837
+
1838
+ class MachineWithInitializeAndSuperTest < Test::Unit::TestCase
1839
+ def setup
1840
+ @klass = Class.new do
1841
+ def initialize
1842
+ super()
1843
+ end
1844
+ end
1845
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1846
+ @object = @klass.new
1847
+ end
1848
+
1849
+ def test_should_initialize_state
1850
+ assert_equal 'parked', @object.state
1851
+ end
1852
+ end
1853
+
1854
+ class MachineWithInitializeArgumentsAndBlockTest < Test::Unit::TestCase
1855
+ def setup
1856
+ @superclass = Class.new do
1857
+ attr_reader :args
1858
+ attr_reader :block_given
1859
+
1860
+ def initialize(*args)
1861
+ @args = args
1862
+ @block_given = block_given?
1863
+ end
1864
+ end
1865
+ @klass = Class.new(@superclass)
1866
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1867
+ @object = @klass.new(1, 2, 3) {}
1868
+ end
1869
+
1870
+ def test_should_initialize_state
1871
+ assert_equal 'parked', @object.state
1872
+ end
1873
+
1874
+ def test_should_preserve_arguments
1875
+ assert_equal [1, 2, 3], @object.args
1876
+ end
1877
+
1878
+ def test_should_preserve_block
1879
+ assert @object.block_given
1880
+ end
1881
+ end
1882
+
1883
+ class MachineWithCustomInitializeTest < Test::Unit::TestCase
1884
+ def setup
1885
+ @klass = Class.new do
1886
+ def initialize
1887
+ initialize_state_machines
1888
+ end
1889
+ end
1890
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1891
+ @object = @klass.new
1892
+ end
1893
+
1894
+ def test_should_initialize_state
1895
+ assert_equal 'parked', @object.state
1896
+ end
1897
+ end
1898
+
1899
+ class MachinePersistenceTest < Test::Unit::TestCase
1900
+ def setup
1901
+ @klass = Class.new do
1902
+ attr_accessor :state_event
1903
+ end
1904
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1905
+ @object = @klass.new
1906
+ end
1907
+
1908
+ def test_should_allow_reading_state
1909
+ assert_equal 'parked', @machine.read(@object, :state)
1910
+ end
1911
+
1912
+ def test_should_allow_reading_custom_attributes
1913
+ assert_nil @machine.read(@object, :event)
1914
+
1915
+ @object.state_event = 'ignite'
1916
+ assert_equal 'ignite', @machine.read(@object, :event)
1917
+ end
1918
+
1919
+ def test_should_allow_reading_custom_instance_variables
1920
+ @klass.class_eval do
1921
+ attr_writer :state_value
1922
+ end
1923
+
1924
+ @object.state_value = 1
1925
+ assert_raise(NoMethodError) { @machine.read(@object, :value) }
1926
+ assert_equal 1, @machine.read(@object, :value, true)
1927
+ end
1928
+
1929
+ def test_should_allow_writing_state
1930
+ @machine.write(@object, :state, 'idling')
1931
+ assert_equal 'idling', @object.state
1932
+ end
1933
+
1934
+ def test_should_allow_writing_custom_attributes
1935
+ @machine.write(@object, :event, 'ignite')
1936
+ assert_equal 'ignite', @object.state_event
1937
+ end
1938
+
1939
+ def test_should_allow_writing_custom_instance_variables
1940
+ @klass.class_eval do
1941
+ attr_reader :state_value
1942
+ end
1943
+
1944
+ assert_raise(NoMethodError) { @machine.write(@object, :value, 1) }
1945
+ assert_equal 1, @machine.write(@object, :value, 1, true)
1946
+ assert_equal 1, @object.state_value
1947
+ end
1948
+ end
1949
+
1950
+
1951
+ class MachineWithStatesTest < Test::Unit::TestCase
1952
+ def setup
1953
+ @klass = Class.new
1954
+ @machine = StateMachine::Machine.new(@klass)
1955
+ @parked, @idling = @machine.state :parked, :idling
1956
+
1957
+ @object = @klass.new
1958
+ end
1959
+
1960
+ def test_should_have_states
1961
+ assert_equal [nil, :parked, :idling], @machine.states.map {|state| state.name}
1962
+ end
1963
+
1964
+ def test_should_allow_state_lookup_by_name
1965
+ assert_equal @parked, @machine.states[:parked]
1966
+ end
1967
+
1968
+ def test_should_allow_state_lookup_by_value
1969
+ assert_equal @parked, @machine.states['parked', :value]
1970
+ end
1971
+
1972
+ def test_should_allow_human_state_name_lookup
1973
+ assert_equal 'parked', @klass.human_state_name(:parked)
1974
+ end
1975
+
1976
+ def test_should_raise_exception_on_invalid_human_state_name_lookup
1977
+ exception = assert_raise(IndexError) {@klass.human_state_name(:invalid)}
1978
+ assert_equal ':invalid is an invalid name', exception.message
1979
+ end
1980
+
1981
+ def test_should_use_stringified_name_for_value
1982
+ assert_equal 'parked', @parked.value
1983
+ end
1984
+
1985
+ def test_should_not_use_custom_matcher
1986
+ assert_nil @parked.matcher
1987
+ end
1988
+
1989
+ def test_should_raise_exception_if_invalid_option_specified
1990
+ exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)}
1991
+ assert_equal 'Invalid key(s): invalid', exception.message
1992
+ end
1993
+ end
1994
+
1995
+ class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
1996
+ def setup
1997
+ @klass = Class.new
1998
+ @machine = StateMachine::Machine.new(@klass)
1999
+ @state = @machine.state :parked, :value => 1
2000
+
2001
+ @object = @klass.new
2002
+ @object.state = 1
2003
+ end
2004
+
2005
+ def test_should_use_custom_value
2006
+ assert_equal 1, @state.value
2007
+ end
2008
+
2009
+ def test_should_allow_lookup_by_custom_value
2010
+ assert_equal @state, @machine.states[1, :value]
2011
+ end
2012
+ end
2013
+
2014
+ class MachineWithStatesWithCustomHumanNamesTest < Test::Unit::TestCase
2015
+ def setup
2016
+ @klass = Class.new
2017
+ @machine = StateMachine::Machine.new(@klass)
2018
+ @state = @machine.state :parked, :human_name => 'stopped'
2019
+ end
2020
+
2021
+ def test_should_use_custom_human_name
2022
+ assert_equal 'stopped', @state.human_name
2023
+ end
2024
+
2025
+ def test_should_allow_human_state_name_lookup
2026
+ assert_equal 'stopped', @klass.human_state_name(:parked)
2027
+ end
2028
+ end
2029
+
2030
+ class MachineWithStatesWithRuntimeDependenciesTest < Test::Unit::TestCase
2031
+ def setup
2032
+ @klass = Class.new
2033
+ @machine = StateMachine::Machine.new(@klass)
2034
+ @machine.state :parked
2035
+ end
2036
+
2037
+ def test_should_not_evaluate_value_during_definition
2038
+ assert_nothing_raised { @machine.state :parked, :value => lambda {raise ArgumentError} }
2039
+ end
2040
+
2041
+ def test_should_not_evaluate_if_not_initial_state
2042
+ @machine.state :parked, :value => lambda {raise ArgumentError}
2043
+ assert_nothing_raised { @klass.new }
2044
+ end
2045
+ end
2046
+
2047
+ class MachineWithStateWithMatchersTest < Test::Unit::TestCase
2048
+ def setup
2049
+ @klass = Class.new
2050
+ @machine = StateMachine::Machine.new(@klass)
2051
+ @state = @machine.state :parked, :if => lambda {|value| !value.nil?}
2052
+
2053
+ @object = @klass.new
2054
+ @object.state = 1
2055
+ end
2056
+
2057
+ def test_should_use_custom_matcher
2058
+ assert_not_nil @state.matcher
2059
+ assert @state.matches?(1)
2060
+ assert !@state.matches?(nil)
2061
+ end
2062
+ end
2063
+
2064
+ class MachineWithCachedStateTest < Test::Unit::TestCase
2065
+ def setup
2066
+ @klass = Class.new
2067
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2068
+ @state = @machine.state :parked, :value => lambda {Object.new}, :cache => true
2069
+
2070
+ @object = @klass.new
2071
+ end
2072
+
2073
+ def test_should_use_evaluated_value
2074
+ assert_instance_of Object, @object.state
2075
+ end
2076
+
2077
+ def test_use_same_value_across_multiple_objects
2078
+ assert_equal @object.state, @klass.new.state
2079
+ end
2080
+ end
2081
+
2082
+ class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
2083
+ def setup
2084
+ @klass = Class.new
2085
+ @machine = StateMachine::Machine.new(@klass)
2086
+
2087
+ @parked, @idling = @machine.state :parked, :idling do
2088
+ def speed
2089
+ 0
2090
+ end
2091
+ end
2092
+ end
2093
+
2094
+ def test_should_define_behaviors_for_each_state
2095
+ assert_not_nil @parked.methods[:speed]
2096
+ assert_not_nil @idling.methods[:speed]
2097
+ end
2098
+
2099
+ def test_should_define_different_behaviors_for_each_state
2100
+ assert_not_equal @parked.methods[:speed], @idling.methods[:speed]
2101
+ end
2102
+ end
2103
+
2104
+ class MachineWithExistingStateTest < Test::Unit::TestCase
2105
+ def setup
2106
+ @klass = Class.new
2107
+ @machine = StateMachine::Machine.new(@klass)
2108
+ @state = @machine.state :parked
2109
+ @same_state = @machine.state :parked, :value => 1
2110
+ end
2111
+
2112
+ def test_should_not_create_a_new_state
2113
+ assert_same @state, @same_state
2114
+ end
2115
+
2116
+ def test_should_update_attributes
2117
+ assert_equal 1, @state.value
2118
+ end
2119
+
2120
+ def test_should_no_longer_be_able_to_look_up_state_by_original_value
2121
+ assert_nil @machine.states['parked', :value]
2122
+ end
2123
+
2124
+ def test_should_be_able_to_look_up_state_by_new_value
2125
+ assert_equal @state, @machine.states[1, :value]
2126
+ end
2127
+ end
2128
+
2129
+ class MachineWithOtherStates < Test::Unit::TestCase
2130
+ def setup
2131
+ @klass = Class.new
2132
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2133
+ @parked, @idling = @machine.other_states(:parked, :idling)
2134
+ end
2135
+
2136
+ def test_should_include_other_states_in_known_states
2137
+ assert_equal [@parked, @idling], @machine.states.to_a
2138
+ end
2139
+
2140
+ def test_should_use_default_value
2141
+ assert_equal 'idling', @idling.value
2142
+ end
2143
+
2144
+ def test_should_not_create_matcher
2145
+ assert_nil @idling.matcher
2146
+ end
2147
+ end
2148
+
2149
+ class MachineWithEventsTest < Test::Unit::TestCase
2150
+ def setup
2151
+ @klass = Class.new
2152
+ @machine = StateMachine::Machine.new(@klass)
2153
+ end
2154
+
2155
+ def test_should_return_the_created_event
2156
+ assert_instance_of StateMachine::Event, @machine.event(:ignite)
2157
+ end
2158
+
2159
+ def test_should_create_event_with_given_name
2160
+ event = @machine.event(:ignite) {}
2161
+ assert_equal :ignite, event.name
2162
+ end
2163
+
2164
+ def test_should_evaluate_block_within_event_context
2165
+ responded = false
2166
+ @machine.event :ignite do
2167
+ responded = respond_to?(:transition)
2168
+ end
2169
+
2170
+ assert responded
2171
+ end
2172
+
2173
+ def test_should_be_aliased_as_on
2174
+ event = @machine.on(:ignite) {}
2175
+ assert_equal :ignite, event.name
2176
+ end
2177
+
2178
+ def test_should_have_events
2179
+ event = @machine.event(:ignite)
2180
+ assert_equal [event], @machine.events.to_a
2181
+ end
2182
+
2183
+ def test_should_allow_human_state_name_lookup
2184
+ @machine.event(:ignite)
2185
+ assert_equal 'ignite', @klass.human_state_event_name(:ignite)
2186
+ end
2187
+
2188
+ def test_should_raise_exception_on_invalid_human_state_event_name_lookup
2189
+ exception = assert_raise(IndexError) {@klass.human_state_event_name(:invalid)}
2190
+ assert_equal ':invalid is an invalid name', exception.message
2191
+ end
2192
+ end
2193
+
2194
+ class MachineWithExistingEventTest < Test::Unit::TestCase
2195
+ def setup
2196
+ @machine = StateMachine::Machine.new(Class.new)
2197
+ @event = @machine.event(:ignite)
2198
+ @same_event = @machine.event(:ignite)
2199
+ end
2200
+
2201
+ def test_should_not_create_new_event
2202
+ assert_same @event, @same_event
2203
+ end
2204
+
2205
+ def test_should_allow_accessing_event_without_block
2206
+ assert_equal @event, @machine.event(:ignite)
2207
+ end
2208
+ end
2209
+
2210
+ class MachineWithEventsWithCustomHumanNamesTest < Test::Unit::TestCase
2211
+ def setup
2212
+ @klass = Class.new
2213
+ @machine = StateMachine::Machine.new(@klass)
2214
+ @event = @machine.event(:ignite, :human_name => 'start')
2215
+ end
2216
+
2217
+ def test_should_use_custom_human_name
2218
+ assert_equal 'start', @event.human_name
2219
+ end
2220
+
2221
+ def test_should_allow_human_state_name_lookup
2222
+ assert_equal 'start', @klass.human_state_event_name(:ignite)
2223
+ end
2224
+ end
2225
+
2226
+ class MachineWithEventsWithTransitionsTest < Test::Unit::TestCase
2227
+ def setup
2228
+ @klass = Class.new
2229
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2230
+ @event = @machine.event(:ignite) do
2231
+ transition :parked => :idling
2232
+ transition :stalled => :idling
2233
+ end
2234
+ end
2235
+
2236
+ def test_should_have_events
2237
+ assert_equal [@event], @machine.events.to_a
2238
+ end
2239
+
2240
+ def test_should_track_states_defined_in_event_transitions
2241
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
2242
+ end
2243
+
2244
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
2245
+ @machine.event :park do
2246
+ transition :idling => :parked
2247
+ end
2248
+
2249
+ assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
2250
+ end
2251
+
2252
+ def test_should_track_state_from_new_events
2253
+ @machine.event :shift_up do
2254
+ transition :idling => :first_gear
2255
+ end
2256
+
2257
+ assert_equal [:parked, :idling, :stalled, :first_gear], @machine.states.map {|state| state.name}
2258
+ end
2259
+ end
2260
+
2261
+ class MachineWithMultipleEventsTest < Test::Unit::TestCase
2262
+ def setup
2263
+ @klass = Class.new
2264
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2265
+ @park, @shift_down = @machine.event(:park, :shift_down) do
2266
+ transition :first_gear => :parked
2267
+ end
2268
+ end
2269
+
2270
+ def test_should_have_events
2271
+ assert_equal [@park, @shift_down], @machine.events.to_a
2272
+ end
2273
+
2274
+ def test_should_define_transitions_for_each_event
2275
+ [@park, @shift_down].each {|event| assert_equal 1, event.branches.size}
2276
+ end
2277
+
2278
+ def test_should_transition_the_same_for_each_event
2279
+ object = @klass.new
2280
+ object.state = 'first_gear'
2281
+ object.park
2282
+ assert_equal 'parked', object.state
2283
+
2284
+ object = @klass.new
2285
+ object.state = 'first_gear'
2286
+ object.shift_down
2287
+ assert_equal 'parked', object.state
2288
+ end
2289
+ end
2290
+
2291
+ class MachineWithTransitionCallbacksTest < Test::Unit::TestCase
2292
+ def setup
2293
+ @klass = Class.new do
2294
+ attr_accessor :callbacks
2295
+ end
2296
+
2297
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2298
+ @event = @machine.event :ignite do
2299
+ transition :parked => :idling
2300
+ end
2301
+
2302
+ @object = @klass.new
2303
+ @object.callbacks = []
2304
+ end
2305
+
2306
+ def test_should_not_raise_exception_if_implicit_option_specified
2307
+ assert_nothing_raised {@machine.before_transition :invalid => :valid, :do => lambda {}}
2308
+ end
2309
+
2310
+ def test_should_raise_exception_if_method_not_specified
2311
+ exception = assert_raise(ArgumentError) {@machine.before_transition :to => :idling}
2312
+ assert_equal 'Method(s) for callback must be specified', exception.message
2313
+ end
2314
+
2315
+ def test_should_invoke_callbacks_during_transition
2316
+ @machine.before_transition lambda {|object| object.callbacks << 'before'}
2317
+ @machine.after_transition lambda {|object| object.callbacks << 'after'}
2318
+ @machine.around_transition lambda {|object, transition, block| object.callbacks << 'before_around'; block.call; object.callbacks << 'after_around'}
2319
+
2320
+ @event.fire(@object)
2321
+ assert_equal %w(before before_around after_around after), @object.callbacks
2322
+ end
2323
+
2324
+ def test_should_allow_multiple_callbacks
2325
+ @machine.before_transition lambda {|object| object.callbacks << 'before1'}, lambda {|object| object.callbacks << 'before2'}
2326
+ @machine.after_transition lambda {|object| object.callbacks << 'after1'}, lambda {|object| object.callbacks << 'after2'}
2327
+ @machine.around_transition(
2328
+ lambda {|object, transition, block| object.callbacks << 'before_around1'; block.call; object.callbacks << 'after_around1'},
2329
+ lambda {|object, transition, block| object.callbacks << 'before_around2'; block.call; object.callbacks << 'after_around2'}
2330
+ )
2331
+
2332
+ @event.fire(@object)
2333
+ assert_equal %w(before1 before2 before_around1 before_around2 after_around2 after_around1 after1 after2), @object.callbacks
2334
+ end
2335
+
2336
+ def test_should_allow_multiple_callbacks_with_requirements
2337
+ @machine.before_transition lambda {|object| object.callbacks << 'before_parked1'}, lambda {|object| object.callbacks << 'before_parked2'}, :from => :parked
2338
+ @machine.before_transition lambda {|object| object.callbacks << 'before_idling1'}, lambda {|object| object.callbacks << 'before_idling2'}, :from => :idling
2339
+ @machine.after_transition lambda {|object| object.callbacks << 'after_parked1'}, lambda {|object| object.callbacks << 'after_parked2'}, :from => :parked
2340
+ @machine.after_transition lambda {|object| object.callbacks << 'after_idling1'}, lambda {|object| object.callbacks << 'after_idling2'}, :from => :idling
2341
+ @machine.around_transition(
2342
+ lambda {|object, transition, block| object.callbacks << 'before_around_parked1'; block.call; object.callbacks << 'after_around_parked1'},
2343
+ lambda {|object, transition, block| object.callbacks << 'before_around_parked2'; block.call; object.callbacks << 'after_around_parked2'},
2344
+ :from => :parked
2345
+ )
2346
+ @machine.around_transition(
2347
+ lambda {|object, transition, block| object.callbacks << 'before_around_idling1'; block.call; object.callbacks << 'after_around_idling1'},
2348
+ lambda {|object, transition, block| object.callbacks << 'before_around_idling2'; block.call; object.callbacks << 'after_around_idling2'},
2349
+ :from => :idling
2350
+ )
2351
+
2352
+ @event.fire(@object)
2353
+ 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
2354
+ end
2355
+
2356
+ def test_should_support_from_requirement
2357
+ @machine.before_transition :from => :parked, :do => lambda {|object| object.callbacks << :parked}
2358
+ @machine.before_transition :from => :idling, :do => lambda {|object| object.callbacks << :idling}
2359
+
2360
+ @event.fire(@object)
2361
+ assert_equal [:parked], @object.callbacks
2362
+ end
2363
+
2364
+ def test_should_support_except_from_requirement
2365
+ @machine.before_transition :except_from => :parked, :do => lambda {|object| object.callbacks << :parked}
2366
+ @machine.before_transition :except_from => :idling, :do => lambda {|object| object.callbacks << :idling}
2367
+
2368
+ @event.fire(@object)
2369
+ assert_equal [:idling], @object.callbacks
2370
+ end
2371
+
2372
+ def test_should_support_to_requirement
2373
+ @machine.before_transition :to => :parked, :do => lambda {|object| object.callbacks << :parked}
2374
+ @machine.before_transition :to => :idling, :do => lambda {|object| object.callbacks << :idling}
2375
+
2376
+ @event.fire(@object)
2377
+ assert_equal [:idling], @object.callbacks
2378
+ end
2379
+
2380
+ def test_should_support_except_to_requirement
2381
+ @machine.before_transition :except_to => :parked, :do => lambda {|object| object.callbacks << :parked}
2382
+ @machine.before_transition :except_to => :idling, :do => lambda {|object| object.callbacks << :idling}
2383
+
2384
+ @event.fire(@object)
2385
+ assert_equal [:parked], @object.callbacks
2386
+ end
2387
+
2388
+ def test_should_support_on_requirement
2389
+ @machine.before_transition :on => :park, :do => lambda {|object| object.callbacks << :park}
2390
+ @machine.before_transition :on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
2391
+
2392
+ @event.fire(@object)
2393
+ assert_equal [:ignite], @object.callbacks
2394
+ end
2395
+
2396
+ def test_should_support_except_on_requirement
2397
+ @machine.before_transition :except_on => :park, :do => lambda {|object| object.callbacks << :park}
2398
+ @machine.before_transition :except_on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
2399
+
2400
+ @event.fire(@object)
2401
+ assert_equal [:park], @object.callbacks
2402
+ end
2403
+
2404
+ def test_should_support_implicit_requirement
2405
+ @machine.before_transition :parked => :idling, :do => lambda {|object| object.callbacks << :parked}
2406
+ @machine.before_transition :idling => :parked, :do => lambda {|object| object.callbacks << :idling}
2407
+
2408
+ @event.fire(@object)
2409
+ assert_equal [:parked], @object.callbacks
2410
+ end
2411
+
2412
+ def test_should_track_states_defined_in_transition_callbacks
2413
+ @machine.before_transition :parked => :idling, :do => lambda {}
2414
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
2415
+ @machine.around_transition :third_gear => :fourth_gear, :do => lambda {}
2416
+
2417
+ assert_equal [:parked, :idling, :first_gear, :second_gear, :third_gear, :fourth_gear], @machine.states.map {|state| state.name}
2418
+ end
2419
+
2420
+ def test_should_not_duplicate_states_defined_in_multiple_event_transitions
2421
+ @machine.before_transition :parked => :idling, :do => lambda {}
2422
+ @machine.after_transition :first_gear => :second_gear, :do => lambda {}
2423
+ @machine.after_transition :parked => :idling, :do => lambda {}
2424
+ @machine.around_transition :parked => :idling, :do => lambda {}
2425
+
2426
+ assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name}
2427
+ end
2428
+
2429
+ def test_should_define_predicates_for_each_state
2430
+ [:parked?, :idling?].each {|predicate| assert @object.respond_to?(predicate)}
2431
+ end
2432
+ end
2433
+
2434
+ class MachineWithFailureCallbacksTest < Test::Unit::TestCase
2435
+ def setup
2436
+ @klass = Class.new do
2437
+ attr_accessor :callbacks
2438
+ end
2439
+
2440
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2441
+ @event = @machine.event :ignite
2442
+
2443
+ @object = @klass.new
2444
+ @object.callbacks = []
2445
+ end
2446
+
2447
+ def test_should_raise_exception_if_implicit_option_specified
2448
+ exception = assert_raise(ArgumentError) {@machine.after_failure :invalid => :valid, :do => lambda {}}
2449
+ assert_equal 'Invalid key(s): invalid', exception.message
2450
+ end
2451
+
2452
+ def test_should_raise_exception_if_method_not_specified
2453
+ exception = assert_raise(ArgumentError) {@machine.after_failure :on => :ignite}
2454
+ assert_equal 'Method(s) for callback must be specified', exception.message
2455
+ end
2456
+
2457
+ def test_should_invoke_callbacks_during_failed_transition
2458
+ @machine.after_failure lambda {|object| object.callbacks << 'failure'}
2459
+
2460
+ @event.fire(@object)
2461
+ assert_equal %w(failure), @object.callbacks
2462
+ end
2463
+
2464
+ def test_should_allow_multiple_callbacks
2465
+ @machine.after_failure lambda {|object| object.callbacks << 'failure1'}, lambda {|object| object.callbacks << 'failure2'}
2466
+
2467
+ @event.fire(@object)
2468
+ assert_equal %w(failure1 failure2), @object.callbacks
2469
+ end
2470
+
2471
+ def test_should_allow_multiple_callbacks_with_requirements
2472
+ @machine.after_failure lambda {|object| object.callbacks << 'failure_ignite1'}, lambda {|object| object.callbacks << 'failure_ignite2'}, :on => :ignite
2473
+ @machine.after_failure lambda {|object| object.callbacks << 'failure_park1'}, lambda {|object| object.callbacks << 'failure_park2'}, :on => :park
2474
+
2475
+ @event.fire(@object)
2476
+ assert_equal %w(failure_ignite1 failure_ignite2), @object.callbacks
2477
+ end
2478
+ end
2479
+
2480
+ class MachineWithPathsTest < Test::Unit::TestCase
2481
+ def setup
2482
+ @klass = Class.new
2483
+ @machine = StateMachine::Machine.new(@klass)
2484
+ @machine.event :ignite do
2485
+ transition :parked => :idling
2486
+ end
2487
+ @machine.event :shift_up do
2488
+ transition :first_gear => :second_gear
2489
+ end
2490
+
2491
+ @object = @klass.new
2492
+ @object.state = 'parked'
2493
+ end
2494
+
2495
+ def test_should_have_paths
2496
+ assert_equal [[StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)]], @machine.paths_for(@object)
2497
+ end
2498
+
2499
+ def test_should_allow_requirement_configuration
2500
+ assert_equal [[StateMachine::Transition.new(@object, @machine, :shift_up, :first_gear, :second_gear)]], @machine.paths_for(@object, :from => :first_gear)
2501
+ end
2502
+ end
2503
+
2504
+ class MachineWithOwnerSubclassTest < Test::Unit::TestCase
2505
+ def setup
2506
+ @klass = Class.new
2507
+ @machine = StateMachine::Machine.new(@klass)
2508
+ @subclass = Class.new(@klass)
2509
+ end
2510
+
2511
+ def test_should_have_a_different_collection_of_state_machines
2512
+ assert_not_same @klass.state_machines, @subclass.state_machines
2513
+ end
2514
+
2515
+ def test_should_have_the_same_attribute_associated_state_machines
2516
+ assert_equal @klass.state_machines, @subclass.state_machines
2517
+ end
2518
+ end
2519
+
2520
+ class MachineWithExistingMachinesOnOwnerClassTest < Test::Unit::TestCase
2521
+ def setup
2522
+ @klass = Class.new
2523
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2524
+ @second_machine = StateMachine::Machine.new(@klass, :status, :initial => :idling)
2525
+ @object = @klass.new
2526
+ end
2527
+
2528
+ def test_should_track_each_state_machine
2529
+ expected = {:state => @machine, :status => @second_machine}
2530
+ assert_equal expected, @klass.state_machines
2531
+ end
2532
+
2533
+ def test_should_initialize_state_for_both_machines
2534
+ assert_equal 'parked', @object.state
2535
+ assert_equal 'idling', @object.status
2536
+ end
2537
+ end
2538
+
2539
+ class MachineWithExistingMachinesWithSameAttributesOnOwnerClassTest < Test::Unit::TestCase
2540
+ def setup
2541
+ @klass = Class.new
2542
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2543
+ @second_machine = StateMachine::Machine.new(@klass, :public_state, :initial => :idling, :attribute => :state)
2544
+ @object = @klass.new
2545
+ end
2546
+
2547
+ def test_should_track_each_state_machine
2548
+ expected = {:state => @machine, :public_state => @second_machine}
2549
+ assert_equal expected, @klass.state_machines
2550
+ end
2551
+
2552
+ def test_should_write_to_state_only_once
2553
+ @klass.class_eval do
2554
+ attr_reader :write_count
2555
+
2556
+ def state=(value)
2557
+ @write_count ||= 0
2558
+ @write_count += 1
2559
+ end
2560
+ end
2561
+ object = @klass.new
2562
+
2563
+ assert_equal 1, object.write_count
2564
+ end
2565
+
2566
+ def test_should_initialize_based_on_first_machine
2567
+ assert_equal 'parked', @object.state
2568
+ end
2569
+
2570
+ def test_should_not_allow_second_machine_to_initialize_state
2571
+ @object.state = nil
2572
+ @second_machine.initialize_state(@object)
2573
+ assert_nil @object.state
2574
+ end
2575
+
2576
+ def test_should_allow_transitions_on_both_machines
2577
+ @machine.event :ignite do
2578
+ transition :parked => :idling
2579
+ end
2580
+
2581
+ @second_machine.event :park do
2582
+ transition :idling => :parked
2583
+ end
2584
+
2585
+ @object.ignite
2586
+ assert_equal 'idling', @object.state
2587
+
2588
+ @object.park
2589
+ assert_equal 'parked', @object.state
2590
+ end
2591
+ end
2592
+
2593
+ class MachineWithNamespaceTest < Test::Unit::TestCase
2594
+ def setup
2595
+ @klass = Class.new
2596
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do
2597
+ event :enable do
2598
+ transition :off => :active
2599
+ end
2600
+
2601
+ event :disable do
2602
+ transition :active => :off
2603
+ end
2604
+ end
2605
+ @object = @klass.new
2606
+ end
2607
+
2608
+ def test_should_namespace_state_predicates
2609
+ [:alarm_active?, :alarm_off?].each do |name|
2610
+ assert @object.respond_to?(name)
2611
+ end
2612
+ end
2613
+
2614
+ def test_should_namespace_event_checks
2615
+ [:can_enable_alarm?, :can_disable_alarm?].each do |name|
2616
+ assert @object.respond_to?(name)
2617
+ end
2618
+ end
2619
+
2620
+ def test_should_namespace_event_transition_readers
2621
+ [:enable_alarm_transition, :disable_alarm_transition].each do |name|
2622
+ assert @object.respond_to?(name)
2623
+ end
2624
+ end
2625
+
2626
+ def test_should_namespace_events
2627
+ [:enable_alarm, :disable_alarm].each do |name|
2628
+ assert @object.respond_to?(name)
2629
+ end
2630
+ end
2631
+
2632
+ def test_should_namespace_bang_events
2633
+ [:enable_alarm!, :disable_alarm!].each do |name|
2634
+ assert @object.respond_to?(name)
2635
+ end
2636
+ end
2637
+ end
2638
+
2639
+ class MachineWithCustomAttributeTest < Test::Unit::TestCase
2640
+ def setup
2641
+ StateMachine::Integrations.const_set('Custom', Module.new do
2642
+ include StateMachine::Integrations::Base
2643
+
2644
+ @defaults = {:action => :save, :use_transactions => false}
2645
+
2646
+ def create_with_scope(name)
2647
+ lambda {}
2648
+ end
2649
+
2650
+ def create_without_scope(name)
2651
+ lambda {}
2652
+ end
2653
+ end)
2654
+
2655
+ @klass = Class.new
2656
+ @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :active, :integration => :custom) do
2657
+ event :ignite do
2658
+ transition :parked => :idling
2659
+ end
2660
+ end
2661
+ @object = @klass.new
2662
+ end
2663
+
2664
+ def test_should_define_a_reader_attribute_for_the_attribute
2665
+ assert @object.respond_to?(:state_id)
2666
+ end
2667
+
2668
+ def test_should_define_a_writer_attribute_for_the_attribute
2669
+ assert @object.respond_to?(:state_id=)
2670
+ end
2671
+
2672
+ def test_should_define_a_predicate_for_the_attribute
2673
+ assert @object.respond_to?(:state?)
2674
+ end
2675
+
2676
+ def test_should_define_a_name_reader_for_the_attribute
2677
+ assert @object.respond_to?(:state_name)
2678
+ end
2679
+
2680
+ def test_should_define_a_human_name_reader_for_the_attribute
2681
+ assert @object.respond_to?(:state_name)
2682
+ end
2683
+
2684
+ def test_should_define_an_event_reader_for_the_attribute
2685
+ assert @object.respond_to?(:state_events)
2686
+ end
2687
+
2688
+ def test_should_define_a_transition_reader_for_the_attribute
2689
+ assert @object.respond_to?(:state_transitions)
2690
+ end
2691
+
2692
+ def test_should_define_a_path_reader_for_the_attribute
2693
+ assert @object.respond_to?(:state_paths)
2694
+ end
2695
+
2696
+ def test_should_define_a_human_attribute_name_reader
2697
+ assert @klass.respond_to?(:human_state_name)
2698
+ end
2699
+
2700
+ def test_should_define_a_human_event_name_reader
2701
+ assert @klass.respond_to?(:human_state_event_name)
2702
+ end
2703
+
2704
+ def test_should_define_singular_with_scope
2705
+ assert @klass.respond_to?(:with_state)
2706
+ end
2707
+
2708
+ def test_should_define_singular_without_scope
2709
+ assert @klass.respond_to?(:without_state)
2710
+ end
2711
+
2712
+ def test_should_define_plural_with_scope
2713
+ assert @klass.respond_to?(:with_states)
2714
+ end
2715
+
2716
+ def test_should_define_plural_without_scope
2717
+ assert @klass.respond_to?(:without_states)
2718
+ end
2719
+
2720
+ def test_should_define_state_machines_reader
2721
+ expected = {:state => @machine}
2722
+ assert_equal expected, @klass.state_machines
2723
+ end
2724
+
2725
+ def teardown
2726
+ StateMachine::Integrations.send(:remove_const, 'Custom')
2727
+ end
2728
+ end
2729
+
2730
+ class MachineFinderWithoutExistingMachineTest < Test::Unit::TestCase
2731
+ def setup
2732
+ @klass = Class.new
2733
+ @machine = StateMachine::Machine.find_or_create(@klass)
2734
+ end
2735
+
2736
+ def test_should_accept_a_block
2737
+ called = false
2738
+ StateMachine::Machine.find_or_create(Class.new) do
2739
+ called = respond_to?(:event)
2740
+ end
2741
+
2742
+ assert called
2743
+ end
2744
+
2745
+ def test_should_create_a_new_machine
2746
+ assert_not_nil @machine
2747
+ end
2748
+
2749
+ def test_should_use_default_state
2750
+ assert_equal :state, @machine.attribute
2751
+ end
2752
+ end
2753
+
2754
+ class MachineFinderWithExistingOnSameClassTest < Test::Unit::TestCase
2755
+ def setup
2756
+ @klass = Class.new
2757
+ @existing_machine = StateMachine::Machine.new(@klass)
2758
+ @machine = StateMachine::Machine.find_or_create(@klass)
2759
+ end
2760
+
2761
+ def test_should_accept_a_block
2762
+ called = false
2763
+ StateMachine::Machine.find_or_create(@klass) do
2764
+ called = respond_to?(:event)
2765
+ end
2766
+
2767
+ assert called
2768
+ end
2769
+
2770
+ def test_should_not_create_a_new_machine
2771
+ assert_same @machine, @existing_machine
2772
+ end
2773
+ end
2774
+
2775
+ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
2776
+ def setup
2777
+ integration = Module.new do
2778
+ include StateMachine::Integrations::Base
2779
+
2780
+ def self.matches?(klass)
2781
+ false
2782
+ end
2783
+ end
2784
+ StateMachine::Integrations.const_set('Custom', integration)
2785
+
2786
+ @base_class = Class.new
2787
+ @base_machine = StateMachine::Machine.new(@base_class, :status, :action => :save, :integration => :custom)
2788
+ @base_machine.event(:ignite) {}
2789
+ @base_machine.before_transition(lambda {})
2790
+ @base_machine.after_transition(lambda {})
2791
+ @base_machine.around_transition(lambda {})
2792
+
2793
+ @klass = Class.new(@base_class)
2794
+ @machine = StateMachine::Machine.find_or_create(@klass, :status) {}
2795
+ end
2796
+
2797
+ def test_should_accept_a_block
2798
+ called = false
2799
+ StateMachine::Machine.find_or_create(Class.new(@base_class)) do
2800
+ called = respond_to?(:event)
2801
+ end
2802
+
2803
+ assert called
2804
+ end
2805
+
2806
+ def test_should_not_create_a_new_machine_if_no_block_or_options
2807
+ machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status)
2808
+
2809
+ assert_same machine, @base_machine
2810
+ end
2811
+
2812
+ def test_should_create_a_new_machine_if_given_options
2813
+ machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
2814
+
2815
+ assert_not_nil machine
2816
+ assert_not_same machine, @base_machine
2817
+ end
2818
+
2819
+ def test_should_create_a_new_machine_if_given_block
2820
+ assert_not_nil @machine
2821
+ assert_not_same @machine, @base_machine
2822
+ end
2823
+
2824
+ def test_should_copy_the_base_attribute
2825
+ assert_equal :status, @machine.attribute
2826
+ end
2827
+
2828
+ def test_should_copy_the_base_configuration
2829
+ assert_equal :save, @machine.action
2830
+ end
2831
+
2832
+ def test_should_copy_events
2833
+ # Can't assert equal arrays since their machines change
2834
+ assert_equal 1, @machine.events.length
2835
+ end
2836
+
2837
+ def test_should_copy_before_callbacks
2838
+ assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before]
2839
+ end
2840
+
2841
+ def test_should_copy_after_transitions
2842
+ assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after]
2843
+ end
2844
+
2845
+ def test_should_use_the_same_integration
2846
+ assert (class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom)
2847
+ end
2848
+
2849
+ def teardown
2850
+ StateMachine::Integrations.send(:remove_const, 'Custom')
2851
+ end
2852
+ end
2853
+
2854
+ class MachineFinderCustomOptionsTest < Test::Unit::TestCase
2855
+ def setup
2856
+ @klass = Class.new
2857
+ @machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
2858
+ @object = @klass.new
2859
+ end
2860
+
2861
+ def test_should_use_custom_attribute
2862
+ assert_equal :status, @machine.attribute
2863
+ end
2864
+
2865
+ def test_should_set_custom_initial_state
2866
+ assert_equal :parked, @machine.initial_state(@object).name
2867
+ end
2868
+ end
2869
+
2870
+ begin
2871
+ # Load library
2872
+ require 'rubygems'
2873
+ gem 'ruby-graphviz', '>=0.9.0'
2874
+ require 'graphviz'
2875
+
2876
+ class MachineDrawingTest < Test::Unit::TestCase
2877
+ def setup
2878
+ @klass = Class.new do
2879
+ def self.name; 'Vehicle'; end
2880
+ end
2881
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2882
+ @machine.event :ignite do
2883
+ transition :parked => :idling
2884
+ end
2885
+ end
2886
+
2887
+ def test_should_raise_exception_if_invalid_option_specified
2888
+ assert_raise(ArgumentError) {@machine.draw(:invalid => true)}
2889
+ end
2890
+
2891
+ def test_should_save_file_with_class_name_by_default
2892
+ graph = @machine.draw
2893
+ assert File.exists?('./Vehicle_state.png')
2894
+ end
2895
+
2896
+ def test_should_allow_base_name_to_be_customized
2897
+ graph = @machine.draw(:name => 'machine')
2898
+ assert File.exists?('./machine.png')
2899
+ end
2900
+
2901
+ def test_should_allow_format_to_be_customized
2902
+ graph = @machine.draw(:format => 'jpg')
2903
+ assert File.exists?('./Vehicle_state.jpg')
2904
+ end
2905
+
2906
+ def test_should_allow_path_to_be_customized
2907
+ graph = @machine.draw(:path => "#{File.dirname(__FILE__)}/")
2908
+ assert File.exists?("#{File.dirname(__FILE__)}/Vehicle_state.png")
2909
+ end
2910
+
2911
+ def test_should_allow_orientation_to_be_landscape
2912
+ graph = @machine.draw(:orientation => 'landscape')
2913
+ assert_equal 'LR', graph['rankdir'].to_s.gsub('"', '')
2914
+ end
2915
+
2916
+ def test_should_allow_orientation_to_be_portrait
2917
+ graph = @machine.draw(:orientation => 'portrait')
2918
+ assert_equal 'TB', graph['rankdir'].to_s.gsub('"', '')
2919
+ end
2920
+
2921
+ def teardown
2922
+ FileUtils.rm Dir["{.,#{File.dirname(__FILE__)}}/*.{png,jpg}"]
2923
+ end
2924
+ end
2925
+
2926
+ class MachineDrawingWithIntegerStatesTest < Test::Unit::TestCase
2927
+ def setup
2928
+ @klass = Class.new do
2929
+ def self.name; 'Vehicle'; end
2930
+ end
2931
+ @machine = StateMachine::Machine.new(@klass, :state_id, :initial => :parked)
2932
+ @machine.event :ignite do
2933
+ transition :parked => :idling
2934
+ end
2935
+ @machine.state :parked, :value => 1
2936
+ @machine.state :idling, :value => 2
2937
+ @graph = @machine.draw
2938
+ end
2939
+
2940
+ def test_should_draw_all_states
2941
+ assert_equal 3, @graph.node_count
2942
+ end
2943
+
2944
+ def test_should_draw_all_events
2945
+ assert_equal 2, @graph.edge_count
2946
+ end
2947
+
2948
+ def test_should_draw_machine
2949
+ assert File.exist?('./Vehicle_state_id.png')
2950
+ ensure
2951
+ FileUtils.rm('./Vehicle_state_id.png')
2952
+ end
2953
+ end
2954
+
2955
+ class MachineDrawingWithNilStatesTest < Test::Unit::TestCase
2956
+ def setup
2957
+ @klass = Class.new do
2958
+ def self.name; 'Vehicle'; end
2959
+ end
2960
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2961
+ @machine.event :ignite do
2962
+ transition :parked => :idling
2963
+ end
2964
+ @machine.state :parked, :value => nil
2965
+ @graph = @machine.draw
2966
+ end
2967
+
2968
+ def test_should_draw_all_states
2969
+ assert_equal 3, @graph.node_count
2970
+ end
2971
+
2972
+ def test_should_draw_all_events
2973
+ assert_equal 2, @graph.edge_count
2974
+ end
2975
+
2976
+ def test_should_draw_machine
2977
+ assert File.exist?('./Vehicle_state.png')
2978
+ ensure
2979
+ FileUtils.rm('./Vehicle_state.png')
2980
+ end
2981
+ end
2982
+
2983
+ class MachineDrawingWithDynamicStatesTest < Test::Unit::TestCase
2984
+ def setup
2985
+ @klass = Class.new do
2986
+ def self.name; 'Vehicle'; end
2987
+ end
2988
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
2989
+ @machine.event :activate do
2990
+ transition :parked => :idling
2991
+ end
2992
+ @machine.state :idling, :value => lambda {Time.now}
2993
+ @graph = @machine.draw
2994
+ end
2995
+
2996
+ def test_should_draw_all_states
2997
+ assert_equal 3, @graph.node_count
2998
+ end
2999
+
3000
+ def test_should_draw_all_events
3001
+ assert_equal 2, @graph.edge_count
3002
+ end
3003
+
3004
+ def test_should_draw_machine
3005
+ assert File.exist?('./Vehicle_state.png')
3006
+ ensure
3007
+ FileUtils.rm('./Vehicle_state.png')
3008
+ end
3009
+ end
3010
+
3011
+ class MachineClassDrawingTest < Test::Unit::TestCase
3012
+ def setup
3013
+ @klass = Class.new do
3014
+ def self.name; 'Vehicle'; end
3015
+ end
3016
+ @machine = StateMachine::Machine.new(@klass)
3017
+ @machine.event :ignite do
3018
+ transition :parked => :idling
3019
+ end
3020
+ end
3021
+
3022
+ def test_should_raise_exception_if_no_class_names_specified
3023
+ exception = assert_raise(ArgumentError) {StateMachine::Machine.draw(nil)}
3024
+ assert_equal 'At least one class must be specified', exception.message
3025
+ end
3026
+
3027
+ def test_should_load_files
3028
+ StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb"))
3029
+ assert defined?(::Switch)
3030
+ ensure
3031
+ FileUtils.rm('./Switch_state.png')
3032
+ end
3033
+
3034
+ def test_should_allow_path_and_format_to_be_customized
3035
+ StateMachine::Machine.draw('Switch', :file => File.expand_path("#{File.dirname(__FILE__)}/../files/switch.rb"), :path => "#{File.dirname(__FILE__)}/", :format => 'jpg')
3036
+ assert File.exist?("#{File.dirname(__FILE__)}/Switch_state.jpg")
3037
+ ensure
3038
+ FileUtils.rm("#{File.dirname(__FILE__)}/Switch_state.jpg")
3039
+ end
3040
+ end
3041
+ rescue LoadError
3042
+ $stderr.puts 'Skipping GraphViz StateMachine::Machine tests. `gem install ruby-graphviz` >= v0.9.0 and try again.'
3043
+ end