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,61 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class IntegrationMatcherTest < Test::Unit::TestCase
4
+ def setup
5
+ @klass = Class.new
6
+ end
7
+
8
+ def test_should_return_nil_if_no_match_found
9
+ assert_nil StateMachine::Integrations.match(@klass)
10
+ end
11
+
12
+ def test_should_return_integration_class_if_match_found
13
+ integration = Module.new do
14
+ include StateMachine::Integrations::Base
15
+
16
+ def self.available?
17
+ true
18
+ end
19
+
20
+ def self.matches?(klass)
21
+ true
22
+ end
23
+ end
24
+ StateMachine::Integrations.const_set('Custom', integration)
25
+
26
+ assert_equal integration, StateMachine::Integrations.match(@klass)
27
+ ensure
28
+ StateMachine::Integrations.send(:remove_const, 'Custom')
29
+ end
30
+ end
31
+
32
+ class IntegrationFinderTest < Test::Unit::TestCase
33
+ def test_should_find_base
34
+ assert_equal StateMachine::Integrations::Base, StateMachine::Integrations.find_by_name(:base)
35
+ end
36
+
37
+ def test_should_find_active_model
38
+ assert_equal StateMachine::Integrations::ActiveModel, StateMachine::Integrations.find_by_name(:active_model)
39
+ end
40
+
41
+ def test_should_find_active_record
42
+ assert_equal StateMachine::Integrations::ActiveRecord, StateMachine::Integrations.find_by_name(:active_record)
43
+ end
44
+
45
+ def test_should_find_data_mapper
46
+ assert_equal StateMachine::Integrations::DataMapper, StateMachine::Integrations.find_by_name(:data_mapper)
47
+ end
48
+
49
+ def test_should_find_mongo_mapper
50
+ assert_equal StateMachine::Integrations::MongoMapper, StateMachine::Integrations.find_by_name(:mongo_mapper)
51
+ end
52
+
53
+ def test_should_find_sequel
54
+ assert_equal StateMachine::Integrations::Sequel, StateMachine::Integrations.find_by_name(:sequel)
55
+ end
56
+
57
+ def test_should_raise_an_exception_if_invalid
58
+ exception = assert_raise(StateMachine::IntegrationNotFound) { StateMachine::Integrations.find_by_name(:invalid) }
59
+ assert_equal ':invalid is an invalid integration', exception.message
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class InvalidEventTest < Test::Unit::TestCase
4
+ def setup
5
+ @object = Object.new
6
+ @invalid_event = StateMachine::InvalidEvent.new(@object, :invalid)
7
+ end
8
+
9
+ def test_should_have_an_object
10
+ assert_equal @object, @invalid_event.object
11
+ end
12
+
13
+ def test_should_have_an_event
14
+ assert_equal :invalid, @invalid_event.event
15
+ end
16
+
17
+ def test_should_generate_a_message
18
+ assert_equal ':invalid is an unknown state machine event', @invalid_event.message
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class InvalidParallelTransitionTest < Test::Unit::TestCase
4
+ def setup
5
+ @object = Object.new
6
+ @events = [:ignite, :disable_alarm]
7
+
8
+ @invalid_transition = StateMachine::InvalidParallelTransition.new(@object, @events)
9
+ end
10
+
11
+ def test_should_have_an_object
12
+ assert_equal @object, @invalid_transition.object
13
+ end
14
+
15
+ def test_should_have_events
16
+ assert_equal @events, @invalid_transition.events
17
+ end
18
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class InvalidTransitionTest < Test::Unit::TestCase
4
+ def setup
5
+ @klass = Class.new
6
+ @machine = StateMachine::Machine.new(@klass)
7
+ @state = @machine.state :parked
8
+ @machine.event :ignite
9
+
10
+ @object = @klass.new
11
+ @object.state = 'parked'
12
+
13
+ @invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :ignite)
14
+ end
15
+
16
+ def test_should_have_an_object
17
+ assert_equal @object, @invalid_transition.object
18
+ end
19
+
20
+ def test_should_have_a_machine
21
+ assert_equal @machine, @invalid_transition.machine
22
+ end
23
+
24
+ def test_should_have_an_event
25
+ assert_equal :ignite, @invalid_transition.event
26
+ end
27
+
28
+ def test_should_have_a_qualified_event
29
+ assert_equal :ignite, @invalid_transition.qualified_event
30
+ end
31
+
32
+ def test_should_have_a_from_value
33
+ assert_equal 'parked', @invalid_transition.from
34
+ end
35
+
36
+ def test_should_have_a_from_name
37
+ assert_equal :parked, @invalid_transition.from_name
38
+ end
39
+
40
+ def test_should_have_a_qualified_from_name
41
+ assert_equal :parked, @invalid_transition.qualified_from_name
42
+ end
43
+
44
+ def test_should_generate_a_message
45
+ assert_equal 'Cannot transition state via :ignite from :parked', @invalid_transition.message
46
+ end
47
+ end
48
+
49
+ class InvalidTransitionWithNamespaceTest < Test::Unit::TestCase
50
+ def setup
51
+ @klass = Class.new
52
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
53
+ @state = @machine.state :active
54
+ @machine.event :disable
55
+
56
+ @object = @klass.new
57
+ @object.state = 'active'
58
+
59
+ @invalid_transition = StateMachine::InvalidTransition.new(@object, @machine, :disable)
60
+ end
61
+
62
+ def test_should_have_an_event
63
+ assert_equal :disable, @invalid_transition.event
64
+ end
65
+
66
+ def test_should_have_a_qualified_event
67
+ assert_equal :disable_alarm, @invalid_transition.qualified_event
68
+ end
69
+
70
+ def test_should_have_a_from_name
71
+ assert_equal :active, @invalid_transition.from_name
72
+ end
73
+
74
+ def test_should_have_a_qualified_from_name
75
+ assert_equal :alarm_active, @invalid_transition.qualified_from_name
76
+ end
77
+ end
@@ -0,0 +1,599 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class MachineCollectionByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @machines = StateMachine::MachineCollection.new
6
+ end
7
+
8
+ def test_should_not_have_any_machines
9
+ assert @machines.empty?
10
+ end
11
+ end
12
+
13
+ class MachineCollectionStateInitializationTest < Test::Unit::TestCase
14
+ def setup
15
+ @machines = StateMachine::MachineCollection.new
16
+
17
+ @klass = Class.new
18
+
19
+ @machines[:state] = StateMachine::Machine.new(@klass, :state, :initial => :parked)
20
+ @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => lambda {|object| :active})
21
+ @machines[:alarm_state].state :active, :value => lambda {'active'}
22
+
23
+ # Prevent the auto-initialization hook from firing
24
+ @klass.class_eval do
25
+ def initialize
26
+ end
27
+ end
28
+
29
+ @object = @klass.new
30
+ @object.state = nil
31
+ @object.alarm_state = nil
32
+ end
33
+
34
+ def test_should_raise_exception_if_invalid_option_specified
35
+ assert_raise(ArgumentError) {@machines.initialize_states(@object, :invalid => true)}
36
+ end
37
+
38
+ def test_should_only_initialize_static_states_prior_to_block
39
+ @machines.initialize_states(@object) do
40
+ @state_in_block = @object.state
41
+ @alarm_state_in_block = @object.alarm_state
42
+ end
43
+
44
+ assert_equal 'parked', @state_in_block
45
+ assert_nil @alarm_state_in_block
46
+ end
47
+
48
+ def test_should_only_initialize_dynamic_states_after_block
49
+ @machines.initialize_states(@object) do
50
+ @alarm_state_in_block = @object.alarm_state
51
+ end
52
+
53
+ assert_nil @alarm_state_in_block
54
+ assert_equal 'active', @object.alarm_state
55
+ end
56
+
57
+ def test_should_initialize_all_states_without_block
58
+ @machines.initialize_states(@object)
59
+
60
+ assert_equal 'parked', @object.state
61
+ assert_equal 'active', @object.alarm_state
62
+ end
63
+
64
+ def test_should_skip_static_states_if_disabled
65
+ @machines.initialize_states(@object, :static => false)
66
+ assert_nil @object.state
67
+ assert_equal 'active', @object.alarm_state
68
+ end
69
+
70
+ def test_should_initialize_existing_static_states_by_default
71
+ @object.state = 'idling'
72
+ @machines.initialize_states(@object)
73
+ assert_equal 'parked', @object.state
74
+ end
75
+
76
+ def test_should_initialize_existing_static_states_if_forced
77
+ @object.state = 'idling'
78
+ @machines.initialize_states(@object, :static => :force)
79
+ assert_equal 'parked', @object.state
80
+ end
81
+
82
+ def test_should_not_initialize_existing_static_states_if_not_forced
83
+ @object.state = 'idling'
84
+ @machines.initialize_states(@object, :static => true)
85
+ assert_equal 'idling', @object.state
86
+ end
87
+
88
+ def test_should_skip_dynamic_states_if_disabled
89
+ @machines.initialize_states(@object, :dynamic => false)
90
+ assert_equal 'parked', @object.state
91
+ assert_nil @object.alarm_state
92
+ end
93
+
94
+ def test_should_not_initialize_existing_dynamic_states_by_default
95
+ @object.alarm_state = 'inactive'
96
+ @machines.initialize_states(@object)
97
+ assert_equal 'inactive', @object.alarm_state
98
+ end
99
+
100
+ def test_should_initialize_existing_dynamic_states_if_forced
101
+ @object.alarm_state = 'inactive'
102
+ @machines.initialize_states(@object, :dynamic => :force)
103
+ assert_equal 'active', @object.alarm_state
104
+ end
105
+
106
+ def test_should_not_initialize_existing_dynamic_states_if_not_forced
107
+ @object.alarm_state = 'inactive'
108
+ @machines.initialize_states(@object, :dynamic => true)
109
+ assert_equal 'inactive', @object.alarm_state
110
+ end
111
+ end
112
+
113
+ class MachineCollectionFireTest < Test::Unit::TestCase
114
+ def setup
115
+ @machines = StateMachine::MachineCollection.new
116
+
117
+ @klass = Class.new do
118
+ attr_reader :saved
119
+
120
+ def save
121
+ @saved = true
122
+ end
123
+ end
124
+
125
+ # First machine
126
+ @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
127
+ @state.event :ignite do
128
+ transition :parked => :idling
129
+ end
130
+ @state.event :park do
131
+ transition :idling => :parked
132
+ end
133
+
134
+ # Second machine
135
+ @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm')
136
+ @alarm_state.event :enable do
137
+ transition :off => :active
138
+ end
139
+ @alarm_state.event :disable do
140
+ transition :active => :off
141
+ end
142
+
143
+ @object = @klass.new
144
+ end
145
+
146
+ def test_should_raise_exception_if_invalid_event_specified
147
+ exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :invalid) }
148
+ assert_equal :invalid, exception.event
149
+
150
+ exception = assert_raise(StateMachine::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) }
151
+ assert_equal :invalid, exception.event
152
+ end
153
+
154
+ def test_should_fail_if_any_event_cannot_transition
155
+ assert !@machines.fire_events(@object, :park, :disable_alarm)
156
+ assert_equal 'parked', @object.state
157
+ assert_equal 'active', @object.alarm_state
158
+ assert !@object.saved
159
+
160
+ assert !@machines.fire_events(@object, :ignite, :enable_alarm)
161
+ assert_equal 'parked', @object.state
162
+ assert_equal 'active', @object.alarm_state
163
+ assert !@object.saved
164
+ end
165
+
166
+ def test_should_run_failure_callbacks_if_any_event_cannot_transition
167
+ @machines[:state].after_failure {@state_failure_run = true}
168
+ @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
169
+
170
+ assert !@machines.fire_events(@object, :park, :disable_alarm)
171
+ assert @state_failure_run
172
+ assert !@alarm_state_failure_run
173
+ end
174
+
175
+ def test_should_be_successful_if_all_events_transition
176
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
177
+ assert_equal 'idling', @object.state
178
+ assert_equal 'off', @object.alarm_state
179
+ assert @object.saved
180
+ end
181
+
182
+ def test_should_not_save_if_skipping_action
183
+ assert @machines.fire_events(@object, :ignite, :disable_alarm, false)
184
+ assert_equal 'idling', @object.state
185
+ assert_equal 'off', @object.alarm_state
186
+ assert !@object.saved
187
+ end
188
+ end
189
+
190
+ class MachineCollectionFireWithTransactionsTest < Test::Unit::TestCase
191
+ def setup
192
+ @machines = StateMachine::MachineCollection.new
193
+
194
+ @klass = Class.new do
195
+ attr_accessor :allow_save
196
+
197
+ def save
198
+ @allow_save
199
+ end
200
+ end
201
+
202
+ StateMachine::Integrations.const_set('Custom', Module.new do
203
+ include StateMachine::Integrations::Base
204
+
205
+ attr_reader :rolled_back
206
+
207
+ def transaction(object)
208
+ @rolled_back = yield
209
+ end
210
+ end)
211
+
212
+ # First machine
213
+ @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save, :integration => :custom)
214
+ @state.event :ignite do
215
+ transition :parked => :idling
216
+ end
217
+
218
+ # Second machine
219
+ @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :action => :save, :namespace => 'alarm', :integration => :custom)
220
+ @alarm_state.event :disable do
221
+ transition :active => :off
222
+ end
223
+
224
+ @object = @klass.new
225
+ end
226
+
227
+ def test_should_not_rollback_if_successful
228
+ @object.allow_save = true
229
+
230
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
231
+ assert_equal true, @state.rolled_back
232
+ assert_nil @alarm_state.rolled_back
233
+ assert_equal 'idling', @object.state
234
+ assert_equal 'off', @object.alarm_state
235
+ end
236
+
237
+ def test_should_rollback_if_not_successful
238
+ @object.allow_save = false
239
+
240
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
241
+ assert_equal false, @state.rolled_back
242
+ assert_nil @alarm_state.rolled_back
243
+ assert_equal 'parked', @object.state
244
+ assert_equal 'active', @object.alarm_state
245
+ end
246
+
247
+ def test_should_run_failure_callbacks_if_not_successful
248
+ @object.allow_save = false
249
+
250
+ @machines[:state].after_failure {@state_failure_run = true}
251
+ @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
252
+
253
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
254
+ assert @state_failure_run
255
+ assert @alarm_state_failure_run
256
+ end
257
+
258
+ def teardown
259
+ StateMachine::Integrations.send(:remove_const, 'Custom')
260
+ end
261
+ end
262
+
263
+ class MachineCollectionFireWithValidationsTest < Test::Unit::TestCase
264
+ def setup
265
+ StateMachine::Integrations.const_set('Custom', Module.new do
266
+ include StateMachine::Integrations::Base
267
+
268
+ def invalidate(object, attribute, message, values = [])
269
+ (object.errors ||= []) << generate_message(message, values)
270
+ end
271
+
272
+ def reset(object)
273
+ object.errors = []
274
+ end
275
+ end)
276
+
277
+ @klass = Class.new do
278
+ attr_accessor :errors
279
+
280
+ def initialize
281
+ @errors = []
282
+ super
283
+ end
284
+ end
285
+
286
+ @machines = StateMachine::MachineCollection.new
287
+ @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :integration => :custom)
288
+ @state.event :ignite do
289
+ transition :parked => :idling
290
+ end
291
+
292
+ @machines[:alarm_state] = @alarm_state = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active, :namespace => 'alarm', :integration => :custom)
293
+ @alarm_state.event :disable do
294
+ transition :active => :off
295
+ end
296
+
297
+ @object = @klass.new
298
+ end
299
+
300
+ def test_should_not_invalidate_if_transitions_exist
301
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
302
+ assert_equal [], @object.errors
303
+ end
304
+
305
+ def test_should_invalidate_if_no_transitions_exist
306
+ @object.state = 'idling'
307
+ @object.alarm_state = 'off'
308
+
309
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
310
+ assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors
311
+ end
312
+
313
+ def test_should_run_failure_callbacks_if_no_transitions_exist
314
+ @object.state = 'idling'
315
+ @object.alarm_state = 'off'
316
+
317
+ @machines[:state].after_failure {@state_failure_run = true}
318
+ @machines[:alarm_state].after_failure {@alarm_state_failure_run = true}
319
+
320
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
321
+ assert @state_failure_run
322
+ assert @alarm_state_failure_run
323
+ end
324
+
325
+ def teardown
326
+ StateMachine::Integrations.send(:remove_const, 'Custom')
327
+ end
328
+ end
329
+
330
+ class MachineCollectionTransitionsWithoutEventsTest < Test::Unit::TestCase
331
+ def setup
332
+ @klass = Class.new
333
+
334
+ @machines = StateMachine::MachineCollection.new
335
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
336
+ @machine.event :ignite do
337
+ transition :parked => :idling
338
+ end
339
+
340
+ @object = @klass.new
341
+ @object.state_event = nil
342
+ @transitions = @machines.transitions(@object, :save)
343
+ end
344
+
345
+ def test_should_be_empty
346
+ assert @transitions.empty?
347
+ end
348
+
349
+ def test_should_perform
350
+ assert_equal true, @transitions.perform
351
+ end
352
+ end
353
+
354
+ class MachineCollectionTransitionsWithBlankEventsTest < Test::Unit::TestCase
355
+ def setup
356
+ @klass = Class.new
357
+
358
+ @machines = StateMachine::MachineCollection.new
359
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
360
+ @machine.event :ignite do
361
+ transition :parked => :idling
362
+ end
363
+
364
+ @object = @klass.new
365
+ @object.state_event = ''
366
+ @transitions = @machines.transitions(@object, :save)
367
+ end
368
+
369
+ def test_should_be_empty
370
+ assert @transitions.empty?
371
+ end
372
+
373
+ def test_should_perform
374
+ assert_equal true, @transitions.perform
375
+ end
376
+ end
377
+
378
+ class MachineCollectionTransitionsWithInvalidEventsTest < Test::Unit::TestCase
379
+ def setup
380
+ @klass = Class.new
381
+
382
+ @machines = StateMachine::MachineCollection.new
383
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
384
+ @machine.event :ignite do
385
+ transition :parked => :idling
386
+ end
387
+
388
+ @object = @klass.new
389
+ @object.state_event = 'invalid'
390
+ @transitions = @machines.transitions(@object, :save)
391
+ end
392
+
393
+ def test_should_be_empty
394
+ assert @transitions.empty?
395
+ end
396
+
397
+ def test_should_not_perform
398
+ assert_equal false, @transitions.perform
399
+ end
400
+ end
401
+
402
+ class MachineCollectionTransitionsWithoutTransitionTest < Test::Unit::TestCase
403
+ def setup
404
+ @klass = Class.new
405
+
406
+ @machines = StateMachine::MachineCollection.new
407
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
408
+ @machine.event :ignite do
409
+ transition :parked => :idling
410
+ end
411
+
412
+ @object = @klass.new
413
+ @object.state = 'idling'
414
+ @object.state_event = 'ignite'
415
+ @transitions = @machines.transitions(@object, :save)
416
+ end
417
+
418
+ def test_should_be_empty
419
+ assert @transitions.empty?
420
+ end
421
+
422
+ def test_should_not_perform
423
+ assert_equal false, @transitions.perform
424
+ end
425
+ end
426
+
427
+ class MachineCollectionTransitionsWithTransitionTest < Test::Unit::TestCase
428
+ def setup
429
+ @klass = Class.new
430
+
431
+ @machines = StateMachine::MachineCollection.new
432
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
433
+ @machine.event :ignite do
434
+ transition :parked => :idling
435
+ end
436
+
437
+ @object = @klass.new
438
+ @object.state_event = 'ignite'
439
+ @transitions = @machines.transitions(@object, :save)
440
+ end
441
+
442
+ def test_should_not_be_empty
443
+ assert_equal 1, @transitions.length
444
+ end
445
+
446
+ def test_should_perform
447
+ assert_equal true, @transitions.perform
448
+ end
449
+ end
450
+
451
+ class MachineCollectionTransitionsWithSameActionsTest < Test::Unit::TestCase
452
+ def setup
453
+ @klass = Class.new
454
+
455
+ @machines = StateMachine::MachineCollection.new
456
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
457
+ @machine.event :ignite do
458
+ transition :parked => :idling
459
+ end
460
+ @machines[:status] = @machine = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save)
461
+ @machine.event :shift_up do
462
+ transition :first_gear => :second_gear
463
+ end
464
+
465
+ @object = @klass.new
466
+ @object.state_event = 'ignite'
467
+ @object.status_event = 'shift_up'
468
+ @transitions = @machines.transitions(@object, :save)
469
+ end
470
+
471
+ def test_should_not_be_empty
472
+ assert_equal 2, @transitions.length
473
+ end
474
+
475
+ def test_should_perform
476
+ assert_equal true, @transitions.perform
477
+ end
478
+ end
479
+
480
+ class MachineCollectionTransitionsWithDifferentActionsTest < Test::Unit::TestCase
481
+ def setup
482
+ @klass = Class.new
483
+
484
+ @machines = StateMachine::MachineCollection.new
485
+ @machines[:state] = @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
486
+ @state.event :ignite do
487
+ transition :parked => :idling
488
+ end
489
+ @machines[:status] = @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :persist)
490
+ @status.event :shift_up do
491
+ transition :first_gear => :second_gear
492
+ end
493
+
494
+ @object = @klass.new
495
+ @object.state_event = 'ignite'
496
+ @object.status_event = 'shift_up'
497
+ @transitions = @machines.transitions(@object, :save)
498
+ end
499
+
500
+ def test_should_only_select_matching_actions
501
+ assert_equal 1, @transitions.length
502
+ end
503
+ end
504
+
505
+ class MachineCollectionTransitionsWithExisitingTransitionsTest < Test::Unit::TestCase
506
+ def setup
507
+ @klass = Class.new
508
+
509
+ @machines = StateMachine::MachineCollection.new
510
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
511
+ @machine.event :ignite do
512
+ transition :parked => :idling
513
+ end
514
+
515
+ @object = @klass.new
516
+ @object.send(:state_event_transition=, StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling))
517
+ @transitions = @machines.transitions(@object, :save)
518
+ end
519
+
520
+ def test_should_not_be_empty
521
+ assert_equal 1, @transitions.length
522
+ end
523
+
524
+ def test_should_perform
525
+ assert_equal true, @transitions.perform
526
+ end
527
+ end
528
+
529
+ class MachineCollectionTransitionsWithCustomOptionsTest < Test::Unit::TestCase
530
+ def setup
531
+ @klass = Class.new
532
+
533
+ @machines = StateMachine::MachineCollection.new
534
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
535
+ @machine.event :ignite do
536
+ transition :parked => :idling
537
+ end
538
+
539
+ @object = @klass.new
540
+ @transitions = @machines.transitions(@object, :save, :after => false)
541
+ end
542
+
543
+ def test_should_use_custom_options
544
+ assert @transitions.skip_after
545
+ end
546
+ end
547
+
548
+ class MachineCollectionFireAttributesWithValidationsTest < Test::Unit::TestCase
549
+ def setup
550
+ @klass = Class.new do
551
+ attr_accessor :errors
552
+
553
+ def initialize
554
+ @errors = []
555
+ super
556
+ end
557
+ end
558
+
559
+ @machines = StateMachine::MachineCollection.new
560
+ @machines[:state] = @machine = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save)
561
+ @machine.event :ignite do
562
+ transition :parked => :idling
563
+ end
564
+
565
+ class << @machine
566
+ def invalidate(object, attribute, message, values = [])
567
+ (object.errors ||= []) << generate_message(message, values)
568
+ end
569
+
570
+ def reset(object)
571
+ object.errors = []
572
+ end
573
+ end
574
+
575
+ @object = @klass.new
576
+ end
577
+
578
+ def test_should_invalidate_if_event_is_invalid
579
+ @object.state_event = 'invalid'
580
+ @machines.transitions(@object, :save)
581
+
582
+ assert !@object.errors.empty?
583
+ end
584
+
585
+ def test_should_invalidate_if_no_transition_exists
586
+ @object.state = 'idling'
587
+ @object.state_event = 'ignite'
588
+ @machines.transitions(@object, :save)
589
+
590
+ assert !@object.errors.empty?
591
+ end
592
+
593
+ def test_should_not_invalidate_if_transition_exists
594
+ @object.state_event = 'ignite'
595
+ @machines.transitions(@object, :save)
596
+
597
+ assert @object.errors.empty?
598
+ end
599
+ end