hsume2-state_machine 1.0.1

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