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,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