spree-state_machine 2.0.0.beta1

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