hsume2-state_machine 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/CHANGELOG.rdoc +413 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +717 -0
  4. data/Rakefile +77 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine.rb +448 -0
  28. data/lib/state_machine/alternate_machine.rb +79 -0
  29. data/lib/state_machine/assertions.rb +36 -0
  30. data/lib/state_machine/branch.rb +224 -0
  31. data/lib/state_machine/callback.rb +236 -0
  32. data/lib/state_machine/condition_proxy.rb +94 -0
  33. data/lib/state_machine/error.rb +13 -0
  34. data/lib/state_machine/eval_helpers.rb +86 -0
  35. data/lib/state_machine/event.rb +304 -0
  36. data/lib/state_machine/event_collection.rb +139 -0
  37. data/lib/state_machine/extensions.rb +149 -0
  38. data/lib/state_machine/initializers.rb +4 -0
  39. data/lib/state_machine/initializers/merb.rb +1 -0
  40. data/lib/state_machine/initializers/rails.rb +25 -0
  41. data/lib/state_machine/integrations.rb +110 -0
  42. data/lib/state_machine/integrations/active_model.rb +502 -0
  43. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  44. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  45. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  46. data/lib/state_machine/integrations/active_record.rb +424 -0
  47. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  48. data/lib/state_machine/integrations/active_record/versions.rb +143 -0
  49. data/lib/state_machine/integrations/base.rb +91 -0
  50. data/lib/state_machine/integrations/data_mapper.rb +392 -0
  51. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  52. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  53. data/lib/state_machine/integrations/mongo_mapper.rb +272 -0
  54. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  55. data/lib/state_machine/integrations/mongo_mapper/versions.rb +110 -0
  56. data/lib/state_machine/integrations/mongoid.rb +357 -0
  57. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  58. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  59. data/lib/state_machine/integrations/sequel.rb +428 -0
  60. data/lib/state_machine/integrations/sequel/versions.rb +36 -0
  61. data/lib/state_machine/machine.rb +1873 -0
  62. data/lib/state_machine/machine_collection.rb +87 -0
  63. data/lib/state_machine/matcher.rb +123 -0
  64. data/lib/state_machine/matcher_helpers.rb +54 -0
  65. data/lib/state_machine/node_collection.rb +157 -0
  66. data/lib/state_machine/path.rb +120 -0
  67. data/lib/state_machine/path_collection.rb +90 -0
  68. data/lib/state_machine/state.rb +271 -0
  69. data/lib/state_machine/state_collection.rb +112 -0
  70. data/lib/state_machine/transition.rb +458 -0
  71. data/lib/state_machine/transition_collection.rb +244 -0
  72. data/lib/tasks/state_machine.rake +1 -0
  73. data/lib/tasks/state_machine.rb +27 -0
  74. data/test/files/en.yml +17 -0
  75. data/test/files/switch.rb +11 -0
  76. data/test/functional/alternate_state_machine_test.rb +122 -0
  77. data/test/functional/state_machine_test.rb +993 -0
  78. data/test/test_helper.rb +4 -0
  79. data/test/unit/assertions_test.rb +40 -0
  80. data/test/unit/branch_test.rb +890 -0
  81. data/test/unit/callback_test.rb +701 -0
  82. data/test/unit/condition_proxy_test.rb +328 -0
  83. data/test/unit/error_test.rb +43 -0
  84. data/test/unit/eval_helpers_test.rb +222 -0
  85. data/test/unit/event_collection_test.rb +358 -0
  86. data/test/unit/event_test.rb +985 -0
  87. data/test/unit/integrations/active_model_test.rb +1097 -0
  88. data/test/unit/integrations/active_record_test.rb +2021 -0
  89. data/test/unit/integrations/base_test.rb +99 -0
  90. data/test/unit/integrations/data_mapper_test.rb +1909 -0
  91. data/test/unit/integrations/mongo_mapper_test.rb +1611 -0
  92. data/test/unit/integrations/mongoid_test.rb +1591 -0
  93. data/test/unit/integrations/sequel_test.rb +1523 -0
  94. data/test/unit/integrations_test.rb +61 -0
  95. data/test/unit/invalid_event_test.rb +20 -0
  96. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  97. data/test/unit/invalid_transition_test.rb +77 -0
  98. data/test/unit/machine_collection_test.rb +599 -0
  99. data/test/unit/machine_test.rb +3043 -0
  100. data/test/unit/matcher_helpers_test.rb +37 -0
  101. data/test/unit/matcher_test.rb +155 -0
  102. data/test/unit/node_collection_test.rb +217 -0
  103. data/test/unit/path_collection_test.rb +266 -0
  104. data/test/unit/path_test.rb +485 -0
  105. data/test/unit/state_collection_test.rb +310 -0
  106. data/test/unit/state_machine_test.rb +31 -0
  107. data/test/unit/state_test.rb +924 -0
  108. data/test/unit/transition_collection_test.rb +2102 -0
  109. data/test/unit/transition_test.rb +1541 -0
  110. metadata +207 -0
@@ -0,0 +1,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