state_machines 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.idea/.name +1 -0
  4. data/.idea/.rakeTasks +7 -0
  5. data/.idea/cssxfire.xml +9 -0
  6. data/.idea/encodings.xml +5 -0
  7. data/.idea/misc.xml +5 -0
  8. data/.idea/modules.xml +12 -0
  9. data/.idea/scopes/scope_settings.xml +5 -0
  10. data/.idea/state_machine2.iml +34 -0
  11. data/.idea/vcs.xml +9 -0
  12. data/.idea/workspace.xml +1156 -0
  13. data/.rspec +3 -0
  14. data/.travis.yml +8 -0
  15. data/Gemfile +4 -0
  16. data/LICENSE.txt +23 -0
  17. data/README.md +29 -0
  18. data/Rakefile +1 -0
  19. data/lib/state_machines/assertions.rb +40 -0
  20. data/lib/state_machines/branch.rb +187 -0
  21. data/lib/state_machines/callback.rb +220 -0
  22. data/lib/state_machines/core.rb +25 -0
  23. data/lib/state_machines/core_ext/class/state_machine.rb +5 -0
  24. data/lib/state_machines/core_ext.rb +2 -0
  25. data/lib/state_machines/error.rb +13 -0
  26. data/lib/state_machines/eval_helpers.rb +87 -0
  27. data/lib/state_machines/event.rb +246 -0
  28. data/lib/state_machines/event_collection.rb +141 -0
  29. data/lib/state_machines/extensions.rb +148 -0
  30. data/lib/state_machines/helper_module.rb +17 -0
  31. data/lib/state_machines/integrations/base.rb +100 -0
  32. data/lib/state_machines/integrations.rb +113 -0
  33. data/lib/state_machines/machine.rb +2234 -0
  34. data/lib/state_machines/machine_collection.rb +84 -0
  35. data/lib/state_machines/macro_methods.rb +520 -0
  36. data/lib/state_machines/matcher.rb +123 -0
  37. data/lib/state_machines/matcher_helpers.rb +54 -0
  38. data/lib/state_machines/node_collection.rb +221 -0
  39. data/lib/state_machines/path.rb +120 -0
  40. data/lib/state_machines/path_collection.rb +90 -0
  41. data/lib/state_machines/state.rb +276 -0
  42. data/lib/state_machines/state_collection.rb +112 -0
  43. data/lib/state_machines/state_context.rb +138 -0
  44. data/lib/state_machines/transition.rb +470 -0
  45. data/lib/state_machines/transition_collection.rb +245 -0
  46. data/lib/state_machines/version.rb +3 -0
  47. data/lib/state_machines/yard.rb +8 -0
  48. data/lib/state_machines.rb +3 -0
  49. data/spec/errors/default_spec.rb +14 -0
  50. data/spec/errors/with_message_spec.rb +39 -0
  51. data/spec/helpers/helper_spec.rb +14 -0
  52. data/spec/internal/app/models/auto_shop.rb +31 -0
  53. data/spec/internal/app/models/car.rb +19 -0
  54. data/spec/internal/app/models/model_base.rb +6 -0
  55. data/spec/internal/app/models/motorcycle.rb +9 -0
  56. data/spec/internal/app/models/traffic_light.rb +47 -0
  57. data/spec/internal/app/models/vehicle.rb +123 -0
  58. data/spec/machine_spec.rb +3167 -0
  59. data/spec/matcher_helpers_spec.rb +39 -0
  60. data/spec/matcher_spec.rb +157 -0
  61. data/spec/models/auto_shop_spec.rb +41 -0
  62. data/spec/models/car_spec.rb +90 -0
  63. data/spec/models/motorcycle_spec.rb +44 -0
  64. data/spec/models/traffic_light_spec.rb +56 -0
  65. data/spec/models/vehicle_spec.rb +580 -0
  66. data/spec/node_collection_spec.rb +371 -0
  67. data/spec/path_collection_spec.rb +271 -0
  68. data/spec/path_spec.rb +488 -0
  69. data/spec/spec_helper.rb +6 -0
  70. data/spec/state_collection_spec.rb +352 -0
  71. data/spec/state_context_spec.rb +442 -0
  72. data/spec/state_machine_spec.rb +29 -0
  73. data/spec/state_spec.rb +970 -0
  74. data/spec/support/migration_helpers.rb +50 -0
  75. data/spec/support/models.rb +6 -0
  76. data/spec/transition_collection_spec.rb +2199 -0
  77. data/spec/transition_spec.rb +1558 -0
  78. data/state_machines.gemspec +23 -0
  79. metadata +194 -0
@@ -0,0 +1,3167 @@
1
+ describe StateMachines::Machine do
2
+ let(:klass) { Class.new }
3
+ let(:machine) { StateMachines::Machine.new(klass) }
4
+
5
+ let(:object) { klass.new }
6
+
7
+ describe 'machine' do
8
+ before(:each) do
9
+ machine
10
+ end
11
+ it 'should_have_an_owner_class' do
12
+ assert_equal klass, machine.owner_class
13
+ end
14
+
15
+ it 'should_have_a_name' do
16
+ assert_equal :state, machine.name
17
+ end
18
+
19
+ it 'should_have_an_attribute' do
20
+ assert_equal :state, machine.attribute
21
+ end
22
+
23
+ it 'should_prefix_custom_attributes_with_attribute' do
24
+ assert_equal :state_event, machine.attribute(:event)
25
+ end
26
+
27
+ it 'should_have_an_initial_state' do
28
+ assert_not_nil machine.initial_state(object)
29
+ end
30
+
31
+ it 'should_have_a_nil_initial_state' do
32
+ assert_nil machine.initial_state(object).value
33
+ end
34
+
35
+ it 'should_not_have_any_events' do
36
+ assert !machine.events.any?
37
+ end
38
+
39
+ it 'should_not_have_any_before_callbacks' do
40
+ assert machine.callbacks[:before].empty?
41
+ end
42
+
43
+ it 'should_not_have_any_after_callbacks' do
44
+ assert machine.callbacks[:after].empty?
45
+ end
46
+
47
+ it 'should_not_have_any_failure_callbacks' do
48
+ assert machine.callbacks[:failure].empty?
49
+ end
50
+
51
+ it 'should_not_have_an_action' do
52
+ assert_nil machine.action
53
+ end
54
+
55
+ it 'should_use_tranactions' do
56
+ assert_equal true, machine.use_transactions
57
+ end
58
+
59
+ it 'should_not_have_a_namespace' do
60
+ assert_nil machine.namespace
61
+ end
62
+
63
+ it 'should_have_a_nil_state' do
64
+ assert_equal [nil], machine.states.keys
65
+ end
66
+
67
+ it 'should_set_initial_on_nil_state' do
68
+ assert machine.state(nil).initial
69
+ end
70
+
71
+ it 'should_generate_default_messages' do
72
+ assert_equal 'is invalid', machine.generate_message(:invalid)
73
+ assert_equal 'cannot transition when parked', machine.generate_message(:invalid_event, [[:state, :parked]])
74
+ assert_equal 'cannot transition via "park"', machine.generate_message(:invalid_transition, [[:event, :park]])
75
+ end
76
+
77
+ it 'should_not_be_extended_by_the_base_integration' do
78
+ assert !(
79
+ class << machine
80
+ ancestors
81
+ end).include?(StateMachines::Integrations::Base)
82
+ end
83
+
84
+
85
+ it 'should_define_a_reader_attribute_for_the_attribute' do
86
+ assert object.respond_to?(:state)
87
+ end
88
+
89
+ it 'should_define_a_writer_attribute_for_the_attribute' do
90
+ assert object.respond_to?(:state=)
91
+ end
92
+
93
+ it 'should_define_a_predicate_for_the_attribute' do
94
+ assert object.respond_to?(:state?)
95
+ end
96
+
97
+ it 'should_define_a_name_reader_for_the_attribute' do
98
+ assert object.respond_to?(:state_name)
99
+ end
100
+
101
+ it 'should_define_an_event_reader_for_the_attribute' do
102
+ assert object.respond_to?(:state_events)
103
+ end
104
+
105
+ it 'should_define_a_transition_reader_for_the_attribute' do
106
+ assert object.respond_to?(:state_transitions)
107
+ end
108
+
109
+ it 'should_define_a_path_reader_for_the_attribute' do
110
+ assert object.respond_to?(:state_paths)
111
+ end
112
+
113
+ it 'should_define_an_event_runner_for_the_attribute' do
114
+ assert object.respond_to?(:fire_state_event)
115
+ end
116
+
117
+ it 'should_not_define_an_event_attribute_reader' do
118
+ assert !object.respond_to?(:state_event)
119
+ end
120
+
121
+ it 'should_not_define_an_event_attribute_writer' do
122
+ assert !object.respond_to?(:state_event=)
123
+ end
124
+
125
+ it 'should_not_define_an_event_transition_attribute_reader' do
126
+ assert !object.respond_to?(:state_event_transition)
127
+ end
128
+
129
+ it 'should_not_define_an_event_transition_attribute_writer' do
130
+ assert !object.respond_to?(:state_event_transition=)
131
+ end
132
+
133
+ it 'should_define_a_human_attribute_name_reader_for_the_attribute' do
134
+ assert klass.respond_to?(:human_state_name)
135
+ end
136
+
137
+ it 'should_define_a_human_event_name_reader_for_the_attribute' do
138
+ assert klass.respond_to?(:human_state_event_name)
139
+ end
140
+
141
+ it 'should_not_define_singular_with_scope' do
142
+ assert !klass.respond_to?(:with_state)
143
+ end
144
+
145
+ it 'should_not_define_singular_without_scope' do
146
+ assert !klass.respond_to?(:without_state)
147
+ end
148
+
149
+ it 'should_not_define_plural_with_scope' do
150
+ assert !klass.respond_to?(:with_states)
151
+ end
152
+
153
+ it 'should_not_define_plural_without_scope' do
154
+ assert !klass.respond_to?(:without_states)
155
+ end
156
+
157
+ it 'should_extend_owner_class_with_class_methods' do
158
+ assert((
159
+ class << klass
160
+ ancestors
161
+ end).include?(StateMachines::ClassMethods))
162
+ end
163
+
164
+ it 'should_include_instance_methods_in_owner_class' do
165
+ assert klass.included_modules.include?(StateMachines::InstanceMethods)
166
+ end
167
+
168
+ it 'should_define_state_machines_reader' do
169
+ expected = {state: machine}
170
+ assert_equal expected, klass.state_machines
171
+ end
172
+
173
+ it 'should_raise_exception_if_invalid_option_specified ' do
174
+ assert_raise(ArgumentError) { StateMachines::Machine.new(Class.new, invalid: true) }
175
+ end
176
+
177
+ it 'should_not_raise_exception_if_custom_messages_specified ' do
178
+ assert_nothing_raised { StateMachines::Machine.new(Class.new, messages: {invalid_transition: 'Custom'}) }
179
+ end
180
+
181
+ it 'should_evaluate_a_block_during_initialization ' do
182
+ called = true
183
+ StateMachines::Machine.new(Class.new) do
184
+ called = respond_to?(:event)
185
+ end
186
+
187
+ assert called
188
+ end
189
+
190
+ it 'should_provide_matcher_helpers_during_initialization ' do
191
+ matchers = []
192
+
193
+ StateMachines::Machine.new(Class.new) do
194
+ matchers = [all, any, same]
195
+ end
196
+
197
+ assert_equal [StateMachines::AllMatcher.instance, StateMachines::AllMatcher.instance, StateMachines::LoopbackMatcher.instance], matchers
198
+ end
199
+
200
+
201
+ end
202
+
203
+
204
+ context 'Drawing' do
205
+ it 'should raise NotImplementedError' do
206
+ machine = StateMachines::Machine.new(Class.new)
207
+ expect { machine.draw(:foo) }.to raise_error(NotImplementedError)
208
+ end
209
+ end
210
+
211
+
212
+ context 'WithCustomName' do
213
+ let!(:machine) { StateMachines::Machine.new(klass, :status) }
214
+ it 'should_use_custom_name' do
215
+ assert_equal :status, machine.name
216
+ end
217
+
218
+ it 'should_use_custom_name_for_attribute' do
219
+ assert_equal :status, machine.attribute
220
+ end
221
+
222
+ it 'should_prefix_custom_attributes_with_custom_name' do
223
+ assert_equal :status_event, machine.attribute(:event)
224
+ end
225
+
226
+ it 'should_define_a_reader_attribute_for_the_attribute' do
227
+ assert object.respond_to?(:status)
228
+ end
229
+
230
+ it 'should_define_a_writer_attribute_for_the_attribute' do
231
+ assert object.respond_to?(:status=)
232
+ end
233
+
234
+ it 'should_define_a_predicate_for_the_attribute' do
235
+ assert object.respond_to?(:status?)
236
+ end
237
+
238
+ it 'should_define_a_name_reader_for_the_attribute' do
239
+ assert object.respond_to?(:status_name)
240
+ end
241
+
242
+ it 'should_define_an_event_reader_for_the_attribute' do
243
+ assert object.respond_to?(:status_events)
244
+ end
245
+
246
+ it 'should_define_a_transition_reader_for_the_attribute' do
247
+ assert object.respond_to?(:status_transitions)
248
+ end
249
+
250
+ it 'should_define_an_event_runner_for_the_attribute' do
251
+ assert object.respond_to?(:fire_status_event)
252
+ end
253
+
254
+ it 'should_define_a_human_attribute_name_reader_for_the_attribute' do
255
+ assert klass.respond_to?(:human_status_name)
256
+ end
257
+
258
+ it 'should_define_a_human_event_name_reader_for_the_attribute' do
259
+ assert klass.respond_to?(:human_status_event_name)
260
+ end
261
+ end
262
+
263
+ context 'WithoutInitialization' do
264
+ let(:klass) do
265
+ Class.new do
266
+ def initialize(attributes = {})
267
+ attributes.each { |attr, value| send("#{attr}=", value) }
268
+ super()
269
+ end
270
+ end
271
+ end
272
+
273
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked, initialize: false) }
274
+
275
+ it 'should_not_have_an_initial_state' do
276
+ object = klass.new
277
+ assert_nil object.state
278
+ end
279
+
280
+ it 'should_still_allow_manual_initialization' do
281
+ klass.send(:include, Module.new do
282
+ def initialize(attributes = {})
283
+ super()
284
+ initialize_state_machines
285
+ end
286
+ end)
287
+
288
+ object = klass.new
289
+ assert_equal 'parked', object.state
290
+ end
291
+ end
292
+
293
+ context 'WithStaticInitialState' do
294
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
295
+
296
+ it 'should_not_have_dynamic_initial_state' do
297
+ assert !machine.dynamic_initial_state?
298
+ end
299
+
300
+ it 'should_have_an_initial_state' do
301
+ object = klass.new
302
+ assert_equal 'parked', machine.initial_state(object).value
303
+ end
304
+
305
+ it 'should_write_to_attribute_when_initializing_state' do
306
+ object = klass.allocate
307
+ machine.initialize_state(object)
308
+ assert_equal 'parked', object.state
309
+ end
310
+
311
+ it 'should_set_initial_on_state_object' do
312
+ assert machine.state(:parked).initial
313
+ end
314
+
315
+ it 'should_set_initial_state_on_created_object' do
316
+ assert_equal 'parked', klass.new.state
317
+ end
318
+
319
+ it 'not_set_initial_state_even_if_not_empty' do
320
+ klass.class_eval do
321
+ def initialize(attributes = {})
322
+ self.state = 'idling'
323
+ super()
324
+ end
325
+ end
326
+ object = klass.new
327
+ assert_equal 'idling', object.state
328
+ end
329
+
330
+ it 'should_set_initial_state_prior_to_initialization' do
331
+ base = Class.new do
332
+ attr_accessor :state_on_init
333
+
334
+ def initialize
335
+ self.state_on_init = state
336
+ end
337
+ end
338
+ klass = Class.new(base)
339
+ StateMachines::Machine.new(klass, initial: :parked)
340
+
341
+ assert_equal 'parked', klass.new.state_on_init
342
+ end
343
+
344
+ it 'should_be_included_in_known_states' do
345
+ assert_equal [:parked], machine.states.keys
346
+ end
347
+ end
348
+
349
+ context 'WithInitialStateWithValueAndOwnerDefault' do
350
+ before(:each) do
351
+ @original_stderr, $stderr = $stderr, StringIO.new
352
+
353
+ end
354
+ let(:state_machine_with_defaults) do
355
+ Class.new(StateMachines::Machine) do
356
+ def owner_class_attribute_default
357
+ 0
358
+ end
359
+ end
360
+ end
361
+
362
+ it 'should_not_warn_about_wrong_default ' do
363
+ state_machine_with_defaults.new(klass, initial: :parked) do
364
+ state :parked, value: 0
365
+ end
366
+ expect($stderr.string).to be_empty
367
+ end
368
+
369
+ it 'should_warn_about_wrong_default ' do
370
+ state_machine_with_defaults.new(klass, initial: :parked) do
371
+ state :parked, value: 666
372
+ end
373
+ expect($stderr.string).to_not be_empty
374
+ end
375
+
376
+ after(:each) do
377
+ $stderr = @original_stderr
378
+ end
379
+ end
380
+
381
+ context 'WithDynamicInitialState ' do
382
+ let(:klass) do
383
+ Class.new do
384
+ attr_accessor :initial_state
385
+ end
386
+ end
387
+
388
+ let!(:machine) do
389
+ machine = StateMachines::Machine.new(klass, initial: lambda { |object| object.initial_state || :default })
390
+ machine.state :parked, :idling, :default
391
+ machine
392
+ end
393
+
394
+ it 'should_have_dynamic_initial_state ' do
395
+ assert machine.dynamic_initial_state?
396
+ end
397
+
398
+ it 'should_use_the_record_for_determining_the_initial_state ' do
399
+ object.initial_state = :parked
400
+ assert_equal :parked, machine.initial_state(object).name
401
+
402
+ object.initial_state = :idling
403
+ assert_equal :idling, machine.initial_state(object).name
404
+ end
405
+
406
+ it 'should_write_to_attribute_when_initializing_state ' do
407
+ object = klass.allocate
408
+ object.initial_state = :parked
409
+ machine.initialize_state(object)
410
+ assert_equal 'parked', object.state
411
+ end
412
+
413
+ it 'should_set_initial_state_on_created_object ' do
414
+ assert_equal 'default', object.state
415
+ end
416
+
417
+ it 'should_not_set_initial_state_even_if_not_empty ' do
418
+ klass.class_eval do
419
+ def initialize(attributes = {})
420
+ self.state = 'parked'
421
+ super()
422
+ end
423
+ end
424
+ object = klass.new
425
+ assert_equal 'parked', object.state
426
+ end
427
+
428
+ it 'should_set_initial_state_after_initialization ' do
429
+ base = Class.new do
430
+ attr_accessor :state_on_init
431
+
432
+ def initialize
433
+ self.state_on_init = state
434
+ end
435
+ end
436
+ klass = Class.new(base)
437
+ machine = StateMachines::Machine.new(klass, initial: lambda { |object| :parked })
438
+ machine.state :parked
439
+
440
+ assert_nil klass.new.state_on_init
441
+ end
442
+
443
+ it 'should_not_be_included_in_known_states ' do
444
+ assert_equal [:parked, :idling, :default], machine.states.map { |state| state.name }
445
+ end
446
+ end
447
+
448
+ context 'stateInitialization ' do
449
+ let!(:machine) { StateMachines::Machine.new(klass, :state, initial: :parked, initialize: false) }
450
+ before(:each) do
451
+ object.state = nil
452
+ end
453
+
454
+ it 'should_set_states_if_nil ' do
455
+ machine.initialize_state(object)
456
+
457
+ assert_equal 'parked', object.state
458
+ end
459
+
460
+ it 'should_set_states_if_empty ' do
461
+ object.state = ''
462
+ machine.initialize_state(object)
463
+
464
+ assert_equal 'parked', object.state
465
+ end
466
+
467
+ it 'should_not_set_states_if_not_empty ' do
468
+ object.state = 'idling'
469
+ machine.initialize_state(object)
470
+
471
+ assert_equal 'idling', object.state
472
+ end
473
+
474
+ it 'should_set_states_if_not_empty_and_forced ' do
475
+ object.state = 'idling'
476
+ machine.initialize_state(object, force: true)
477
+
478
+ assert_equal 'parked', object.state
479
+ end
480
+
481
+ it 'should_not_set_state_if_nil_and_nil_is_valid_state ' do
482
+ machine.state :initial, value: nil
483
+ machine.initialize_state(object)
484
+
485
+ assert_nil object.state
486
+ end
487
+
488
+ it 'should_write_to_hash_if_specified ' do
489
+ machine.initialize_state(object, to: hash = {})
490
+ assert_equal({'state' => 'parked'}, hash)
491
+ end
492
+
493
+ it 'should_not_write_to_object_if_writing_to_hash ' do
494
+ machine.initialize_state(object, to: {})
495
+ assert_nil object.state
496
+ end
497
+ end
498
+
499
+ context 'WithCustomAction' do
500
+ let(:machine) { StateMachines::Machine.new(Class.new, action: :save) }
501
+
502
+ it 'should_use_the_custom_action ' do
503
+ assert_equal :save, machine.action
504
+ end
505
+ end
506
+
507
+ context 'WithNilAction' do
508
+ let!(:machine) { StateMachines::Machine.new(Class.new, action: nil, integration: :custom) }
509
+ before(:all) do
510
+ integration = Module.new do
511
+ include StateMachines::Integrations::Base
512
+
513
+ @defaults = {action: :save}
514
+ end
515
+ StateMachines::Integrations.const_set('Custom', integration)
516
+ end
517
+
518
+ it 'should_have_a_nil_action ' do
519
+ assert_nil machine.action
520
+ end
521
+
522
+ after(:all) do
523
+ StateMachines::Integrations.send(:remove_const, 'Custom')
524
+ end
525
+ end
526
+
527
+ context 'WithoutIntegration' do
528
+ it ' transaction_should_yield ' do
529
+ yielded = false
530
+ machine.within_transaction(object) do
531
+ yielded = true
532
+ end
533
+
534
+ assert yielded
535
+ end
536
+
537
+ it ' invalidation_should_do_nothing ' do
538
+ assert_nil machine.invalidate(object, :state, :invalid_transition, [[:event, ' park ']])
539
+ end
540
+
541
+ it ' reset_should_do_nothing ' do
542
+ assert_nil machine.reset(object)
543
+ end
544
+
545
+ it ' errors_for_should_be_empty ' do
546
+ assert_equal '', machine.errors_for(object)
547
+ end
548
+ end
549
+
550
+ context 'WithCustomIntegration' do
551
+ before(:each) do
552
+ integration = Module.new do
553
+ include StateMachines::Integrations::Base
554
+
555
+ def self.matching_ancestors
556
+ ['Vehicle']
557
+ end
558
+ end
559
+
560
+ StateMachines::Integrations.const_set('Custom', integration)
561
+
562
+ superclass = Class.new
563
+ self.class.const_set('Vehicle', superclass)
564
+
565
+ @klass = Class.new(superclass)
566
+ end
567
+
568
+ it 'should_be_extended_by_the_integration_if_explicit' do
569
+ machine = StateMachines::Machine.new(@klass, :integration => :custom)
570
+ assert((class << machine; ancestors; end).include?(StateMachines::Integrations::Custom))
571
+ end
572
+
573
+ it 'should_not_be_extended_by_the_integration_if_implicit_but_not_available' do
574
+ StateMachines::Integrations::Custom.class_eval do
575
+ class << self; remove_method :matching_ancestors; end
576
+ def self.matching_ancestors
577
+ []
578
+ end
579
+ end
580
+
581
+ machine = StateMachines::Machine.new(@klass)
582
+ assert(!(class << machine; ancestors; end).include?(StateMachines::Integrations::Custom))
583
+ end
584
+
585
+ it 'should_not_be_extended_by_the_integration_if_implicit_but_not_matched' do
586
+ StateMachines::Integrations::Custom.class_eval do
587
+ class << self; remove_method :matching_ancestors; end
588
+ def self.matching_ancestors
589
+ []
590
+ end
591
+ end
592
+
593
+ machine = StateMachines::Machine.new(@klass)
594
+ assert(!(class << machine; ancestors; end).include?(StateMachines::Integrations::Custom))
595
+ end
596
+
597
+ xit 'should_be_extended_by_the_integration_if_implicit_and_available_and_matches' do
598
+ machine = StateMachines::Machine.new(@klass)
599
+ assert((class << machine; ancestors; end).include?(StateMachines::Integrations::Custom))
600
+ end
601
+
602
+ it 'should_not_be_extended_by_the_integration_if_nil' do
603
+ machine = StateMachines::Machine.new(@klass, :integration => nil)
604
+ assert(!(class << machine; ancestors; end).include?(StateMachines::Integrations::Custom))
605
+ end
606
+
607
+ it 'should_not_be_extended_by_the_integration_if_false' do
608
+ machine = StateMachines::Machine.new(@klass, :integration => false)
609
+ assert(!(class << machine; ancestors; end).include?(StateMachines::Integrations::Custom))
610
+ end
611
+
612
+ after(:each) do
613
+ self.class.send(:remove_const, 'Vehicle')
614
+ StateMachines::Integrations.send(:remove_const, 'Custom')
615
+ end
616
+ end
617
+
618
+ context 'WithIntegration' do
619
+
620
+ before(:each) do
621
+ StateMachines::Integrations.const_set('Custom', Module.new do
622
+ include StateMachines::Integrations::Base
623
+
624
+ @defaults = {action: :save, use_transactions: false}
625
+
626
+ attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
627
+
628
+ def after_initialize
629
+ @initialized = true
630
+ end
631
+
632
+ def create_with_scope(name)
633
+ (@with_scopes ||= []) << name
634
+ lambda {}
635
+ end
636
+
637
+ def create_without_scope(name)
638
+ (@without_scopes ||= []) << name
639
+ lambda {}
640
+ end
641
+
642
+ def transaction(object)
643
+ @ran_transaction = true
644
+ yield
645
+ end
646
+ end)
647
+
648
+ end
649
+
650
+ let(:machine) { StateMachines::Machine.new(Class.new, integration: :custom) }
651
+
652
+ it 'should_call_after_initialize_hook ' do
653
+ assert machine.initialized
654
+ end
655
+
656
+ it 'should_use_the_default_action ' do
657
+ assert_equal :save, machine.action
658
+ end
659
+
660
+ it 'should_use_the_custom_action_if_specified ' do
661
+ machine = StateMachines::Machine.new(Class.new, integration: :custom, action: :save!)
662
+ assert_equal :save!, machine.action
663
+ end
664
+
665
+ it 'should_use_the_default_use_transactions ' do
666
+ assert_equal false, machine.use_transactions
667
+ end
668
+
669
+ it 'should_use_the_custom_use_transactions_if_specified ' do
670
+ machine = StateMachines::Machine.new(Class.new, integration: :custom, use_transactions: true)
671
+ assert_equal true, machine.use_transactions
672
+ end
673
+
674
+ it 'should_define_a_singular_and_plural_with_scope ' do
675
+ assert_equal %w(with_state with_states), machine.with_scopes
676
+ end
677
+
678
+ it 'should_define_a_singular_and_plural_without_scope ' do
679
+ assert_equal %w(without_state without_states), machine.without_scopes
680
+ end
681
+
682
+ after(:each) do
683
+ StateMachines::Integrations.send(:remove_const, 'Custom')
684
+ end
685
+ end
686
+
687
+ context 'WithActionUndefined' do
688
+ let!(:machine) { StateMachines::Machine.new(klass, action: :save) }
689
+
690
+ it 'should_define_an_event_attribute_reader ' do
691
+ assert object.respond_to?(:state_event)
692
+ end
693
+
694
+ it 'should_define_an_event_attribute_writer ' do
695
+ assert object.respond_to?(:state_event=)
696
+ end
697
+
698
+ it 'should_define_an_event_transition_attribute_reader ' do
699
+ assert object.respond_to?(:state_event_transition, true)
700
+ end
701
+
702
+ it 'should_define_an_event_transition_attribute_writer ' do
703
+ assert object.respond_to?(:state_event_transition=, true)
704
+ end
705
+
706
+ it 'should_not_define_action ' do
707
+ assert !object.respond_to?(:save)
708
+ end
709
+
710
+ it 'should_not_mark_action_hook_as_defined ' do
711
+ assert !machine.action_hook?
712
+ end
713
+ end
714
+
715
+ context 'WithActionDefinedInClass' do
716
+ let(:klass) do
717
+ Class.new do
718
+ def save
719
+ end
720
+ end
721
+ end
722
+
723
+ let!(:machine) { StateMachines::Machine.new(klass, action: :save) }
724
+
725
+ it 'should_define_an_event_attribute_reader ' do
726
+ assert object.respond_to?(:state_event)
727
+ end
728
+
729
+ it 'should_define_an_event_attribute_writer ' do
730
+ assert object.respond_to?(:state_event=)
731
+ end
732
+
733
+ it 'should_define_an_event_transition_attribute_reader ' do
734
+ assert object.respond_to?(:state_event_transition, true)
735
+ end
736
+
737
+ it 'should_define_an_event_transition_attribute_writer ' do
738
+ assert object.respond_to?(:state_event_transition=, true)
739
+ end
740
+
741
+ it 'should_not_define_action ' do
742
+ assert !klass.ancestors.any? { |ancestor| ancestor != klass && ancestor.method_defined?(:save) }
743
+ end
744
+
745
+ it 'should_not_mark_action_hook_as_defined ' do
746
+ assert !machine.action_hook?
747
+ end
748
+ end
749
+
750
+ context 'WithActionDefinedInIncludedModule' do
751
+ before(:each) do
752
+
753
+ @mod = mod = Module.new do
754
+ def save
755
+ end
756
+ end
757
+ @klass = Class.new do
758
+ include mod
759
+ end
760
+ @machine = StateMachines::Machine.new(@klass, action: :save)
761
+ @object = @klass.new
762
+ end
763
+
764
+
765
+
766
+ it 'should_define_an_event_attribute_reader ' do
767
+ assert @object.respond_to?(:state_event)
768
+ end
769
+
770
+ it 'should_define_an_event_attribute_writer ' do
771
+ assert @object.respond_to?(:state_event=)
772
+ end
773
+
774
+ it 'should_define_an_event_transition_attribute_reader ' do
775
+ assert @object.respond_to?(:state_event_transition, true)
776
+ end
777
+
778
+ it 'should_define_an_event_transition_attribute_writer ' do
779
+ assert @object.respond_to?(:state_event_transition=, true)
780
+ end
781
+
782
+ it 'should_define_action ' do
783
+ assert @klass.ancestors.any? { |ancestor| ![@klass, @mod].include?(ancestor) && ancestor.method_defined?(:save) }
784
+ end
785
+
786
+ it 'should_keep_action_public ' do
787
+ assert @klass.public_method_defined?(:save)
788
+ end
789
+
790
+ it 'should_mark_action_hook_as_defined ' do
791
+ assert @machine.action_hook?
792
+ end
793
+ end
794
+
795
+ context 'WithActionDefinedInSuperclass' do
796
+ let(:superclass) do
797
+ Class.new do
798
+ def save
799
+ end
800
+ end
801
+ end
802
+ let(:klass) { Class.new(superclass) }
803
+ let!(:machine) { StateMachines::Machine.new(klass, action: :save) }
804
+
805
+ it 'should_define_an_event_attribute_reader ' do
806
+ assert object.respond_to?(:state_event)
807
+ end
808
+
809
+ it 'should_define_an_event_attribute_writer ' do
810
+ assert object.respond_to?(:state_event=)
811
+ end
812
+
813
+ it 'should_define_an_event_transition_attribute_reader ' do
814
+ assert object.respond_to?(:state_event_transition, true)
815
+ end
816
+
817
+ it 'should_define_an_event_transition_attribute_writer ' do
818
+ assert object.respond_to?(:state_event_transition=, true)
819
+ end
820
+
821
+ it 'should_define_action ' do
822
+ assert klass.ancestors.any? { |ancestor| ![klass, superclass].include?(ancestor) && ancestor.method_defined?(:save) }
823
+ end
824
+
825
+ it 'should_keep_action_public ' do
826
+ assert klass.public_method_defined?(:save)
827
+ end
828
+
829
+ it 'should_mark_action_hook_as_defined ' do
830
+ assert machine.action_hook?
831
+ end
832
+ end
833
+
834
+ context 'WithPrivateAction' do
835
+
836
+ let(:superclass) do
837
+ Class.new do
838
+ private
839
+ def save
840
+ end
841
+ end
842
+ end
843
+ let(:klass) { Class.new(superclass) }
844
+ let!(:machine) { StateMachines::Machine.new(klass, action: :save) }
845
+
846
+ it 'should_define_an_event_attribute_reader ' do
847
+ assert object.respond_to?(:state_event)
848
+ end
849
+
850
+ it 'should_define_an_event_attribute_writer ' do
851
+ assert object.respond_to?(:state_event=)
852
+ end
853
+
854
+ it 'should_define_an_event_transition_attribute_reader ' do
855
+ assert object.respond_to?(:state_event_transition, true)
856
+ end
857
+
858
+ it 'should_define_an_event_transition_attribute_writer ' do
859
+ assert object.respond_to?(:state_event_transition=, true)
860
+ end
861
+
862
+ it 'should_define_action ' do
863
+ assert klass.ancestors.any? { |ancestor| ![klass, superclass].include?(ancestor) && ancestor.private_method_defined?(:save) }
864
+ end
865
+
866
+ it 'should_keep_action_private ' do
867
+ assert klass.private_method_defined?(:save)
868
+ end
869
+
870
+ it 'should_mark_action_hook_as_defined ' do
871
+ assert machine.action_hook?
872
+ end
873
+ end
874
+
875
+ context 'WithActionAlreadyOverridden' do
876
+
877
+ before(:each) do
878
+ @superclass = Class.new do
879
+ def save
880
+ end
881
+ end
882
+ @klass = Class.new(@superclass)
883
+
884
+ StateMachines::Machine.new(@klass, :action => :save)
885
+ @machine = StateMachines::Machine.new(@klass, :status, :action => :save)
886
+ @object = @klass.new
887
+ end
888
+
889
+ it 'should_not_redefine_action ' do
890
+ assert_equal 1, @klass.ancestors.select { |ancestor| ![@klass, @superclass].include?(ancestor) && ancestor.method_defined?(:save) }.length
891
+ end
892
+
893
+ it 'should_mark_action_hook_as_defined ' do
894
+ assert @machine.action_hook?
895
+ end
896
+ end
897
+
898
+ context 'WithCustomPlural' do
899
+
900
+ before(:each) do
901
+ @integration = Module.new do
902
+ include StateMachines::Integrations::Base
903
+
904
+ class << self; attr_accessor :with_scopes, :without_scopes; end
905
+ @with_scopes = []
906
+ @without_scopes = []
907
+
908
+ def create_with_scope(name)
909
+ StateMachines::Integrations::Custom.with_scopes << name
910
+ lambda {}
911
+ end
912
+
913
+ def create_without_scope(name)
914
+ StateMachines::Integrations::Custom.without_scopes << name
915
+ lambda {}
916
+ end
917
+ end
918
+
919
+ StateMachines::Integrations.const_set('Custom', @integration)
920
+ end
921
+
922
+
923
+ it 'should_define_a_singular_and_plural_with_scope' do
924
+ StateMachines::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
925
+ assert_equal %w(with_state with_staties), @integration.with_scopes
926
+ end
927
+
928
+ it 'should_define_a_singular_and_plural_without_scope' do
929
+ StateMachines::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
930
+ assert_equal %w(without_state without_staties), @integration.without_scopes
931
+ end
932
+
933
+ it 'should_define_single_with_scope_if_singular_same_as_plural' do
934
+ StateMachines::Machine.new(Class.new, :integration => :custom, :plural => 'state')
935
+ assert_equal %w(with_state), @integration.with_scopes
936
+ end
937
+
938
+ it 'should_define_single_without_scope_if_singular_same_as_plural' do
939
+ StateMachines::Machine.new(Class.new, :integration => :custom, :plural => 'state')
940
+ assert_equal %w(without_state), @integration.without_scopes
941
+ end
942
+
943
+ after(:each) do
944
+ StateMachines::Integrations.send(:remove_const, 'Custom')
945
+ end
946
+ end
947
+
948
+ context 'WithCustomInvalidation' do
949
+ let!(:integration) do
950
+ integration = Module.new do
951
+ include StateMachines::Integrations::Base
952
+
953
+ def invalidate(object, attribute, message, values = [])
954
+ object.error = generate_message(message, values)
955
+ end
956
+ end
957
+ StateMachines::Integrations.const_set('Custom', integration)
958
+ integration
959
+ end
960
+
961
+ let!(:klass) do
962
+ Class.new do
963
+ attr_accessor :error
964
+ end
965
+ end
966
+
967
+ let!(:machine) do
968
+ machine = StateMachines::Machine.new(klass, integration: :custom, messages: {invalid_transition: 'cannot %s'})
969
+ machine.state :parked
970
+ machine
971
+ end
972
+
973
+ before(:each) do
974
+
975
+ object.state = 'parked'
976
+ end
977
+
978
+ it ' generate_custom_message ' do
979
+ assert_equal 'cannot park', machine.generate_message(:invalid_transition, [[:event, :park]])
980
+ end
981
+
982
+ it ' use_custom_message ' do
983
+ machine.invalidate(object, :state, :invalid_transition, [[:event, 'park']])
984
+ assert_equal 'cannot park', object.error
985
+ end
986
+
987
+ after(:each) do
988
+ StateMachines::Integrations.send(:remove_const, 'Custom')
989
+ end
990
+ end
991
+
992
+ context 'AfterBeingCopied' do
993
+ before(:each) do
994
+ @machine = StateMachines::Machine.new(Class.new, :state, initial: :parked)
995
+ @machine.event(:ignite) {}
996
+ @machine.before_transition(lambda {})
997
+ @machine.after_transition(lambda {})
998
+ @machine.around_transition(lambda {})
999
+ @machine.after_failure(lambda {})
1000
+ @copied_machine = @machine.clone
1001
+ end
1002
+
1003
+ it 'should_not_have_the_same_collection_of_states ' do
1004
+ assert_not_same @copied_machine.states, @machine.states
1005
+ end
1006
+
1007
+ it 'should_copy_each_state ' do
1008
+ assert_not_same @copied_machine.states[:parked], @machine.states[:parked]
1009
+ end
1010
+
1011
+ it 'should_update_machine_for_each_state ' do
1012
+ assert_equal @copied_machine, @copied_machine.states[:parked].machine
1013
+ end
1014
+
1015
+ it 'should_not_update_machine_for_original_state ' do
1016
+ assert_equal @machine, @machine.states[:parked].machine
1017
+ end
1018
+
1019
+ it 'should_not_have_the_same_collection_of_events ' do
1020
+ assert_not_same @copied_machine.events, @machine.events
1021
+ end
1022
+
1023
+ it 'should_copy_each_event ' do
1024
+ assert_not_same @copied_machine.events[:ignite], @machine.events[:ignite]
1025
+ end
1026
+
1027
+ it 'should_update_machine_for_each_event ' do
1028
+ assert_equal @copied_machine, @copied_machine.events[:ignite].machine
1029
+ end
1030
+
1031
+ it 'should_not_update_machine_for_original_event ' do
1032
+ assert_equal @machine, @machine.events[:ignite].machine
1033
+ end
1034
+
1035
+ it 'should_not_have_the_same_callbacks ' do
1036
+ assert_not_same @copied_machine.callbacks, @machine.callbacks
1037
+ end
1038
+
1039
+ it 'should_not_have_the_same_before_callbacks ' do
1040
+ assert_not_same @copied_machine.callbacks[:before], @machine.callbacks[:before]
1041
+ end
1042
+
1043
+ it 'should_not_have_the_same_after_callbacks ' do
1044
+ assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
1045
+ end
1046
+
1047
+ it 'should_not_have_the_same_failure_callbacks ' do
1048
+ assert_not_same @copied_machine.callbacks[:failure], @machine.callbacks[:failure]
1049
+ end
1050
+ end
1051
+
1052
+ context 'AfterChangingOwnerClass' do
1053
+ let(:original_class) { Class.new }
1054
+ let(:machine) { StateMachines::Machine.new(original_class) }
1055
+ let(:new_class) { Class.new(original_class) }
1056
+ let(:new_machine) do
1057
+ new_machine = machine.clone
1058
+ new_machine.owner_class = new_class
1059
+ new_machine
1060
+ end
1061
+ let(:object) { new_class.new }
1062
+
1063
+ it 'should_update_owner_class ' do
1064
+ assert_equal new_class, new_machine.owner_class
1065
+ end
1066
+
1067
+ it 'should_not_change_original_owner_class ' do
1068
+ assert_equal original_class, machine.owner_class
1069
+ end
1070
+
1071
+ it 'should_change_the_associated_machine_in_the_new_class ' do
1072
+ assert_equal new_machine, new_class.state_machines[:state]
1073
+ end
1074
+
1075
+ it 'should_not_change_the_associated_machine_in_the_original_class ' do
1076
+ assert_equal machine, original_class.state_machines[:state]
1077
+ end
1078
+ end
1079
+
1080
+ context 'AfterChangingInitialState' do
1081
+ let(:machine) do
1082
+ machine = StateMachines::Machine.new(klass, initial: :parked)
1083
+ machine.initial_state = :idling
1084
+ machine
1085
+ end
1086
+
1087
+ it 'should_change_the_initial_state' do
1088
+ assert_equal :idling, machine.initial_state(object).name
1089
+ end
1090
+
1091
+ it 'should_include_in_known_states' do
1092
+ assert_equal [:parked, :idling], machine.states.map { |state| state.name }
1093
+ end
1094
+
1095
+ it 'should_reset_original_initial_state' do
1096
+ assert !machine.state(:parked).initial
1097
+ end
1098
+
1099
+ it 'should_set_new_state_to_initial' do
1100
+ assert machine.state(:idling).initial
1101
+ end
1102
+ end
1103
+
1104
+ context 'WithHelpers' do
1105
+ it 'should_throw_exception_with_invalid_scope' do
1106
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) { machine.define_helper(:invalid, :park) {} }
1107
+ end
1108
+ end
1109
+
1110
+ context 'WithInstanceHelpers' do
1111
+ before(:each) do
1112
+ @original_stderr, $stderr = $stderr, StringIO.new
1113
+
1114
+ @klass = Class.new
1115
+ @machine = StateMachines::Machine.new(@klass)
1116
+ @object = @klass.new
1117
+ end
1118
+
1119
+ it 'should_not_redefine_existing_public_methods' do
1120
+ @klass.class_eval do
1121
+ def park
1122
+ true
1123
+ end
1124
+ end
1125
+
1126
+ @machine.define_helper(:instance, :park) {}
1127
+ assert_equal true, @object.park
1128
+ end
1129
+
1130
+ it 'should_not_redefine_existing_protected_methods' do
1131
+ @klass.class_eval do
1132
+ protected
1133
+ def park
1134
+ true
1135
+ end
1136
+ end
1137
+
1138
+ @machine.define_helper(:instance, :park) {}
1139
+ assert_equal true, @object.send(:park)
1140
+ end
1141
+
1142
+ it 'should_not_redefine_existing_private_methods' do
1143
+ @klass.class_eval do
1144
+ private
1145
+ def park
1146
+ true
1147
+ end
1148
+ end
1149
+
1150
+ @machine.define_helper(:instance, :park) {}
1151
+ assert_equal true, @object.send(:park)
1152
+ end
1153
+
1154
+ it 'should_warn_if_defined_in_superclass' do
1155
+ superclass = Class.new do
1156
+ def park
1157
+ end
1158
+ end
1159
+ @klass = Class.new(superclass)
1160
+ @machine = StateMachines::Machine.new(@klass)
1161
+
1162
+ @machine.define_helper(:instance, :park) {}
1163
+ assert_equal "Instance method \"park\" is already defined in #{superclass}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1164
+ end
1165
+
1166
+ it 'should_warn_if_defined_in_multiple_superclasses' do
1167
+ superclass1 = Class.new do
1168
+ def park
1169
+ end
1170
+ end
1171
+ superclass2 = Class.new(superclass1) do
1172
+ def park
1173
+ end
1174
+ end
1175
+ @klass = Class.new(superclass2)
1176
+ @machine = StateMachines::Machine.new(@klass)
1177
+
1178
+ @machine.define_helper(:instance, :park) {}
1179
+ assert_equal "Instance method \"park\" is already defined in #{superclass1}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1180
+ end
1181
+
1182
+ it 'should_warn_if_defined_in_module_prior_to_helper_module' do
1183
+ mod = Module.new do
1184
+ def park
1185
+ end
1186
+ end
1187
+ @klass = Class.new do
1188
+ include mod
1189
+ end
1190
+ @machine = StateMachines::Machine.new(@klass)
1191
+
1192
+ @machine.define_helper(:instance, :park) {}
1193
+ assert_equal "Instance method \"park\" is already defined in #{mod}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1194
+ end
1195
+
1196
+ it 'should_not_warn_if_defined_in_module_after_helper_module' do
1197
+ klass = Class.new
1198
+ StateMachines::Machine.new(klass)
1199
+
1200
+ mod = Module.new do
1201
+ def park
1202
+ end
1203
+ end
1204
+ @klass.class_eval do
1205
+ extend mod
1206
+ end
1207
+
1208
+ @machine.define_helper(:instance, :park) {}
1209
+ assert_equal '', $stderr.string
1210
+ end
1211
+
1212
+ it 'should_define_if_ignoring_method_conflicts_and_defined_in_superclass' do
1213
+
1214
+ StateMachines::Machine.ignore_method_conflicts = true
1215
+
1216
+ superclass = Class.new do
1217
+ def park
1218
+ end
1219
+ end
1220
+ @klass = Class.new(superclass)
1221
+ @machine = StateMachines::Machine.new(@klass)
1222
+
1223
+ @machine.define_helper(:instance, :park) { true }
1224
+ assert_equal '', $stderr.string
1225
+ assert_equal true, @klass.new.park
1226
+
1227
+ end
1228
+
1229
+ it 'should_define_nonexistent_methods' do
1230
+ @machine.define_helper(:instance, :park) { false }
1231
+ assert_equal false, @object.park
1232
+ end
1233
+
1234
+ it 'should_warn_if_defined_multiple_times' do
1235
+ @machine.define_helper(:instance, :park) {}
1236
+ @machine.define_helper(:instance, :park) {}
1237
+
1238
+ assert_equal "Instance method \"park\" is already defined in #{@klass} :state instance helpers, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1239
+
1240
+ end
1241
+
1242
+ it 'should_pass_context_as_arguments' do
1243
+ helper_args = nil
1244
+ @machine.define_helper(:instance, :park) { |*args| helper_args = args }
1245
+ @object.park
1246
+ assert_equal 2, helper_args.length
1247
+ assert_equal [@machine, @object], helper_args
1248
+ end
1249
+
1250
+ it 'should_pass_method_arguments_through' do
1251
+ helper_args = nil
1252
+ @machine.define_helper(:instance, :park) { |*args| helper_args = args }
1253
+ @object.park(1, 2, 3)
1254
+ assert_equal 5, helper_args.length
1255
+ assert_equal [@machine, @object, 1, 2, 3], helper_args
1256
+ end
1257
+
1258
+ it 'should_allow_string_evaluation' do
1259
+ @machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1260
+ def park
1261
+ false
1262
+ end
1263
+ end_eval
1264
+ assert_equal false, @object.park
1265
+ end
1266
+
1267
+
1268
+ after(:each) do
1269
+ StateMachines::Machine.ignore_method_conflicts = false
1270
+ $stderr = @original_stderr
1271
+ end
1272
+ end
1273
+
1274
+ context 'WithClassHelpers' do
1275
+ before(:each) do
1276
+ @original_stderr, $stderr = $stderr, StringIO.new
1277
+ end
1278
+
1279
+ it 'should_not_redefine_existing_public_methods' do
1280
+ class << klass
1281
+ def states
1282
+ []
1283
+ end
1284
+ end
1285
+
1286
+ machine.define_helper(:class, :states) {}
1287
+ assert_equal [], klass.states
1288
+ end
1289
+
1290
+ it 'should_not_redefine_existing_protected_methods' do
1291
+ class << klass
1292
+ protected
1293
+ def states
1294
+ []
1295
+ end
1296
+ end
1297
+
1298
+ machine.define_helper(:class, :states) {}
1299
+ assert_equal [], klass.send(:states)
1300
+ end
1301
+
1302
+ it 'should_not_redefine_existing_private_methods' do
1303
+ class << klass
1304
+ private
1305
+ def states
1306
+ []
1307
+ end
1308
+ end
1309
+
1310
+ machine.define_helper(:class, :states) {}
1311
+ assert_equal [], klass.send(:states)
1312
+ end
1313
+
1314
+ it 'should_warn_if_defined_in_superclass' do
1315
+
1316
+
1317
+
1318
+ superclass = Class.new do
1319
+ def self.park
1320
+ end
1321
+ end
1322
+ klass = Class.new(superclass)
1323
+ machine = StateMachines::Machine.new(klass)
1324
+
1325
+ machine.define_helper(:class, :park) {}
1326
+ assert_equal "Class method \"park\" is already defined in #{superclass}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1327
+ # ensure
1328
+ $stderr = @original_stderr
1329
+ end
1330
+
1331
+ it 'should_warn_if_defined_in_multiple_superclasses' do
1332
+
1333
+ superclass1 = Class.new do
1334
+ def self.park
1335
+ end
1336
+ end
1337
+ superclass2 = Class.new(superclass1) do
1338
+ def self.park
1339
+ end
1340
+ end
1341
+ klass = Class.new(superclass2)
1342
+ machine = StateMachines::Machine.new(klass)
1343
+
1344
+ machine.define_helper(:class, :park) {}
1345
+ assert_equal "Class method \"park\" is already defined in #{superclass1}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1346
+
1347
+ end
1348
+
1349
+ it 'should_warn_if_defined_in_module_prior_to_helper_module' do
1350
+
1351
+
1352
+
1353
+ mod = Module.new do
1354
+ def park
1355
+ end
1356
+ end
1357
+ klass = Class.new do
1358
+ extend mod
1359
+ end
1360
+ machine = StateMachines::Machine.new(klass)
1361
+
1362
+ machine.define_helper(:class, :park) {}
1363
+ assert_equal "Class method \"park\" is already defined in #{mod}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1364
+ # ensure
1365
+
1366
+ end
1367
+
1368
+
1369
+ it 'should_not_warn_if_defined_in_module_after_helper_module' do
1370
+
1371
+
1372
+
1373
+
1374
+ machine = StateMachines::Machine.new(klass)
1375
+
1376
+ mod = Module.new do
1377
+ def park
1378
+ end
1379
+ end
1380
+ klass.class_eval do
1381
+ extend mod
1382
+ end
1383
+
1384
+ machine.define_helper(:class, :park) {}
1385
+ assert_equal '', $stderr.string
1386
+ # ensure
1387
+ $stderr = @original_stderr
1388
+ end
1389
+
1390
+ it 'should_define_if_ignoring_method_conflicts_and_defined_in_superclass' do
1391
+
1392
+
1393
+ StateMachines::Machine.ignore_method_conflicts = true
1394
+
1395
+ superclass = Class.new do
1396
+ def self.park
1397
+ end
1398
+ end
1399
+ klass = Class.new(superclass)
1400
+ machine = StateMachines::Machine.new(klass)
1401
+
1402
+ machine.define_helper(:class, :park) { true }
1403
+ assert_equal '', $stderr.string
1404
+ assert_equal true, klass.park
1405
+ # ensure
1406
+ StateMachines::Machine.ignore_method_conflicts = false
1407
+ $stderr = @original_stderr
1408
+ end
1409
+
1410
+ it 'should_define_nonexistent_methods' do
1411
+ machine.define_helper(:class, :states) { [] }
1412
+ assert_equal [], klass.states
1413
+ end
1414
+
1415
+ it 'should_warn_if_defined_multiple_times' do
1416
+
1417
+
1418
+
1419
+ machine.define_helper(:class, :states) {}
1420
+ machine.define_helper(:class, :states) {}
1421
+
1422
+ assert_equal "Class method \"states\" is already defined in #{klass} :state class helpers, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n", $stderr.string
1423
+ # ensure
1424
+ $stderr = @original_stderr
1425
+ end
1426
+
1427
+ it 'should_pass_context_as_arguments' do
1428
+ helper_args = nil
1429
+ machine.define_helper(:class, :states) { |*args| helper_args = args }
1430
+ klass.states
1431
+ assert_equal 2, helper_args.length
1432
+ assert_equal [machine, klass], helper_args
1433
+ end
1434
+
1435
+ it 'should_pass_method_arguments_through' do
1436
+ helper_args = nil
1437
+ machine.define_helper(:class, :states) { |*args| helper_args = args }
1438
+ klass.states(1, 2, 3)
1439
+ assert_equal 5, helper_args.length
1440
+ assert_equal [machine, klass, 1, 2, 3], helper_args
1441
+ end
1442
+
1443
+ it 'should_allow_string_evaluation' do
1444
+ machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
1445
+ def states
1446
+ []
1447
+ end
1448
+ end_eval
1449
+ assert_equal [], klass.states
1450
+ end
1451
+
1452
+ after(:each) do
1453
+ $stderr = @original_stderr
1454
+ end
1455
+ end
1456
+
1457
+ context 'WithConflictingHelpersBeforeDefinition' do
1458
+
1459
+ before(:each) do
1460
+ @original_stderr, $stderr = $stderr, StringIO.new
1461
+
1462
+ @superclass = Class.new do
1463
+ def self.with_state
1464
+ :with_state
1465
+ end
1466
+
1467
+ def self.with_states
1468
+ :with_states
1469
+ end
1470
+
1471
+ def self.without_state
1472
+ :without_state
1473
+ end
1474
+
1475
+ def self.without_states
1476
+ :without_states
1477
+ end
1478
+
1479
+ def self.human_state_name
1480
+ :human_state_name
1481
+ end
1482
+
1483
+ def self.human_state_event_name
1484
+ :human_state_event_name
1485
+ end
1486
+
1487
+ attr_accessor :status
1488
+
1489
+ def state
1490
+ 'parked'
1491
+ end
1492
+
1493
+ def state=(value)
1494
+ self.status = value
1495
+ end
1496
+
1497
+ def state?
1498
+ true
1499
+ end
1500
+
1501
+ def state_name
1502
+ :parked
1503
+ end
1504
+
1505
+ def human_state_name
1506
+ 'parked'
1507
+ end
1508
+
1509
+ def state_events
1510
+ [:ignite]
1511
+ end
1512
+
1513
+ def state_transitions
1514
+ [{:parked => :idling}]
1515
+ end
1516
+
1517
+ def state_paths
1518
+ [[{:parked => :idling}]]
1519
+ end
1520
+
1521
+ def fire_state_event
1522
+ true
1523
+ end
1524
+ end
1525
+ @klass = Class.new(@superclass)
1526
+
1527
+ StateMachines::Integrations.const_set('Custom', Module.new do
1528
+ include StateMachines::Integrations::Base
1529
+
1530
+ def create_with_scope(name)
1531
+ lambda {|klass, values| []}
1532
+ end
1533
+
1534
+ def create_without_scope(name)
1535
+ lambda {|klass, values| []}
1536
+ end
1537
+ end)
1538
+
1539
+ @machine = StateMachines::Machine.new(@klass, :integration => :custom)
1540
+ @machine.state :parked, :idling
1541
+ @machine.event :ignite
1542
+ @object = @klass.new
1543
+ end
1544
+
1545
+ it 'should_not_redefine_singular_with_scope' do
1546
+ assert_equal :with_state, @klass.with_state
1547
+ end
1548
+
1549
+ it 'should_not_redefine_plural_with_scope' do
1550
+ assert_equal :with_states, @klass.with_states
1551
+ end
1552
+
1553
+ it 'should_not_redefine_singular_without_scope' do
1554
+ assert_equal :without_state, @klass.without_state
1555
+ end
1556
+
1557
+ it 'should_not_redefine_plural_without_scope' do
1558
+ assert_equal :without_states, @klass.without_states
1559
+ end
1560
+
1561
+ it 'should_not_redefine_human_attribute_name_reader' do
1562
+ assert_equal :human_state_name, @klass.human_state_name
1563
+ end
1564
+
1565
+ it 'should_not_redefine_human_event_name_reader' do
1566
+ assert_equal :human_state_event_name, @klass.human_state_event_name
1567
+ end
1568
+
1569
+ it 'should_not_redefine_attribute_reader' do
1570
+ assert_equal 'parked', @object.state
1571
+ end
1572
+
1573
+ it 'should_not_redefine_attribute_writer' do
1574
+ @object.state = 'parked'
1575
+ assert_equal 'parked', @object.status
1576
+ end
1577
+
1578
+ it 'should_not_define_attribute_predicate' do
1579
+ assert @object.state?
1580
+ end
1581
+
1582
+ it 'should_not_redefine_attribute_name_reader' do
1583
+ assert_equal :parked, @object.state_name
1584
+ end
1585
+
1586
+ it 'should_not_redefine_attribute_human_name_reader' do
1587
+ assert_equal 'parked', @object.human_state_name
1588
+ end
1589
+
1590
+ it 'should_not_redefine_attribute_events_reader' do
1591
+ assert_equal [:ignite], @object.state_events
1592
+ end
1593
+
1594
+ it 'should_not_redefine_attribute_transitions_reader' do
1595
+ assert_equal [{:parked => :idling}], @object.state_transitions
1596
+ end
1597
+
1598
+ it 'should_not_redefine_attribute_paths_reader' do
1599
+ assert_equal [[{:parked => :idling}]], @object.state_paths
1600
+ end
1601
+
1602
+ it 'should_not_redefine_event_runner' do
1603
+ assert_equal true, @object.fire_state_event
1604
+ end
1605
+
1606
+ it 'should_output_warning' do
1607
+ expected = [
1608
+ 'Instance method "state_events"',
1609
+ 'Instance method "state_transitions"',
1610
+ 'Instance method "fire_state_event"',
1611
+ 'Instance method "state_paths"',
1612
+ 'Class method "human_state_name"',
1613
+ 'Class method "human_state_event_name"',
1614
+ 'Instance method "state_name"',
1615
+ 'Instance method "human_state_name"',
1616
+ 'Class method "with_state"',
1617
+ 'Class method "with_states"',
1618
+ 'Class method "without_state"',
1619
+ 'Class method "without_states"'
1620
+ ].map {|method| "#{method} is already defined in #{@superclass.to_s}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.\n"}.join
1621
+
1622
+ assert_equal expected, $stderr.string
1623
+ end
1624
+
1625
+ after(:each) do
1626
+ $stderr = @original_stderr
1627
+ StateMachines::Integrations.send(:remove_const, 'Custom')
1628
+ end
1629
+
1630
+
1631
+
1632
+ end
1633
+
1634
+ context 'WithConflictingHelpersAfterDefinition' do
1635
+ let(:klass) do
1636
+ Class.new do
1637
+ def self.with_state
1638
+ :with_state
1639
+ end
1640
+
1641
+ def self.with_states
1642
+ :with_states
1643
+ end
1644
+
1645
+ def self.without_state
1646
+ :without_state
1647
+ end
1648
+
1649
+ def self.without_states
1650
+ :without_states
1651
+ end
1652
+
1653
+ def self.human_state_name
1654
+ :human_state_name
1655
+ end
1656
+
1657
+ def self.human_state_event_name
1658
+ :human_state_event_name
1659
+ end
1660
+
1661
+ attr_accessor :status
1662
+
1663
+ def state
1664
+ 'parked'
1665
+ end
1666
+
1667
+ def state=(value)
1668
+ self.status = value
1669
+ end
1670
+
1671
+ def state?
1672
+ true
1673
+ end
1674
+
1675
+ def state_name
1676
+ :parked
1677
+ end
1678
+
1679
+ def human_state_name
1680
+ 'parked'
1681
+ end
1682
+
1683
+ def state_events
1684
+ [:ignite]
1685
+ end
1686
+
1687
+ def state_transitions
1688
+ [{parked: :idling}]
1689
+ end
1690
+
1691
+ def state_paths
1692
+ [[{parked: :idling}]]
1693
+ end
1694
+
1695
+ def fire_state_event
1696
+ true
1697
+ end
1698
+ end
1699
+ end
1700
+
1701
+ before(:each) do
1702
+
1703
+ @original_stderr, $stderr = $stderr, StringIO.new
1704
+
1705
+ StateMachines::Integrations.const_set('Custom', Module.new do
1706
+ include StateMachines::Integrations::Base
1707
+
1708
+ def create_with_scope(name)
1709
+ lambda { |klass, values| [] }
1710
+ end
1711
+
1712
+ def create_without_scope(name)
1713
+ lambda { |klass, values| [] }
1714
+ end
1715
+ end)
1716
+
1717
+ end
1718
+
1719
+ after(:each) do
1720
+ $stderr = @original_stderr
1721
+ end
1722
+ let!(:machine) do
1723
+ machine = StateMachines::Machine.new(klass, integration: :custom)
1724
+ machine.state :parked, :idling
1725
+ machine.event :ignite
1726
+ machine
1727
+ end
1728
+
1729
+ it 'should_not_redefine_singular_with_scope' do
1730
+ assert_equal :with_state, klass.with_state
1731
+ end
1732
+
1733
+ it 'should_not_redefine_plural_with_scope' do
1734
+ assert_equal :with_states, klass.with_states
1735
+ end
1736
+
1737
+ it 'should_not_redefine_singular_without_scope' do
1738
+ assert_equal :without_state, klass.without_state
1739
+ end
1740
+
1741
+ it 'should_not_redefine_plural_without_scope' do
1742
+ assert_equal :without_states, klass.without_states
1743
+ end
1744
+
1745
+ it 'should_not_redefine_human_attribute_name_reader' do
1746
+ assert_equal :human_state_name, klass.human_state_name
1747
+ end
1748
+
1749
+ it 'should_not_redefine_human_event_name_reader' do
1750
+ assert_equal :human_state_event_name, klass.human_state_event_name
1751
+ end
1752
+
1753
+ it 'should_not_redefine_attribute_reader' do
1754
+ assert_equal 'parked', object.state
1755
+ end
1756
+
1757
+ it 'should_not_redefine_attribute_writer' do
1758
+ object.state = 'parked'
1759
+ assert_equal 'parked', object.status
1760
+ end
1761
+
1762
+ it 'should_not_define_attribute_predicate' do
1763
+ assert object.state?
1764
+ end
1765
+
1766
+ it 'should_not_redefine_attribute_name_reader' do
1767
+ assert_equal :parked, object.state_name
1768
+ end
1769
+
1770
+ it 'should_not_redefine_attribute_human_name_reader' do
1771
+ assert_equal 'parked', object.human_state_name
1772
+ end
1773
+
1774
+ it 'should_not_redefine_attribute_events_reader' do
1775
+ assert_equal [:ignite], object.state_events
1776
+ end
1777
+
1778
+ it 'should_not_redefine_attribute_transitions_reader' do
1779
+ assert_equal [{parked: :idling}], object.state_transitions
1780
+ end
1781
+
1782
+ it 'should_not_redefine_attribute_paths_reader' do
1783
+ assert_equal [[{parked: :idling}]], object.state_paths
1784
+ end
1785
+
1786
+ it 'should_not_redefine_event_runner' do
1787
+ assert_equal true, object.fire_state_event
1788
+ end
1789
+
1790
+ it 'should_allow_super_chaining' do
1791
+ klass.class_eval do
1792
+ def self.with_state(*states)
1793
+ super
1794
+ end
1795
+
1796
+ def self.with_states(*states)
1797
+ super
1798
+ end
1799
+
1800
+ def self.without_state(*states)
1801
+ super
1802
+ end
1803
+
1804
+ def self.without_states(*states)
1805
+ super
1806
+ end
1807
+
1808
+ def self.human_state_name(state)
1809
+ super
1810
+ end
1811
+
1812
+ def self.human_state_event_name(event)
1813
+ super
1814
+ end
1815
+
1816
+ attr_accessor :status
1817
+
1818
+ def state
1819
+ super
1820
+ end
1821
+
1822
+ def state=(value)
1823
+ super
1824
+ end
1825
+
1826
+ def state?(state)
1827
+ super
1828
+ end
1829
+
1830
+ def state_name
1831
+ super
1832
+ end
1833
+
1834
+ def human_state_name
1835
+ super
1836
+ end
1837
+
1838
+ def state_events
1839
+ super
1840
+ end
1841
+
1842
+ def state_transitions
1843
+ super
1844
+ end
1845
+
1846
+ def state_paths
1847
+ super
1848
+ end
1849
+
1850
+ def fire_state_event(event)
1851
+ super
1852
+ end
1853
+ end
1854
+
1855
+ assert_equal [], klass.with_state
1856
+ assert_equal [], klass.with_states
1857
+ assert_equal [], klass.without_state
1858
+ assert_equal [], klass.without_states
1859
+ assert_equal 'parked', klass.human_state_name(:parked)
1860
+ assert_equal 'ignite', klass.human_state_event_name(:ignite)
1861
+
1862
+ assert_equal nil, object.state
1863
+ object.state = 'idling'
1864
+ assert_equal 'idling', object.state
1865
+ assert_equal nil, object.status
1866
+ assert_equal false, object.state?(:parked)
1867
+ assert_equal :idling, object.state_name
1868
+ assert_equal 'idling', object.human_state_name
1869
+ assert_equal [], object.state_events
1870
+ assert_equal [], object.state_transitions
1871
+ assert_equal [], object.state_paths
1872
+ assert_equal false, object.fire_state_event(:ignite)
1873
+ end
1874
+
1875
+ it 'should_not_output_warning' do
1876
+ assert_equal '', $stderr.string
1877
+ end
1878
+
1879
+ after(:each) do
1880
+ $stderr = @original_stderr
1881
+ StateMachines::Integrations.send(:remove_const, 'Custom')
1882
+ end
1883
+ end
1884
+
1885
+ context 'WithSuperclassConflictingHelpersAfterDefinition' do
1886
+ before(:each) do
1887
+ @original_stderr, $stderr = $stderr, StringIO.new
1888
+
1889
+ @superclass = Class.new
1890
+ @klass = Class.new(@superclass)
1891
+
1892
+ @machine = StateMachines::Machine.new(@klass)
1893
+ @machine.state :parked, :idling
1894
+ @machine.event :ignite
1895
+
1896
+ @superclass.class_eval do
1897
+ def state?
1898
+ true
1899
+ end
1900
+ end
1901
+
1902
+ @object = @klass.new
1903
+ end
1904
+
1905
+
1906
+
1907
+ it 'should_call_superclass_attribute_predicate_without_arguments' do
1908
+ expect(@object.state?).to be_truthy
1909
+ end
1910
+
1911
+ it 'should_define_attribute_predicate_with_arguments' do
1912
+ expect(@object.state?(:parked)).to be_falsy
1913
+ end
1914
+
1915
+ after(:each) do
1916
+ $stderr = @original_stderr
1917
+ end
1918
+ end
1919
+
1920
+ context 'WithoutInitialize' do
1921
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
1922
+
1923
+ it 'should_initialize_state' do
1924
+ assert_equal 'parked', object.state
1925
+ end
1926
+ end
1927
+
1928
+ context 'WithInitializeWithoutSuper' do
1929
+ let(:klass) do
1930
+ Class.new do
1931
+ def initialize
1932
+ end
1933
+ end
1934
+ end
1935
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
1936
+
1937
+
1938
+ it 'should_not_initialize_state' do
1939
+ assert_nil object.state
1940
+ end
1941
+ end
1942
+
1943
+ context 'WithInitializeAndSuper' do
1944
+ let(:klass) do
1945
+ Class.new do
1946
+ def initialize
1947
+ super
1948
+ end
1949
+ end
1950
+ end
1951
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
1952
+
1953
+
1954
+ it 'should_initialize_state' do
1955
+ assert_equal 'parked', object.state
1956
+ end
1957
+ end
1958
+
1959
+ context 'WithInitializeArgumentsAndBlock' do
1960
+ let(:superclass) do
1961
+ Class.new do
1962
+ attr_reader :args
1963
+ attr_reader :block_given
1964
+
1965
+ def initialize(*args)
1966
+ @args = args
1967
+ @block_given = block_given?
1968
+ end
1969
+ end
1970
+ end
1971
+
1972
+ let(:klass) { Class.new(superclass) }
1973
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
1974
+ let(:object) { klass.new(1, 2, 3) {} }
1975
+
1976
+ it 'should_initialize_state' do
1977
+ assert_equal 'parked', object.state
1978
+ end
1979
+
1980
+ it 'should_preserve_arguments' do
1981
+ assert_equal [1, 2, 3], object.args
1982
+ end
1983
+
1984
+ it 'should_preserve_block' do
1985
+ assert object.block_given
1986
+ end
1987
+ end
1988
+
1989
+ context 'WithCustomInitialize' do
1990
+ let(:klass) do
1991
+ Class.new do
1992
+ def initialize(state = nil, options = {})
1993
+ @state = state
1994
+ initialize_state_machines(options)
1995
+ end
1996
+ end
1997
+ end
1998
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
1999
+
2000
+
2001
+ it 'should_initialize_state' do
2002
+ assert_equal 'parked', object.state
2003
+ end
2004
+
2005
+ it 'should_allow_custom_options' do
2006
+ machine.state :idling
2007
+ object = klass.new('idling', static: :force)
2008
+ assert_equal 'parked', object.state
2009
+ end
2010
+ end
2011
+
2012
+ context 'Persistence' do
2013
+ let(:klass) do
2014
+ Class.new do
2015
+ attr_accessor :state_event
2016
+ end
2017
+ end
2018
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2019
+
2020
+
2021
+ it 'should_allow_reading_state' do
2022
+ assert_equal 'parked', machine.read(object, :state)
2023
+ end
2024
+
2025
+ it 'should_allow_reading_custom_attributes' do
2026
+ assert_nil machine.read(object, :event)
2027
+
2028
+ object.state_event = 'ignite'
2029
+ assert_equal 'ignite', machine.read(object, :event)
2030
+ end
2031
+
2032
+ it 'should_allow_reading_custom_instance_variables' do
2033
+ klass.class_eval do
2034
+ attr_writer :state_value
2035
+ end
2036
+
2037
+ object.state_value = 1
2038
+ assert_raise(NoMethodError) { machine.read(object, :value) }
2039
+ assert_equal 1, machine.read(object, :value, true)
2040
+ end
2041
+
2042
+ it 'should_allow_writing_state' do
2043
+ machine.write(object, :state, 'idling')
2044
+ assert_equal 'idling', object.state
2045
+ end
2046
+
2047
+ it 'should_allow_writing_custom_attributes' do
2048
+ machine.write(object, :event, 'ignite')
2049
+ assert_equal 'ignite', object.state_event
2050
+ end
2051
+
2052
+ it 'should_allow_writing_custom_instance_variables' do
2053
+ klass.class_eval do
2054
+ attr_reader :state_value
2055
+ end
2056
+
2057
+ assert_raise(NoMethodError) { machine.write(object, :value, 1) }
2058
+ assert_equal 1, machine.write(object, :value, 1, true)
2059
+ assert_equal 1, object.state_value
2060
+ end
2061
+ end
2062
+
2063
+ context 'WithStates' do
2064
+ before(:each) do
2065
+ @parked, @idling = machine.state :parked, :idling
2066
+ end
2067
+
2068
+ it 'should_have_states' do
2069
+ assert_equal [nil, :parked, :idling], machine.states.map { |state| state.name }
2070
+ end
2071
+
2072
+ it 'should_allow_state_lookup_by_name' do
2073
+ assert_equal @parked, machine.states[:parked]
2074
+ end
2075
+
2076
+ it 'should_allow_state_lookup_by_value' do
2077
+ assert_equal @parked, machine.states['parked', :value]
2078
+ end
2079
+
2080
+ it 'should_allow_human_state_name_lookup' do
2081
+ assert_equal 'parked', klass.human_state_name(:parked)
2082
+ end
2083
+
2084
+ it 'should_raise_exception_on_invalid_human_state_name_lookup' do
2085
+ assert_raise(IndexError) { klass.human_state_name(:invalid) }
2086
+ # FIXME
2087
+ #assert_equal ':invalid is an invalid name', exception.message
2088
+ end
2089
+
2090
+ it 'should_use_stringified_name_for_value' do
2091
+ assert_equal 'parked', @parked.value
2092
+ end
2093
+
2094
+ it 'should_not_use_custom_matcher' do
2095
+ assert_nil @parked.matcher
2096
+ end
2097
+
2098
+ it 'should_raise_exception_if_invalid_option_specified' do
2099
+ assert_raise(ArgumentError) { machine.state(:first_gear, invalid: true) }
2100
+ # FIXME
2101
+ #assert_equal 'Invalid key(s): invalid', exception.message
2102
+ end
2103
+
2104
+ it 'should_raise_exception_if_conflicting_type_used_for_name' do
2105
+ assert_raise(ArgumentError) { machine.state 'first_gear' }
2106
+ # FIXME
2107
+ #assert_equal '"first_gear" state defined as String, :parked defined as Symbol; all states must be consistent', exception.message
2108
+ end
2109
+
2110
+ it 'should_not_raise_exception_if_conflicting_type_is_nil_for_name' do
2111
+ assert_nothing_raised { machine.state nil }
2112
+ end
2113
+ end
2114
+
2115
+ context 'WithStatesWithCustomValues' do
2116
+ let(:machine) { StateMachines::Machine.new(klass) }
2117
+ let(:state) { machine.state :parked, value: 1 }
2118
+
2119
+ let(:object) do
2120
+ object = klass.new
2121
+ object.state = 1
2122
+ object
2123
+ end
2124
+
2125
+ it 'should_use_custom_value' do
2126
+ assert_equal 1, state.value
2127
+ end
2128
+
2129
+ it 'should_allow_lookup_by_custom_value' do
2130
+ assert_equal state, machine.states[1, :value]
2131
+ end
2132
+ end
2133
+
2134
+ context 'WithStatesWithCustomHumanNames' do
2135
+ let!(:machine) { StateMachines::Machine.new(klass) }
2136
+ let!(:state) { machine.state :parked, human_name: 'stopped' }
2137
+
2138
+
2139
+ it 'should_use_custom_human_name' do
2140
+ assert_equal 'stopped', state.human_name
2141
+ end
2142
+
2143
+ it 'should_allow_human_state_name_lookup' do
2144
+ assert_equal 'stopped', klass.human_state_name(:parked)
2145
+ end
2146
+ end
2147
+
2148
+ context 'WithStatesWithRuntimeDependencies' do
2149
+ before(:each) do
2150
+ machine.state :parked
2151
+ end
2152
+
2153
+ it 'should_not_evaluate_value_during_definition' do
2154
+ assert_nothing_raised { machine.state :parked, value: lambda { fail ArgumentError } }
2155
+ end
2156
+
2157
+ it 'should_not_evaluate_if_not_initial_state' do
2158
+ machine.state :parked, value: lambda { fail ArgumentError }
2159
+ assert_nothing_raised { klass.new }
2160
+ end
2161
+ end
2162
+
2163
+ context 'WithStateWithMatchers' do
2164
+ let!(:state) { machine.state :parked, if: lambda { |value| value } }
2165
+ before(:each) do
2166
+ object.state = 1
2167
+ end
2168
+
2169
+ it 'should_use_custom_matcher' do
2170
+ assert_not_nil state.matcher
2171
+ assert state.matches?(1)
2172
+ assert !state.matches?(nil)
2173
+ end
2174
+ end
2175
+
2176
+ context 'WithCachedState' do
2177
+
2178
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2179
+ let!(:state) { machine.state :parked, value: lambda { Object.new }, cache: true }
2180
+
2181
+
2182
+ it 'should_use_evaluated_value' do
2183
+ assert_instance_of Object, object.state
2184
+ end
2185
+
2186
+ it 'use_same_value_across_multiple_objects' do
2187
+ assert_equal object.state, klass.new.state
2188
+ end
2189
+ end
2190
+
2191
+ context 'WithStatesWithBehaviors' do
2192
+
2193
+ let(:machine) { StateMachines::Machine.new(klass) }
2194
+ before(:each) do
2195
+ @parked, @idling = machine.state :parked, :idling do
2196
+ def speed
2197
+ 0
2198
+ end
2199
+ end
2200
+ end
2201
+
2202
+ it 'should_define_behaviors_for_each_state' do
2203
+ assert_not_nil @parked.context_methods[:speed]
2204
+ assert_not_nil @idling.context_methods[:speed]
2205
+ end
2206
+
2207
+ it 'should_define_different_behaviors_for_each_state' do
2208
+ assert_not_equal @parked.context_methods[:speed], @idling.context_methods[:speed]
2209
+ end
2210
+ end
2211
+
2212
+ context 'WithExistingState' do
2213
+
2214
+ let!(:state) { machine.state :parked }
2215
+ let!(:same_state) { machine.state :parked, value: 1 }
2216
+
2217
+ it 'should_not_create_a_new_state' do
2218
+ assert_same state, same_state
2219
+ end
2220
+
2221
+ it 'should_update_attributes' do
2222
+ assert_equal 1, state.value
2223
+ end
2224
+
2225
+ it 'should_no_longer_be_able_to_look_up_state_by_original_value' do
2226
+ assert_nil machine.states['parked', :value]
2227
+ end
2228
+
2229
+ it 'should_be_able_to_look_up_state_by_new_value' do
2230
+ assert_equal state, machine.states[1, :value]
2231
+ end
2232
+ end
2233
+
2234
+ context 'WithStateMatchers' do
2235
+
2236
+
2237
+ it 'should_empty_array_for_all_matcher' do
2238
+ assert_equal [], machine.state(StateMachines::AllMatcher.instance)
2239
+ end
2240
+
2241
+ it 'should_return_referenced_states_for_blacklist_matcher' do
2242
+ assert_instance_of StateMachines::State, machine.state(StateMachines::BlacklistMatcher.new([:parked]))
2243
+ end
2244
+
2245
+ it 'should_not_allow_configurations' do
2246
+ assert_raise(ArgumentError) { machine.state(StateMachines::BlacklistMatcher.new([:parked]), human_name: 'Parked') }
2247
+ # FIXME
2248
+ #assert_equal 'Cannot configure states when using matchers (using {:human_name=>"Parked"})', exception.message
2249
+ end
2250
+
2251
+ it 'should_track_referenced_states' do
2252
+ machine.state(StateMachines::BlacklistMatcher.new([:parked]))
2253
+ assert_equal [nil, :parked], machine.states.map { |state| state.name }
2254
+ end
2255
+
2256
+ it 'should_eval_context_for_matching_states' do
2257
+ contexts_run = []
2258
+ machine.event(StateMachines::BlacklistMatcher.new([:parked])) { contexts_run << name }
2259
+
2260
+ machine.event :parked
2261
+ assert_equal [], contexts_run
2262
+
2263
+ machine.event :idling
2264
+ assert_equal [:idling], contexts_run
2265
+
2266
+ machine.event :first_gear, :second_gear
2267
+ assert_equal [:idling, :first_gear, :second_gear], contexts_run
2268
+ end
2269
+ end
2270
+
2271
+ context 'WithOtherStates' do
2272
+ let(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2273
+ before(:each) do
2274
+ @parked, @idling = machine.other_states(:parked, :idling)
2275
+ end
2276
+
2277
+ it 'should_include_other_states_in_known_states ' do
2278
+ assert_equal [@parked, @idling], machine.states.to_a
2279
+ end
2280
+
2281
+ it 'should_use_default_value ' do
2282
+ assert_equal 'idling', @idling.value
2283
+ end
2284
+
2285
+ it 'should_not_create_matcher ' do
2286
+ assert_nil @idling.matcher
2287
+ end
2288
+ end
2289
+
2290
+ context ' WithEvents ' do
2291
+
2292
+ it 'should_return_the_created_event ' do
2293
+ assert_instance_of StateMachines::Event, machine.event(:ignite)
2294
+ end
2295
+
2296
+ it 'should_create_event_with_given_name ' do
2297
+ event = machine.event(:ignite) {}
2298
+ assert_equal :ignite, event.name
2299
+ end
2300
+
2301
+ it 'should_evaluate_block_within_event_context ' do
2302
+ responded = false
2303
+ machine.event :ignite do
2304
+ responded = respond_to?(:transition)
2305
+ end
2306
+
2307
+ assert responded
2308
+ end
2309
+
2310
+ it 'should_be_aliased_as_on ' do
2311
+ event = machine.on(:ignite) {}
2312
+ assert_equal :ignite, event.name
2313
+ end
2314
+
2315
+ it 'should_have_events ' do
2316
+ event = machine.event(:ignite)
2317
+ assert_equal [event], machine.events.to_a
2318
+ end
2319
+
2320
+ it 'should_allow_human_state_name_lookup ' do
2321
+ machine.event(:ignite)
2322
+ assert_equal 'ignite', klass.human_state_event_name(:ignite)
2323
+ end
2324
+
2325
+ it 'should_raise_exception_on_invalid_human_state_event_name_lookup ' do
2326
+ machine
2327
+ assert_raise(IndexError) { klass.human_state_event_name(:invalid) }
2328
+ # FIXME
2329
+ #assert_equal ' : invalid is an invalid name ', exception.message
2330
+ end
2331
+
2332
+ it 'should_raise_exception_if_conflicting_type_used_for_name ' do
2333
+ machine.event :park
2334
+ assert_raise(ArgumentError) { machine.event 'ignite' }
2335
+ # FIXME
2336
+ #assert_equal ' "ignite" event defined as String, :park defined as Symbol; all events must be consistent ', exception.message
2337
+ end
2338
+ end
2339
+
2340
+ context ' WithExistingEvent ' do
2341
+ let!(:machine) { StateMachines::Machine.new(Class.new) }
2342
+ let!(:event) { machine.event(:ignite) }
2343
+ let!(:same_event) { machine.event(:ignite) }
2344
+
2345
+ it 'should_not_create_new_event ' do
2346
+ assert_same event, same_event
2347
+ end
2348
+
2349
+ it 'should_allow_accessing_event_without_block ' do
2350
+ assert_equal event, machine.event(:ignite)
2351
+ end
2352
+ end
2353
+
2354
+ context ' WithEventsWithCustomHumanNames ' do
2355
+ let!(:event) { machine.event(:ignite, human_name: 'start ') }
2356
+
2357
+ it 'should_use_custom_human_name ' do
2358
+ assert_equal 'start ', event.human_name
2359
+ end
2360
+
2361
+ it 'should_allow_human_state_name_lookup ' do
2362
+ assert_equal 'start ', klass.human_state_event_name(:ignite)
2363
+ end
2364
+ end
2365
+
2366
+ context ' WithEventMatchers ' do
2367
+
2368
+
2369
+ it 'should_empty_array_for_all_matcher ' do
2370
+ assert_equal [], machine.event(StateMachines::AllMatcher.instance)
2371
+ end
2372
+
2373
+ it 'should_return_referenced_events_for_blacklist_matcher ' do
2374
+ assert_instance_of StateMachines::Event, machine.event(StateMachines::BlacklistMatcher.new([:park]))
2375
+ end
2376
+
2377
+ it 'should_not_allow_configurations ' do
2378
+ assert_raise(ArgumentError) { machine.event(StateMachines::BlacklistMatcher.new([:park]), human_name: ' Park ') }
2379
+ # FIXME
2380
+ #assert_equal ' Cannot configure events when using matchers (using { :human_name => "Park" }) ', exception.message
2381
+ end
2382
+
2383
+ it 'should_track_referenced_events ' do
2384
+ machine.event(StateMachines::BlacklistMatcher.new([:park]))
2385
+ assert_equal [:park], machine.events.map { |event| event.name }
2386
+ end
2387
+
2388
+ it 'should_eval_context_for_matching_events ' do
2389
+ contexts_run = []
2390
+ machine.event(StateMachines::BlacklistMatcher.new([:park])) { contexts_run << name }
2391
+
2392
+ machine.event :park
2393
+ assert_equal [], contexts_run
2394
+
2395
+ machine.event :ignite
2396
+ assert_equal [:ignite], contexts_run
2397
+
2398
+ machine.event :shift_up, :shift_down
2399
+ assert_equal [:ignite, :shift_up, :shift_down], contexts_run
2400
+ end
2401
+ end
2402
+
2403
+ context 'WithEventsWithTransitions' do
2404
+
2405
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2406
+ let!(:event) do
2407
+ machine.event(:ignite) do
2408
+ transition parked: :idling
2409
+ transition stalled: :idling
2410
+ end
2411
+ end
2412
+
2413
+ it 'should_have_events ' do
2414
+ assert_equal [event], machine.events.to_a
2415
+ end
2416
+
2417
+ it 'should_track_states_defined_in_event_transitions ' do
2418
+ assert_equal [:parked, :idling, :stalled], machine.states.map { |state| state.name }
2419
+ end
2420
+
2421
+ it 'should_not_duplicate_states_defined_in_multiple_event_transitions ' do
2422
+ machine.event :park do
2423
+ transition idling: :parked
2424
+ end
2425
+
2426
+ assert_equal [:parked, :idling, :stalled], machine.states.map { |state| state.name }
2427
+ end
2428
+
2429
+ it 'should_track_state_from_new_events ' do
2430
+ machine.event :shift_up do
2431
+ transition idling: :first_gear
2432
+ end
2433
+
2434
+ assert_equal [:parked, :idling, :stalled, :first_gear], machine.states.map { |state| state.name }
2435
+ end
2436
+ end
2437
+
2438
+ context ' WithMultipleEvents ' do
2439
+
2440
+ let(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2441
+ before(:each) do
2442
+ @park, @shift_down = machine.event(:park, :shift_down) do
2443
+ transition first_gear: :parked
2444
+ end
2445
+ end
2446
+
2447
+ it 'should_have_events ' do
2448
+ assert_equal [@park, @shift_down], machine.events.to_a
2449
+ end
2450
+
2451
+ it 'should_define_transitions_for_each_event ' do
2452
+ [@park, @shift_down].each { |event| assert_equal 1, event.branches.size }
2453
+ end
2454
+
2455
+ it 'should_transition_the_same_for_each_event ' do
2456
+ object = klass.new
2457
+ object.state = 'first_gear'
2458
+ object.park
2459
+ assert_equal 'parked', object.state
2460
+
2461
+ object = klass.new
2462
+ object.state = 'first_gear'
2463
+ object.shift_down
2464
+ assert_equal 'parked', object.state
2465
+ end
2466
+ end
2467
+
2468
+ context ' WithTransitions ' do
2469
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2470
+
2471
+ it 'should_require_on_event ' do
2472
+ assert_raise(ArgumentError) { machine.transition(parked: :idling) }
2473
+ # FIXME
2474
+ #assert_equal ' Must specify : on event ', exception.message
2475
+ end
2476
+
2477
+ it 'should_not_allow_except_on_option ' do
2478
+ assert_raise(ArgumentError) { machine.transition(except_on: :ignite, on: :ignite) }
2479
+ # FIXME
2480
+ #assert_equal ' Invalid key(s) : except_on ', exception.message
2481
+ end
2482
+
2483
+ it 'should_allow_transitioning_without_a_to_state ' do
2484
+ assert_nothing_raised { machine.transition(from: :parked, on: :ignite) }
2485
+ end
2486
+
2487
+ it 'should_allow_transitioning_without_a_from_state ' do
2488
+ assert_nothing_raised { machine.transition(to: :idling, on: :ignite) }
2489
+ end
2490
+
2491
+ it 'should_allow_except_from_option ' do
2492
+ assert_nothing_raised { machine.transition(except_from: :idling, on: :ignite) }
2493
+ end
2494
+
2495
+ it 'should_allow_except_to_option ' do
2496
+ assert_nothing_raised { machine.transition(except_to: :parked, on: :ignite) }
2497
+ end
2498
+
2499
+ it 'should_allow_implicit_options ' do
2500
+ branch = machine.transition(first_gear: :second_gear, on: :shift_up)
2501
+ assert_instance_of StateMachines::Branch, branch
2502
+
2503
+ state_requirements = branch.state_requirements
2504
+ assert_equal 1, state_requirements.length
2505
+
2506
+ assert_instance_of StateMachines::WhitelistMatcher, state_requirements[0][:from]
2507
+ assert_equal [:first_gear], state_requirements[0][:from].values
2508
+ assert_instance_of StateMachines::WhitelistMatcher, state_requirements[0][:to]
2509
+ assert_equal [:second_gear], state_requirements[0][:to].values
2510
+ assert_instance_of StateMachines::WhitelistMatcher, branch.event_requirement
2511
+ assert_equal [:shift_up], branch.event_requirement.values
2512
+ end
2513
+
2514
+ it 'should_allow_multiple_implicit_options ' do
2515
+ branch = machine.transition(first_gear: :second_gear, second_gear: :third_gear, on: :shift_up)
2516
+
2517
+ state_requirements = branch.state_requirements
2518
+ assert_equal 2, state_requirements.length
2519
+ end
2520
+
2521
+ it 'should_allow_verbose_options ' do
2522
+ branch = machine.transition(from: :parked, to: :idling, on: :ignite)
2523
+ assert_instance_of StateMachines::Branch, branch
2524
+ end
2525
+
2526
+ it 'should_include_all_transition_states_in_machine_states ' do
2527
+ machine.transition(parked: :idling, on: :ignite)
2528
+
2529
+ assert_equal [:parked, :idling], machine.states.map { |state| state.name }
2530
+ end
2531
+
2532
+ it 'should_include_all_transition_events_in_machine_events ' do
2533
+ machine.transition(parked: :idling, on: :ignite)
2534
+
2535
+ assert_equal [:ignite], machine.events.map { |event| event.name }
2536
+ end
2537
+
2538
+ it 'should_allow_multiple_events ' do
2539
+ branches = machine.transition(parked: :ignite, on: [:ignite, :shift_up])
2540
+
2541
+ assert_equal 2, branches.length
2542
+ assert_equal [:ignite, :shift_up], machine.events.map { |event| event.name }
2543
+ end
2544
+
2545
+ it 'should_not_modify_options ' do
2546
+ options = {parked: :idling, on: :ignite}
2547
+ machine.transition(options)
2548
+
2549
+ assert_equal options, parked: :idling, on: :ignite
2550
+ end
2551
+ end
2552
+
2553
+ context ' WithTransitionCallbacks ' do
2554
+
2555
+ let(:klass) do
2556
+ Class.new do
2557
+ attr_accessor :callbacks
2558
+ end
2559
+ end
2560
+
2561
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2562
+ let!(:event) do
2563
+ machine.event :ignite do
2564
+ transition parked: :idling
2565
+ end
2566
+ end
2567
+ before(:each) do
2568
+ object.callbacks = []
2569
+ end
2570
+
2571
+ it 'should_not_raise_exception_if_implicit_option_specified ' do
2572
+ assert_nothing_raised { machine.before_transition invalid: :valid, do: lambda {} }
2573
+ end
2574
+
2575
+ it 'should_raise_exception_if_method_not_specified ' do
2576
+ assert_raise(ArgumentError) { machine.before_transition to: :idling }
2577
+ # FIXME
2578
+ #assert_equal ' Method(s) for callback must be specified ', exception.message
2579
+ end
2580
+
2581
+ it 'should_invoke_callbacks_during_transition' do
2582
+ machine.before_transition lambda { |object| object.callbacks << 'before' }
2583
+ machine.after_transition lambda { |object| object.callbacks << 'after' }
2584
+ machine.around_transition lambda { |object, transition, block| object.callbacks << 'before_around'; block.call; object.callbacks << 'after_around' }
2585
+
2586
+ event.fire(object)
2587
+ assert_equal %w(before before_around after_around after), object.callbacks
2588
+ end
2589
+
2590
+ it 'should_allow_multiple_callbacks ' do
2591
+ machine.before_transition lambda { |object| object.callbacks << 'before1' }, lambda { |object| object.callbacks << 'before2' }
2592
+ machine.after_transition lambda { |object| object.callbacks << 'after1' }, lambda { |object| object.callbacks << 'after2' }
2593
+ machine.around_transition(
2594
+ lambda { |object, transition, block| object.callbacks << 'before_around1'; block.call; object.callbacks << 'after_around1' },
2595
+ lambda { |object, transition, block| object.callbacks << 'before_around2'; block.call; object.callbacks << 'after_around2' }
2596
+ )
2597
+
2598
+ event.fire(object)
2599
+ assert_equal %w(before1 before2 before_around1 before_around2 after_around2 after_around1 after1 after2), object.callbacks
2600
+ end
2601
+
2602
+ it 'should_allow_multiple_callbacks_with_requirements ' do
2603
+ machine.before_transition lambda { |object| object.callbacks << 'before_parked1' }, lambda { |object| object.callbacks << 'before_parked2' }, from: :parked
2604
+ machine.before_transition lambda { |object| object.callbacks << 'before_idling1' }, lambda { |object| object.callbacks << 'before_idling2' }, from: :idling
2605
+ machine.after_transition lambda { |object| object.callbacks << 'after_parked1' }, lambda { |object| object.callbacks << 'after_parked2' }, from: :parked
2606
+ machine.after_transition lambda { |object| object.callbacks << 'after_idling1' }, lambda { |object| object.callbacks << 'after_idling2' }, from: :idling
2607
+ machine.around_transition(
2608
+ lambda { |object, transition, block| object.callbacks << 'before_around_parked1'; block.call; object.callbacks << 'after_around_parked1' },
2609
+ lambda { |object, transition, block| object.callbacks << 'before_around_parked2'; block.call; object.callbacks << 'after_around_parked2' },
2610
+ from: :parked
2611
+ )
2612
+ machine.around_transition(
2613
+ lambda { |object, transition, block| object.callbacks << 'before_around_idling1'; block.call; object.callbacks << 'after_around_idling1' },
2614
+ lambda { |object, transition, block| object.callbacks << 'before_around_idling2'; block.call; object.callbacks << 'after_around_idling2' },
2615
+ from: :idling
2616
+ )
2617
+
2618
+ event.fire(object)
2619
+ 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
2620
+ end
2621
+
2622
+ it 'should_support_from_requirement ' do
2623
+ machine.before_transition from: :parked, do: lambda { |object| object.callbacks << :parked }
2624
+ machine.before_transition from: :idling, do: lambda { |object| object.callbacks << :idling }
2625
+
2626
+ event.fire(object)
2627
+ assert_equal [:parked], object.callbacks
2628
+ end
2629
+
2630
+ it 'should_support_except_from_requirement ' do
2631
+ machine.before_transition except_from: :parked, do: lambda { |object| object.callbacks << :parked }
2632
+ machine.before_transition except_from: :idling, do: lambda { |object| object.callbacks << :idling }
2633
+
2634
+ event.fire(object)
2635
+ assert_equal [:idling], object.callbacks
2636
+ end
2637
+
2638
+ it 'should_support_to_requirement ' do
2639
+ machine.before_transition to: :parked, do: lambda { |object| object.callbacks << :parked }
2640
+ machine.before_transition to: :idling, do: lambda { |object| object.callbacks << :idling }
2641
+
2642
+ event.fire(object)
2643
+ assert_equal [:idling], object.callbacks
2644
+ end
2645
+
2646
+ it 'should_support_except_to_requirement ' do
2647
+ machine.before_transition except_to: :parked, do: lambda { |object| object.callbacks << :parked }
2648
+ machine.before_transition except_to: :idling, do: lambda { |object| object.callbacks << :idling }
2649
+
2650
+ event.fire(object)
2651
+ assert_equal [:parked], object.callbacks
2652
+ end
2653
+
2654
+ it 'should_support_on_requirement ' do
2655
+ machine.before_transition on: :park, do: lambda { |object| object.callbacks << :park }
2656
+ machine.before_transition on: :ignite, do: lambda { |object| object.callbacks << :ignite }
2657
+
2658
+ event.fire(object)
2659
+ assert_equal [:ignite], object.callbacks
2660
+ end
2661
+
2662
+ it 'should_support_except_on_requirement ' do
2663
+ machine.before_transition except_on: :park, do: lambda { |object| object.callbacks << :park }
2664
+ machine.before_transition except_on: :ignite, do: lambda { |object| object.callbacks << :ignite }
2665
+
2666
+ event.fire(object)
2667
+ assert_equal [:park], object.callbacks
2668
+ end
2669
+
2670
+ it 'should_support_implicit_requirement ' do
2671
+ machine.before_transition parked: :idling, do: lambda { |object| object.callbacks << :parked }
2672
+ machine.before_transition idling: :parked, do: lambda { |object| object.callbacks << :idling }
2673
+
2674
+ event.fire(object)
2675
+ assert_equal [:parked], object.callbacks
2676
+ end
2677
+
2678
+ it 'should_track_states_defined_in_transition_callbacks ' do
2679
+ machine.before_transition parked: :idling, do: lambda {}
2680
+ machine.after_transition first_gear: :second_gear, do: lambda {}
2681
+ machine.around_transition third_gear: :fourth_gear, do: lambda {}
2682
+
2683
+ assert_equal [:parked, :idling, :first_gear, :second_gear, :third_gear, :fourth_gear], machine.states.map { |state| state.name }
2684
+ end
2685
+
2686
+ it 'should_not_duplicate_states_defined_in_multiple_event_transitions ' do
2687
+ machine.before_transition parked: :idling, do: lambda {}
2688
+ machine.after_transition first_gear: :second_gear, do: lambda {}
2689
+ machine.after_transition parked: :idling, do: lambda {}
2690
+ machine.around_transition parked: :idling, do: lambda {}
2691
+
2692
+ assert_equal [:parked, :idling, :first_gear, :second_gear], machine.states.map { |state| state.name }
2693
+ end
2694
+
2695
+ it 'should_define_predicates_for_each_state ' do
2696
+ [:parked?, :idling?].each { |predicate| assert object.respond_to?(predicate) }
2697
+ end
2698
+ end
2699
+
2700
+ context ' WithFailureCallbacks ' do
2701
+
2702
+ let!(:klass) do
2703
+ Class.new do
2704
+ attr_accessor :callbacks
2705
+ end
2706
+ end
2707
+
2708
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2709
+ let!(:event) { machine.event :ignite }
2710
+
2711
+ before(:each) do
2712
+ object.callbacks = []
2713
+ end
2714
+
2715
+ it 'should_raise_exception_if_implicit_option_specified ' do
2716
+ assert_raise(ArgumentError) { machine.after_failure invalid: :valid, do: lambda {} }
2717
+ # FIXME
2718
+ #assert_equal 'Invalid key(s) : invalid ', exception.message
2719
+ end
2720
+
2721
+ it 'should_raise_exception_if_method_not_specified ' do
2722
+ assert_raise(ArgumentError) { machine.after_failure on: :ignite }
2723
+ # FIXME
2724
+ #assert_equal 'Method(s) for callback must be specified ', exception.message
2725
+ end
2726
+
2727
+ it 'should_invoke_callbacks_during_failed_transition' do
2728
+ machine.after_failure lambda { |object| object.callbacks << 'failure' }
2729
+
2730
+ event.fire(object)
2731
+ assert_equal %w(failure), object.callbacks
2732
+ end
2733
+
2734
+ it 'should_allow_multiple_callbacks ' do
2735
+ machine.after_failure lambda { |object| object.callbacks << 'failure1' }, lambda { |object| object.callbacks << 'failure2' }
2736
+
2737
+ event.fire(object)
2738
+ assert_equal %w(failure1 failure2), object.callbacks
2739
+ end
2740
+
2741
+ it 'should_allow_multiple_callbacks_with_requirements ' do
2742
+ machine.after_failure lambda { |object| object.callbacks << 'failure_ignite1' }, lambda { |object| object.callbacks << 'failure_ignite2' }, on: :ignite
2743
+ machine.after_failure lambda { |object| object.callbacks << 'failure_park1' }, lambda { |object| object.callbacks << 'failure_park2' }, on: :park
2744
+
2745
+ event.fire(object)
2746
+ assert_equal %w(failure_ignite1 failure_ignite2), object.callbacks
2747
+ end
2748
+ end
2749
+
2750
+ context ' WithPaths ' do
2751
+ before(:each) do
2752
+ machine.event :ignite do
2753
+ transition parked: :idling
2754
+ end
2755
+ machine.event :shift_up do
2756
+ transition first_gear: :second_gear
2757
+ end
2758
+
2759
+ object.state = 'parked'
2760
+ end
2761
+
2762
+ it 'should_have_paths ' do
2763
+ assert_equal [[StateMachines::Transition.new(object, machine, :ignite, :parked, :idling)]], machine.paths_for(object)
2764
+ end
2765
+
2766
+ it 'should_allow_requirement_configuration ' do
2767
+ assert_equal [[StateMachines::Transition.new(object, machine, :shift_up, :first_gear, :second_gear)]], machine.paths_for(object, from: :first_gear)
2768
+ end
2769
+ end
2770
+
2771
+ context ' WithOwnerSubclass ' do
2772
+ before(:each) do
2773
+ machine
2774
+ end
2775
+ let(:subclass) { Class.new(klass) }
2776
+
2777
+ it 'should_have_a_different_collection_of_state_machines ' do
2778
+ assert_not_same klass.state_machines, subclass.state_machines
2779
+ end
2780
+
2781
+ it 'should_have_the_same_attribute_associated_state_machines ' do
2782
+ assert_equal klass.state_machines, subclass.state_machines
2783
+ end
2784
+ end
2785
+
2786
+ context ' WithExistingMachinesOnOwnerClass ' do
2787
+
2788
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2789
+ let!(:second_machine) { StateMachines::Machine.new(klass, :status, initial: :idling) }
2790
+
2791
+
2792
+ it 'should_track_each_state_machine ' do
2793
+ expected = {state: machine, status: second_machine}
2794
+ assert_equal expected, klass.state_machines
2795
+ end
2796
+
2797
+ it 'should_initialize_state_for_both_machines ' do
2798
+ assert_equal 'parked', object.state
2799
+ assert_equal 'idling', object.status
2800
+ end
2801
+ end
2802
+
2803
+ context ' WithExistingMachinesWithSameAttributesOnOwnerClass ' do
2804
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2805
+ let!(:second_machine) { StateMachines::Machine.new(klass, :public_state, initial: :idling, attribute: :state) }
2806
+ let!(:object) { klass.new }
2807
+
2808
+ it 'should_track_each_state_machine ' do
2809
+ expected = {state: machine, public_state: second_machine}
2810
+ assert_equal expected, klass.state_machines
2811
+ end
2812
+
2813
+ it 'should_write_to_state_only_once ' do
2814
+ klass.class_eval do
2815
+ attr_reader :write_count
2816
+
2817
+ def state=(value)
2818
+ @write_count ||= 0
2819
+ @write_count += 1
2820
+ end
2821
+ end
2822
+ object = klass.new
2823
+
2824
+ assert_equal 1, object.write_count
2825
+ end
2826
+
2827
+ it 'should_initialize_based_on_first_machine ' do
2828
+ assert_equal 'parked', object.state
2829
+ end
2830
+
2831
+ it 'should_not_allow_second_machine_to_initialize_state ' do
2832
+ object.state = nil
2833
+ second_machine.initialize_state(object)
2834
+ assert_nil object.state
2835
+ end
2836
+
2837
+ it 'should_allow_transitions_on_both_machines ' do
2838
+ machine.event :ignite do
2839
+ transition parked: :idling
2840
+ end
2841
+
2842
+ second_machine.event :park do
2843
+ transition idling: :parked
2844
+ end
2845
+
2846
+ object.ignite
2847
+ assert_equal 'idling', object.state
2848
+
2849
+ object.park
2850
+ assert_equal 'parked', object.state
2851
+ end
2852
+
2853
+ it 'should_copy_new_states_to_sibling_machines ' do
2854
+ first_gear= machine.state :first_gear
2855
+ assert_equal first_gear, second_machine.state(:first_gear)
2856
+
2857
+ second_gear = second_machine.state :second_gear
2858
+ assert_equal second_gear, machine.state(:second_gear)
2859
+ end
2860
+
2861
+ it 'should_copy_all_existing_states_to_new_machines ' do
2862
+ third_machine = StateMachines::Machine.new(klass, :protected_state, attribute: :state)
2863
+
2864
+ assert_equal machine.state(:parked), third_machine.state(:parked)
2865
+ assert_equal machine.state(:idling), third_machine.state(:idling)
2866
+ end
2867
+ end
2868
+
2869
+ context ' WithExistingMachinesWithSameAttributesOnOwnerSubclass ' do
2870
+ let!(:machine) { StateMachines::Machine.new(klass, initial: :parked) }
2871
+ let!(:second_machine) { StateMachines::Machine.new(klass, :public_state, initial: :idling, attribute: :state) }
2872
+ let(:subclass) { Class.new(klass) }
2873
+ let(:object) { subclass.new }
2874
+
2875
+ it 'should_not_copy_sibling_machines_to_subclass_after_initialization ' do
2876
+ subclass.state_machine(:state) {}
2877
+ assert_equal klass.state_machine(:public_state), subclass.state_machine(:public_state)
2878
+ end
2879
+
2880
+ it 'should_copy_sibling_machines_to_subclass_after_new_state ' do
2881
+ subclass_machine = subclass.state_machine(:state) {}
2882
+ subclass_machine.state :first_gear
2883
+ assert_not_equal klass.state_machine(:public_state), subclass.state_machine(:public_state)
2884
+ end
2885
+
2886
+ it 'should_copy_new_states_to_sibling_machines ' do
2887
+ subclass_machine = subclass.state_machine(:state) {}
2888
+ first_gear= subclass_machine.state :first_gear
2889
+
2890
+ second_subclass_machine = subclass.state_machine(:public_state)
2891
+ assert_equal first_gear, second_subclass_machine.state(:first_gear)
2892
+ end
2893
+ end
2894
+
2895
+ context ' WithNamespace ' do
2896
+ let!(:machine) do
2897
+ StateMachines::Machine.new(klass, namespace: 'alarm', initial: :active) do
2898
+ event :enable do
2899
+ transition off: :active
2900
+ end
2901
+
2902
+ event :disable do
2903
+ transition active: :off
2904
+ end
2905
+ end
2906
+ end
2907
+
2908
+ it 'should_namespace_state_predicates ' do
2909
+ [:alarm_active?, :alarm_off?].each do |name|
2910
+ assert object.respond_to?(name)
2911
+ end
2912
+ end
2913
+
2914
+ it 'should_namespace_event_checks ' do
2915
+ [:can_enable_alarm?, :can_disable_alarm?].each do |name|
2916
+ assert object.respond_to?(name)
2917
+ end
2918
+ end
2919
+
2920
+ it 'should_namespace_event_transition_readers ' do
2921
+ [:enable_alarm_transition, :disable_alarm_transition].each do |name|
2922
+ assert object.respond_to?(name)
2923
+ end
2924
+ end
2925
+
2926
+ it 'should_namespace_events ' do
2927
+ [:enable_alarm, :disable_alarm].each do |name|
2928
+ assert object.respond_to?(name)
2929
+ end
2930
+ end
2931
+
2932
+ it 'should_namespace_bang_events ' do
2933
+ [:enable_alarm!, :disable_alarm!].each do |name|
2934
+ assert object.respond_to?(name)
2935
+ end
2936
+ end
2937
+ end
2938
+
2939
+ context ' WithCustomAttribute ' do
2940
+
2941
+ let!(:machine) do
2942
+ StateMachines::Integrations.const_set('Custom', Module.new do
2943
+ include StateMachines::Integrations::Base
2944
+
2945
+ @defaults = {action: :save, use_transactions: false}
2946
+
2947
+ def create_with_scope(name)
2948
+ -> {}
2949
+ end
2950
+
2951
+ def create_without_scope(name)
2952
+ -> {}
2953
+ end
2954
+ end)
2955
+
2956
+ StateMachines::Machine.new(klass, :state, attribute: :state_id, initial: :active, integration: :custom) do
2957
+ event :ignite do
2958
+ transition parked: :idling
2959
+ end
2960
+ end
2961
+ end
2962
+ let(:object) { klass.new }
2963
+
2964
+ it 'should_define_a_reader_attribute_for_the_attribute ' do
2965
+ assert object.respond_to?(:state_id)
2966
+ end
2967
+
2968
+ it 'should_define_a_writer_attribute_for_the_attribute ' do
2969
+ assert object.respond_to?(:state_id=)
2970
+ end
2971
+
2972
+ it 'should_define_a_predicate_for_the_attribute ' do
2973
+ assert object.respond_to?(:state?)
2974
+ end
2975
+
2976
+ it 'should_define_a_name_reader_for_the_attribute ' do
2977
+ assert object.respond_to?(:state_name)
2978
+ end
2979
+
2980
+ it 'should_define_a_human_name_reader_for_the_attribute ' do
2981
+ assert object.respond_to?(:state_name)
2982
+ end
2983
+
2984
+ it 'should_define_an_event_reader_for_the_attribute ' do
2985
+ assert object.respond_to?(:state_events)
2986
+ end
2987
+
2988
+ it 'should_define_a_transition_reader_for_the_attribute ' do
2989
+ assert object.respond_to?(:state_transitions)
2990
+ end
2991
+
2992
+ it 'should_define_a_path_reader_for_the_attribute ' do
2993
+ assert object.respond_to?(:state_paths)
2994
+ end
2995
+
2996
+ it 'should_define_an_event_runner_for_the_attribute ' do
2997
+ assert object.respond_to?(:fire_state_event)
2998
+ end
2999
+
3000
+ it 'should_define_a_human_attribute_name_reader ' do
3001
+ assert klass.respond_to?(:human_state_name)
3002
+ end
3003
+
3004
+ it 'should_define_a_human_event_name_reader ' do
3005
+ assert klass.respond_to?(:human_state_event_name)
3006
+ end
3007
+
3008
+ it 'should_define_singular_with_scope ' do
3009
+ assert klass.respond_to?(:with_state)
3010
+ end
3011
+
3012
+ it 'should_define_singular_without_scope ' do
3013
+ assert klass.respond_to?(:without_state)
3014
+ end
3015
+
3016
+ it 'should_define_plural_with_scope ' do
3017
+ assert klass.respond_to?(:with_states)
3018
+ end
3019
+
3020
+ it 'should_define_plural_without_scope ' do
3021
+ assert klass.respond_to?(:without_states)
3022
+ end
3023
+
3024
+ it 'should_define_state_machines_reader ' do
3025
+ expected = {state: machine}
3026
+ assert_equal expected, klass.state_machines
3027
+ end
3028
+
3029
+ after(:each) do
3030
+ StateMachines::Integrations.send(:remove_const, 'Custom')
3031
+ end
3032
+ end
3033
+
3034
+ context ' FinderWithoutExistingMachine ' do
3035
+ let(:machine) { StateMachines::Machine.find_or_create(klass) }
3036
+
3037
+ it 'should_accept_a_block ' do
3038
+ called = false
3039
+ StateMachines::Machine.find_or_create(Class.new) do
3040
+ called = respond_to?(:event)
3041
+ end
3042
+
3043
+ assert called
3044
+ end
3045
+
3046
+ it 'should_create_a_new_machine ' do
3047
+ assert_not_nil machine
3048
+ end
3049
+
3050
+ it 'should_use_default_state ' do
3051
+ assert_equal :state, machine.attribute
3052
+ end
3053
+ end
3054
+
3055
+ context ' FinderWithExistingOnSameClass ' do
3056
+
3057
+ let!(:existing_machine) { StateMachines::Machine.new(klass) }
3058
+ let!(:machine) { StateMachines::Machine.find_or_create(klass) }
3059
+
3060
+ it 'should_accept_a_block ' do
3061
+ called = false
3062
+ StateMachines::Machine.find_or_create(klass) do
3063
+ called = respond_to?(:event)
3064
+ end
3065
+
3066
+ assert called
3067
+ end
3068
+
3069
+ it 'should_not_create_a_new_machine ' do
3070
+ assert_same machine, existing_machine
3071
+ end
3072
+ end
3073
+
3074
+ context ' FinderWithExistingMachineOnSuperclass ' do
3075
+
3076
+ before(:each) do
3077
+ integration = Module.new do
3078
+ include StateMachines::Integrations::Base
3079
+
3080
+ def self.matches?(klass)
3081
+ false
3082
+ end
3083
+ end
3084
+ StateMachines::Integrations.const_set('Custom', integration)
3085
+
3086
+ @base_class = Class.new
3087
+ @base_machine = StateMachines::Machine.new(@base_class, :status, :action => :save, :integration => :custom)
3088
+ @base_machine.event(:ignite) {}
3089
+ @base_machine.before_transition(lambda {})
3090
+ @base_machine.after_transition(lambda {})
3091
+ @base_machine.around_transition(lambda {})
3092
+
3093
+ @klass = Class.new(@base_class)
3094
+ @machine = StateMachines::Machine.find_or_create(@klass, :status) {}
3095
+ end
3096
+
3097
+
3098
+ it 'should_accept_a_block' do
3099
+ called = false
3100
+ StateMachines::Machine.find_or_create(Class.new(@base_class)) do
3101
+ called = respond_to?(:event)
3102
+ end
3103
+
3104
+ assert called
3105
+ end
3106
+
3107
+ it 'should_not_create_a_new_machine_if_no_block_or_options' do
3108
+ machine = StateMachines::Machine.find_or_create(Class.new(@base_class), :status)
3109
+
3110
+ assert_same machine, @base_machine
3111
+ end
3112
+
3113
+ it 'should_create_a_new_machine_if_given_options' do
3114
+ machine = StateMachines::Machine.find_or_create(@klass, :status, :initial => :parked)
3115
+
3116
+ assert_not_nil machine
3117
+ assert_not_same machine, @base_machine
3118
+ end
3119
+
3120
+ it 'should_create_a_new_machine_if_given_block' do
3121
+ assert_not_nil @machine
3122
+ assert_not_same @machine, @base_machine
3123
+ end
3124
+
3125
+ it 'should_copy_the_base_attribute' do
3126
+ assert_equal :status, @machine.attribute
3127
+ end
3128
+
3129
+ it 'should_copy_the_base_configuration' do
3130
+ assert_equal :save, @machine.action
3131
+ end
3132
+
3133
+ it 'should_copy_events' do
3134
+ # Can't assert equal arrays since their machines change
3135
+ assert_equal 1, @machine.events.length
3136
+ end
3137
+
3138
+ it 'should_copy_before_callbacks' do
3139
+ assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before]
3140
+ end
3141
+
3142
+ it 'should_copy_after_transitions' do
3143
+ assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after]
3144
+ end
3145
+
3146
+ it 'should_use_the_same_integration' do
3147
+ assert((class << @machine; ancestors; end).include?(StateMachines::Integrations::Custom))
3148
+ end
3149
+
3150
+ after(:each) do
3151
+ StateMachines::Integrations.send(:remove_const, 'Custom')
3152
+ end
3153
+ end
3154
+
3155
+ context 'FinderCustomOptions' do
3156
+ let!(:machine) { StateMachines::Machine.find_or_create(klass, :status, initial: :parked) }
3157
+
3158
+ it 'should_use_custom_attribute' do
3159
+ assert_equal :status, machine.attribute
3160
+ end
3161
+
3162
+ it 'should_set_custom_initial_state' do
3163
+ assert_equal :parked, machine.initial_state(object).name
3164
+ end
3165
+ end
3166
+
3167
+ end