pluginaweek-state_machine 0.7.6
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 +273 -0
- data/LICENSE +20 -0
- data/README.rdoc +466 -0
- data/Rakefile +98 -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 +429 -0
- data/lib/state_machine/assertions.rb +36 -0
- data/lib/state_machine/callback.rb +189 -0
- data/lib/state_machine/condition_proxy.rb +94 -0
- data/lib/state_machine/eval_helpers.rb +67 -0
- data/lib/state_machine/event.rb +251 -0
- data/lib/state_machine/event_collection.rb +113 -0
- data/lib/state_machine/extensions.rb +158 -0
- data/lib/state_machine/guard.rb +219 -0
- data/lib/state_machine/integrations.rb +68 -0
- data/lib/state_machine/integrations/active_record.rb +444 -0
- data/lib/state_machine/integrations/active_record/locale.rb +10 -0
- data/lib/state_machine/integrations/active_record/observer.rb +41 -0
- data/lib/state_machine/integrations/data_mapper.rb +325 -0
- data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
- data/lib/state_machine/integrations/sequel.rb +292 -0
- data/lib/state_machine/machine.rb +1431 -0
- data/lib/state_machine/machine_collection.rb +146 -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 +152 -0
- data/lib/state_machine/state.rb +249 -0
- data/lib/state_machine/state_collection.rb +112 -0
- data/lib/state_machine/transition.rb +367 -0
- data/tasks/state_machine.rake +1 -0
- data/tasks/state_machine.rb +30 -0
- data/test/classes/switch.rb +11 -0
- data/test/functional/state_machine_test.rb +941 -0
- data/test/test_helper.rb +4 -0
- data/test/unit/assertions_test.rb +40 -0
- data/test/unit/callback_test.rb +455 -0
- data/test/unit/condition_proxy_test.rb +328 -0
- data/test/unit/eval_helpers_test.rb +129 -0
- data/test/unit/event_collection_test.rb +293 -0
- data/test/unit/event_test.rb +605 -0
- data/test/unit/guard_test.rb +862 -0
- data/test/unit/integrations/active_record_test.rb +1001 -0
- data/test/unit/integrations/data_mapper_test.rb +694 -0
- data/test/unit/integrations/sequel_test.rb +486 -0
- data/test/unit/integrations_test.rb +42 -0
- data/test/unit/invalid_event_test.rb +7 -0
- data/test/unit/invalid_transition_test.rb +7 -0
- data/test/unit/machine_collection_test.rb +710 -0
- data/test/unit/machine_test.rb +1910 -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 +207 -0
- data/test/unit/state_collection_test.rb +280 -0
- data/test/unit/state_machine_test.rb +31 -0
- data/test/unit/state_test.rb +795 -0
- data/test/unit/transition_test.rb +1113 -0
- metadata +161 -0
@@ -0,0 +1,1910 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
class MachineByDefaultTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@klass = Class.new
|
6
|
+
@machine = StateMachine::Machine.new(@klass)
|
7
|
+
@object = @klass.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_should_have_an_owner_class
|
11
|
+
assert_equal @klass, @machine.owner_class
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_should_have_a_name
|
15
|
+
assert_equal :state, @machine.name
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_should_have_an_attribute
|
19
|
+
assert_equal :state, @machine.attribute
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_should_prefix_custom_attributes_with_attribute
|
23
|
+
assert_equal :state_event, @machine.attribute(:event)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_have_an_initial_state
|
27
|
+
assert_not_nil @machine.initial_state(@object)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_should_have_a_nil_initial_state
|
31
|
+
assert_nil @machine.initial_state(@object).value
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_should_not_have_any_events
|
35
|
+
assert !@machine.events.any?
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_should_not_have_any_before_callbacks
|
39
|
+
assert @machine.callbacks[:before].empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_should_not_have_any_after_callbacks
|
43
|
+
assert @machine.callbacks[:after].empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_should_not_have_an_action
|
47
|
+
assert_nil @machine.action
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_should_use_tranactions
|
51
|
+
assert_equal true, @machine.use_transactions
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_should_not_have_a_namespace
|
55
|
+
assert_nil @machine.namespace
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_should_have_a_nil_state
|
59
|
+
assert_equal [nil], @machine.states.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_should_set_initial_on_nil_state
|
63
|
+
assert @machine.state(nil).initial
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_should_generate_default_messages
|
67
|
+
assert_equal 'is invalid', @machine.generate_message(:invalid)
|
68
|
+
assert_equal 'cannot transition when parked', @machine.generate_message(:invalid_event, [[:state, :parked]])
|
69
|
+
assert_equal 'cannot transition via "park"', @machine.generate_message(:invalid_transition, [[:event, :park]])
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_should_not_be_extended_by_the_active_record_integration
|
73
|
+
assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::ActiveRecord)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_should_not_be_extended_by_the_datamapper_integration
|
77
|
+
assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::DataMapper)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_should_not_be_extended_by_the_sequel_integration
|
81
|
+
assert !(class << @machine; ancestors; end).include?(StateMachine::Integrations::Sequel)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_should_define_a_reader_attribute_for_the_attribute
|
85
|
+
assert @object.respond_to?(:state)
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_should_define_a_writer_attribute_for_the_attribute
|
89
|
+
assert @object.respond_to?(:state=)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_should_define_a_predicate_for_the_attribute
|
93
|
+
assert @object.respond_to?(:state?)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_should_define_a_name_reader_for_the_attribute
|
97
|
+
assert @object.respond_to?(:state_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_should_define_an_event_reader_for_the_attribute
|
101
|
+
assert @object.respond_to?(:state_events)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_should_define_a_transition_reader_for_the_attribute
|
105
|
+
assert @object.respond_to?(:state_transitions)
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_should_not_define_an_event_attribute_reader
|
109
|
+
assert !@object.respond_to?(:state_event)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_should_not_define_an_event_attribute_writer
|
113
|
+
assert !@object.respond_to?(:state_event=)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_should_not_define_an_event_transition_attribute_reader
|
117
|
+
assert !@object.respond_to?(:state_event_transition)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_should_not_define_an_event_transition_attribute_writer
|
121
|
+
assert !@object.respond_to?(:state_event_transition=)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_should_not_define_singular_with_scope
|
125
|
+
assert !@klass.respond_to?(:with_state)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_should_not_define_singular_without_scope
|
129
|
+
assert !@klass.respond_to?(:without_state)
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_should_not_define_plural_with_scope
|
133
|
+
assert !@klass.respond_to?(:with_states)
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_should_not_define_plural_without_scope
|
137
|
+
assert !@klass.respond_to?(:without_states)
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_should_extend_owner_class_with_class_methods
|
141
|
+
assert (class << @klass; ancestors; end).include?(StateMachine::ClassMethods)
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_should_include_instance_methods_in_owner_class
|
145
|
+
assert @klass.included_modules.include?(StateMachine::InstanceMethods)
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_should_define_state_machines_reader
|
149
|
+
expected = {:state => @machine}
|
150
|
+
assert_equal expected, @klass.state_machines
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class MachineWithCustomAttributeTest < Test::Unit::TestCase
|
155
|
+
def setup
|
156
|
+
@klass = Class.new
|
157
|
+
@machine = StateMachine::Machine.new(@klass, :status)
|
158
|
+
@object = @klass.new
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_should_use_custom_attribute_for_name
|
162
|
+
assert_equal :status, @machine.name
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_should_use_custom_attribute
|
166
|
+
assert_equal :status, @machine.attribute
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_should_prefix_custom_attributes_with_custom_attribute
|
170
|
+
assert_equal :status_event, @machine.attribute(:event)
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_should_define_a_reader_attribute_for_the_attribute
|
174
|
+
assert @object.respond_to?(:status)
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_should_define_a_writer_attribute_for_the_attribute
|
178
|
+
assert @object.respond_to?(:status=)
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_should_define_a_predicate_for_the_attribute
|
182
|
+
assert @object.respond_to?(:status?)
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_should_define_a_name_reader_for_the_attribute
|
186
|
+
assert @object.respond_to?(:status_name)
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_should_define_an_event_reader_for_the_attribute
|
190
|
+
assert @object.respond_to?(:status_events)
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_should_define_a_transition_reader_for_the_attribute
|
194
|
+
assert @object.respond_to?(:status_transitions)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class MachineWithStaticInitialStateTest < Test::Unit::TestCase
|
199
|
+
def setup
|
200
|
+
@klass = Class.new do
|
201
|
+
def initialize(attributes = {})
|
202
|
+
attributes.each {|attr, value| send("#{attr}=", value)}
|
203
|
+
super()
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_should_have_an_initial_state
|
211
|
+
object = @klass.new
|
212
|
+
assert_equal 'parked', @machine.initial_state(object).value
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_should_set_initial_on_state_object
|
216
|
+
assert @machine.state(:parked).initial
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_should_set_initial_state_if_existing_is_nil
|
220
|
+
object = @klass.new(:state => nil)
|
221
|
+
assert_equal 'parked', object.state
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_should_set_initial_state_if_existing_is_empty
|
225
|
+
object = @klass.new(:state => '')
|
226
|
+
assert_equal 'parked', object.state
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_should_not_set_initial_state_if_existing_is_not_empty
|
230
|
+
object = @klass.new(:state => 'idling')
|
231
|
+
assert_equal 'idling', object.state
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_should_be_included_in_known_states
|
235
|
+
assert_equal [:parked], @machine.states.keys
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class MachineWithDynamicInitialStateTest < Test::Unit::TestCase
|
240
|
+
def setup
|
241
|
+
@klass = Class.new do
|
242
|
+
attr_accessor :initial_state
|
243
|
+
end
|
244
|
+
@machine = StateMachine::Machine.new(@klass, :initial => lambda {|object| object.initial_state || :default})
|
245
|
+
@machine.state :parked, :idling, :default
|
246
|
+
@object = @klass.new
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_should_use_the_record_for_determining_the_initial_state
|
250
|
+
@object.initial_state = :parked
|
251
|
+
assert_equal :parked, @machine.initial_state(@object).name
|
252
|
+
|
253
|
+
@object.initial_state = :idling
|
254
|
+
assert_equal :idling, @machine.initial_state(@object).name
|
255
|
+
end
|
256
|
+
|
257
|
+
def test_should_set_initial_state_on_created_object
|
258
|
+
assert_equal 'default', @object.state
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_should_not_be_included_in_known_states
|
262
|
+
assert_equal [:parked, :idling, :default], @machine.states.map {|state| state.name}
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class MachineWithCustomActionTest < Test::Unit::TestCase
|
267
|
+
def setup
|
268
|
+
@machine = StateMachine::Machine.new(Class.new, :action => :save)
|
269
|
+
end
|
270
|
+
|
271
|
+
def test_should_use_the_custom_action
|
272
|
+
assert_equal :save, @machine.action
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class MachineWithNilActionTest < Test::Unit::TestCase
|
277
|
+
def setup
|
278
|
+
integration = Module.new do
|
279
|
+
class << self; attr_reader :defaults; end
|
280
|
+
@defaults = {:action => :save}
|
281
|
+
end
|
282
|
+
StateMachine::Integrations.const_set('Custom', integration)
|
283
|
+
@machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom)
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_should_have_a_nil_action
|
287
|
+
assert_nil @machine.action
|
288
|
+
end
|
289
|
+
|
290
|
+
def teardown
|
291
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
class MachineWithoutIntegrationTest < Test::Unit::TestCase
|
296
|
+
def setup
|
297
|
+
@klass = Class.new
|
298
|
+
@machine = StateMachine::Machine.new(@klass)
|
299
|
+
@object = @klass.new
|
300
|
+
end
|
301
|
+
|
302
|
+
def test_transaction_should_yield
|
303
|
+
@yielded = false
|
304
|
+
@machine.within_transaction(@object) do
|
305
|
+
@yielded = true
|
306
|
+
end
|
307
|
+
|
308
|
+
assert @yielded
|
309
|
+
end
|
310
|
+
|
311
|
+
def test_invalidation_should_do_nothing
|
312
|
+
assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_reset_should_do_nothing
|
316
|
+
assert_nil @machine.reset(@object)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
class MachineWithCustomIntegrationTest < Test::Unit::TestCase
|
321
|
+
def setup
|
322
|
+
StateMachine::Integrations.const_set('Custom', Module.new)
|
323
|
+
@machine = StateMachine::Machine.new(Class.new, :integration => :custom)
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_should_be_extended_by_the_integration
|
327
|
+
assert (class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom)
|
328
|
+
end
|
329
|
+
|
330
|
+
def teardown
|
331
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
class MachineWithIntegrationTest < Test::Unit::TestCase
|
336
|
+
def setup
|
337
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
338
|
+
class << self; attr_reader :defaults; end
|
339
|
+
@defaults = {:action => :save, :use_transactions => false}
|
340
|
+
|
341
|
+
attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
|
342
|
+
|
343
|
+
def after_initialize
|
344
|
+
@initialized = true
|
345
|
+
end
|
346
|
+
|
347
|
+
def create_with_scope(name)
|
348
|
+
(@with_scopes ||= []) << name
|
349
|
+
lambda {}
|
350
|
+
end
|
351
|
+
|
352
|
+
def create_without_scope(name)
|
353
|
+
(@without_scopes ||= []) << name
|
354
|
+
lambda {}
|
355
|
+
end
|
356
|
+
|
357
|
+
def transaction(object)
|
358
|
+
@ran_transaction = true
|
359
|
+
yield
|
360
|
+
end
|
361
|
+
end)
|
362
|
+
|
363
|
+
@machine = StateMachine::Machine.new(Class.new, :integration => :custom)
|
364
|
+
end
|
365
|
+
|
366
|
+
def test_should_call_after_initialize_hook
|
367
|
+
assert @machine.initialized
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_should_use_the_default_action
|
371
|
+
assert_equal :save, @machine.action
|
372
|
+
end
|
373
|
+
|
374
|
+
def test_should_use_the_custom_action_if_specified
|
375
|
+
machine = StateMachine::Machine.new(Class.new, :integration => :custom, :action => :save!)
|
376
|
+
assert_equal :save!, machine.action
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_should_use_the_default_use_transactions
|
380
|
+
assert_equal false, @machine.use_transactions
|
381
|
+
end
|
382
|
+
|
383
|
+
def test_should_use_the_custom_use_transactions_if_specified
|
384
|
+
machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true)
|
385
|
+
assert_equal true, machine.use_transactions
|
386
|
+
end
|
387
|
+
|
388
|
+
def test_should_define_a_singular_and_plural_with_scope
|
389
|
+
assert_equal %w(with_state with_states), @machine.with_scopes
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_should_define_a_singular_and_plural_without_scope
|
393
|
+
assert_equal %w(without_state without_states), @machine.without_scopes
|
394
|
+
end
|
395
|
+
|
396
|
+
def teardown
|
397
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
class MachineWithActionTest < Test::Unit::TestCase
|
402
|
+
def setup
|
403
|
+
@klass = Class.new do
|
404
|
+
def save
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
@machine = StateMachine::Machine.new(@klass, :action => :save)
|
409
|
+
@object = @klass.new
|
410
|
+
end
|
411
|
+
|
412
|
+
def test_should_define_an_event_attribute_reader
|
413
|
+
assert @object.respond_to?(:state_event)
|
414
|
+
end
|
415
|
+
|
416
|
+
def test_should_define_an_event_attribute_writer
|
417
|
+
assert @object.respond_to?(:state_event=)
|
418
|
+
end
|
419
|
+
|
420
|
+
def test_should_define_an_event_transition_attribute_reader
|
421
|
+
assert @object.respond_to?(:state_event_transition)
|
422
|
+
end
|
423
|
+
|
424
|
+
def test_should_define_an_event_transition_attribute_writer
|
425
|
+
assert @object.respond_to?(:state_event_transition=)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
class MachineWithActionUndefinedTest < Test::Unit::TestCase
|
430
|
+
def setup
|
431
|
+
@klass = Class.new
|
432
|
+
@machine = StateMachine::Machine.new(@klass, :action => :save)
|
433
|
+
@object = @klass.new
|
434
|
+
end
|
435
|
+
|
436
|
+
def test_should_define_an_event_attribute_reader
|
437
|
+
assert @object.respond_to?(:state_event)
|
438
|
+
end
|
439
|
+
|
440
|
+
def test_should_define_an_event_attribute_writer
|
441
|
+
assert @object.respond_to?(:state_event=)
|
442
|
+
end
|
443
|
+
|
444
|
+
def test_should_define_an_event_transition_attribute_reader
|
445
|
+
assert @object.respond_to?(:state_event_transition)
|
446
|
+
end
|
447
|
+
|
448
|
+
def test_should_define_an_event_transition_attribute_writer
|
449
|
+
assert @object.respond_to?(:state_event_transition=)
|
450
|
+
end
|
451
|
+
|
452
|
+
def test_should_not_define_action
|
453
|
+
assert !@object.respond_to?(:save)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
class MachineWithCustomPluralTest < Test::Unit::TestCase
|
458
|
+
def setup
|
459
|
+
@integration = Module.new do
|
460
|
+
class << self; attr_accessor :with_scopes, :without_scopes; end
|
461
|
+
@with_scopes = []
|
462
|
+
@without_scopes = []
|
463
|
+
|
464
|
+
def create_with_scope(name)
|
465
|
+
StateMachine::Integrations::Custom.with_scopes << name
|
466
|
+
lambda {}
|
467
|
+
end
|
468
|
+
|
469
|
+
def create_without_scope(name)
|
470
|
+
StateMachine::Integrations::Custom.without_scopes << name
|
471
|
+
lambda {}
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
StateMachine::Integrations.const_set('Custom', @integration)
|
476
|
+
@machine = StateMachine::Machine.new(Class.new, :integration => :custom, :plural => 'staties')
|
477
|
+
end
|
478
|
+
|
479
|
+
def test_should_define_a_singular_and_plural_with_scope
|
480
|
+
assert_equal %w(with_state with_staties), @integration.with_scopes
|
481
|
+
end
|
482
|
+
|
483
|
+
def test_should_define_a_singular_and_plural_without_scope
|
484
|
+
assert_equal %w(without_state without_staties), @integration.without_scopes
|
485
|
+
end
|
486
|
+
|
487
|
+
def teardown
|
488
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
class MachineWithCustomInvalidationTest < Test::Unit::TestCase
|
493
|
+
def setup
|
494
|
+
@integration = Module.new do
|
495
|
+
def invalidate(object, attribute, message, values = [])
|
496
|
+
object.error = generate_message(message, values)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
StateMachine::Integrations.const_set('Custom', @integration)
|
500
|
+
|
501
|
+
@klass = Class.new do
|
502
|
+
attr_accessor :error
|
503
|
+
end
|
504
|
+
|
505
|
+
@machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'})
|
506
|
+
@machine.state :parked
|
507
|
+
|
508
|
+
@object = @klass.new
|
509
|
+
@object.state = 'parked'
|
510
|
+
end
|
511
|
+
|
512
|
+
def test_generate_custom_message
|
513
|
+
assert_equal 'cannot park', @machine.generate_message(:invalid_transition, [[:event, :park]])
|
514
|
+
end
|
515
|
+
|
516
|
+
def test_use_custom_message
|
517
|
+
@machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
|
518
|
+
assert_equal 'cannot park', @object.error
|
519
|
+
end
|
520
|
+
|
521
|
+
def teardown
|
522
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
class MachineTest < Test::Unit::TestCase
|
527
|
+
def test_should_raise_exception_if_invalid_option_specified
|
528
|
+
assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)}
|
529
|
+
end
|
530
|
+
|
531
|
+
def test_should_not_raise_exception_if_custom_messages_specified
|
532
|
+
assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})}
|
533
|
+
end
|
534
|
+
|
535
|
+
def test_should_evaluate_a_block_during_initialization
|
536
|
+
called = true
|
537
|
+
StateMachine::Machine.new(Class.new) do
|
538
|
+
called = respond_to?(:event)
|
539
|
+
end
|
540
|
+
|
541
|
+
assert called
|
542
|
+
end
|
543
|
+
|
544
|
+
def test_should_provide_matcher_helpers_during_initialization
|
545
|
+
matchers = []
|
546
|
+
|
547
|
+
StateMachine::Machine.new(Class.new) do
|
548
|
+
matchers = [all, any, same]
|
549
|
+
end
|
550
|
+
|
551
|
+
assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
class MachineAfterBeingCopiedTest < Test::Unit::TestCase
|
556
|
+
def setup
|
557
|
+
@machine = StateMachine::Machine.new(Class.new, :state, :initial => :parked)
|
558
|
+
@machine.event(:ignite) {}
|
559
|
+
@machine.before_transition(lambda {})
|
560
|
+
@machine.after_transition(lambda {})
|
561
|
+
|
562
|
+
@copied_machine = @machine.clone
|
563
|
+
end
|
564
|
+
|
565
|
+
def test_should_not_have_the_same_collection_of_states
|
566
|
+
assert_not_same @copied_machine.states, @machine.states
|
567
|
+
end
|
568
|
+
|
569
|
+
def test_should_copy_each_state
|
570
|
+
assert_not_same @copied_machine.states[:parked], @machine.states[:parked]
|
571
|
+
end
|
572
|
+
|
573
|
+
def test_should_update_machine_for_each_state
|
574
|
+
assert_equal @copied_machine, @copied_machine.states[:parked].machine
|
575
|
+
end
|
576
|
+
|
577
|
+
def test_should_not_update_machine_for_original_state
|
578
|
+
assert_equal @machine, @machine.states[:parked].machine
|
579
|
+
end
|
580
|
+
|
581
|
+
def test_should_not_have_the_same_collection_of_events
|
582
|
+
assert_not_same @copied_machine.events, @machine.events
|
583
|
+
end
|
584
|
+
|
585
|
+
def test_should_copy_each_event
|
586
|
+
assert_not_same @copied_machine.events[:ignite], @machine.events[:ignite]
|
587
|
+
end
|
588
|
+
|
589
|
+
def test_should_update_machine_for_each_event
|
590
|
+
assert_equal @copied_machine, @copied_machine.events[:ignite].machine
|
591
|
+
end
|
592
|
+
|
593
|
+
def test_should_not_update_machine_for_original_event
|
594
|
+
assert_equal @machine, @machine.events[:ignite].machine
|
595
|
+
end
|
596
|
+
|
597
|
+
def test_should_not_have_the_same_callbacks
|
598
|
+
assert_not_same @copied_machine.callbacks, @machine.callbacks
|
599
|
+
end
|
600
|
+
|
601
|
+
def test_should_not_have_the_same_before_callbacks
|
602
|
+
assert_not_same @copied_machine.callbacks[:before], @machine.callbacks[:before]
|
603
|
+
end
|
604
|
+
|
605
|
+
def test_should_not_have_the_same_after_callbacks
|
606
|
+
assert_not_same @copied_machine.callbacks[:after], @machine.callbacks[:after]
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
class MachineAfterChangingOwnerClassTest < Test::Unit::TestCase
|
611
|
+
def setup
|
612
|
+
@original_class = Class.new
|
613
|
+
@machine = StateMachine::Machine.new(@original_class)
|
614
|
+
|
615
|
+
@new_class = Class.new(@original_class)
|
616
|
+
@new_machine = @machine.clone
|
617
|
+
@new_machine.owner_class = @new_class
|
618
|
+
|
619
|
+
@object = @new_class.new
|
620
|
+
end
|
621
|
+
|
622
|
+
def test_should_update_owner_class
|
623
|
+
assert_equal @new_class, @new_machine.owner_class
|
624
|
+
end
|
625
|
+
|
626
|
+
def test_should_not_change_original_owner_class
|
627
|
+
assert_equal @original_class, @machine.owner_class
|
628
|
+
end
|
629
|
+
|
630
|
+
def test_should_change_the_associated_machine_in_the_new_class
|
631
|
+
assert_equal @new_machine, @new_class.state_machines[:state]
|
632
|
+
end
|
633
|
+
|
634
|
+
def test_should_not_change_the_associated_machine_in_the_original_class
|
635
|
+
assert_equal @machine, @original_class.state_machines[:state]
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
class MachineAfterChangingInitialState < Test::Unit::TestCase
|
640
|
+
def setup
|
641
|
+
@klass = Class.new
|
642
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
643
|
+
@machine.initial_state = :idling
|
644
|
+
|
645
|
+
@object = @klass.new
|
646
|
+
end
|
647
|
+
|
648
|
+
def test_should_change_the_initial_state
|
649
|
+
assert_equal :idling, @machine.initial_state(@object).name
|
650
|
+
end
|
651
|
+
|
652
|
+
def test_should_include_in_known_states
|
653
|
+
assert_equal [:parked, :idling], @machine.states.map {|state| state.name}
|
654
|
+
end
|
655
|
+
|
656
|
+
def test_should_reset_original_initial_state
|
657
|
+
assert !@machine.state(:parked).initial
|
658
|
+
end
|
659
|
+
|
660
|
+
def test_should_set_new_state_to_initial
|
661
|
+
assert @machine.state(:idling).initial
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
class MachineWithInstanceHelpersTest < Test::Unit::TestCase
|
666
|
+
def setup
|
667
|
+
@klass = Class.new
|
668
|
+
@machine = StateMachine::Machine.new(@klass)
|
669
|
+
@object = @klass.new
|
670
|
+
end
|
671
|
+
|
672
|
+
def test_should_not_redefine_existing_public_methods
|
673
|
+
@klass.class_eval do
|
674
|
+
def state
|
675
|
+
'parked'
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
@machine.define_instance_method(:state) {}
|
680
|
+
assert_equal 'parked', @object.state
|
681
|
+
end
|
682
|
+
|
683
|
+
def test_should_not_redefine_existing_protected_methods
|
684
|
+
@klass.class_eval do
|
685
|
+
protected
|
686
|
+
def state
|
687
|
+
'parked'
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
@machine.define_instance_method(:state) {}
|
692
|
+
assert_equal 'parked', @object.send(:state)
|
693
|
+
end
|
694
|
+
|
695
|
+
def test_should_not_redefine_existing_private_methods
|
696
|
+
@klass.class_eval do
|
697
|
+
private
|
698
|
+
def state
|
699
|
+
'parked'
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
@machine.define_instance_method(:state) {}
|
704
|
+
assert_equal 'parked', @object.send(:state)
|
705
|
+
end
|
706
|
+
|
707
|
+
def test_should_define_nonexistent_methods
|
708
|
+
@machine.define_instance_method(:state) {'parked'}
|
709
|
+
assert_equal 'parked', @object.state
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
class MachineWithClassHelpersTest < Test::Unit::TestCase
|
714
|
+
def setup
|
715
|
+
@klass = Class.new
|
716
|
+
@machine = StateMachine::Machine.new(@klass)
|
717
|
+
end
|
718
|
+
|
719
|
+
def test_should_not_redefine_existing_public_methods
|
720
|
+
class << @klass
|
721
|
+
def states
|
722
|
+
[]
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
@machine.define_class_method(:states) {}
|
727
|
+
assert_equal [], @klass.states
|
728
|
+
end
|
729
|
+
|
730
|
+
def test_should_not_redefine_existing_protected_methods
|
731
|
+
class << @klass
|
732
|
+
protected
|
733
|
+
def states
|
734
|
+
[]
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
@machine.define_class_method(:states) {}
|
739
|
+
assert_equal [], @klass.send(:states)
|
740
|
+
end
|
741
|
+
|
742
|
+
def test_should_not_redefine_existing_private_methods
|
743
|
+
class << @klass
|
744
|
+
private
|
745
|
+
def states
|
746
|
+
[]
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
@machine.define_class_method(:states) {}
|
751
|
+
assert_equal [], @klass.send(:states)
|
752
|
+
end
|
753
|
+
|
754
|
+
def test_should_define_nonexistent_methods
|
755
|
+
@machine.define_class_method(:states) {[]}
|
756
|
+
assert_equal [], @klass.states
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
class MachineWithConflictingHelpersTest < Test::Unit::TestCase
|
761
|
+
def setup
|
762
|
+
@klass = Class.new do
|
763
|
+
def self.with_state
|
764
|
+
:with_state
|
765
|
+
end
|
766
|
+
|
767
|
+
def self.with_states
|
768
|
+
:with_states
|
769
|
+
end
|
770
|
+
|
771
|
+
def self.without_state
|
772
|
+
:without_state
|
773
|
+
end
|
774
|
+
|
775
|
+
def self.without_states
|
776
|
+
:without_states
|
777
|
+
end
|
778
|
+
|
779
|
+
attr_accessor :status
|
780
|
+
|
781
|
+
def state
|
782
|
+
'parked'
|
783
|
+
end
|
784
|
+
|
785
|
+
def state=(value)
|
786
|
+
self.status = value
|
787
|
+
end
|
788
|
+
|
789
|
+
def state?
|
790
|
+
true
|
791
|
+
end
|
792
|
+
|
793
|
+
def state_name
|
794
|
+
:parked
|
795
|
+
end
|
796
|
+
|
797
|
+
def state_events
|
798
|
+
[:ignite]
|
799
|
+
end
|
800
|
+
|
801
|
+
def state_transitions
|
802
|
+
[{:parked => :idling}]
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
807
|
+
def create_with_scope(name)
|
808
|
+
lambda {|klass, values| []}
|
809
|
+
end
|
810
|
+
|
811
|
+
def create_without_scope(name)
|
812
|
+
lambda {|klass, values| []}
|
813
|
+
end
|
814
|
+
end)
|
815
|
+
|
816
|
+
@machine = StateMachine::Machine.new(@klass, :integration => :custom)
|
817
|
+
@machine.state :parked, :idling
|
818
|
+
@object = @klass.new
|
819
|
+
end
|
820
|
+
|
821
|
+
def test_should_not_redefine_singular_with_scope
|
822
|
+
assert_equal :with_state, @klass.with_state
|
823
|
+
end
|
824
|
+
|
825
|
+
def test_should_not_redefine_plural_with_scope
|
826
|
+
assert_equal :with_states, @klass.with_states
|
827
|
+
end
|
828
|
+
|
829
|
+
def test_should_not_redefine_singular_without_scope
|
830
|
+
assert_equal :without_state, @klass.without_state
|
831
|
+
end
|
832
|
+
|
833
|
+
def test_should_not_redefine_plural_without_scope
|
834
|
+
assert_equal :without_states, @klass.without_states
|
835
|
+
end
|
836
|
+
|
837
|
+
def test_should_not_redefine_attribute_writer
|
838
|
+
assert_equal 'parked', @object.state
|
839
|
+
end
|
840
|
+
|
841
|
+
def test_should_not_redefine_attribute_writer
|
842
|
+
@object.state = 'parked'
|
843
|
+
assert_equal 'parked', @object.status
|
844
|
+
end
|
845
|
+
|
846
|
+
def test_should_not_define_attribute_predicate
|
847
|
+
assert @object.state?
|
848
|
+
end
|
849
|
+
|
850
|
+
def test_should_not_redefine_attribute_name_reader
|
851
|
+
assert_equal :parked, @object.state_name
|
852
|
+
end
|
853
|
+
|
854
|
+
def test_should_not_redefine_attribute_events_reader
|
855
|
+
assert_equal [:ignite], @object.state_events
|
856
|
+
end
|
857
|
+
|
858
|
+
def test_should_not_redefine_attribute_transitions_reader
|
859
|
+
assert_equal [{:parked => :idling}], @object.state_transitions
|
860
|
+
end
|
861
|
+
|
862
|
+
def test_should_allow_super_chaining
|
863
|
+
@klass.class_eval do
|
864
|
+
def self.with_state(*states)
|
865
|
+
super == []
|
866
|
+
end
|
867
|
+
|
868
|
+
def self.with_states(*states)
|
869
|
+
super == []
|
870
|
+
end
|
871
|
+
|
872
|
+
def self.without_state(*states)
|
873
|
+
super == []
|
874
|
+
end
|
875
|
+
|
876
|
+
def self.without_states(*states)
|
877
|
+
super == []
|
878
|
+
end
|
879
|
+
|
880
|
+
attr_accessor :status
|
881
|
+
|
882
|
+
def state
|
883
|
+
super || 'parked'
|
884
|
+
end
|
885
|
+
|
886
|
+
def state=(value)
|
887
|
+
super
|
888
|
+
self.status = value
|
889
|
+
end
|
890
|
+
|
891
|
+
def state?(state)
|
892
|
+
super ? 1 : 0
|
893
|
+
end
|
894
|
+
|
895
|
+
def state_name
|
896
|
+
super == :parked ? 1 : 0
|
897
|
+
end
|
898
|
+
|
899
|
+
def state_events
|
900
|
+
super == []
|
901
|
+
end
|
902
|
+
|
903
|
+
def state_transitions
|
904
|
+
super == []
|
905
|
+
end
|
906
|
+
end
|
907
|
+
|
908
|
+
assert_equal true, @klass.with_state
|
909
|
+
assert_equal true, @klass.with_states
|
910
|
+
assert_equal true, @klass.without_state
|
911
|
+
assert_equal true, @klass.without_states
|
912
|
+
|
913
|
+
assert_equal 'parked', @object.state
|
914
|
+
@object.state = 'idling'
|
915
|
+
assert_equal 'idling', @object.status
|
916
|
+
assert_equal 0, @object.state?(:parked)
|
917
|
+
assert_equal 0, @object.state_name
|
918
|
+
assert_equal true, @object.state_events
|
919
|
+
assert_equal true, @object.state_transitions
|
920
|
+
end
|
921
|
+
|
922
|
+
def teardown
|
923
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
class MachineWithoutInitializeTest < Test::Unit::TestCase
|
928
|
+
def setup
|
929
|
+
@klass = Class.new
|
930
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
931
|
+
@object = @klass.new
|
932
|
+
end
|
933
|
+
|
934
|
+
def test_should_initialize_state
|
935
|
+
assert_equal 'parked', @object.state
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
939
|
+
class MachineWithInitializeWithoutSuperTest < Test::Unit::TestCase
|
940
|
+
def setup
|
941
|
+
@klass = Class.new do
|
942
|
+
def initialize
|
943
|
+
end
|
944
|
+
end
|
945
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
946
|
+
@object = @klass.new
|
947
|
+
end
|
948
|
+
|
949
|
+
def test_should_not_initialize_state
|
950
|
+
assert_nil @object.state
|
951
|
+
end
|
952
|
+
end
|
953
|
+
|
954
|
+
class MachineWithInitializeAndSuperTest < Test::Unit::TestCase
|
955
|
+
def setup
|
956
|
+
@klass = Class.new do
|
957
|
+
def initialize
|
958
|
+
super()
|
959
|
+
end
|
960
|
+
end
|
961
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
962
|
+
@object = @klass.new
|
963
|
+
end
|
964
|
+
|
965
|
+
def test_should_initialize_state
|
966
|
+
assert_equal 'parked', @object.state
|
967
|
+
end
|
968
|
+
end
|
969
|
+
|
970
|
+
class MachineWithInitializeArgumentsAndBlockTest < Test::Unit::TestCase
|
971
|
+
def setup
|
972
|
+
@superclass = Class.new do
|
973
|
+
attr_reader :args
|
974
|
+
attr_reader :block_given
|
975
|
+
|
976
|
+
def initialize(*args)
|
977
|
+
@args = args
|
978
|
+
@block_given = block_given?
|
979
|
+
end
|
980
|
+
end
|
981
|
+
@klass = Class.new(@superclass)
|
982
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
983
|
+
@object = @klass.new(1, 2, 3) {}
|
984
|
+
end
|
985
|
+
|
986
|
+
def test_should_initialize_state
|
987
|
+
assert_equal 'parked', @object.state
|
988
|
+
end
|
989
|
+
|
990
|
+
def test_should_preserve_arguments
|
991
|
+
assert_equal [1, 2, 3], @object.args
|
992
|
+
end
|
993
|
+
|
994
|
+
def test_should_preserve_block
|
995
|
+
assert @object.block_given
|
996
|
+
end
|
997
|
+
end
|
998
|
+
|
999
|
+
class MachineWithCustomInitializeTest < Test::Unit::TestCase
|
1000
|
+
def setup
|
1001
|
+
@klass = Class.new do
|
1002
|
+
def initialize
|
1003
|
+
initialize_state_machines
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1007
|
+
@object = @klass.new
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
def test_should_initialize_state
|
1011
|
+
assert_equal 'parked', @object.state
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
class MachinePersistenceTest < Test::Unit::TestCase
|
1016
|
+
def setup
|
1017
|
+
@klass = Class.new do
|
1018
|
+
attr_accessor :state_event
|
1019
|
+
end
|
1020
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1021
|
+
@object = @klass.new
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
def test_should_allow_reading_state
|
1025
|
+
assert_equal 'parked', @machine.read(@object, :state)
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
def test_should_allow_reading_custom_attributes
|
1029
|
+
assert_nil @machine.read(@object, :event)
|
1030
|
+
|
1031
|
+
@object.state_event = 'ignite'
|
1032
|
+
assert_equal 'ignite', @machine.read(@object, :event)
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def test_should_allow_reading_custom_instance_variables
|
1036
|
+
@klass.class_eval do
|
1037
|
+
attr_writer :state_value
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
@object.state_value = 1
|
1041
|
+
assert_raise(NoMethodError) { @machine.read(@object, :value) }
|
1042
|
+
assert_equal 1, @machine.read(@object, :value, true)
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def test_should_allow_writing_state
|
1046
|
+
@machine.write(@object, :state, 'idling')
|
1047
|
+
assert_equal 'idling', @object.state
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
def test_should_allow_writing_custom_attributes
|
1051
|
+
@machine.write(@object, :event, 'ignite')
|
1052
|
+
assert_equal 'ignite', @object.state_event
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
|
1057
|
+
class MachineWithStatesTest < Test::Unit::TestCase
|
1058
|
+
def setup
|
1059
|
+
@klass = Class.new
|
1060
|
+
@machine = StateMachine::Machine.new(@klass)
|
1061
|
+
@parked, @idling = @machine.state :parked, :idling
|
1062
|
+
|
1063
|
+
@object = @klass.new
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
def test_should_have_states
|
1067
|
+
assert_equal [nil, :parked, :idling], @machine.states.map {|state| state.name}
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
def test_should_allow_state_lookup_by_name
|
1071
|
+
assert_equal @parked, @machine.states[:parked]
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
def test_should_allow_state_lookup_by_value
|
1075
|
+
assert_equal @parked, @machine.states['parked', :value]
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
def test_should_use_stringified_name_for_value
|
1079
|
+
assert_equal 'parked', @parked.value
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
def test_should_not_use_custom_matcher
|
1083
|
+
assert_nil @parked.matcher
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
def test_should_raise_exception_if_invalid_option_specified
|
1087
|
+
exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)}
|
1088
|
+
assert_equal 'Invalid key(s): invalid', exception.message
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
|
1093
|
+
def setup
|
1094
|
+
@klass = Class.new
|
1095
|
+
@machine = StateMachine::Machine.new(@klass)
|
1096
|
+
@state = @machine.state :parked, :value => 1
|
1097
|
+
|
1098
|
+
@object = @klass.new
|
1099
|
+
@object.state = 1
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
def test_should_use_custom_value
|
1103
|
+
assert_equal 1, @state.value
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
def test_should_allow_lookup_by_custom_value
|
1107
|
+
assert_equal @state, @machine.states[1, :value]
|
1108
|
+
end
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
class MachineWithStatesWithRuntimeDependenciesTest < Test::Unit::TestCase
|
1112
|
+
def setup
|
1113
|
+
@klass = Class.new
|
1114
|
+
@machine = StateMachine::Machine.new(@klass)
|
1115
|
+
@machine.state :parked
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
def test_should_not_evaluate_value_during_definition
|
1119
|
+
assert_nothing_raised { @machine.state :parked, :value => lambda {raise ArgumentError} }
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
def test_should_not_evaluate_if_not_initial_state
|
1123
|
+
@machine.state :parked, :value => lambda {raise ArgumentError}
|
1124
|
+
assert_nothing_raised { @klass.new }
|
1125
|
+
end
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
class MachineWithStateWithMatchersTest < Test::Unit::TestCase
|
1129
|
+
def setup
|
1130
|
+
@klass = Class.new
|
1131
|
+
@machine = StateMachine::Machine.new(@klass)
|
1132
|
+
@state = @machine.state :parked, :if => lambda {|value| !value.nil?}
|
1133
|
+
|
1134
|
+
@object = @klass.new
|
1135
|
+
@object.state = 1
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
def test_should_use_custom_matcher
|
1139
|
+
assert_not_nil @state.matcher
|
1140
|
+
assert @state.matches?(1)
|
1141
|
+
assert !@state.matches?(nil)
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
class MachineWithCachedStateTest < Test::Unit::TestCase
|
1146
|
+
def setup
|
1147
|
+
@klass = Class.new
|
1148
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1149
|
+
@state = @machine.state :parked, :value => lambda {Object.new}, :cache => true
|
1150
|
+
|
1151
|
+
@object = @klass.new
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
def test_should_use_evaluated_value
|
1155
|
+
assert_instance_of Object, @object.state
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
def test_use_same_value_across_multiple_objects
|
1159
|
+
assert_equal @object.state, @klass.new.state
|
1160
|
+
end
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
|
1164
|
+
def setup
|
1165
|
+
@klass = Class.new
|
1166
|
+
@machine = StateMachine::Machine.new(@klass)
|
1167
|
+
|
1168
|
+
@parked, @idling = @machine.state :parked, :idling do
|
1169
|
+
def speed
|
1170
|
+
0
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
def test_should_define_behaviors_for_each_state
|
1176
|
+
assert_not_nil @parked.methods[:speed]
|
1177
|
+
assert_not_nil @idling.methods[:speed]
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
def test_should_define_different_behaviors_for_each_state
|
1181
|
+
assert_not_equal @parked.methods[:speed], @idling.methods[:speed]
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
class MachineWithExistingStateTest < Test::Unit::TestCase
|
1186
|
+
def setup
|
1187
|
+
@klass = Class.new
|
1188
|
+
@machine = StateMachine::Machine.new(@klass)
|
1189
|
+
@state = @machine.state :parked
|
1190
|
+
@same_state = @machine.state :parked, :value => 1
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
def test_should_not_create_a_new_state
|
1194
|
+
assert_same @state, @same_state
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
def test_should_update_attributes
|
1198
|
+
assert_equal 1, @state.value
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
def test_should_no_longer_be_able_to_look_up_state_by_original_value
|
1202
|
+
assert_nil @machine.states['parked', :value]
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
def test_should_be_able_to_look_up_state_by_new_value
|
1206
|
+
assert_equal @state, @machine.states[1, :value]
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
class MachineWithOtherStates < Test::Unit::TestCase
|
1211
|
+
def setup
|
1212
|
+
@klass = Class.new
|
1213
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1214
|
+
@parked, @idling = @machine.other_states(:parked, :idling)
|
1215
|
+
end
|
1216
|
+
|
1217
|
+
def test_should_include_other_states_in_known_states
|
1218
|
+
assert_equal [@parked, @idling], @machine.states.to_a
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
def test_should_use_default_value
|
1222
|
+
assert_equal 'idling', @idling.value
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
def test_should_not_create_matcher
|
1226
|
+
assert_nil @idling.matcher
|
1227
|
+
end
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
class MachineWithEventsTest < Test::Unit::TestCase
|
1231
|
+
def setup
|
1232
|
+
@klass = Class.new
|
1233
|
+
@machine = StateMachine::Machine.new(@klass)
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
def test_should_return_the_created_event
|
1237
|
+
assert_instance_of StateMachine::Event, @machine.event(:ignite)
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
def test_should_create_event_with_given_name
|
1241
|
+
event = @machine.event(:ignite) {}
|
1242
|
+
assert_equal :ignite, event.name
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
def test_should_evaluate_block_within_event_context
|
1246
|
+
responded = false
|
1247
|
+
@machine.event :ignite do
|
1248
|
+
responded = respond_to?(:transition)
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
assert responded
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
def test_should_be_aliased_as_on
|
1255
|
+
event = @machine.on(:ignite) {}
|
1256
|
+
assert_equal :ignite, event.name
|
1257
|
+
end
|
1258
|
+
|
1259
|
+
def test_should_have_events
|
1260
|
+
event = @machine.event(:ignite)
|
1261
|
+
assert_equal [event], @machine.events.to_a
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
class MachineWithExistingEventTest < Test::Unit::TestCase
|
1266
|
+
def setup
|
1267
|
+
@machine = StateMachine::Machine.new(Class.new)
|
1268
|
+
@event = @machine.event(:ignite)
|
1269
|
+
@same_event = @machine.event(:ignite)
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
def test_should_not_create_new_event
|
1273
|
+
assert_same @event, @same_event
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
def test_should_allow_accessing_event_without_block
|
1277
|
+
assert_equal @event, @machine.event(:ignite)
|
1278
|
+
end
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
class MachineWithEventsWithTransitionsTest < Test::Unit::TestCase
|
1282
|
+
def setup
|
1283
|
+
@klass = Class.new
|
1284
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1285
|
+
@event = @machine.event(:ignite) do
|
1286
|
+
transition :parked => :idling
|
1287
|
+
transition :stalled => :idling
|
1288
|
+
end
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
def test_should_have_events
|
1292
|
+
assert_equal [@event], @machine.events.to_a
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
def test_should_track_states_defined_in_event_transitions
|
1296
|
+
assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
def test_should_not_duplicate_states_defined_in_multiple_event_transitions
|
1300
|
+
@machine.event :park do
|
1301
|
+
transition :idling => :parked
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
assert_equal [:parked, :idling, :stalled], @machine.states.map {|state| state.name}
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
def test_should_track_state_from_new_events
|
1308
|
+
@machine.event :shift_up do
|
1309
|
+
transition :idling => :first_gear
|
1310
|
+
end
|
1311
|
+
|
1312
|
+
assert_equal [:parked, :idling, :stalled, :first_gear], @machine.states.map {|state| state.name}
|
1313
|
+
end
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
class MachineWithMultipleEventsTest < Test::Unit::TestCase
|
1317
|
+
def setup
|
1318
|
+
@klass = Class.new
|
1319
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1320
|
+
@park, @shift_down = @machine.event(:park, :shift_down) do
|
1321
|
+
transition :first_gear => :parked
|
1322
|
+
end
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
def test_should_have_events
|
1326
|
+
assert_equal [@park, @shift_down], @machine.events.to_a
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
def test_should_define_transitions_for_each_event
|
1330
|
+
[@park, @shift_down].each {|event| assert_equal 1, event.guards.size}
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
def test_should_transition_the_same_for_each_event
|
1334
|
+
object = @klass.new
|
1335
|
+
object.state = 'first_gear'
|
1336
|
+
object.park
|
1337
|
+
assert_equal 'parked', object.state
|
1338
|
+
|
1339
|
+
object = @klass.new
|
1340
|
+
object.state = 'first_gear'
|
1341
|
+
object.shift_down
|
1342
|
+
assert_equal 'parked', object.state
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
class MachineWithTransitionCallbacksTest < Test::Unit::TestCase
|
1347
|
+
def setup
|
1348
|
+
@klass = Class.new do
|
1349
|
+
attr_accessor :callbacks
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1353
|
+
@event = @machine.event :ignite do
|
1354
|
+
transition :parked => :idling
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
@object = @klass.new
|
1358
|
+
@object.callbacks = []
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
def test_should_not_raise_exception_if_implicit_option_specified
|
1362
|
+
assert_nothing_raised {@machine.before_transition :invalid => :valid, :do => lambda {}}
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
def test_should_raise_exception_if_method_not_specified
|
1366
|
+
exception = assert_raise(ArgumentError) {@machine.before_transition :to => :idling}
|
1367
|
+
assert_equal 'Method(s) for callback must be specified', exception.message
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
def test_should_invoke_callbacks_during_transition
|
1371
|
+
@machine.before_transition lambda {|object| object.callbacks << 'before'}
|
1372
|
+
@machine.after_transition lambda {|object| object.callbacks << 'after'}
|
1373
|
+
|
1374
|
+
@event.fire(@object)
|
1375
|
+
assert_equal %w(before after), @object.callbacks
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
def test_should_support_from_requirement
|
1379
|
+
@machine.before_transition :from => :parked, :do => lambda {|object| object.callbacks << :parked}
|
1380
|
+
@machine.before_transition :from => :idling, :do => lambda {|object| object.callbacks << :idling}
|
1381
|
+
|
1382
|
+
@event.fire(@object)
|
1383
|
+
assert_equal [:parked], @object.callbacks
|
1384
|
+
end
|
1385
|
+
|
1386
|
+
def test_should_support_except_from_requirement
|
1387
|
+
@machine.before_transition :except_from => :parked, :do => lambda {|object| object.callbacks << :parked}
|
1388
|
+
@machine.before_transition :except_from => :idling, :do => lambda {|object| object.callbacks << :idling}
|
1389
|
+
|
1390
|
+
@event.fire(@object)
|
1391
|
+
assert_equal [:idling], @object.callbacks
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
def test_should_support_to_requirement
|
1395
|
+
@machine.before_transition :to => :parked, :do => lambda {|object| object.callbacks << :parked}
|
1396
|
+
@machine.before_transition :to => :idling, :do => lambda {|object| object.callbacks << :idling}
|
1397
|
+
|
1398
|
+
@event.fire(@object)
|
1399
|
+
assert_equal [:idling], @object.callbacks
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
def test_should_support_except_to_requirement
|
1403
|
+
@machine.before_transition :except_to => :parked, :do => lambda {|object| object.callbacks << :parked}
|
1404
|
+
@machine.before_transition :except_to => :idling, :do => lambda {|object| object.callbacks << :idling}
|
1405
|
+
|
1406
|
+
@event.fire(@object)
|
1407
|
+
assert_equal [:parked], @object.callbacks
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
def test_should_support_on_requirement
|
1411
|
+
@machine.before_transition :on => :park, :do => lambda {|object| object.callbacks << :park}
|
1412
|
+
@machine.before_transition :on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
|
1413
|
+
|
1414
|
+
@event.fire(@object)
|
1415
|
+
assert_equal [:ignite], @object.callbacks
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
def test_should_support_except_on_requirement
|
1419
|
+
@machine.before_transition :except_on => :park, :do => lambda {|object| object.callbacks << :park}
|
1420
|
+
@machine.before_transition :except_on => :ignite, :do => lambda {|object| object.callbacks << :ignite}
|
1421
|
+
|
1422
|
+
@event.fire(@object)
|
1423
|
+
assert_equal [:park], @object.callbacks
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
def test_should_support_implicit_requirement
|
1427
|
+
@machine.before_transition :parked => :idling, :do => lambda {|object| object.callbacks << :parked}
|
1428
|
+
@machine.before_transition :idling => :parked, :do => lambda {|object| object.callbacks << :idling}
|
1429
|
+
|
1430
|
+
@event.fire(@object)
|
1431
|
+
assert_equal [:parked], @object.callbacks
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
def test_should_track_states_defined_in_transition_callbacks
|
1435
|
+
@machine.before_transition :parked => :idling, :do => lambda {}
|
1436
|
+
@machine.after_transition :first_gear => :second_gear, :do => lambda {}
|
1437
|
+
|
1438
|
+
assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name}
|
1439
|
+
end
|
1440
|
+
|
1441
|
+
def test_should_not_duplicate_states_defined_in_multiple_event_transitions
|
1442
|
+
@machine.before_transition :parked => :idling, :do => lambda {}
|
1443
|
+
@machine.after_transition :first_gear => :second_gear, :do => lambda {}
|
1444
|
+
@machine.after_transition :parked => :idling, :do => lambda {}
|
1445
|
+
|
1446
|
+
assert_equal [:parked, :idling, :first_gear, :second_gear], @machine.states.map {|state| state.name}
|
1447
|
+
end
|
1448
|
+
|
1449
|
+
def test_should_define_predicates_for_each_state
|
1450
|
+
[:parked?, :idling?].each {|predicate| assert @object.respond_to?(predicate)}
|
1451
|
+
end
|
1452
|
+
end
|
1453
|
+
|
1454
|
+
class MachineWithOwnerSubclassTest < Test::Unit::TestCase
|
1455
|
+
def setup
|
1456
|
+
@klass = Class.new
|
1457
|
+
@machine = StateMachine::Machine.new(@klass)
|
1458
|
+
@subclass = Class.new(@klass)
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
def test_should_have_a_different_collection_of_state_machines
|
1462
|
+
assert_not_same @klass.state_machines, @subclass.state_machines
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
def test_should_have_the_same_attribute_associated_state_machines
|
1466
|
+
assert_equal @klass.state_machines, @subclass.state_machines
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
class MachineWithExistingMachinesOnOwnerClassTest < Test::Unit::TestCase
|
1471
|
+
def setup
|
1472
|
+
@klass = Class.new
|
1473
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1474
|
+
@second_machine = StateMachine::Machine.new(@klass, :status, :initial => :idling)
|
1475
|
+
@object = @klass.new
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
def test_should_track_each_state_machine
|
1479
|
+
expected = {:state => @machine, :status => @second_machine}
|
1480
|
+
assert_equal expected, @klass.state_machines
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
def test_should_initialize_state_for_both_machines
|
1484
|
+
assert_equal 'parked', @object.state
|
1485
|
+
assert_equal 'idling', @object.status
|
1486
|
+
end
|
1487
|
+
end
|
1488
|
+
|
1489
|
+
class MachineWithNamespaceTest < Test::Unit::TestCase
|
1490
|
+
def setup
|
1491
|
+
@klass = Class.new
|
1492
|
+
@machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do
|
1493
|
+
event :enable do
|
1494
|
+
transition :off => :active
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
event :disable do
|
1498
|
+
transition :active => :off
|
1499
|
+
end
|
1500
|
+
end
|
1501
|
+
@object = @klass.new
|
1502
|
+
end
|
1503
|
+
|
1504
|
+
def test_should_namespace_state_predicates
|
1505
|
+
[:alarm_active?, :alarm_off?].each do |name|
|
1506
|
+
assert @object.respond_to?(name)
|
1507
|
+
end
|
1508
|
+
end
|
1509
|
+
|
1510
|
+
def test_should_namespace_event_checks
|
1511
|
+
[:can_enable_alarm?, :can_disable_alarm?].each do |name|
|
1512
|
+
assert @object.respond_to?(name)
|
1513
|
+
end
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
def test_should_namespace_event_transition_readers
|
1517
|
+
[:enable_alarm_transition, :disable_alarm_transition].each do |name|
|
1518
|
+
assert @object.respond_to?(name)
|
1519
|
+
end
|
1520
|
+
end
|
1521
|
+
|
1522
|
+
def test_should_namespace_events
|
1523
|
+
[:enable_alarm, :disable_alarm].each do |name|
|
1524
|
+
assert @object.respond_to?(name)
|
1525
|
+
end
|
1526
|
+
end
|
1527
|
+
|
1528
|
+
def test_should_namespace_bang_events
|
1529
|
+
[:enable_alarm!, :disable_alarm!].each do |name|
|
1530
|
+
assert @object.respond_to?(name)
|
1531
|
+
end
|
1532
|
+
end
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
class MachineWithCustomNameTest < Test::Unit::TestCase
|
1536
|
+
def setup
|
1537
|
+
StateMachine::Integrations.const_set('Custom', Module.new do
|
1538
|
+
class << self; attr_reader :defaults; end
|
1539
|
+
@defaults = {:action => :save, :use_transactions => false}
|
1540
|
+
|
1541
|
+
def create_with_scope(name)
|
1542
|
+
lambda {}
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
def create_without_scope(name)
|
1546
|
+
lambda {}
|
1547
|
+
end
|
1548
|
+
end)
|
1549
|
+
|
1550
|
+
@klass = Class.new
|
1551
|
+
@machine = StateMachine::Machine.new(@klass, :state_id, :as => 'state', :initial => :active, :integration => :custom) do
|
1552
|
+
event :ignite do
|
1553
|
+
transition :parked => :idling
|
1554
|
+
end
|
1555
|
+
end
|
1556
|
+
@object = @klass.new
|
1557
|
+
end
|
1558
|
+
|
1559
|
+
def test_should_not_define_a_reader_attribute_for_the_attribute
|
1560
|
+
assert !@object.respond_to?(:state)
|
1561
|
+
end
|
1562
|
+
|
1563
|
+
def test_should_not_define_a_writer_attribute_for_the_attribute
|
1564
|
+
assert !@object.respond_to?(:state=)
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
def test_should_define_a_predicate_for_the_attribute
|
1568
|
+
assert @object.respond_to?(:state?)
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
def test_should_define_a_name_reader_for_the_attribute
|
1572
|
+
assert @object.respond_to?(:state_name)
|
1573
|
+
end
|
1574
|
+
|
1575
|
+
def test_should_define_an_event_reader_for_the_attribute
|
1576
|
+
assert @object.respond_to?(:state_events)
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
def test_should_define_a_transition_reader_for_the_attribute
|
1580
|
+
assert @object.respond_to?(:state_transitions)
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
def test_should_define_singular_with_scope
|
1584
|
+
assert @klass.respond_to?(:with_state)
|
1585
|
+
end
|
1586
|
+
|
1587
|
+
def test_should_define_singular_without_scope
|
1588
|
+
assert @klass.respond_to?(:without_state)
|
1589
|
+
end
|
1590
|
+
|
1591
|
+
def test_should_define_plural_with_scope
|
1592
|
+
assert @klass.respond_to?(:with_states)
|
1593
|
+
end
|
1594
|
+
|
1595
|
+
def test_should_define_plural_without_scope
|
1596
|
+
assert @klass.respond_to?(:without_states)
|
1597
|
+
end
|
1598
|
+
|
1599
|
+
def teardown
|
1600
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
1601
|
+
end
|
1602
|
+
end
|
1603
|
+
|
1604
|
+
class MachineFinderWithoutExistingMachineTest < Test::Unit::TestCase
|
1605
|
+
def setup
|
1606
|
+
@klass = Class.new
|
1607
|
+
@machine = StateMachine::Machine.find_or_create(@klass)
|
1608
|
+
end
|
1609
|
+
|
1610
|
+
def test_should_accept_a_block
|
1611
|
+
called = false
|
1612
|
+
StateMachine::Machine.find_or_create(Class.new) do
|
1613
|
+
called = respond_to?(:event)
|
1614
|
+
end
|
1615
|
+
|
1616
|
+
assert called
|
1617
|
+
end
|
1618
|
+
|
1619
|
+
def test_should_create_a_new_machine
|
1620
|
+
assert_not_nil @machine
|
1621
|
+
end
|
1622
|
+
|
1623
|
+
def test_should_use_default_state
|
1624
|
+
assert_equal :state, @machine.attribute
|
1625
|
+
end
|
1626
|
+
end
|
1627
|
+
|
1628
|
+
class MachineFinderWithExistingOnSameClassTest < Test::Unit::TestCase
|
1629
|
+
def setup
|
1630
|
+
@klass = Class.new
|
1631
|
+
@existing_machine = StateMachine::Machine.new(@klass)
|
1632
|
+
@machine = StateMachine::Machine.find_or_create(@klass)
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
def test_should_accept_a_block
|
1636
|
+
called = false
|
1637
|
+
StateMachine::Machine.find_or_create(@klass) do
|
1638
|
+
called = respond_to?(:event)
|
1639
|
+
end
|
1640
|
+
|
1641
|
+
assert called
|
1642
|
+
end
|
1643
|
+
|
1644
|
+
def test_should_not_create_a_new_machine
|
1645
|
+
assert_same @machine, @existing_machine
|
1646
|
+
end
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
|
1650
|
+
def setup
|
1651
|
+
integration = Module.new do
|
1652
|
+
def self.matches?(klass)
|
1653
|
+
false
|
1654
|
+
end
|
1655
|
+
end
|
1656
|
+
StateMachine::Integrations.const_set('Custom', integration)
|
1657
|
+
|
1658
|
+
@base_class = Class.new
|
1659
|
+
@base_machine = StateMachine::Machine.new(@base_class, :status, :action => :save, :integration => :custom)
|
1660
|
+
@base_machine.event(:ignite) {}
|
1661
|
+
@base_machine.before_transition(lambda {})
|
1662
|
+
@base_machine.after_transition(lambda {})
|
1663
|
+
|
1664
|
+
@klass = Class.new(@base_class)
|
1665
|
+
@machine = StateMachine::Machine.find_or_create(@klass, :status) {}
|
1666
|
+
end
|
1667
|
+
|
1668
|
+
def test_should_accept_a_block
|
1669
|
+
called = false
|
1670
|
+
StateMachine::Machine.find_or_create(Class.new(@base_class)) do
|
1671
|
+
called = respond_to?(:event)
|
1672
|
+
end
|
1673
|
+
|
1674
|
+
assert called
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
def test_should_not_create_a_new_machine_if_no_block_or_options
|
1678
|
+
machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status)
|
1679
|
+
|
1680
|
+
assert_same machine, @base_machine
|
1681
|
+
end
|
1682
|
+
|
1683
|
+
def test_should_create_a_new_machine_if_given_options
|
1684
|
+
machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
|
1685
|
+
|
1686
|
+
assert_not_nil machine
|
1687
|
+
assert_not_same machine, @base_machine
|
1688
|
+
end
|
1689
|
+
|
1690
|
+
def test_should_create_a_new_machine_if_given_block
|
1691
|
+
assert_not_nil @machine
|
1692
|
+
assert_not_same @machine, @base_machine
|
1693
|
+
end
|
1694
|
+
|
1695
|
+
def test_should_copy_the_base_attribute
|
1696
|
+
assert_equal :status, @machine.attribute
|
1697
|
+
end
|
1698
|
+
|
1699
|
+
def test_should_copy_the_base_configuration
|
1700
|
+
assert_equal :save, @machine.action
|
1701
|
+
end
|
1702
|
+
|
1703
|
+
def test_should_copy_events
|
1704
|
+
# Can't assert equal arrays since their machines change
|
1705
|
+
assert_equal 1, @machine.events.length
|
1706
|
+
end
|
1707
|
+
|
1708
|
+
def test_should_copy_before_callbacks
|
1709
|
+
assert_equal @base_machine.callbacks[:before], @machine.callbacks[:before]
|
1710
|
+
end
|
1711
|
+
|
1712
|
+
def test_should_copy_after_transitions
|
1713
|
+
assert_equal @base_machine.callbacks[:after], @machine.callbacks[:after]
|
1714
|
+
end
|
1715
|
+
|
1716
|
+
def test_should_use_the_same_integration
|
1717
|
+
assert (class << @machine; ancestors; end).include?(StateMachine::Integrations::Custom)
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
def teardown
|
1721
|
+
StateMachine::Integrations.send(:remove_const, 'Custom')
|
1722
|
+
end
|
1723
|
+
end
|
1724
|
+
|
1725
|
+
class MachineFinderCustomOptionsTest < Test::Unit::TestCase
|
1726
|
+
def setup
|
1727
|
+
@klass = Class.new
|
1728
|
+
@machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
|
1729
|
+
@object = @klass.new
|
1730
|
+
end
|
1731
|
+
|
1732
|
+
def test_should_use_custom_attribute
|
1733
|
+
assert_equal :status, @machine.attribute
|
1734
|
+
end
|
1735
|
+
|
1736
|
+
def test_should_set_custom_initial_state
|
1737
|
+
assert_equal :parked, @machine.initial_state(@object).name
|
1738
|
+
end
|
1739
|
+
end
|
1740
|
+
|
1741
|
+
begin
|
1742
|
+
# Load library
|
1743
|
+
require 'rubygems'
|
1744
|
+
require 'graphviz'
|
1745
|
+
|
1746
|
+
class MachineDrawingTest < Test::Unit::TestCase
|
1747
|
+
def setup
|
1748
|
+
@klass = Class.new do
|
1749
|
+
def self.name; 'Vehicle'; end
|
1750
|
+
end
|
1751
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1752
|
+
@machine.event :ignite do
|
1753
|
+
transition :parked => :idling
|
1754
|
+
end
|
1755
|
+
end
|
1756
|
+
|
1757
|
+
def test_should_raise_exception_if_invalid_option_specified
|
1758
|
+
assert_raise(ArgumentError) {@machine.draw(:invalid => true)}
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
def test_should_save_file_with_class_name_by_default
|
1762
|
+
graph = @machine.draw(:output => false)
|
1763
|
+
assert_equal './Vehicle_state.png', graph.instance_variable_get('@filename')
|
1764
|
+
end
|
1765
|
+
|
1766
|
+
def test_should_allow_base_name_to_be_customized
|
1767
|
+
graph = @machine.draw(:name => 'machine', :output => false)
|
1768
|
+
assert_equal './machine.png', graph.instance_variable_get('@filename')
|
1769
|
+
end
|
1770
|
+
|
1771
|
+
def test_should_allow_format_to_be_customized
|
1772
|
+
graph = @machine.draw(:format => 'jpg', :output => false)
|
1773
|
+
assert_equal './Vehicle_state.jpg', graph.instance_variable_get('@filename')
|
1774
|
+
assert_equal 'jpg', graph.instance_variable_get('@format')
|
1775
|
+
end
|
1776
|
+
|
1777
|
+
def test_should_allow_path_to_be_customized
|
1778
|
+
graph = @machine.draw(:path => "#{File.dirname(__FILE__)}/", :output => false)
|
1779
|
+
assert_equal "#{File.dirname(__FILE__)}/Vehicle_state.png", graph.instance_variable_get('@filename')
|
1780
|
+
end
|
1781
|
+
|
1782
|
+
def test_should_allow_orientation_to_be_landscape
|
1783
|
+
graph = @machine.draw(:orientation => 'landscape', :output => false)
|
1784
|
+
assert_equal 'LR', graph['rankdir']
|
1785
|
+
end
|
1786
|
+
|
1787
|
+
def test_should_allow_orientation_to_be_portrait
|
1788
|
+
graph = @machine.draw(:orientation => 'portrait', :output => false)
|
1789
|
+
assert_equal 'TB', graph['rankdir']
|
1790
|
+
end
|
1791
|
+
end
|
1792
|
+
|
1793
|
+
class MachineDrawingWithIntegerStatesTest < Test::Unit::TestCase
|
1794
|
+
def setup
|
1795
|
+
@klass = Class.new do
|
1796
|
+
def self.name; 'Vehicle'; end
|
1797
|
+
end
|
1798
|
+
@machine = StateMachine::Machine.new(@klass, :state_id, :initial => :parked)
|
1799
|
+
@machine.event :ignite do
|
1800
|
+
transition :parked => :idling
|
1801
|
+
end
|
1802
|
+
@machine.state :parked, :value => 1
|
1803
|
+
@machine.state :idling, :value => 2
|
1804
|
+
@graph = @machine.draw
|
1805
|
+
end
|
1806
|
+
|
1807
|
+
def test_should_draw_all_states
|
1808
|
+
assert_equal 3, @graph.node_count
|
1809
|
+
end
|
1810
|
+
|
1811
|
+
def test_should_draw_all_events
|
1812
|
+
assert_equal 2, @graph.edge_count
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
def test_should_draw_machine
|
1816
|
+
assert File.exist?('./Vehicle_state_id.png')
|
1817
|
+
ensure
|
1818
|
+
FileUtils.rm('./Vehicle_state_id.png')
|
1819
|
+
end
|
1820
|
+
end
|
1821
|
+
|
1822
|
+
class MachineDrawingWithNilStatesTest < Test::Unit::TestCase
|
1823
|
+
def setup
|
1824
|
+
@klass = Class.new do
|
1825
|
+
def self.name; 'Vehicle'; end
|
1826
|
+
end
|
1827
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1828
|
+
@machine.event :ignite do
|
1829
|
+
transition :parked => :idling
|
1830
|
+
end
|
1831
|
+
@machine.state :parked, :value => nil
|
1832
|
+
@graph = @machine.draw
|
1833
|
+
end
|
1834
|
+
|
1835
|
+
def test_should_draw_all_states
|
1836
|
+
assert_equal 3, @graph.node_count
|
1837
|
+
end
|
1838
|
+
|
1839
|
+
def test_should_draw_all_events
|
1840
|
+
assert_equal 2, @graph.edge_count
|
1841
|
+
end
|
1842
|
+
|
1843
|
+
def test_should_draw_machine
|
1844
|
+
assert File.exist?('./Vehicle_state.png')
|
1845
|
+
ensure
|
1846
|
+
FileUtils.rm('./Vehicle_state.png')
|
1847
|
+
end
|
1848
|
+
end
|
1849
|
+
|
1850
|
+
class MachineDrawingWithDynamicStatesTest < Test::Unit::TestCase
|
1851
|
+
def setup
|
1852
|
+
@klass = Class.new do
|
1853
|
+
def self.name; 'Vehicle'; end
|
1854
|
+
end
|
1855
|
+
@machine = StateMachine::Machine.new(@klass, :initial => :parked)
|
1856
|
+
@machine.event :activate do
|
1857
|
+
transition :parked => :idling
|
1858
|
+
end
|
1859
|
+
@machine.state :idling, :value => lambda {Time.now}
|
1860
|
+
@graph = @machine.draw
|
1861
|
+
end
|
1862
|
+
|
1863
|
+
def test_should_draw_all_states
|
1864
|
+
assert_equal 3, @graph.node_count
|
1865
|
+
end
|
1866
|
+
|
1867
|
+
def test_should_draw_all_events
|
1868
|
+
assert_equal 2, @graph.edge_count
|
1869
|
+
end
|
1870
|
+
|
1871
|
+
def test_should_draw_machine
|
1872
|
+
assert File.exist?('./Vehicle_state.png')
|
1873
|
+
ensure
|
1874
|
+
FileUtils.rm('./Vehicle_state.png')
|
1875
|
+
end
|
1876
|
+
end
|
1877
|
+
|
1878
|
+
class MachineClassDrawingTest < Test::Unit::TestCase
|
1879
|
+
def setup
|
1880
|
+
@klass = Class.new do
|
1881
|
+
def self.name; 'Vehicle'; end
|
1882
|
+
end
|
1883
|
+
@machine = StateMachine::Machine.new(@klass)
|
1884
|
+
@machine.event :ignite do
|
1885
|
+
transition :parked => :idling
|
1886
|
+
end
|
1887
|
+
end
|
1888
|
+
|
1889
|
+
def test_should_raise_exception_if_no_class_names_specified
|
1890
|
+
exception = assert_raise(ArgumentError) {StateMachine::Machine.draw(nil)}
|
1891
|
+
assert_equal 'At least one class must be specified', exception.message
|
1892
|
+
end
|
1893
|
+
|
1894
|
+
def test_should_load_files
|
1895
|
+
StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../classes/switch.rb")
|
1896
|
+
assert defined?(::Switch)
|
1897
|
+
ensure
|
1898
|
+
FileUtils.rm('./Switch_state.png')
|
1899
|
+
end
|
1900
|
+
|
1901
|
+
def test_should_allow_path_and_format_to_be_customized
|
1902
|
+
StateMachine::Machine.draw('Switch', :file => "#{File.dirname(__FILE__)}/../classes/switch.rb", :path => "#{File.dirname(__FILE__)}/", :format => 'jpg')
|
1903
|
+
assert File.exist?("#{File.dirname(__FILE__)}/Switch_state.jpg")
|
1904
|
+
ensure
|
1905
|
+
FileUtils.rm("#{File.dirname(__FILE__)}/Switch_state.jpg")
|
1906
|
+
end
|
1907
|
+
end
|
1908
|
+
rescue LoadError
|
1909
|
+
$stderr.puts 'Skipping GraphViz StateMachine::Machine tests. `gem install ruby-graphviz` and try again.'
|
1910
|
+
end
|