enum_state_machine 0.0.1

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