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