hsume2-state_machine 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. data/CHANGELOG.rdoc +413 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +717 -0
  4. data/Rakefile +77 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine.rb +448 -0
  28. data/lib/state_machine/alternate_machine.rb +79 -0
  29. data/lib/state_machine/assertions.rb +36 -0
  30. data/lib/state_machine/branch.rb +224 -0
  31. data/lib/state_machine/callback.rb +236 -0
  32. data/lib/state_machine/condition_proxy.rb +94 -0
  33. data/lib/state_machine/error.rb +13 -0
  34. data/lib/state_machine/eval_helpers.rb +86 -0
  35. data/lib/state_machine/event.rb +304 -0
  36. data/lib/state_machine/event_collection.rb +139 -0
  37. data/lib/state_machine/extensions.rb +149 -0
  38. data/lib/state_machine/initializers.rb +4 -0
  39. data/lib/state_machine/initializers/merb.rb +1 -0
  40. data/lib/state_machine/initializers/rails.rb +25 -0
  41. data/lib/state_machine/integrations.rb +110 -0
  42. data/lib/state_machine/integrations/active_model.rb +502 -0
  43. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  44. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  45. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  46. data/lib/state_machine/integrations/active_record.rb +424 -0
  47. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  48. data/lib/state_machine/integrations/active_record/versions.rb +143 -0
  49. data/lib/state_machine/integrations/base.rb +91 -0
  50. data/lib/state_machine/integrations/data_mapper.rb +392 -0
  51. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  52. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  53. data/lib/state_machine/integrations/mongo_mapper.rb +272 -0
  54. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  55. data/lib/state_machine/integrations/mongo_mapper/versions.rb +110 -0
  56. data/lib/state_machine/integrations/mongoid.rb +357 -0
  57. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  58. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  59. data/lib/state_machine/integrations/sequel.rb +428 -0
  60. data/lib/state_machine/integrations/sequel/versions.rb +36 -0
  61. data/lib/state_machine/machine.rb +1873 -0
  62. data/lib/state_machine/machine_collection.rb +87 -0
  63. data/lib/state_machine/matcher.rb +123 -0
  64. data/lib/state_machine/matcher_helpers.rb +54 -0
  65. data/lib/state_machine/node_collection.rb +157 -0
  66. data/lib/state_machine/path.rb +120 -0
  67. data/lib/state_machine/path_collection.rb +90 -0
  68. data/lib/state_machine/state.rb +271 -0
  69. data/lib/state_machine/state_collection.rb +112 -0
  70. data/lib/state_machine/transition.rb +458 -0
  71. data/lib/state_machine/transition_collection.rb +244 -0
  72. data/lib/tasks/state_machine.rake +1 -0
  73. data/lib/tasks/state_machine.rb +27 -0
  74. data/test/files/en.yml +17 -0
  75. data/test/files/switch.rb +11 -0
  76. data/test/functional/alternate_state_machine_test.rb +122 -0
  77. data/test/functional/state_machine_test.rb +993 -0
  78. data/test/test_helper.rb +4 -0
  79. data/test/unit/assertions_test.rb +40 -0
  80. data/test/unit/branch_test.rb +890 -0
  81. data/test/unit/callback_test.rb +701 -0
  82. data/test/unit/condition_proxy_test.rb +328 -0
  83. data/test/unit/error_test.rb +43 -0
  84. data/test/unit/eval_helpers_test.rb +222 -0
  85. data/test/unit/event_collection_test.rb +358 -0
  86. data/test/unit/event_test.rb +985 -0
  87. data/test/unit/integrations/active_model_test.rb +1097 -0
  88. data/test/unit/integrations/active_record_test.rb +2021 -0
  89. data/test/unit/integrations/base_test.rb +99 -0
  90. data/test/unit/integrations/data_mapper_test.rb +1909 -0
  91. data/test/unit/integrations/mongo_mapper_test.rb +1611 -0
  92. data/test/unit/integrations/mongoid_test.rb +1591 -0
  93. data/test/unit/integrations/sequel_test.rb +1523 -0
  94. data/test/unit/integrations_test.rb +61 -0
  95. data/test/unit/invalid_event_test.rb +20 -0
  96. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  97. data/test/unit/invalid_transition_test.rb +77 -0
  98. data/test/unit/machine_collection_test.rb +599 -0
  99. data/test/unit/machine_test.rb +3043 -0
  100. data/test/unit/matcher_helpers_test.rb +37 -0
  101. data/test/unit/matcher_test.rb +155 -0
  102. data/test/unit/node_collection_test.rb +217 -0
  103. data/test/unit/path_collection_test.rb +266 -0
  104. data/test/unit/path_test.rb +485 -0
  105. data/test/unit/state_collection_test.rb +310 -0
  106. data/test/unit/state_machine_test.rb +31 -0
  107. data/test/unit/state_test.rb +924 -0
  108. data/test/unit/transition_collection_test.rb +2102 -0
  109. data/test/unit/transition_test.rb +1541 -0
  110. metadata +207 -0
@@ -0,0 +1,1591 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ # Load library
4
+ require 'rubygems'
5
+
6
+ gem 'mongoid', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=2.0.0'
7
+ require 'mongoid'
8
+
9
+ # Establish database connection
10
+ Mongoid.configure do |config|
11
+ config.master = Mongo::Connection.new('127.0.0.1', 27017, :slave_ok => true).db('test')
12
+ end
13
+
14
+ module MongoidTest
15
+ class BaseTestCase < Test::Unit::TestCase
16
+ def default_test
17
+ end
18
+
19
+ protected
20
+ # Creates a new Mongoid model (and the associated table)
21
+ def new_model(table_name = :foo, &block)
22
+
23
+ model = Class.new do
24
+ (class << self; self; end).class_eval do
25
+ define_method(:name) { "MongoidTest::#{table_name.to_s.capitalize}" }
26
+ define_method(:to_s) { name }
27
+ end
28
+ end
29
+
30
+ model.class_eval do
31
+ include Mongoid::Document
32
+ self.collection_name = table_name
33
+
34
+ field :state, :type => String
35
+ end
36
+ model.class_eval(&block) if block_given?
37
+ model.collection.remove
38
+ model
39
+ end
40
+ end
41
+
42
+ class IntegrationTest < BaseTestCase
43
+ def test_should_have_an_integration_name
44
+ assert_equal :mongoid, StateMachine::Integrations::Mongoid.integration_name
45
+ end
46
+
47
+ def test_should_be_available
48
+ assert StateMachine::Integrations::Mongoid.available?
49
+ end
50
+
51
+ def test_should_match_if_class_includes_mongoid
52
+ assert StateMachine::Integrations::Mongoid.matches?(new_model)
53
+ end
54
+
55
+ def test_should_not_match_if_class_does_not_include_mongoid
56
+ assert !StateMachine::Integrations::Mongoid.matches?(Class.new)
57
+ end
58
+
59
+ def test_should_have_defaults
60
+ assert_equal e = {:action => :save}, StateMachine::Integrations::Mongoid.defaults
61
+ end
62
+
63
+ def test_should_have_a_locale_path
64
+ assert_not_nil StateMachine::Integrations::Mongoid.locale_path
65
+ end
66
+ end
67
+
68
+ class MachineByDefaultTest < BaseTestCase
69
+ def setup
70
+ @model = new_model
71
+ @machine = StateMachine::Machine.new(@model)
72
+ end
73
+
74
+ def test_should_use_save_as_action
75
+ assert_equal :save, @machine.action
76
+ end
77
+
78
+ def test_should_create_notifier_before_callback
79
+ assert_equal 1, @machine.callbacks[:before].size
80
+ end
81
+
82
+ def test_should_create_notifier_after_callback
83
+ assert_equal 1, @machine.callbacks[:after].size
84
+ end
85
+ end
86
+
87
+ class MachineWithStatesTest < BaseTestCase
88
+ def setup
89
+ @model = new_model
90
+ @machine = StateMachine::Machine.new(@model)
91
+ @machine.state :first_gear
92
+ end
93
+
94
+ def test_should_humanize_name
95
+ assert_equal 'first gear', @machine.state(:first_gear).human_name
96
+ end
97
+ end
98
+
99
+ class MachineWithStaticInitialStateTest < BaseTestCase
100
+ def setup
101
+ @model = new_model do
102
+ attr_accessor :value
103
+ end
104
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
105
+ end
106
+
107
+ def test_should_set_initial_state_on_created_object
108
+ record = @model.new
109
+ assert_equal 'parked', record.state
110
+ end
111
+
112
+ def test_should_set_initial_state_with_nil_attributes
113
+ record = @model.new(nil)
114
+ assert_equal 'parked', record.state
115
+ end
116
+
117
+ def test_should_still_set_attributes
118
+ record = @model.new(:value => 1)
119
+ assert_equal 1, record.value
120
+ end
121
+
122
+ def test_should_still_allow_initialize_blocks
123
+ block_args = nil
124
+ record = @model.new do |*args|
125
+ block_args = args
126
+ end
127
+
128
+ assert_equal [record], block_args
129
+ end
130
+
131
+ def test_should_set_initial_state_before_setting_attributes
132
+ @model.class_eval do
133
+ attr_accessor :state_during_setter
134
+
135
+ define_method(:value=) do |value|
136
+ self.state_during_setter = state
137
+ end
138
+ end
139
+
140
+ record = @model.new(:value => 1)
141
+ assert_equal 'parked', record.state_during_setter
142
+ end
143
+
144
+ def test_should_not_set_initial_state_after_already_initialized
145
+ record = @model.new(:value => 1)
146
+ assert_equal 'parked', record.state
147
+
148
+ record.state = 'idling'
149
+ record.process({})
150
+ assert_equal 'idling', record.state
151
+ end
152
+
153
+ def test_should_use_stored_values_when_loading_from_database
154
+ @machine.state :idling
155
+
156
+ record = @model.find(@model.create(:state => 'idling').id)
157
+ assert_equal 'idling', record.state
158
+ end
159
+
160
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
161
+ @machine.state nil
162
+
163
+ record = @model.find(@model.create(:state => nil).id)
164
+ assert_nil record.state
165
+ end
166
+ end
167
+
168
+ class MachineWithDynamicInitialStateTest < BaseTestCase
169
+ def setup
170
+ @model = new_model do
171
+ attr_accessor :value
172
+ end
173
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
174
+ @machine.state :parked
175
+ end
176
+
177
+ def test_should_set_initial_state_on_created_object
178
+ record = @model.new
179
+ assert_equal 'parked', record.state
180
+ end
181
+
182
+ def test_should_still_set_attributes
183
+ record = @model.new(:value => 1)
184
+ assert_equal 1, record.value
185
+ end
186
+
187
+ def test_should_still_allow_initialize_blocks
188
+ block_args = nil
189
+ record = @model.new do |*args|
190
+ block_args = args
191
+ end
192
+
193
+ assert_equal [record], block_args
194
+ end
195
+
196
+ def test_should_set_initial_state_after_setting_attributes
197
+ @model.class_eval do
198
+ attr_accessor :state_during_setter
199
+
200
+ define_method(:value=) do |value|
201
+ self.state_during_setter = state || 'nil'
202
+ end
203
+ end
204
+
205
+ record = @model.new(:value => 1)
206
+ assert_equal 'nil', record.state_during_setter
207
+ end
208
+
209
+ def test_should_not_set_initial_state_after_already_initialized
210
+ record = @model.new(:value => 1)
211
+ assert_equal 'parked', record.state
212
+
213
+ record.state = 'idling'
214
+ record.process({})
215
+ assert_equal 'idling', record.state
216
+ end
217
+
218
+ def test_should_use_stored_values_when_loading_from_database
219
+ @machine.state :idling
220
+
221
+ record = @model.find(@model.create(:state => 'idling').id)
222
+ assert_equal 'idling', record.state
223
+ end
224
+
225
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
226
+ @machine.state nil
227
+
228
+ record = @model.find(@model.create(:state => nil).id)
229
+ assert_nil record.state
230
+ end
231
+ end
232
+
233
+ class MachineWithEventsTest < BaseTestCase
234
+ def setup
235
+ @model = new_model
236
+ @machine = StateMachine::Machine.new(@model)
237
+ @machine.event :shift_up
238
+ end
239
+
240
+ def test_should_humanize_name
241
+ assert_equal 'shift up', @machine.event(:shift_up).human_name
242
+ end
243
+ end
244
+
245
+ class MachineWithColumnDefaultTest < BaseTestCase
246
+ def setup
247
+ @model = new_model do
248
+ field :status, :type => String, :default => 'idling'
249
+ end
250
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
251
+ @record = @model.new
252
+ end
253
+
254
+ def test_should_use_machine_default
255
+ assert_equal 'parked', @record.status
256
+ end
257
+ end
258
+
259
+ class MachineWithConflictingPredicateTest < BaseTestCase
260
+ def setup
261
+ @model = new_model do
262
+ def state?(*args)
263
+ true
264
+ end
265
+ end
266
+
267
+ @machine = StateMachine::Machine.new(@model)
268
+ @record = @model.new
269
+ end
270
+
271
+ def test_should_not_define_attribute_predicate
272
+ assert @record.state?
273
+ end
274
+ end
275
+
276
+ class MachineWithColumnStateAttributeTest < BaseTestCase
277
+ def setup
278
+ @model = new_model
279
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
280
+ @machine.other_states(:idling)
281
+
282
+ @record = @model.new
283
+ end
284
+
285
+ def test_should_not_override_the_column_reader
286
+ @record[:state] = 'parked'
287
+ assert_equal 'parked', @record.state
288
+ end
289
+
290
+ def test_should_not_override_the_column_writer
291
+ @record.state = 'parked'
292
+ assert_equal 'parked', @record[:state]
293
+ end
294
+
295
+ def test_should_have_an_attribute_predicate
296
+ assert @record.respond_to?(:state?)
297
+ end
298
+
299
+ def test_should_test_for_existence_on_predicate_without_parameters
300
+ assert @record.state?
301
+
302
+ @record.state = nil
303
+ assert !@record.state?
304
+ end
305
+
306
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
307
+ assert !@record.state?(:idling)
308
+ end
309
+
310
+ def test_should_return_true_for_predicate_if_matches_current_value
311
+ assert @record.state?(:parked)
312
+ end
313
+
314
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
315
+ assert_raise(IndexError) { @record.state?(:invalid) }
316
+ end
317
+ end
318
+
319
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
320
+ def setup
321
+ @model = new_model do
322
+ def initialize
323
+ # Skip attribute initialization
324
+ @initialized_state_machines = true
325
+ super
326
+ end
327
+ end
328
+
329
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
330
+ @machine.other_states(:idling)
331
+ @record = @model.new
332
+ end
333
+
334
+ def test_should_define_a_new_key_for_the_attribute
335
+ assert_not_nil @model.fields['status']
336
+ end
337
+
338
+ def test_should_define_a_reader_attribute_for_the_attribute
339
+ assert @record.respond_to?(:status)
340
+ end
341
+
342
+ def test_should_define_a_writer_attribute_for_the_attribute
343
+ assert @record.respond_to?(:status=)
344
+ end
345
+
346
+ def test_should_define_an_attribute_predicate
347
+ assert @record.respond_to?(:status?)
348
+ end
349
+ end
350
+
351
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
352
+ def setup
353
+ @model = new_model do
354
+ def status
355
+ self['status']
356
+ end
357
+
358
+ def status=(value)
359
+ self['status'] = value
360
+ end
361
+ end
362
+
363
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
364
+ @machine.other_states(:idling)
365
+ @record = @model.new
366
+ end
367
+
368
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
369
+ assert !@record.status?(:idling)
370
+ end
371
+
372
+ def test_should_return_true_for_predicate_if_matches_current_value
373
+ assert @record.status?(:parked)
374
+ end
375
+
376
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
377
+ assert_raise(IndexError) { @record.status?(:invalid) }
378
+ end
379
+
380
+ def test_should_set_initial_state_on_created_object
381
+ assert_equal 'parked', @record.status
382
+ end
383
+ end
384
+
385
+ class MachineWithAliasedAttributeTest < BaseTestCase
386
+ def setup
387
+ @model = new_model do
388
+ alias_attribute :vehicle_status, :state
389
+ end
390
+
391
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
392
+ @machine.state :parked
393
+
394
+ @record = @model.new
395
+ end
396
+
397
+ def test_should_check_custom_attribute_for_predicate
398
+ @record.vehicle_status = nil
399
+ assert !@record.status?(:parked)
400
+
401
+ @record.vehicle_status = 'parked'
402
+ assert @record.status?(:parked)
403
+ end
404
+ end
405
+
406
+ class MachineWithInitializedStateTest < BaseTestCase
407
+ def setup
408
+ @model = new_model
409
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
410
+ @machine.state :idling
411
+ end
412
+
413
+ def test_should_allow_nil_initial_state_when_static
414
+ @machine.state nil
415
+
416
+ record = @model.new(:state => nil)
417
+ assert_nil record.state
418
+ end
419
+
420
+ def test_should_allow_nil_initial_state_when_dynamic
421
+ @machine.state nil
422
+
423
+ @machine.initial_state = lambda {:parked}
424
+ record = @model.new(:state => nil)
425
+ assert_nil record.state
426
+ end
427
+
428
+ def test_should_allow_different_initial_state_when_static
429
+ record = @model.new(:state => 'idling')
430
+ assert_equal 'idling', record.state
431
+ end
432
+
433
+ def test_should_allow_different_initial_state_when_dynamic
434
+ @machine.initial_state = lambda {:parked}
435
+ record = @model.new(:state => 'idling')
436
+ assert_equal 'idling', record.state
437
+ end
438
+
439
+ def test_should_use_default_state_if_protected
440
+ @model.class_eval do
441
+ attr_protected :state
442
+ end
443
+
444
+ record = @model.new(:state => 'idling')
445
+ assert_equal 'parked', record.state
446
+ end
447
+ end
448
+
449
+ class MachineMultipleTest < BaseTestCase
450
+ def setup
451
+ @model = new_model do
452
+ key :status, String, :default => 'idling'
453
+ end
454
+ @state_machine = StateMachine::Machine.new(@model, :initial => :parked)
455
+ @status_machine = StateMachine::Machine.new(@model, :status, :initial => :idling)
456
+ end
457
+
458
+ def test_should_should_initialize_each_state
459
+ record = @model.new
460
+ assert_equal 'parked', record.state
461
+ assert_equal 'idling', record.status
462
+ end
463
+ end
464
+
465
+ class MachineWithLoopbackTest < BaseTestCase
466
+ def setup
467
+ @model = new_model do
468
+ field :updated_at, :type => Time
469
+
470
+ before_update do |record|
471
+ record.updated_at = Time.now
472
+ end
473
+ end
474
+
475
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
476
+ @machine.event :park
477
+
478
+ @record = @model.create(:updated_at => Time.now - 1)
479
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
480
+
481
+ @timestamp = @record.updated_at
482
+ @transition.perform
483
+ end
484
+
485
+ def test_should_update_record
486
+ assert_not_equal @timestamp, @record.updated_at
487
+ end
488
+ end
489
+
490
+ class MachineWithDirtyAttributesTest < BaseTestCase
491
+ def setup
492
+ @model = new_model
493
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
494
+ @machine.event :ignite
495
+ @machine.state :idling
496
+
497
+ @record = @model.create
498
+
499
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
500
+ @transition.perform(false)
501
+ end
502
+
503
+ def test_should_include_state_in_changed_attributes
504
+ assert_equal %w(state), @record.changed
505
+ end
506
+
507
+ def test_should_track_attribute_change
508
+ assert_equal %w(parked idling), @record.changes['state']
509
+ end
510
+
511
+ def test_should_not_reset_changes_on_multiple_transitions
512
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
513
+ transition.perform(false)
514
+
515
+ assert_equal %w(parked idling), @record.changes['state']
516
+ end
517
+
518
+ def test_should_not_have_changes_when_loaded_from_database
519
+ record = @model.find(@record.id)
520
+ assert !record.changed?
521
+ end
522
+ end
523
+
524
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
525
+ def setup
526
+ @model = new_model
527
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
528
+ @machine.event :park
529
+
530
+ @record = @model.create
531
+
532
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
533
+ @transition.perform(false)
534
+ end
535
+
536
+ def test_should_include_state_in_changed_attributes
537
+ assert_equal %w(state), @record.changed
538
+ end
539
+
540
+ def test_should_track_attribute_changes
541
+ assert_equal %w(parked parked), @record.changes['state']
542
+ end
543
+ end
544
+
545
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
546
+ def setup
547
+ @model = new_model do
548
+ key :status, String, :default => 'idling'
549
+ end
550
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
551
+ @machine.event :ignite
552
+ @machine.state :idling
553
+
554
+ @record = @model.create
555
+
556
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
557
+ @transition.perform(false)
558
+ end
559
+
560
+ def test_should_include_state_in_changed_attributes
561
+ assert_equal %w(status), @record.changed
562
+ end
563
+
564
+ def test_should_track_attribute_change
565
+ assert_equal %w(parked idling), @record.changes['status']
566
+ end
567
+
568
+ def test_should_not_reset_changes_on_multiple_transitions
569
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
570
+ transition.perform(false)
571
+
572
+ assert_equal %w(parked idling), @record.changes['status']
573
+ end
574
+ end
575
+
576
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
577
+ def setup
578
+ @model = new_model do
579
+ key :status, String, :default => 'idling'
580
+ end
581
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
582
+ @machine.event :park
583
+
584
+ @record = @model.create
585
+
586
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
587
+ @transition.perform(false)
588
+ end
589
+
590
+ def test_should_include_state_in_changed_attributes
591
+ assert_equal %w(status), @record.changed
592
+ end
593
+
594
+ def test_should_track_attribute_changes
595
+ assert_equal %w(parked parked), @record.changes['status']
596
+ end
597
+ end
598
+
599
+ class MachineWithDirtyAttributeAndStateEventsTest < BaseTestCase
600
+ def setup
601
+ @model = new_model
602
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
603
+ @machine.event :ignite
604
+
605
+ @record = @model.create
606
+ @record.state_event = 'ignite'
607
+ end
608
+
609
+ def test_should_include_state_in_changed_attributes
610
+ assert_equal %w(state), @record.changed
611
+ end
612
+
613
+ def test_should_track_attribute_change
614
+ assert_equal %w(parked parked), @record.changes['state']
615
+ end
616
+
617
+ def test_should_not_reset_changes_on_multiple_changes
618
+ @record.state_event = 'ignite'
619
+ assert_equal %w(parked parked), @record.changes['state']
620
+ end
621
+
622
+ def test_should_not_include_state_in_changed_attributes_if_nil
623
+ @record = @model.create
624
+ @record.state_event = nil
625
+
626
+ assert_equal [], @record.changed
627
+ end
628
+ end
629
+
630
+ class MachineWithCallbacksTest < BaseTestCase
631
+ def setup
632
+ @model = new_model
633
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
634
+ @machine.other_states :idling
635
+ @machine.event :ignite
636
+
637
+ @record = @model.new(:state => 'parked')
638
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
639
+ end
640
+
641
+ def test_should_run_before_callbacks
642
+ called = false
643
+ @machine.before_transition {called = true}
644
+
645
+ @transition.perform
646
+ assert called
647
+ end
648
+
649
+ def test_should_pass_record_to_before_callbacks_with_one_argument
650
+ record = nil
651
+ @machine.before_transition {|arg| record = arg}
652
+
653
+ @transition.perform
654
+ assert_equal @record, record
655
+ end
656
+
657
+ def test_should_pass_record_and_transition_to_before_callbacks_with_multiple_arguments
658
+ callback_args = nil
659
+ @machine.before_transition {|*args| callback_args = args}
660
+
661
+ @transition.perform
662
+ assert_equal [@record, @transition], callback_args
663
+ end
664
+
665
+ def test_should_run_before_callbacks_outside_the_context_of_the_record
666
+ context = nil
667
+ @machine.before_transition {context = self}
668
+
669
+ @transition.perform
670
+ assert_equal self, context
671
+ end
672
+
673
+ def test_should_run_after_callbacks
674
+ called = false
675
+ @machine.after_transition {called = true}
676
+
677
+ @transition.perform
678
+ assert called
679
+ end
680
+
681
+ def test_should_pass_record_to_after_callbacks_with_one_argument
682
+ record = nil
683
+ @machine.after_transition {|arg| record = arg}
684
+
685
+ @transition.perform
686
+ assert_equal @record, record
687
+ end
688
+
689
+ def test_should_pass_record_and_transition_to_after_callbacks_with_multiple_arguments
690
+ callback_args = nil
691
+ @machine.after_transition {|*args| callback_args = args}
692
+
693
+ @transition.perform
694
+ assert_equal [@record, @transition], callback_args
695
+ end
696
+
697
+ def test_should_run_after_callbacks_outside_the_context_of_the_record
698
+ context = nil
699
+ @machine.after_transition {context = self}
700
+
701
+ @transition.perform
702
+ assert_equal self, context
703
+ end
704
+
705
+ def test_should_run_around_callbacks
706
+ before_called = false
707
+ after_called = false
708
+ @machine.around_transition {|block| before_called = true; block.call; after_called = true}
709
+
710
+ @transition.perform
711
+ assert before_called
712
+ assert after_called
713
+ end
714
+
715
+ def test_should_include_transition_states_in_known_states
716
+ @machine.before_transition :to => :first_gear, :do => lambda {}
717
+
718
+ assert_equal [:parked, :idling, :first_gear], @machine.states.map {|state| state.name}
719
+ end
720
+
721
+ def test_should_allow_symbolic_callbacks
722
+ callback_args = nil
723
+
724
+ klass = class << @record; self; end
725
+ klass.send(:define_method, :after_ignite) do |*args|
726
+ callback_args = args
727
+ end
728
+
729
+ @machine.before_transition(:after_ignite)
730
+
731
+ @transition.perform
732
+ assert_equal [@transition], callback_args
733
+ end
734
+
735
+ def test_should_allow_string_callbacks
736
+ class << @record
737
+ attr_reader :callback_result
738
+ end
739
+
740
+ @machine.before_transition('@callback_result = [1, 2, 3]')
741
+ @transition.perform
742
+
743
+ assert_equal [1, 2, 3], @record.callback_result
744
+ end
745
+ end
746
+
747
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
748
+ def setup
749
+ @callbacks = []
750
+
751
+ @model = new_model
752
+ @machine = StateMachine::Machine.new(@model)
753
+ @machine.state :parked, :idling
754
+ @machine.event :ignite
755
+ @machine.before_transition {@callbacks << :before_1; false}
756
+ @machine.before_transition {@callbacks << :before_2}
757
+ @machine.after_transition {@callbacks << :after}
758
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
759
+
760
+ @record = @model.new(:state => 'parked')
761
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
762
+ @result = @transition.perform
763
+ end
764
+
765
+ def test_should_not_be_successful
766
+ assert !@result
767
+ end
768
+
769
+ def test_should_not_change_current_state
770
+ assert_equal 'parked', @record.state
771
+ end
772
+
773
+ def test_should_not_run_action
774
+ assert @record.new_record?
775
+ end
776
+
777
+ def test_should_not_run_further_callbacks
778
+ assert_equal [:before_1], @callbacks
779
+ end
780
+ end
781
+
782
+ class MachineWithFailedActionTest < BaseTestCase
783
+ def setup
784
+ @model = new_model do
785
+ validates_numericality_of :state
786
+ end
787
+
788
+ @machine = StateMachine::Machine.new(@model)
789
+ @machine.state :parked, :idling
790
+ @machine.event :ignite
791
+
792
+ @callbacks = []
793
+ @machine.before_transition {@callbacks << :before}
794
+ @machine.after_transition {@callbacks << :after}
795
+ @machine.after_failure {@callbacks << :after_failure}
796
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
797
+
798
+ @record = @model.new(:state => 'parked')
799
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
800
+ @result = @transition.perform
801
+ end
802
+
803
+ def test_should_not_be_successful
804
+ assert !@result
805
+ end
806
+
807
+ def test_should_not_change_current_state
808
+ assert_equal 'parked', @record.state
809
+ end
810
+
811
+ def test_should_not_save_record
812
+ assert @record.new_record?
813
+ end
814
+
815
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
816
+ assert_equal [:before, :around_before, :after_failure], @callbacks
817
+ end
818
+ end
819
+
820
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
821
+ def setup
822
+ @callbacks = []
823
+
824
+ @model = new_model
825
+ @machine = StateMachine::Machine.new(@model)
826
+ @machine.state :parked, :idling
827
+ @machine.event :ignite
828
+ @machine.after_transition {@callbacks << :after_1; false}
829
+ @machine.after_transition {@callbacks << :after_2}
830
+ @machine.around_transition {|block| @callbacks << :around_before; block.call; @callbacks << :around_after}
831
+
832
+ @record = @model.new(:state => 'parked')
833
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
834
+ @result = @transition.perform
835
+ end
836
+
837
+ def test_should_be_successful
838
+ assert @result
839
+ end
840
+
841
+ def test_should_change_current_state
842
+ assert_equal 'idling', @record.state
843
+ end
844
+
845
+ def test_should_save_record
846
+ assert !@record.new_record?
847
+ end
848
+
849
+ def test_should_not_run_further_after_callbacks
850
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
851
+ end
852
+ end
853
+
854
+ class MachineWithValidationsTest < BaseTestCase
855
+ def setup
856
+ @model = new_model
857
+ @machine = StateMachine::Machine.new(@model)
858
+ @machine.state :parked
859
+
860
+ @record = @model.new
861
+ end
862
+
863
+ def test_should_invalidate_using_errors
864
+ I18n.backend = I18n::Backend::Simple.new if Object.const_defined?(:ActiveModel)
865
+ @record.state = 'parked'
866
+
867
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']])
868
+ assert_equal ['State cannot transition via "park"'], @record.errors.full_messages
869
+ end
870
+
871
+ def test_should_auto_prefix_custom_attributes_on_invalidation
872
+ @machine.invalidate(@record, :event, :invalid)
873
+
874
+ assert_equal ['State event is invalid'], @record.errors.full_messages
875
+ end
876
+
877
+ def test_should_clear_errors_on_reset
878
+ @record.state = 'parked'
879
+ @record.errors.add(:state, 'is invalid')
880
+
881
+ @machine.reset(@record)
882
+ assert_equal [], @record.errors.full_messages
883
+ end
884
+
885
+ def test_should_be_valid_if_state_is_known
886
+ @record.state = 'parked'
887
+
888
+ assert @record.valid?
889
+ end
890
+
891
+ def test_should_not_be_valid_if_state_is_unknown
892
+ @record.state = 'invalid'
893
+
894
+ assert !@record.valid?
895
+ assert_equal ['State is invalid'], @record.errors.full_messages
896
+ end
897
+ end
898
+
899
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
900
+ def setup
901
+ @model = new_model do
902
+ alias_attribute :status, :state
903
+ end
904
+
905
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
906
+ @machine.state :parked
907
+
908
+ @record = @model.new
909
+ end
910
+
911
+ def test_should_add_validation_errors_to_custom_attribute
912
+ @record.state = 'invalid'
913
+
914
+ assert !@record.valid?
915
+ assert_equal ['State is invalid'], @record.errors.full_messages
916
+
917
+ @record.state = 'parked'
918
+ assert @record.valid?
919
+ end
920
+ end
921
+
922
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
923
+ def setup
924
+ @model = new_model do
925
+ attr_accessor :seatbealt
926
+ end
927
+
928
+ @machine = StateMachine::Machine.new(@model)
929
+ @machine.state :first_gear do
930
+ validates_presence_of :seatbelt, :key => :first_gear
931
+ end
932
+ @machine.state :second_gear do
933
+ validates_presence_of :seatbelt, :key => :second_gear
934
+ end
935
+ @machine.other_states :parked
936
+ end
937
+
938
+ def test_should_be_valid_if_validation_fails_outside_state_scope
939
+ record = @model.new(:state => 'parked', :seatbelt => nil)
940
+ assert record.valid?
941
+ end
942
+
943
+ def test_should_be_invalid_if_validation_fails_within_state_scope
944
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
945
+ assert !record.valid?
946
+ end
947
+
948
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
949
+ record = @model.new(:state => 'second_gear', :seatbelt => true)
950
+ assert record.valid?
951
+ end
952
+ end
953
+
954
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
955
+ def setup
956
+ @model = new_model
957
+ @machine = StateMachine::Machine.new(@model)
958
+ @machine.event :ignite do
959
+ transition :parked => :idling
960
+ end
961
+
962
+ @record = @model.new
963
+ @record.state = 'parked'
964
+ @record.state_event = 'ignite'
965
+ end
966
+
967
+ def test_should_fail_if_event_is_invalid
968
+ @record.state_event = 'invalid'
969
+ assert !@record.valid?
970
+ assert_equal ['State event is invalid'], @record.errors.full_messages
971
+ end
972
+
973
+ def test_should_fail_if_event_has_no_transition
974
+ @record.state = 'idling'
975
+ assert !@record.valid?
976
+ assert_equal ['State event cannot transition when idling'], @record.errors.full_messages
977
+ end
978
+
979
+ def test_should_be_successful_if_event_has_transition
980
+ assert @record.valid?
981
+ end
982
+
983
+ def test_should_run_before_callbacks
984
+ ran_callback = false
985
+ @machine.before_transition { ran_callback = true }
986
+
987
+ @record.valid?
988
+ assert ran_callback
989
+ end
990
+
991
+ def test_should_run_around_callbacks_before_yield
992
+ ran_callback = false
993
+ @machine.around_transition {|block| ran_callback = true; block.call }
994
+
995
+ @record.valid?
996
+ assert ran_callback
997
+ end
998
+
999
+ def test_should_persist_new_state
1000
+ @record.valid?
1001
+ assert_equal 'idling', @record.state
1002
+ end
1003
+
1004
+ def test_should_not_run_after_callbacks
1005
+ ran_callback = false
1006
+ @machine.after_transition { ran_callback = true }
1007
+
1008
+ @record.valid?
1009
+ assert !ran_callback
1010
+ end
1011
+
1012
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
1013
+ @model.class_eval do
1014
+ attr_accessor :seatbelt
1015
+ validates_presence_of :seatbelt
1016
+ end
1017
+
1018
+ ran_callback = false
1019
+ @machine.after_transition { ran_callback = true }
1020
+
1021
+ @record.valid?
1022
+ assert !ran_callback
1023
+ end
1024
+
1025
+ def test_should_not_run_around_callbacks_after_yield
1026
+ ran_callback = false
1027
+ @machine.around_transition {|block| block.call; ran_callback = true }
1028
+
1029
+ @record.valid?
1030
+ assert !ran_callback
1031
+ end
1032
+
1033
+ def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
1034
+ @model.class_eval do
1035
+ attr_accessor :seatbelt
1036
+ validates_presence_of :seatbelt
1037
+ end
1038
+
1039
+ ran_callback = false
1040
+ @machine.around_transition {|block| block.call; ran_callback = true }
1041
+
1042
+ @record.valid?
1043
+ assert !ran_callback
1044
+ end
1045
+
1046
+ def test_should_run_failure_callbacks_if_validation_fails
1047
+ @model.class_eval do
1048
+ attr_accessor :seatbelt
1049
+ validates_presence_of :seatbelt
1050
+ end
1051
+
1052
+ ran_callback = false
1053
+ @machine.after_failure { ran_callback = true }
1054
+
1055
+ @record.valid?
1056
+ assert ran_callback
1057
+ end
1058
+ end
1059
+
1060
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
1061
+ def setup
1062
+ @model = new_model
1063
+ @machine = StateMachine::Machine.new(@model)
1064
+ @machine.event :ignite do
1065
+ transition :parked => :idling
1066
+ end
1067
+
1068
+ @record = @model.new
1069
+ @record.state = 'parked'
1070
+ @record.state_event = 'ignite'
1071
+ end
1072
+
1073
+ def test_should_fail_if_event_is_invalid
1074
+ @record.state_event = 'invalid'
1075
+ assert_equal false, @record.save
1076
+ end
1077
+
1078
+ def test_should_fail_if_event_has_no_transition
1079
+ @record.state = 'idling'
1080
+ assert_equal false, @record.save
1081
+ end
1082
+
1083
+ def test_should_be_successful_if_event_has_transition
1084
+ assert_equal true, @record.save
1085
+ end
1086
+
1087
+ def test_should_run_before_callbacks
1088
+ ran_callback = false
1089
+ @machine.before_transition { ran_callback = true }
1090
+
1091
+ @record.save
1092
+ assert ran_callback
1093
+ end
1094
+
1095
+ def test_should_run_before_callbacks_once
1096
+ before_count = 0
1097
+ @machine.before_transition { before_count += 1 }
1098
+
1099
+ @record.save
1100
+ assert_equal 1, before_count
1101
+ end
1102
+
1103
+ def test_should_run_around_callbacks_before_yield
1104
+ ran_callback = false
1105
+ @machine.around_transition {|block| ran_callback = true; block.call }
1106
+
1107
+ @record.save
1108
+ assert ran_callback
1109
+ end
1110
+
1111
+ def test_should_run_around_callbacks_before_yield_once
1112
+ around_before_count = 0
1113
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1114
+
1115
+ @record.save
1116
+ assert_equal 1, around_before_count
1117
+ end
1118
+
1119
+ def test_should_persist_new_state
1120
+ @record.save
1121
+ assert_equal 'idling', @record.state
1122
+ end
1123
+
1124
+ def test_should_run_after_callbacks
1125
+ ran_callback = false
1126
+ @machine.after_transition { ran_callback = true }
1127
+
1128
+ @record.save
1129
+ assert ran_callback
1130
+ end
1131
+
1132
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1133
+ @model.class_eval do
1134
+ validates_numericality_of :state
1135
+ end
1136
+
1137
+ ran_callback = false
1138
+ @machine.after_transition { ran_callback = true }
1139
+
1140
+ begin; @record.save; rescue; end
1141
+ assert !ran_callback
1142
+ end
1143
+
1144
+ def test_should_run_failure_callbacks__if_fails
1145
+ @model.class_eval do
1146
+ validates_numericality_of :state
1147
+ end
1148
+
1149
+ ran_callback = false
1150
+ @machine.after_failure { ran_callback = true }
1151
+
1152
+ begin; @record.save; rescue; end
1153
+ assert ran_callback
1154
+ end
1155
+
1156
+ def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
1157
+ @model.class_eval do
1158
+ validates_numericality_of :state
1159
+ end
1160
+
1161
+ ran_callback = false
1162
+ @machine.around_transition {|block| block.call; ran_callback = true }
1163
+
1164
+ begin; @record.save; rescue; end
1165
+ assert !ran_callback
1166
+ end
1167
+
1168
+ def test_should_run_around_callbacks_after_yield
1169
+ ran_callback = false
1170
+ @machine.around_transition {|block| block.call; ran_callback = true }
1171
+
1172
+ @record.save
1173
+ assert ran_callback
1174
+ end
1175
+ end
1176
+
1177
+ class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
1178
+ def setup
1179
+ @model = new_model
1180
+ @machine = StateMachine::Machine.new(@model)
1181
+ @machine.event :ignite do
1182
+ transition :parked => :idling
1183
+ end
1184
+
1185
+ @record = @model.new
1186
+ @record.state = 'parked'
1187
+ @record.state_event = 'ignite'
1188
+ end
1189
+
1190
+ def test_should_fail_if_event_is_invalid
1191
+ @record.state_event = 'invalid'
1192
+ assert_raise(Mongoid::Errors::Validations) { @record.save! }
1193
+ end
1194
+
1195
+ def test_should_fail_if_event_has_no_transition
1196
+ @record.state = 'idling'
1197
+ assert_raise(Mongoid::Errors::Validations) { @record.save! }
1198
+ end
1199
+
1200
+ def test_should_be_successful_if_event_has_transition
1201
+ assert_equal true, @record.save!
1202
+ end
1203
+
1204
+ def test_should_run_before_callbacks
1205
+ ran_callback = false
1206
+ @machine.before_transition { ran_callback = true }
1207
+
1208
+ @record.save!
1209
+ assert ran_callback
1210
+ end
1211
+
1212
+ def test_should_run_before_callbacks_once
1213
+ before_count = 0
1214
+ @machine.before_transition { before_count += 1 }
1215
+
1216
+ @record.save!
1217
+ assert_equal 1, before_count
1218
+ end
1219
+
1220
+ def test_should_run_around_callbacks_before_yield
1221
+ ran_callback = false
1222
+ @machine.around_transition {|block| ran_callback = true; block.call }
1223
+
1224
+ @record.save!
1225
+ assert ran_callback
1226
+ end
1227
+
1228
+ def test_should_run_around_callbacks_before_yield_once
1229
+ around_before_count = 0
1230
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1231
+
1232
+ @record.save!
1233
+ assert_equal 1, around_before_count
1234
+ end
1235
+
1236
+ def test_should_persist_new_state
1237
+ @record.save!
1238
+ assert_equal 'idling', @record.state
1239
+ end
1240
+
1241
+ def test_should_persist_new_state
1242
+ @record.save!
1243
+ assert_equal 'idling', @record.state
1244
+ end
1245
+
1246
+ def test_should_run_after_callbacks
1247
+ ran_callback = false
1248
+ @machine.after_transition { ran_callback = true }
1249
+
1250
+ @record.save!
1251
+ assert ran_callback
1252
+ end
1253
+
1254
+ def test_should_run_around_callbacks_after_yield
1255
+ ran_callback = false
1256
+ @machine.around_transition {|block| block.call; ran_callback = true }
1257
+
1258
+ @record.save!
1259
+ assert ran_callback
1260
+ end
1261
+ end
1262
+
1263
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1264
+ def setup
1265
+ @superclass = new_model do
1266
+ def persist
1267
+ upsert
1268
+ end
1269
+ end
1270
+ @model = Class.new(@superclass)
1271
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
1272
+ @machine.event :ignite do
1273
+ transition :parked => :idling
1274
+ end
1275
+
1276
+ @record = @model.new
1277
+ @record.state = 'parked'
1278
+ @record.state_event = 'ignite'
1279
+ end
1280
+
1281
+ def test_should_not_transition_on_valid?
1282
+ @record.valid?
1283
+ assert_equal 'parked', @record.state
1284
+ end
1285
+
1286
+ def test_should_not_transition_on_save
1287
+ @record.save
1288
+ assert_equal 'parked', @record.state
1289
+ end
1290
+
1291
+ def test_should_not_transition_on_save!
1292
+ @record.save!
1293
+ assert_equal 'parked', @record.state
1294
+ end
1295
+
1296
+ def test_should_transition_on_custom_action
1297
+ @record.persist
1298
+ assert_equal 'idling', @record.state
1299
+ end
1300
+ end
1301
+
1302
+ class MachineWithScopesTest < BaseTestCase
1303
+ def setup
1304
+ @model = new_model
1305
+ @machine = StateMachine::Machine.new(@model)
1306
+ @machine.state :parked, :first_gear
1307
+ @machine.state :idling, :value => lambda {'idling'}
1308
+ end
1309
+
1310
+ def test_should_create_singular_with_scope
1311
+ assert @model.respond_to?(:with_state)
1312
+ end
1313
+
1314
+ def test_should_only_include_records_with_state_in_singular_with_scope
1315
+ parked = @model.create :state => 'parked'
1316
+ idling = @model.create :state => 'idling'
1317
+
1318
+ assert_equal [parked], @model.with_state(:parked).to_a
1319
+ end
1320
+
1321
+ def test_should_create_plural_with_scope
1322
+ assert @model.respond_to?(:with_states)
1323
+ end
1324
+
1325
+ def test_should_only_include_records_with_states_in_plural_with_scope
1326
+ parked = @model.create :state => 'parked'
1327
+ idling = @model.create :state => 'idling'
1328
+
1329
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).to_a
1330
+ end
1331
+
1332
+ def test_should_create_singular_without_scope
1333
+ assert @model.respond_to?(:without_state)
1334
+ end
1335
+
1336
+ def test_should_only_include_records_without_state_in_singular_without_scope
1337
+ parked = @model.create :state => 'parked'
1338
+ idling = @model.create :state => 'idling'
1339
+
1340
+ assert_equal [parked], @model.without_state(:idling).to_a
1341
+ end
1342
+
1343
+ def test_should_create_plural_without_scope
1344
+ assert @model.respond_to?(:without_states)
1345
+ end
1346
+
1347
+ def test_should_only_include_records_without_states_in_plural_without_scope
1348
+ parked = @model.create :state => 'parked'
1349
+ idling = @model.create :state => 'idling'
1350
+ first_gear = @model.create :state => 'first_gear'
1351
+
1352
+ assert_equal [parked, idling], @model.without_states(:first_gear).to_a
1353
+ end
1354
+
1355
+ def test_should_allow_chaining_scopes
1356
+ parked = @model.create :state => 'parked'
1357
+ idling = @model.create :state => 'idling'
1358
+
1359
+ assert_equal [idling], @model.without_state(:parked).with_state(:idling).all
1360
+ end
1361
+ end
1362
+
1363
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1364
+ def setup
1365
+ @model = new_model
1366
+ @machine = StateMachine::Machine.new(@model, :state)
1367
+
1368
+ MongoidTest.const_set('Foo', @model)
1369
+
1370
+ @subclass = Class.new(@model) do
1371
+ def self.name
1372
+ 'MongoidTest::SubFoo'
1373
+ end
1374
+ end
1375
+ @subclass_machine = @subclass.state_machine(:state) {}
1376
+ @subclass_machine.state :parked, :idling, :first_gear
1377
+
1378
+ MongoidTest.const_set('SubFoo', @subclass)
1379
+ end
1380
+
1381
+ def test_should_only_include_records_with_subclass_states_in_with_scope
1382
+ parked = @subclass.create :state => 'parked'
1383
+ idling = @subclass.create :state => 'idling'
1384
+
1385
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).to_a
1386
+ end
1387
+
1388
+ def test_should_only_include_records_without_subclass_states_in_without_scope
1389
+ parked = @subclass.create :state => 'parked'
1390
+ idling = @subclass.create :state => 'idling'
1391
+ first_gear = @subclass.create :state => 'first_gear'
1392
+
1393
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).to_a
1394
+ end
1395
+
1396
+ def teardown
1397
+ MongoidTest.send(:remove_const, 'SubFoo')
1398
+ MongoidTest.send(:remove_const, 'Foo')
1399
+ end
1400
+ end
1401
+
1402
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
1403
+ def setup
1404
+ @model = new_model
1405
+ @machine = StateMachine::Machine.new(@model, :status)
1406
+ end
1407
+
1408
+ def test_should_create_singular_with_scope
1409
+ assert @model.respond_to?(:with_status)
1410
+ end
1411
+
1412
+ def test_should_create_plural_with_scope
1413
+ assert @model.respond_to?(:with_statuses)
1414
+ end
1415
+ end
1416
+
1417
+ class MachineWithDefaultScope < BaseTestCase
1418
+ def setup
1419
+ @model = new_model
1420
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
1421
+ @machine.state :idling
1422
+
1423
+ @model.class_eval do
1424
+ default_scope with_state(:parked, :idling)
1425
+ end
1426
+ end
1427
+
1428
+ def test_should_set_initial_state_on_created_object
1429
+ object = @model.new
1430
+ assert_equal 'parked', object.state
1431
+ end
1432
+ end
1433
+
1434
+ class MachineWithInternationalizationTest < BaseTestCase
1435
+ def setup
1436
+ I18n.backend = I18n::Backend::Simple.new
1437
+
1438
+ # Initialize the backend
1439
+ StateMachine::Machine.new(new_model)
1440
+ I18n.backend.translate(:en, 'mongoid.errors.messages.invalid_transition', :event => 'ignite', :value => 'idling')
1441
+
1442
+ @model = new_model
1443
+ end
1444
+
1445
+ def test_should_use_defaults
1446
+ I18n.backend.store_translations(:en, {
1447
+ :mongoid => {:errors => {:messages => {:invalid_transition => 'cannot %{event}'}}}
1448
+ })
1449
+
1450
+ machine = StateMachine::Machine.new(@model)
1451
+ machine.state :parked, :idling
1452
+ machine.event :ignite
1453
+
1454
+ record = @model.new(:state => 'idling')
1455
+
1456
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1457
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1458
+ end
1459
+
1460
+ def test_should_allow_customized_error_key
1461
+ I18n.backend.store_translations(:en, {
1462
+ :mongoid => {:errors => {:messages => {:bad_transition => 'cannot %{event}'}}}
1463
+ })
1464
+
1465
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
1466
+ machine.state :parked, :idling
1467
+
1468
+ record = @model.new(:state => 'idling')
1469
+
1470
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1471
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1472
+ end
1473
+
1474
+ def test_should_allow_customized_error_string
1475
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot %{event}'})
1476
+ machine.state :parked, :idling
1477
+
1478
+ record = @model.new(:state => 'idling')
1479
+
1480
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1481
+ assert_equal ['State cannot ignite'], record.errors.full_messages
1482
+ end
1483
+
1484
+ def test_should_allow_customized_state_key_scoped_to_class_and_machine
1485
+ I18n.backend.store_translations(:en, {
1486
+ :mongoid => {:state_machines => {:'mongoid_test/foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
1487
+ })
1488
+
1489
+ machine = StateMachine::Machine.new(@model)
1490
+ machine.state :parked
1491
+
1492
+ assert_equal 'shutdown', machine.state(:parked).human_name
1493
+ end
1494
+
1495
+ def test_should_allow_customized_state_key_scoped_to_machine
1496
+ I18n.backend.store_translations(:en, {
1497
+ :mongoid => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
1498
+ })
1499
+
1500
+ machine = StateMachine::Machine.new(@model)
1501
+ machine.state :parked
1502
+
1503
+ assert_equal 'shutdown', machine.state(:parked).human_name
1504
+ end
1505
+
1506
+ def test_should_allow_customized_state_key_unscoped
1507
+ I18n.backend.store_translations(:en, {
1508
+ :mongoid => {:state_machines => {:states => {:parked => 'shutdown'}}}
1509
+ })
1510
+
1511
+ machine = StateMachine::Machine.new(@model)
1512
+ machine.state :parked
1513
+
1514
+ assert_equal 'shutdown', machine.state(:parked).human_name
1515
+ end
1516
+
1517
+ def test_should_allow_customized_event_key_scoped_to_class_and_machine
1518
+ I18n.backend.store_translations(:en, {
1519
+ :mongoid => {:state_machines => {:'mongoid_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
1520
+ })
1521
+
1522
+ machine = StateMachine::Machine.new(@model)
1523
+ machine.event :park
1524
+
1525
+ assert_equal 'stop', machine.event(:park).human_name
1526
+ end
1527
+
1528
+ def test_should_allow_customized_event_key_scoped_to_machine
1529
+ I18n.backend.store_translations(:en, {
1530
+ :mongoid => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
1531
+ })
1532
+
1533
+ machine = StateMachine::Machine.new(@model)
1534
+ machine.event :park
1535
+
1536
+ assert_equal 'stop', machine.event(:park).human_name
1537
+ end
1538
+
1539
+ def test_should_allow_customized_event_key_unscoped
1540
+ I18n.backend.store_translations(:en, {
1541
+ :mongoid => {:state_machines => {:events => {:park => 'stop'}}}
1542
+ })
1543
+
1544
+ machine = StateMachine::Machine.new(@model)
1545
+ machine.event :park
1546
+
1547
+ assert_equal 'stop', machine.event(:park).human_name
1548
+ end
1549
+
1550
+ def test_should_only_add_locale_once_in_load_path
1551
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongoid/locale\.rb$}}.length
1552
+
1553
+ # Create another Mongoid model that will triger the i18n feature
1554
+ new_model
1555
+
1556
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{mongoid/locale\.rb$}}.length
1557
+ end
1558
+
1559
+ def test_should_add_locale_to_beginning_of_load_path
1560
+ @original_load_path = I18n.load_path
1561
+ I18n.backend = I18n::Backend::Simple.new
1562
+
1563
+ app_locale = File.dirname(__FILE__) + '/../../files/en.yml'
1564
+ default_locale = File.dirname(__FILE__) + '/../../../lib/state_machine/integrations/mongoid/locale.rb'
1565
+ I18n.load_path = [app_locale]
1566
+
1567
+ StateMachine::Machine.new(@model)
1568
+
1569
+ assert_equal [default_locale, app_locale].map {|path| File.expand_path(path)}, I18n.load_path.map {|path| File.expand_path(path)}
1570
+ ensure
1571
+ I18n.load_path = @original_load_path
1572
+ end
1573
+
1574
+ def test_should_prefer_other_locales_first
1575
+ @original_load_path = I18n.load_path
1576
+ I18n.backend = I18n::Backend::Simple.new
1577
+ I18n.load_path = [File.dirname(__FILE__) + '/../../files/en.yml']
1578
+
1579
+ machine = StateMachine::Machine.new(@model)
1580
+ machine.state :parked, :idling
1581
+ machine.event :ignite
1582
+
1583
+ record = @model.new(:state => 'idling')
1584
+
1585
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
1586
+ assert_equal ['State cannot transition'], record.errors.full_messages
1587
+ ensure
1588
+ I18n.load_path = @original_load_path
1589
+ end
1590
+ end
1591
+ end