state_machines 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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