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.
- data/CHANGELOG.rdoc +413 -0
- data/LICENSE +20 -0
- data/README.rdoc +717 -0
- data/Rakefile +77 -0
- data/examples/AutoShop_state.png +0 -0
- data/examples/Car_state.png +0 -0
- data/examples/TrafficLight_state.png +0 -0
- data/examples/Vehicle_state.png +0 -0
- data/examples/auto_shop.rb +11 -0
- data/examples/car.rb +19 -0
- data/examples/merb-rest/controller.rb +51 -0
- data/examples/merb-rest/model.rb +28 -0
- data/examples/merb-rest/view_edit.html.erb +24 -0
- data/examples/merb-rest/view_index.html.erb +23 -0
- data/examples/merb-rest/view_new.html.erb +13 -0
- data/examples/merb-rest/view_show.html.erb +17 -0
- data/examples/rails-rest/controller.rb +43 -0
- data/examples/rails-rest/migration.rb +11 -0
- data/examples/rails-rest/model.rb +23 -0
- data/examples/rails-rest/view_edit.html.erb +25 -0
- data/examples/rails-rest/view_index.html.erb +23 -0
- data/examples/rails-rest/view_new.html.erb +14 -0
- data/examples/rails-rest/view_show.html.erb +17 -0
- data/examples/traffic_light.rb +7 -0
- data/examples/vehicle.rb +31 -0
- data/init.rb +1 -0
- data/lib/state_machine.rb +448 -0
- data/lib/state_machine/alternate_machine.rb +79 -0
- data/lib/state_machine/assertions.rb +36 -0
- data/lib/state_machine/branch.rb +224 -0
- data/lib/state_machine/callback.rb +236 -0
- data/lib/state_machine/condition_proxy.rb +94 -0
- data/lib/state_machine/error.rb +13 -0
- data/lib/state_machine/eval_helpers.rb +86 -0
- data/lib/state_machine/event.rb +304 -0
- data/lib/state_machine/event_collection.rb +139 -0
- data/lib/state_machine/extensions.rb +149 -0
- data/lib/state_machine/initializers.rb +4 -0
- data/lib/state_machine/initializers/merb.rb +1 -0
- data/lib/state_machine/initializers/rails.rb +25 -0
- data/lib/state_machine/integrations.rb +110 -0
- data/lib/state_machine/integrations/active_model.rb +502 -0
- data/lib/state_machine/integrations/active_model/locale.rb +11 -0
- data/lib/state_machine/integrations/active_model/observer.rb +45 -0
- data/lib/state_machine/integrations/active_model/versions.rb +31 -0
- data/lib/state_machine/integrations/active_record.rb +424 -0
- data/lib/state_machine/integrations/active_record/locale.rb +20 -0
- data/lib/state_machine/integrations/active_record/versions.rb +143 -0
- data/lib/state_machine/integrations/base.rb +91 -0
- data/lib/state_machine/integrations/data_mapper.rb +392 -0
- data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
- data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
- data/lib/state_machine/integrations/mongo_mapper.rb +272 -0
- data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +110 -0
- data/lib/state_machine/integrations/mongoid.rb +357 -0
- data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
- data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
- data/lib/state_machine/integrations/sequel.rb +428 -0
- data/lib/state_machine/integrations/sequel/versions.rb +36 -0
- data/lib/state_machine/machine.rb +1873 -0
- data/lib/state_machine/machine_collection.rb +87 -0
- data/lib/state_machine/matcher.rb +123 -0
- data/lib/state_machine/matcher_helpers.rb +54 -0
- data/lib/state_machine/node_collection.rb +157 -0
- data/lib/state_machine/path.rb +120 -0
- data/lib/state_machine/path_collection.rb +90 -0
- data/lib/state_machine/state.rb +271 -0
- data/lib/state_machine/state_collection.rb +112 -0
- data/lib/state_machine/transition.rb +458 -0
- data/lib/state_machine/transition_collection.rb +244 -0
- data/lib/tasks/state_machine.rake +1 -0
- data/lib/tasks/state_machine.rb +27 -0
- data/test/files/en.yml +17 -0
- data/test/files/switch.rb +11 -0
- data/test/functional/alternate_state_machine_test.rb +122 -0
- data/test/functional/state_machine_test.rb +993 -0
- data/test/test_helper.rb +4 -0
- data/test/unit/assertions_test.rb +40 -0
- data/test/unit/branch_test.rb +890 -0
- data/test/unit/callback_test.rb +701 -0
- data/test/unit/condition_proxy_test.rb +328 -0
- data/test/unit/error_test.rb +43 -0
- data/test/unit/eval_helpers_test.rb +222 -0
- data/test/unit/event_collection_test.rb +358 -0
- data/test/unit/event_test.rb +985 -0
- data/test/unit/integrations/active_model_test.rb +1097 -0
- data/test/unit/integrations/active_record_test.rb +2021 -0
- data/test/unit/integrations/base_test.rb +99 -0
- data/test/unit/integrations/data_mapper_test.rb +1909 -0
- data/test/unit/integrations/mongo_mapper_test.rb +1611 -0
- data/test/unit/integrations/mongoid_test.rb +1591 -0
- data/test/unit/integrations/sequel_test.rb +1523 -0
- data/test/unit/integrations_test.rb +61 -0
- data/test/unit/invalid_event_test.rb +20 -0
- data/test/unit/invalid_parallel_transition_test.rb +18 -0
- data/test/unit/invalid_transition_test.rb +77 -0
- data/test/unit/machine_collection_test.rb +599 -0
- data/test/unit/machine_test.rb +3043 -0
- data/test/unit/matcher_helpers_test.rb +37 -0
- data/test/unit/matcher_test.rb +155 -0
- data/test/unit/node_collection_test.rb +217 -0
- data/test/unit/path_collection_test.rb +266 -0
- data/test/unit/path_test.rb +485 -0
- data/test/unit/state_collection_test.rb +310 -0
- data/test/unit/state_machine_test.rb +31 -0
- data/test/unit/state_test.rb +924 -0
- data/test/unit/transition_collection_test.rb +2102 -0
- data/test/unit/transition_test.rb +1541 -0
- metadata +207 -0
@@ -0,0 +1,358 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class EventCollectionByDefaultTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@machine = StateMachine::Machine.new(Class.new)
|
6
|
+
@events = StateMachine::EventCollection.new(@machine)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_should_not_have_any_nodes
|
10
|
+
assert_equal 0, @events.length
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_should_have_a_machine
|
14
|
+
assert_equal @machine, @events.machine
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_should_not_have_any_valid_events_for_an_object
|
18
|
+
assert @events.valid_for(@object).empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_should_not_have_any_transitions_for_an_object
|
22
|
+
assert @events.transitions_for(@object).empty?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class EventCollectionTest < Test::Unit::TestCase
|
27
|
+
def setup
|
28
|
+
machine = StateMachine::Machine.new(Class.new, :namespace => 'alarm')
|
29
|
+
@events = StateMachine::EventCollection.new(machine)
|
30
|
+
|
31
|
+
@events << @open = StateMachine::Event.new(machine, :enable)
|
32
|
+
machine.events.concat(@events)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_should_index_by_name
|
36
|
+
assert_equal @open, @events[:enable, :name]
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_should_index_by_name_by_default
|
40
|
+
assert_equal @open, @events[:enable]
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_should_index_by_qualified_name
|
44
|
+
assert_equal @open, @events[:enable_alarm, :qualified_name]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class EventCollectionWithEventsWithTransitionsTest < Test::Unit::TestCase
|
49
|
+
def setup
|
50
|
+
@klass = Class.new
|
51
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
52
|
+
@events = StateMachine::EventCollection.new(@machine)
|
53
|
+
|
54
|
+
@machine.state :idling, :first_gear
|
55
|
+
|
56
|
+
@events << @ignite = StateMachine::Event.new(@machine, :ignite)
|
57
|
+
@ignite.transition :parked => :idling
|
58
|
+
|
59
|
+
@events << @park = StateMachine::Event.new(@machine, :park)
|
60
|
+
@park.transition :idling => :parked
|
61
|
+
|
62
|
+
@events << @shift_up = StateMachine::Event.new(@machine, :shift_up)
|
63
|
+
@shift_up.transition :parked => :first_gear
|
64
|
+
@shift_up.transition :idling => :first_gear, :if => lambda{false}
|
65
|
+
|
66
|
+
@machine.events.concat(@events)
|
67
|
+
|
68
|
+
@object = @klass.new
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_should_find_valid_events_based_on_current_state
|
72
|
+
assert_equal [@ignite, @shift_up], @events.valid_for(@object)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_should_filter_valid_events_by_from_state
|
76
|
+
assert_equal [@park], @events.valid_for(@object, :from => :idling)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_should_filter_valid_events_by_to_state
|
80
|
+
assert_equal [@shift_up], @events.valid_for(@object, :to => :first_gear)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_should_filter_valid_events_by_event
|
84
|
+
assert_equal [@ignite], @events.valid_for(@object, :on => :ignite)
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_should_filter_valid_events_by_multiple_requirements
|
88
|
+
assert_equal [], @events.valid_for(@object, :from => :idling, :to => :first_gear)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_should_allow_finding_valid_events_without_guards
|
92
|
+
assert_equal [@shift_up], @events.valid_for(@object, :from => :idling, :to => :first_gear, :guard => false)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_should_find_valid_transitions_based_on_current_state
|
96
|
+
assert_equal [
|
97
|
+
StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
|
98
|
+
StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)
|
99
|
+
], @events.transitions_for(@object)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_should_filter_valid_transitions_by_from_state
|
103
|
+
assert_equal [StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)], @events.transitions_for(@object, :from => :idling)
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_should_filter_valid_transitions_by_to_state
|
107
|
+
assert_equal [StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)], @events.transitions_for(@object, :to => :first_gear)
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_should_filter_valid_transitions_by_event
|
111
|
+
assert_equal [StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)], @events.transitions_for(@object, :on => :ignite)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_should_filter_valid_transitions_by_multiple_requirements
|
115
|
+
assert_equal [], @events.transitions_for(@object, :from => :idling, :to => :first_gear)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_should_allow_finding_valid_transitions_without_guards
|
119
|
+
assert_equal [StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)], @events.transitions_for(@object, :from => :idling, :to => :first_gear, :guard => false)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class EventCollectionWithMultipleEventsTest < Test::Unit::TestCase
|
124
|
+
def setup
|
125
|
+
@klass = Class.new
|
126
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
127
|
+
@events = StateMachine::EventCollection.new(@machine)
|
128
|
+
|
129
|
+
@machine.state :first_gear
|
130
|
+
@machine.event :park, :shift_down
|
131
|
+
|
132
|
+
@events << @park = StateMachine::Event.new(@machine, :park)
|
133
|
+
@park.transition :first_gear => :parked
|
134
|
+
|
135
|
+
@events << @shift_down = StateMachine::Event.new(@machine, :shift_down)
|
136
|
+
@shift_down.transition :first_gear => :parked
|
137
|
+
|
138
|
+
@machine.events.concat(@events)
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_should_only_include_all_valid_events_for_an_object
|
142
|
+
object = @klass.new
|
143
|
+
object.state = 'first_gear'
|
144
|
+
assert_equal [@park, @shift_down], @events.valid_for(object)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class EventCollectionWithoutMachineActionTest < Test::Unit::TestCase
|
149
|
+
def setup
|
150
|
+
@klass = Class.new
|
151
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
152
|
+
@events = StateMachine::EventCollection.new(@machine)
|
153
|
+
@events << StateMachine::Event.new(@machine, :ignite)
|
154
|
+
@machine.events.concat(@events)
|
155
|
+
|
156
|
+
@object = @klass.new
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_should_not_have_an_attribute_transition
|
160
|
+
assert_nil @events.attribute_transition_for(@object)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class EventCollectionAttributeWithMachineActionTest < Test::Unit::TestCase
|
165
|
+
def setup
|
166
|
+
@klass = Class.new do
|
167
|
+
def save
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save)
|
172
|
+
@events = StateMachine::EventCollection.new(@machine)
|
173
|
+
|
174
|
+
@machine.state :parked, :idling
|
175
|
+
@events << @ignite = StateMachine::Event.new(@machine, :ignite)
|
176
|
+
@machine.events.concat(@events)
|
177
|
+
|
178
|
+
@object = @klass.new
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_should_not_have_transition_if_nil
|
182
|
+
@object.state_event = nil
|
183
|
+
assert_nil @events.attribute_transition_for(@object)
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_should_not_have_transition_if_empty
|
187
|
+
@object.state_event = ''
|
188
|
+
assert_nil @events.attribute_transition_for(@object)
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_should_have_invalid_transition_if_invalid_event_specified
|
192
|
+
@object.state_event = 'invalid'
|
193
|
+
assert_equal false, @events.attribute_transition_for(@object)
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_should_have_invalid_transition_if_event_cannot_be_fired
|
197
|
+
@object.state_event = 'ignite'
|
198
|
+
assert_equal false, @events.attribute_transition_for(@object)
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_should_have_valid_transition_if_event_can_be_fired
|
202
|
+
@ignite.transition :parked => :idling
|
203
|
+
@object.state_event = 'ignite'
|
204
|
+
|
205
|
+
assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_should_have_valid_transition_if_already_defined_in_transition_cache
|
209
|
+
@ignite.transition :parked => :idling
|
210
|
+
@object.state_event = nil
|
211
|
+
@object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
|
212
|
+
|
213
|
+
assert_equal transition, @events.attribute_transition_for(@object)
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_should_use_transition_cache_if_both_event_and_transition_are_present
|
217
|
+
@ignite.transition :parked => :idling
|
218
|
+
@object.state_event = 'ignite'
|
219
|
+
@object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
|
220
|
+
|
221
|
+
assert_equal transition, @events.attribute_transition_for(@object)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
class EventCollectionAttributeWithNamespacedMachineTest < Test::Unit::TestCase
|
226
|
+
def setup
|
227
|
+
@klass = Class.new do
|
228
|
+
def save
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
@machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active, :action => :save)
|
233
|
+
@events = StateMachine::EventCollection.new(@machine)
|
234
|
+
|
235
|
+
@machine.state :active, :off
|
236
|
+
@events << @disable = StateMachine::Event.new(@machine, :disable)
|
237
|
+
@machine.events.concat(@events)
|
238
|
+
|
239
|
+
@object = @klass.new
|
240
|
+
end
|
241
|
+
|
242
|
+
def test_should_not_have_transition_if_nil
|
243
|
+
@object.state_event = nil
|
244
|
+
assert_nil @events.attribute_transition_for(@object)
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_should_have_invalid_transition_if_event_cannot_be_fired
|
248
|
+
@object.state_event = 'disable'
|
249
|
+
assert_equal false, @events.attribute_transition_for(@object)
|
250
|
+
end
|
251
|
+
|
252
|
+
def test_should_have_valid_transition_if_event_can_be_fired
|
253
|
+
@disable.transition :active => :off
|
254
|
+
@object.state_event = 'disable'
|
255
|
+
|
256
|
+
assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
class EventCollectionWithValidationsTest < Test::Unit::TestCase
|
261
|
+
def setup
|
262
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
263
|
+
include StateMachine::Integrations::Base
|
264
|
+
|
265
|
+
def invalidate(object, attribute, message, values = [])
|
266
|
+
(object.errors ||= []) << generate_message(message, values)
|
267
|
+
end
|
268
|
+
|
269
|
+
def reset(object)
|
270
|
+
object.errors = []
|
271
|
+
end
|
272
|
+
end)
|
273
|
+
|
274
|
+
@klass = Class.new do
|
275
|
+
attr_accessor :errors
|
276
|
+
|
277
|
+
def initialize
|
278
|
+
@errors = []
|
279
|
+
super
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save, :integration => :custom)
|
284
|
+
@events = StateMachine::EventCollection.new(@machine)
|
285
|
+
|
286
|
+
@parked, @idling = @machine.state :parked, :idling
|
287
|
+
@events << @ignite = StateMachine::Event.new(@machine, :ignite)
|
288
|
+
@machine.events.concat(@events)
|
289
|
+
|
290
|
+
@object = @klass.new
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_should_invalidate_if_invalid_event_specified
|
294
|
+
@object.state_event = 'invalid'
|
295
|
+
@events.attribute_transition_for(@object, true)
|
296
|
+
|
297
|
+
assert_equal ['is invalid'], @object.errors
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_should_invalidate_if_event_cannot_be_fired
|
301
|
+
@object.state = 'idling'
|
302
|
+
@object.state_event = 'ignite'
|
303
|
+
@events.attribute_transition_for(@object, true)
|
304
|
+
|
305
|
+
assert_equal ['cannot transition when idling'], @object.errors
|
306
|
+
end
|
307
|
+
|
308
|
+
def test_should_invalidate_with_human_name_if_invalid_event_specified
|
309
|
+
@idling.human_name = 'waiting'
|
310
|
+
@object.state = 'idling'
|
311
|
+
@object.state_event = 'ignite'
|
312
|
+
@events.attribute_transition_for(@object, true)
|
313
|
+
|
314
|
+
assert_equal ['cannot transition when waiting'], @object.errors
|
315
|
+
end
|
316
|
+
|
317
|
+
def test_should_not_invalidate_event_can_be_fired
|
318
|
+
@ignite.transition :parked => :idling
|
319
|
+
@object.state_event = 'ignite'
|
320
|
+
@events.attribute_transition_for(@object, true)
|
321
|
+
|
322
|
+
assert_equal [], @object.errors
|
323
|
+
end
|
324
|
+
|
325
|
+
def teardown
|
326
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
class EventCollectionWithCustomMachineAttributeTest < Test::Unit::TestCase
|
331
|
+
def setup
|
332
|
+
@klass = Class.new do
|
333
|
+
def save
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
@machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :parked, :action => :save)
|
338
|
+
@events = StateMachine::EventCollection.new(@machine)
|
339
|
+
|
340
|
+
@machine.state :parked, :idling
|
341
|
+
@events << @ignite = StateMachine::Event.new(@machine, :ignite)
|
342
|
+
@machine.events.concat(@events)
|
343
|
+
|
344
|
+
@object = @klass.new
|
345
|
+
end
|
346
|
+
|
347
|
+
def test_should_not_have_transition_if_nil
|
348
|
+
@object.state_event = nil
|
349
|
+
assert_nil @events.attribute_transition_for(@object)
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_should_have_valid_transition_if_event_can_be_fired
|
353
|
+
@ignite.transition :parked => :idling
|
354
|
+
@object.state_event = 'ignite'
|
355
|
+
|
356
|
+
assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
|
357
|
+
end
|
358
|
+
end
|
@@ -0,0 +1,985 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class EventByDefaultTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@klass = Class.new
|
6
|
+
@machine = StateMachine::Machine.new(@klass)
|
7
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
8
|
+
|
9
|
+
@object = @klass.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_have_a_machine
|
13
|
+
assert_equal @machine, @event.machine
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_have_a_name
|
17
|
+
assert_equal :ignite, @event.name
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_have_a_qualified_name
|
21
|
+
assert_equal :ignite, @event.qualified_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_have_a_human_name
|
25
|
+
assert_equal 'ignite', @event.human_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_should_not_have_any_branches
|
29
|
+
assert @event.branches.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_have_no_known_states
|
33
|
+
assert @event.known_states.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_should_not_be_able_to_fire
|
37
|
+
assert !@event.can_fire?(@object)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_should_not_have_a_transition
|
41
|
+
assert_nil @event.transition_for(@object)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_should_define_a_predicate
|
45
|
+
assert @object.respond_to?(:can_ignite?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_should_define_a_transition_accessor
|
49
|
+
assert @object.respond_to?(:ignite_transition)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_should_define_an_action
|
53
|
+
assert @object.respond_to?(:ignite)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_should_define_a_bang_action
|
57
|
+
assert @object.respond_to?(:ignite!)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class EventTest < Test::Unit::TestCase
|
62
|
+
def setup
|
63
|
+
@machine = StateMachine::Machine.new(Class.new)
|
64
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
65
|
+
@event.transition :parked => :idling
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_allow_changing_machine
|
69
|
+
new_machine = StateMachine::Machine.new(Class.new)
|
70
|
+
@event.machine = new_machine
|
71
|
+
assert_equal new_machine, @event.machine
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_should_allow_changing_human_name
|
75
|
+
@event.human_name = 'Stop'
|
76
|
+
assert_equal 'Stop', @event.human_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_should_provide_matcher_helpers_during_initialization
|
80
|
+
matchers = []
|
81
|
+
|
82
|
+
@event.instance_eval do
|
83
|
+
matchers = [all, any, same]
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_should_use_pretty_inspect
|
90
|
+
assert_match "#<StateMachine::Event name=:ignite transitions=[:parked => :idling]>", @event.inspect
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class EventWithHumanNameTest < Test::Unit::TestCase
|
95
|
+
def setup
|
96
|
+
@klass = Class.new
|
97
|
+
@machine = StateMachine::Machine.new(@klass)
|
98
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => 'start')
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_should_use_custom_human_name
|
102
|
+
assert_equal 'start', @event.human_name
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class EventWithDynamicHumanNameTest < Test::Unit::TestCase
|
107
|
+
def setup
|
108
|
+
@klass = Class.new
|
109
|
+
@machine = StateMachine::Machine.new(@klass)
|
110
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => lambda {|event, object| ['start', object]})
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_should_use_custom_human_name
|
114
|
+
human_name, klass = @event.human_name
|
115
|
+
assert_equal 'start', human_name
|
116
|
+
assert_equal @klass, klass
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_should_allow_custom_class_to_be_passed_through
|
120
|
+
human_name, klass = @event.human_name(1)
|
121
|
+
assert_equal 'start', human_name
|
122
|
+
assert_equal 1, klass
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_should_not_cache_value
|
126
|
+
assert_not_same @event.human_name, @event.human_name
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class EventWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
|
131
|
+
def setup
|
132
|
+
require 'stringio'
|
133
|
+
@original_stderr, $stderr = $stderr, StringIO.new
|
134
|
+
|
135
|
+
@superclass = Class.new do
|
136
|
+
def can_ignite?
|
137
|
+
0
|
138
|
+
end
|
139
|
+
|
140
|
+
def ignite_transition
|
141
|
+
0
|
142
|
+
end
|
143
|
+
|
144
|
+
def ignite
|
145
|
+
0
|
146
|
+
end
|
147
|
+
|
148
|
+
def ignite!
|
149
|
+
0
|
150
|
+
end
|
151
|
+
end
|
152
|
+
@klass = Class.new(@superclass)
|
153
|
+
@machine = StateMachine::Machine.new(@klass)
|
154
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
155
|
+
@object = @klass.new
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_should_not_redefine_predicate
|
159
|
+
assert_equal 0, @object.can_ignite?
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_should_not_redefine_transition_accessor
|
163
|
+
assert_equal 0, @object.ignite_transition
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_should_not_redefine_action
|
167
|
+
assert_equal 0, @object.ignite
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_should_not_redefine_bang_action
|
171
|
+
assert_equal 0, @object.ignite!
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_should_output_warning
|
175
|
+
expected = %w(can_ignite? ignite_transition ignite ignite!).map do |method|
|
176
|
+
"Instance method \"#{method}\" is already defined in #{@superclass.to_s}, use generic helper instead.\n"
|
177
|
+
end.join
|
178
|
+
|
179
|
+
assert_equal expected, $stderr.string
|
180
|
+
end
|
181
|
+
|
182
|
+
def teardown
|
183
|
+
$stderr = @original_stderr
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class EventWithConflictingHelpersAfterDefinitionTest < Test::Unit::TestCase
|
188
|
+
def setup
|
189
|
+
require 'stringio'
|
190
|
+
@original_stderr, $stderr = $stderr, StringIO.new
|
191
|
+
|
192
|
+
@klass = Class.new do
|
193
|
+
def can_ignite?
|
194
|
+
0
|
195
|
+
end
|
196
|
+
|
197
|
+
def ignite_transition
|
198
|
+
0
|
199
|
+
end
|
200
|
+
|
201
|
+
def ignite
|
202
|
+
0
|
203
|
+
end
|
204
|
+
|
205
|
+
def ignite!
|
206
|
+
0
|
207
|
+
end
|
208
|
+
end
|
209
|
+
@machine = StateMachine::Machine.new(@klass)
|
210
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
211
|
+
@object = @klass.new
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_should_not_redefine_predicate
|
215
|
+
assert_equal 0, @object.can_ignite?
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_should_not_redefine_transition_accessor
|
219
|
+
assert_equal 0, @object.ignite_transition
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_should_not_redefine_action
|
223
|
+
assert_equal 0, @object.ignite
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_should_not_redefine_bang_action
|
227
|
+
assert_equal 0, @object.ignite!
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_should_allow_super_chaining
|
231
|
+
@klass.class_eval do
|
232
|
+
def can_ignite?
|
233
|
+
super
|
234
|
+
end
|
235
|
+
|
236
|
+
def ignite_transition
|
237
|
+
super
|
238
|
+
end
|
239
|
+
|
240
|
+
def ignite
|
241
|
+
super
|
242
|
+
end
|
243
|
+
|
244
|
+
def ignite!
|
245
|
+
super
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
assert_equal false, @object.can_ignite?
|
250
|
+
assert_equal nil, @object.ignite_transition
|
251
|
+
assert_equal false, @object.ignite
|
252
|
+
assert_raise(StateMachine::InvalidTransition) { @object.ignite! }
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_should_not_output_warning
|
256
|
+
assert_equal '', $stderr.string
|
257
|
+
end
|
258
|
+
|
259
|
+
def teardown
|
260
|
+
$stderr = @original_stderr
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class EventWithConflictingMachineTest < Test::Unit::TestCase
|
265
|
+
def setup
|
266
|
+
require 'stringio'
|
267
|
+
@original_stderr, $stderr = $stderr, StringIO.new
|
268
|
+
|
269
|
+
@klass = Class.new
|
270
|
+
@state_machine = StateMachine::Machine.new(@klass, :state)
|
271
|
+
@state_machine.state :parked, :idling
|
272
|
+
@state_machine.events << @state_event = StateMachine::Event.new(@state_machine, :ignite)
|
273
|
+
end
|
274
|
+
|
275
|
+
def test_should_not_overwrite_first_event
|
276
|
+
@status_machine = StateMachine::Machine.new(@klass, :status)
|
277
|
+
@status_machine.state :first_gear, :second_gear
|
278
|
+
@status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite)
|
279
|
+
|
280
|
+
@object = @klass.new
|
281
|
+
@object.state = 'parked'
|
282
|
+
@object.status = 'first_gear'
|
283
|
+
|
284
|
+
@state_event.transition(:parked => :idling)
|
285
|
+
@status_event.transition(:parked => :first_gear)
|
286
|
+
|
287
|
+
@object.ignite
|
288
|
+
assert_equal 'idling', @object.state
|
289
|
+
assert_equal 'first_gear', @object.status
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_should_output_warning
|
293
|
+
@status_machine = StateMachine::Machine.new(@klass, :status)
|
294
|
+
@status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite)
|
295
|
+
|
296
|
+
assert_equal "Event :ignite for :status is already defined in :state\n", $stderr.string
|
297
|
+
end
|
298
|
+
|
299
|
+
def test_should_not_output_warning_if_using_different_namespace
|
300
|
+
@status_machine = StateMachine::Machine.new(@klass, :status, :namespace => 'alarm')
|
301
|
+
@status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite)
|
302
|
+
|
303
|
+
assert_equal '', $stderr.string
|
304
|
+
end
|
305
|
+
|
306
|
+
def teardown
|
307
|
+
$stderr = @original_stderr
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
class EventWithNamespaceTest < Test::Unit::TestCase
|
312
|
+
def setup
|
313
|
+
@klass = Class.new
|
314
|
+
@machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
|
315
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :enable)
|
316
|
+
@object = @klass.new
|
317
|
+
end
|
318
|
+
|
319
|
+
def test_should_have_a_name
|
320
|
+
assert_equal :enable, @event.name
|
321
|
+
end
|
322
|
+
|
323
|
+
def test_should_have_a_qualified_name
|
324
|
+
assert_equal :enable_alarm, @event.qualified_name
|
325
|
+
end
|
326
|
+
|
327
|
+
def test_should_namespace_predicate
|
328
|
+
assert @object.respond_to?(:can_enable_alarm?)
|
329
|
+
end
|
330
|
+
|
331
|
+
def test_should_namespace_transition_accessor
|
332
|
+
assert @object.respond_to?(:enable_alarm_transition)
|
333
|
+
end
|
334
|
+
|
335
|
+
def test_should_namespace_action
|
336
|
+
assert @object.respond_to?(:enable_alarm)
|
337
|
+
end
|
338
|
+
|
339
|
+
def test_should_namespace_bang_action
|
340
|
+
assert @object.respond_to?(:enable_alarm!)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
class EventTransitionsTest < Test::Unit::TestCase
|
345
|
+
def setup
|
346
|
+
@machine = StateMachine::Machine.new(Class.new)
|
347
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
348
|
+
end
|
349
|
+
|
350
|
+
def test_should_not_raise_exception_if_implicit_option_specified
|
351
|
+
assert_nothing_raised {@event.transition(:invalid => :valid)}
|
352
|
+
end
|
353
|
+
|
354
|
+
def test_should_not_allow_on_option
|
355
|
+
exception = assert_raise(ArgumentError) {@event.transition(:on => :ignite)}
|
356
|
+
assert_equal 'Invalid key(s): on', exception.message
|
357
|
+
end
|
358
|
+
|
359
|
+
def test_should_automatically_set_on_option
|
360
|
+
branch = @event.transition(:to => :idling)
|
361
|
+
assert_instance_of StateMachine::WhitelistMatcher, branch.event_requirement
|
362
|
+
assert_equal [:ignite], branch.event_requirement.values
|
363
|
+
end
|
364
|
+
|
365
|
+
def test_should_not_allow_except_to_option
|
366
|
+
exception = assert_raise(ArgumentError) {@event.transition(:except_to => :parked)}
|
367
|
+
assert_equal 'Invalid key(s): except_to', exception.message
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_should_not_allow_except_on_option
|
371
|
+
exception = assert_raise(ArgumentError) {@event.transition(:except_on => :ignite)}
|
372
|
+
assert_equal 'Invalid key(s): except_on', exception.message
|
373
|
+
end
|
374
|
+
|
375
|
+
def test_should_allow_transitioning_without_a_to_state
|
376
|
+
assert_nothing_raised {@event.transition(:from => :parked)}
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_should_allow_transitioning_without_a_from_state
|
380
|
+
assert_nothing_raised {@event.transition(:to => :idling)}
|
381
|
+
end
|
382
|
+
|
383
|
+
def test_should_allow_except_from_option
|
384
|
+
assert_nothing_raised {@event.transition(:except_from => :idling)}
|
385
|
+
end
|
386
|
+
|
387
|
+
def test_should_allow_transitioning_from_a_single_state
|
388
|
+
assert @event.transition(:parked => :idling)
|
389
|
+
end
|
390
|
+
|
391
|
+
def test_should_allow_transitioning_from_multiple_states
|
392
|
+
assert @event.transition([:parked, :idling] => :idling)
|
393
|
+
end
|
394
|
+
|
395
|
+
def test_should_have_transitions
|
396
|
+
branch = @event.transition(:to => :idling)
|
397
|
+
assert_equal [branch], @event.branches
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
class EventAfterBeingCopiedTest < Test::Unit::TestCase
|
402
|
+
def setup
|
403
|
+
@machine = StateMachine::Machine.new(Class.new)
|
404
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
405
|
+
@copied_event = @event.dup
|
406
|
+
end
|
407
|
+
|
408
|
+
def test_should_not_have_the_same_collection_of_branches
|
409
|
+
assert_not_same @event.branches, @copied_event.branches
|
410
|
+
end
|
411
|
+
|
412
|
+
def test_should_not_have_the_same_collection_of_known_states
|
413
|
+
assert_not_same @event.known_states, @copied_event.known_states
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class EventWithoutTransitionsTest < Test::Unit::TestCase
|
418
|
+
def setup
|
419
|
+
@klass = Class.new
|
420
|
+
@machine = StateMachine::Machine.new(@klass)
|
421
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
422
|
+
@object = @klass.new
|
423
|
+
end
|
424
|
+
|
425
|
+
def test_should_not_be_able_to_fire
|
426
|
+
assert !@event.can_fire?(@object)
|
427
|
+
end
|
428
|
+
|
429
|
+
def test_should_not_have_a_transition
|
430
|
+
assert_nil @event.transition_for(@object)
|
431
|
+
end
|
432
|
+
|
433
|
+
def test_should_not_fire
|
434
|
+
assert !@event.fire(@object)
|
435
|
+
end
|
436
|
+
|
437
|
+
def test_should_not_change_the_current_state
|
438
|
+
@event.fire(@object)
|
439
|
+
assert_nil @object.state
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
class EventWithTransitionsTest < Test::Unit::TestCase
|
444
|
+
def setup
|
445
|
+
@klass = Class.new
|
446
|
+
@machine = StateMachine::Machine.new(@klass)
|
447
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
448
|
+
@event.transition(:parked => :idling)
|
449
|
+
@event.transition(:first_gear => :idling)
|
450
|
+
end
|
451
|
+
|
452
|
+
def test_should_include_all_transition_states_in_known_states
|
453
|
+
assert_equal [:parked, :idling, :first_gear], @event.known_states
|
454
|
+
end
|
455
|
+
|
456
|
+
def test_should_include_new_transition_states_after_calling_known_states
|
457
|
+
@event.known_states
|
458
|
+
@event.transition(:stalled => :idling)
|
459
|
+
|
460
|
+
assert_equal [:parked, :idling, :first_gear, :stalled], @event.known_states
|
461
|
+
end
|
462
|
+
|
463
|
+
def test_should_use_pretty_inspect
|
464
|
+
assert_match "#<StateMachine::Event name=:ignite transitions=[:parked => :idling, :first_gear => :idling]>", @event.inspect
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
class EventWithoutMatchingTransitionsTest < Test::Unit::TestCase
|
469
|
+
def setup
|
470
|
+
@klass = Class.new
|
471
|
+
@machine = StateMachine::Machine.new(@klass)
|
472
|
+
@machine.state :parked, :idling
|
473
|
+
|
474
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
475
|
+
@event.transition(:parked => :idling)
|
476
|
+
|
477
|
+
@object = @klass.new
|
478
|
+
@object.state = 'idling'
|
479
|
+
end
|
480
|
+
|
481
|
+
def test_should_not_be_able_to_fire
|
482
|
+
assert !@event.can_fire?(@object)
|
483
|
+
end
|
484
|
+
|
485
|
+
def test_should_be_able_to_fire_with_custom_from_state
|
486
|
+
assert @event.can_fire?(@object, :from => :parked)
|
487
|
+
end
|
488
|
+
|
489
|
+
def test_should_not_have_a_transition
|
490
|
+
assert_nil @event.transition_for(@object)
|
491
|
+
end
|
492
|
+
|
493
|
+
def test_should_have_a_transition_with_custom_from_state
|
494
|
+
assert_not_nil @event.transition_for(@object, :from => :parked)
|
495
|
+
end
|
496
|
+
|
497
|
+
def test_should_not_fire
|
498
|
+
assert !@event.fire(@object)
|
499
|
+
end
|
500
|
+
|
501
|
+
def test_should_not_change_the_current_state
|
502
|
+
@event.fire(@object)
|
503
|
+
assert_equal 'idling', @object.state
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase
|
508
|
+
def setup
|
509
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
510
|
+
include StateMachine::Integrations::Base
|
511
|
+
|
512
|
+
def invalidate(object, attribute, message, values = [])
|
513
|
+
(object.errors ||= []) << generate_message(message, values)
|
514
|
+
end
|
515
|
+
|
516
|
+
def reset(object)
|
517
|
+
object.errors = []
|
518
|
+
end
|
519
|
+
end)
|
520
|
+
|
521
|
+
@klass = Class.new do
|
522
|
+
attr_accessor :errors
|
523
|
+
end
|
524
|
+
|
525
|
+
@machine = StateMachine::Machine.new(@klass, :integration => :custom)
|
526
|
+
@machine.state :parked, :idling
|
527
|
+
|
528
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
529
|
+
@event.transition(:parked => :idling, :if => lambda {false})
|
530
|
+
|
531
|
+
@object = @klass.new
|
532
|
+
@object.state = 'parked'
|
533
|
+
end
|
534
|
+
|
535
|
+
def test_should_not_be_able_to_fire
|
536
|
+
assert !@event.can_fire?(@object)
|
537
|
+
end
|
538
|
+
|
539
|
+
def test_should_be_able_to_fire_with_disabled_guards
|
540
|
+
assert @event.can_fire?(@object, :guard => false)
|
541
|
+
end
|
542
|
+
|
543
|
+
def test_should_not_have_a_transition
|
544
|
+
assert_nil @event.transition_for(@object)
|
545
|
+
end
|
546
|
+
|
547
|
+
def test_should_have_a_transition_with_disabled_guards
|
548
|
+
assert_not_nil @event.transition_for(@object, :guard => false)
|
549
|
+
end
|
550
|
+
|
551
|
+
def test_should_not_fire
|
552
|
+
assert !@event.fire(@object)
|
553
|
+
end
|
554
|
+
|
555
|
+
def test_should_not_change_the_current_state
|
556
|
+
@event.fire(@object)
|
557
|
+
assert_equal 'parked', @object.state
|
558
|
+
end
|
559
|
+
|
560
|
+
def test_should_invalidate_the_state
|
561
|
+
@event.fire(@object)
|
562
|
+
assert_equal ['cannot transition via "ignite"'], @object.errors
|
563
|
+
end
|
564
|
+
|
565
|
+
def test_should_invalidate_with_human_event_name
|
566
|
+
@event.human_name = 'start'
|
567
|
+
@event.fire(@object)
|
568
|
+
assert_equal ['cannot transition via "start"'], @object.errors
|
569
|
+
end
|
570
|
+
|
571
|
+
def test_should_reset_existing_error
|
572
|
+
@object.errors = ['invalid']
|
573
|
+
|
574
|
+
@event.fire(@object)
|
575
|
+
assert_equal ['cannot transition via "ignite"'], @object.errors
|
576
|
+
end
|
577
|
+
|
578
|
+
def test_should_run_failure_callbacks
|
579
|
+
callback_args = nil
|
580
|
+
@machine.after_failure {|*args| callback_args = args}
|
581
|
+
|
582
|
+
@event.fire(@object)
|
583
|
+
|
584
|
+
object, transition = callback_args
|
585
|
+
assert_equal @object, object
|
586
|
+
assert_not_nil transition
|
587
|
+
assert_equal @object, transition.object
|
588
|
+
assert_equal @machine, transition.machine
|
589
|
+
assert_equal :ignite, transition.event
|
590
|
+
assert_equal :parked, transition.from_name
|
591
|
+
assert_equal :parked, transition.to_name
|
592
|
+
end
|
593
|
+
|
594
|
+
def teardown
|
595
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
class EventWithMatchingEnabledTransitionsTest < Test::Unit::TestCase
|
600
|
+
def setup
|
601
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
602
|
+
include StateMachine::Integrations::Base
|
603
|
+
|
604
|
+
def invalidate(object, attribute, message, values = [])
|
605
|
+
(object.errors ||= []) << generate_message(message, values)
|
606
|
+
end
|
607
|
+
|
608
|
+
def reset(object)
|
609
|
+
object.errors = []
|
610
|
+
end
|
611
|
+
end)
|
612
|
+
|
613
|
+
@klass = Class.new do
|
614
|
+
attr_accessor :errors
|
615
|
+
end
|
616
|
+
|
617
|
+
@machine = StateMachine::Machine.new(@klass, :integration => :custom)
|
618
|
+
@machine.state :parked, :idling
|
619
|
+
|
620
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
621
|
+
@event.transition(:parked => :idling)
|
622
|
+
|
623
|
+
@object = @klass.new
|
624
|
+
@object.state = 'parked'
|
625
|
+
end
|
626
|
+
|
627
|
+
def test_should_be_able_to_fire
|
628
|
+
assert @event.can_fire?(@object)
|
629
|
+
end
|
630
|
+
|
631
|
+
def test_should_have_a_transition
|
632
|
+
transition = @event.transition_for(@object)
|
633
|
+
assert_not_nil transition
|
634
|
+
assert_equal 'parked', transition.from
|
635
|
+
assert_equal 'idling', transition.to
|
636
|
+
assert_equal :ignite, transition.event
|
637
|
+
end
|
638
|
+
|
639
|
+
def test_should_fire
|
640
|
+
assert @event.fire(@object)
|
641
|
+
end
|
642
|
+
|
643
|
+
def test_should_change_the_current_state
|
644
|
+
@event.fire(@object)
|
645
|
+
assert_equal 'idling', @object.state
|
646
|
+
end
|
647
|
+
|
648
|
+
def test_should_reset_existing_error
|
649
|
+
@object.errors = ['invalid']
|
650
|
+
|
651
|
+
@event.fire(@object)
|
652
|
+
assert_equal [], @object.errors
|
653
|
+
end
|
654
|
+
|
655
|
+
def test_should_not_invalidate_the_state
|
656
|
+
@event.fire(@object)
|
657
|
+
assert_equal [], @object.errors
|
658
|
+
end
|
659
|
+
|
660
|
+
def teardown
|
661
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
class EventWithTransitionWithoutToStateTest < Test::Unit::TestCase
|
666
|
+
def setup
|
667
|
+
@klass = Class.new
|
668
|
+
@machine = StateMachine::Machine.new(@klass)
|
669
|
+
@machine.state :parked
|
670
|
+
|
671
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :park)
|
672
|
+
@event.transition(:from => :parked)
|
673
|
+
|
674
|
+
@object = @klass.new
|
675
|
+
@object.state = 'parked'
|
676
|
+
end
|
677
|
+
|
678
|
+
def test_should_be_able_to_fire
|
679
|
+
assert @event.can_fire?(@object)
|
680
|
+
end
|
681
|
+
|
682
|
+
def test_should_have_a_transition
|
683
|
+
transition = @event.transition_for(@object)
|
684
|
+
assert_not_nil transition
|
685
|
+
assert_equal 'parked', transition.from
|
686
|
+
assert_equal 'parked', transition.to
|
687
|
+
assert_equal :park, transition.event
|
688
|
+
end
|
689
|
+
|
690
|
+
def test_should_fire
|
691
|
+
assert @event.fire(@object)
|
692
|
+
end
|
693
|
+
|
694
|
+
def test_should_not_change_the_current_state
|
695
|
+
@event.fire(@object)
|
696
|
+
assert_equal 'parked', @object.state
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
class EventWithTransitionWithNilToStateTest < Test::Unit::TestCase
|
701
|
+
def setup
|
702
|
+
@klass = Class.new
|
703
|
+
@machine = StateMachine::Machine.new(@klass)
|
704
|
+
@machine.state nil, :idling
|
705
|
+
|
706
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :park)
|
707
|
+
@event.transition(:idling => nil)
|
708
|
+
|
709
|
+
@object = @klass.new
|
710
|
+
@object.state = 'idling'
|
711
|
+
end
|
712
|
+
|
713
|
+
def test_should_be_able_to_fire
|
714
|
+
assert @event.can_fire?(@object)
|
715
|
+
end
|
716
|
+
|
717
|
+
def test_should_have_a_transition
|
718
|
+
transition = @event.transition_for(@object)
|
719
|
+
assert_not_nil transition
|
720
|
+
assert_equal 'idling', transition.from
|
721
|
+
assert_equal nil, transition.to
|
722
|
+
assert_equal :park, transition.event
|
723
|
+
end
|
724
|
+
|
725
|
+
def test_should_fire
|
726
|
+
assert @event.fire(@object)
|
727
|
+
end
|
728
|
+
|
729
|
+
def test_should_not_change_the_current_state
|
730
|
+
@event.fire(@object)
|
731
|
+
assert_equal nil, @object.state
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
class EventWithMultipleTransitionsTest < Test::Unit::TestCase
|
736
|
+
def setup
|
737
|
+
@klass = Class.new
|
738
|
+
@machine = StateMachine::Machine.new(@klass)
|
739
|
+
@machine.state :parked, :idling
|
740
|
+
|
741
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
742
|
+
@event.transition(:idling => :idling)
|
743
|
+
@event.transition(:parked => :idling) # This one should get used
|
744
|
+
@event.transition(:parked => :parked)
|
745
|
+
|
746
|
+
@object = @klass.new
|
747
|
+
@object.state = 'parked'
|
748
|
+
end
|
749
|
+
|
750
|
+
def test_should_be_able_to_fire
|
751
|
+
assert @event.can_fire?(@object)
|
752
|
+
end
|
753
|
+
|
754
|
+
def test_should_have_a_transition
|
755
|
+
transition = @event.transition_for(@object)
|
756
|
+
assert_not_nil transition
|
757
|
+
assert_equal 'parked', transition.from
|
758
|
+
assert_equal 'idling', transition.to
|
759
|
+
assert_equal :ignite, transition.event
|
760
|
+
end
|
761
|
+
|
762
|
+
def test_should_allow_specific_transition_selection_using_from
|
763
|
+
transition = @event.transition_for(@object, :from => :idling)
|
764
|
+
|
765
|
+
assert_not_nil transition
|
766
|
+
assert_equal 'idling', transition.from
|
767
|
+
assert_equal 'idling', transition.to
|
768
|
+
assert_equal :ignite, transition.event
|
769
|
+
end
|
770
|
+
|
771
|
+
def test_should_allow_specific_transition_selection_using_to
|
772
|
+
transition = @event.transition_for(@object, :from => :parked, :to => :parked)
|
773
|
+
|
774
|
+
assert_not_nil transition
|
775
|
+
assert_equal 'parked', transition.from
|
776
|
+
assert_equal 'parked', transition.to
|
777
|
+
assert_equal :ignite, transition.event
|
778
|
+
end
|
779
|
+
|
780
|
+
def test_should_not_allow_specific_transition_selection_using_on
|
781
|
+
exception = assert_raise(ArgumentError) { @event.transition_for(@object, :on => :park) }
|
782
|
+
assert_equal 'Invalid key(s): on', exception.message
|
783
|
+
end
|
784
|
+
|
785
|
+
def test_should_fire
|
786
|
+
assert @event.fire(@object)
|
787
|
+
end
|
788
|
+
|
789
|
+
def test_should_change_the_current_state
|
790
|
+
@event.fire(@object)
|
791
|
+
assert_equal 'idling', @object.state
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
class EventWithMachineActionTest < Test::Unit::TestCase
|
796
|
+
def setup
|
797
|
+
@klass = Class.new do
|
798
|
+
attr_reader :saved
|
799
|
+
|
800
|
+
def save
|
801
|
+
@saved = true
|
802
|
+
end
|
803
|
+
end
|
804
|
+
|
805
|
+
@machine = StateMachine::Machine.new(@klass, :action => :save)
|
806
|
+
@machine.state :parked, :idling
|
807
|
+
|
808
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
809
|
+
@event.transition(:parked => :idling)
|
810
|
+
|
811
|
+
@object = @klass.new
|
812
|
+
@object.state = 'parked'
|
813
|
+
end
|
814
|
+
|
815
|
+
def test_should_run_action_on_fire
|
816
|
+
@event.fire(@object)
|
817
|
+
assert @object.saved
|
818
|
+
end
|
819
|
+
|
820
|
+
def test_should_not_run_action_if_configured_to_skip
|
821
|
+
@event.fire(@object, false)
|
822
|
+
assert !@object.saved
|
823
|
+
end
|
824
|
+
end
|
825
|
+
|
826
|
+
class EventWithInvalidCurrentStateTest < Test::Unit::TestCase
|
827
|
+
def setup
|
828
|
+
@klass = Class.new
|
829
|
+
@machine = StateMachine::Machine.new(@klass)
|
830
|
+
@machine.state :parked, :idling
|
831
|
+
|
832
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
833
|
+
@event.transition(:parked => :idling)
|
834
|
+
|
835
|
+
@object = @klass.new
|
836
|
+
@object.state = 'invalid'
|
837
|
+
end
|
838
|
+
|
839
|
+
def test_should_raise_exception_when_checking_availability
|
840
|
+
exception = assert_raise(ArgumentError) { @event.can_fire?(@object) }
|
841
|
+
assert_equal '"invalid" is not a known state value', exception.message
|
842
|
+
end
|
843
|
+
|
844
|
+
def test_should_raise_exception_when_finding_transition
|
845
|
+
exception = assert_raise(ArgumentError) { @event.transition_for(@object) }
|
846
|
+
assert_equal '"invalid" is not a known state value', exception.message
|
847
|
+
end
|
848
|
+
|
849
|
+
def test_should_raise_exception_when_firing
|
850
|
+
exception = assert_raise(ArgumentError) { @event.fire(@object) }
|
851
|
+
assert_equal '"invalid" is not a known state value', exception.message
|
852
|
+
end
|
853
|
+
end
|
854
|
+
|
855
|
+
class EventOnFailureTest < Test::Unit::TestCase
|
856
|
+
def setup
|
857
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
858
|
+
include StateMachine::Integrations::Base
|
859
|
+
|
860
|
+
def invalidate(object, attribute, message, values = [])
|
861
|
+
(object.errors ||= []) << generate_message(message, values)
|
862
|
+
end
|
863
|
+
|
864
|
+
def reset(object)
|
865
|
+
object.errors = []
|
866
|
+
end
|
867
|
+
end)
|
868
|
+
|
869
|
+
@klass = Class.new do
|
870
|
+
attr_accessor :errors
|
871
|
+
end
|
872
|
+
|
873
|
+
@machine = StateMachine::Machine.new(@klass, :integration => :custom)
|
874
|
+
@machine.state :parked
|
875
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
876
|
+
|
877
|
+
@object = @klass.new
|
878
|
+
@object.state = 'parked'
|
879
|
+
end
|
880
|
+
|
881
|
+
def test_should_invalidate_the_state
|
882
|
+
@event.fire(@object)
|
883
|
+
assert_equal ['cannot transition via "ignite"'], @object.errors
|
884
|
+
end
|
885
|
+
|
886
|
+
def test_should_run_failure_callbacks
|
887
|
+
callback_args = nil
|
888
|
+
@machine.after_failure {|*args| callback_args = args}
|
889
|
+
|
890
|
+
@event.fire(@object)
|
891
|
+
|
892
|
+
object, transition = callback_args
|
893
|
+
assert_equal @object, object
|
894
|
+
assert_not_nil transition
|
895
|
+
assert_equal @object, transition.object
|
896
|
+
assert_equal @machine, transition.machine
|
897
|
+
assert_equal :ignite, transition.event
|
898
|
+
assert_equal :parked, transition.from_name
|
899
|
+
assert_equal :parked, transition.to_name
|
900
|
+
end
|
901
|
+
|
902
|
+
def teardown
|
903
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
904
|
+
end
|
905
|
+
end
|
906
|
+
|
907
|
+
class EventWithMarshallingTest < Test::Unit::TestCase
|
908
|
+
def setup
|
909
|
+
@klass = Class.new do
|
910
|
+
def save
|
911
|
+
true
|
912
|
+
end
|
913
|
+
end
|
914
|
+
self.class.const_set('Example', @klass)
|
915
|
+
|
916
|
+
@machine = StateMachine::Machine.new(@klass, :action => :save)
|
917
|
+
@machine.state :parked, :idling
|
918
|
+
|
919
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite)
|
920
|
+
@event.transition(:parked => :idling)
|
921
|
+
|
922
|
+
@object = @klass.new
|
923
|
+
@object.state = 'parked'
|
924
|
+
end
|
925
|
+
|
926
|
+
def test_should_marshal_during_before_callbacks
|
927
|
+
@machine.before_transition {|object, transition| Marshal.dump(object)}
|
928
|
+
assert_nothing_raised { @event.fire(@object) }
|
929
|
+
end
|
930
|
+
|
931
|
+
def test_should_marshal_during_action
|
932
|
+
@klass.class_eval do
|
933
|
+
def save
|
934
|
+
Marshal.dump(self)
|
935
|
+
end
|
936
|
+
end
|
937
|
+
|
938
|
+
assert_nothing_raised { @event.fire(@object) }
|
939
|
+
end
|
940
|
+
|
941
|
+
def test_should_marshal_during_after_callbacks
|
942
|
+
@machine.after_transition {|object, transition| Marshal.dump(object)}
|
943
|
+
assert_nothing_raised { @event.fire(@object) }
|
944
|
+
end
|
945
|
+
|
946
|
+
def teardown
|
947
|
+
self.class.send(:remove_const, 'Example')
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
begin
|
952
|
+
# Load library
|
953
|
+
require 'rubygems'
|
954
|
+
gem 'ruby-graphviz', '>=0.9.0'
|
955
|
+
require 'graphviz'
|
956
|
+
|
957
|
+
class EventDrawingTest < Test::Unit::TestCase
|
958
|
+
def setup
|
959
|
+
states = [:parked, :idling, :first_gear]
|
960
|
+
|
961
|
+
@machine = StateMachine::Machine.new(Class.new, :initial => :parked)
|
962
|
+
@machine.other_states(*states)
|
963
|
+
|
964
|
+
graph = GraphViz.new('G')
|
965
|
+
states.each {|state| graph.add_node(state.to_s)}
|
966
|
+
|
967
|
+
@machine.events << @event = StateMachine::Event.new(@machine , :park)
|
968
|
+
@event.transition :parked => :idling
|
969
|
+
@event.transition :first_gear => :parked
|
970
|
+
@event.transition :except_from => :parked, :to => :parked
|
971
|
+
|
972
|
+
@edges = @event.draw(graph)
|
973
|
+
end
|
974
|
+
|
975
|
+
def test_should_generate_edges_for_each_transition
|
976
|
+
assert_equal 4, @edges.size
|
977
|
+
end
|
978
|
+
|
979
|
+
def test_should_use_event_name_for_edge_label
|
980
|
+
assert_equal 'park', @edges.first['label'].to_s.gsub('"', '')
|
981
|
+
end
|
982
|
+
end
|
983
|
+
rescue LoadError
|
984
|
+
$stderr.puts 'Skipping GraphViz StateMachine::Event tests. `gem install ruby-graphviz` >= v0.9.0 and try again.'
|
985
|
+
end
|