joelind-state_machine 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/CHANGELOG.rdoc +297 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +466 -0
  4. data/Rakefile +98 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine.rb +388 -0
  28. data/lib/state_machine/assertions.rb +36 -0
  29. data/lib/state_machine/callback.rb +189 -0
  30. data/lib/state_machine/condition_proxy.rb +94 -0
  31. data/lib/state_machine/eval_helpers.rb +67 -0
  32. data/lib/state_machine/event.rb +252 -0
  33. data/lib/state_machine/event_collection.rb +122 -0
  34. data/lib/state_machine/extensions.rb +149 -0
  35. data/lib/state_machine/guard.rb +230 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +492 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +351 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +322 -0
  43. data/lib/state_machine/machine.rb +1467 -0
  44. data/lib/state_machine/machine_collection.rb +155 -0
  45. data/lib/state_machine/matcher.rb +123 -0
  46. data/lib/state_machine/matcher_helpers.rb +54 -0
  47. data/lib/state_machine/node_collection.rb +152 -0
  48. data/lib/state_machine/state.rb +249 -0
  49. data/lib/state_machine/state_collection.rb +112 -0
  50. data/lib/state_machine/transition.rb +394 -0
  51. data/tasks/state_machine.rake +1 -0
  52. data/tasks/state_machine.rb +30 -0
  53. data/test/classes/switch.rb +11 -0
  54. data/test/functional/state_machine_test.rb +941 -0
  55. data/test/test_helper.rb +4 -0
  56. data/test/unit/assertions_test.rb +40 -0
  57. data/test/unit/callback_test.rb +455 -0
  58. data/test/unit/condition_proxy_test.rb +328 -0
  59. data/test/unit/eval_helpers_test.rb +120 -0
  60. data/test/unit/event_collection_test.rb +326 -0
  61. data/test/unit/event_test.rb +743 -0
  62. data/test/unit/guard_test.rb +908 -0
  63. data/test/unit/integrations/active_record_test.rb +1374 -0
  64. data/test/unit/integrations/data_mapper_test.rb +962 -0
  65. data/test/unit/integrations/sequel_test.rb +859 -0
  66. data/test/unit/integrations_test.rb +42 -0
  67. data/test/unit/invalid_event_test.rb +7 -0
  68. data/test/unit/invalid_transition_test.rb +7 -0
  69. data/test/unit/machine_collection_test.rb +938 -0
  70. data/test/unit/machine_test.rb +2004 -0
  71. data/test/unit/matcher_helpers_test.rb +37 -0
  72. data/test/unit/matcher_test.rb +155 -0
  73. data/test/unit/node_collection_test.rb +207 -0
  74. data/test/unit/state_collection_test.rb +280 -0
  75. data/test/unit/state_machine_test.rb +31 -0
  76. data/test/unit/state_test.rb +795 -0
  77. data/test/unit/transition_test.rb +1212 -0
  78. metadata +163 -0
@@ -0,0 +1,328 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class Validateable
4
+ class << self
5
+ def validate(*args, &block)
6
+ args << block if block_given?
7
+ args
8
+ end
9
+ end
10
+ end
11
+
12
+ class ConditionProxyTest < Test::Unit::TestCase
13
+ def test_should_call_class_with_same_arguments
14
+ options = {}
15
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {})
16
+ validation = condition_proxy.validate(:name, options)
17
+
18
+ assert_equal [:name, options], validation
19
+ end
20
+
21
+ def test_should_pass_block_through_to_class
22
+ options = {}
23
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {})
24
+
25
+ proxy_block = lambda {}
26
+ validation = condition_proxy.validate(:name, options, &proxy_block)
27
+
28
+ assert_equal [:name, options, proxy_block], validation
29
+ end
30
+
31
+ def test_should_pass_object_into_proxy_condition
32
+ condition_args = []
33
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {|*args| condition_args = args})
34
+ options = condition_proxy.validate[0]
35
+
36
+ object = Validateable.new
37
+ options[:if].call(object)
38
+
39
+ assert_equal [object], condition_args
40
+ end
41
+
42
+ def test_should_evaluate_symbol_condition
43
+ klass = Class.new(Validateable) do
44
+ attr_accessor :callback_called
45
+
46
+ def callback
47
+ @callback_called = true
48
+ end
49
+ end
50
+
51
+ condition_proxy = StateMachine::ConditionProxy.new(klass, :callback)
52
+ options = condition_proxy.validate[0]
53
+
54
+ object = klass.new
55
+ options[:if].call(object)
56
+
57
+ assert object.callback_called
58
+ end
59
+
60
+ def test_should_evaluate_string_condition
61
+ klass = Class.new(Validateable) do
62
+ attr_accessor :callback_called
63
+ end
64
+
65
+ condition_proxy = StateMachine::ConditionProxy.new(klass, '@callback_called = true')
66
+ options = condition_proxy.validate[0]
67
+
68
+ object = klass.new
69
+ options[:if].call(object)
70
+
71
+ assert object.callback_called
72
+ end
73
+ end
74
+
75
+ class ConditionProxyWithoutConditionsTest < Test::Unit::TestCase
76
+ def setup
77
+ @proxy_result = nil
78
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {@proxy_result})
79
+
80
+ @object = Validateable.new
81
+ @options = condition_proxy.validate[0]
82
+ end
83
+
84
+ def test_should_have_options_configuration
85
+ assert_instance_of Hash, @options
86
+ end
87
+
88
+ def test_should_have_if_option
89
+ assert_not_nil @options[:if]
90
+ end
91
+
92
+ def test_should_be_false_if_proxy_condition_is_false
93
+ @proxy_result = false
94
+ assert !@options[:if].call(@object)
95
+ end
96
+
97
+ def test_should_be_true_if_proxy_condition_is_true
98
+ @proxy_result = true
99
+ assert @options[:if].call(@object)
100
+ end
101
+
102
+ def test_should_be_true_if_proxy_condition_is_not_true
103
+ @proxy_result = 1
104
+ assert @options[:if].call(@object)
105
+ end
106
+ end
107
+
108
+ class ConditionProxyWithIfConditionTest < Test::Unit::TestCase
109
+ def setup
110
+ @proxy_result = nil
111
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {@proxy_result})
112
+
113
+ @object = Validateable.new
114
+
115
+ @condition_result = nil
116
+ @options = condition_proxy.validate(:if => lambda {@condition_result})[0]
117
+ end
118
+
119
+ def test_should_have_if_option
120
+ assert_not_nil @options[:if]
121
+ end
122
+
123
+ def test_should_be_false_if_proxy_condition_is_false
124
+ @proxy_result = false
125
+ assert !@options[:if].call(@object)
126
+ end
127
+
128
+ def test_should_be_false_if_original_condition_is_false
129
+ @condition_result = false
130
+ assert !@options[:if].call(@object)
131
+ end
132
+
133
+ def test_should_be_true_if_proxy_and_original_condition_are_true
134
+ @proxy_result = true
135
+ @condition_result = true
136
+ assert @options[:if].call(@object)
137
+ end
138
+
139
+ def test_should_evaluate_symbol_condition
140
+ klass = Class.new(Validateable) do
141
+ attr_accessor :callback
142
+ end
143
+
144
+ condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
145
+ options = condition_proxy.validate(:if => :callback)[0]
146
+
147
+ object = klass.new
148
+ object.callback = false
149
+ assert !options[:if].call(object)
150
+
151
+ object.callback = true
152
+ assert options[:if].call(object)
153
+ end
154
+
155
+ def test_should_evaluate_string_condition
156
+ klass = Class.new(Validateable) do
157
+ attr_accessor :callback
158
+ end
159
+
160
+ condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
161
+ options = condition_proxy.validate(:if => '@callback')[0]
162
+
163
+ object = klass.new
164
+ object.callback = false
165
+ assert !options[:if].call(object)
166
+
167
+ object.callback = true
168
+ assert options[:if].call(object)
169
+ end
170
+ end
171
+
172
+ class ConditionProxyWithMultipleIfConditionsTest < Test::Unit::TestCase
173
+ def setup
174
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {true})
175
+
176
+ @object = Validateable.new
177
+
178
+ @first_condition_result = nil
179
+ @second_condition_result = nil
180
+ @options = condition_proxy.validate(:if => [lambda {@first_condition_result}, lambda {@second_condition_result}])[0]
181
+ end
182
+
183
+ def test_should_be_true_if_all_conditions_are_true
184
+ @first_condition_result = true
185
+ @second_condition_result = true
186
+ assert @options[:if].call(@object)
187
+ end
188
+
189
+ def test_should_be_false_if_any_condition_is_false
190
+ @first_condition_result = true
191
+ @second_condition_result = false
192
+ assert !@options[:if].call(@object)
193
+
194
+ @first_condition_result = false
195
+ @second_condition_result = true
196
+ assert !@options[:if].call(@object)
197
+ end
198
+ end
199
+
200
+ class ConditionProxyWithUnlessConditionTest < Test::Unit::TestCase
201
+ def setup
202
+ @proxy_result = nil
203
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {@proxy_result})
204
+
205
+ @object = Validateable.new
206
+
207
+ @condition_result = nil
208
+ @options = condition_proxy.validate(:unless => lambda {@condition_result})[0]
209
+ end
210
+
211
+ def test_should_have_if_option
212
+ assert_not_nil @options[:if]
213
+ end
214
+
215
+ def test_should_be_false_if_proxy_condition_is_false
216
+ @proxy_result = false
217
+ assert !@options[:if].call(@object)
218
+ end
219
+
220
+ def test_should_be_false_if_original_condition_is_true
221
+ @condition_result = true
222
+ assert !@options[:if].call(@object)
223
+ end
224
+
225
+ def test_should_be_true_if_proxy_is_true_and_original_condition_is_false
226
+ @proxy_result = true
227
+ @condition_result = false
228
+ assert @options[:if].call(@object)
229
+ end
230
+
231
+ def test_should_evaluate_symbol_condition
232
+ klass = Class.new(Validateable) do
233
+ attr_accessor :callback
234
+ end
235
+
236
+ condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
237
+ options = condition_proxy.validate(:unless => :callback)[0]
238
+
239
+ object = klass.new
240
+ object.callback = true
241
+ assert !options[:if].call(object)
242
+
243
+ object.callback = false
244
+ assert options[:if].call(object)
245
+ end
246
+
247
+ def test_should_evaluate_string_condition
248
+ klass = Class.new(Validateable) do
249
+ attr_accessor :callback
250
+ end
251
+
252
+ condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
253
+ options = condition_proxy.validate(:unless => '@callback')[0]
254
+
255
+ object = klass.new
256
+ object.callback = true
257
+ assert !options[:if].call(object)
258
+
259
+ object.callback = false
260
+ assert options[:if].call(object)
261
+ end
262
+ end
263
+
264
+ class ConditionProxyWithMultipleUnlessConditionsTest < Test::Unit::TestCase
265
+ def setup
266
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {true})
267
+
268
+ @object = Validateable.new
269
+
270
+ @first_condition_result = nil
271
+ @second_condition_result = nil
272
+ @options = condition_proxy.validate(:unless => [lambda {@first_condition_result}, lambda {@second_condition_result}])[0]
273
+ end
274
+
275
+ def test_should_be_true_if_all_conditions_are_false
276
+ @first_condition_result = false
277
+ @second_condition_result = false
278
+ assert @options[:if].call(@object)
279
+ end
280
+
281
+ def test_should_be_false_if_any_condition_is_true
282
+ @first_condition_result = true
283
+ @second_condition_result = false
284
+ assert !@options[:if].call(@object)
285
+
286
+ @first_condition_result = false
287
+ @second_condition_result = true
288
+ assert !@options[:if].call(@object)
289
+ end
290
+ end
291
+
292
+ class ConditionProxyWithIfAndUnlessConditionsTest < Test::Unit::TestCase
293
+ def setup
294
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {true})
295
+
296
+ @object = Validateable.new
297
+
298
+ @if_condition_result = nil
299
+ @unless_condition_result = nil
300
+ @options = condition_proxy.validate(:if => lambda {@if_condition_result}, :unless => lambda {@unless_condition_result})[0]
301
+ end
302
+
303
+ def test_should_be_false_if_if_condition_is_false
304
+ @if_condition_result = false
305
+ @unless_condition_result = false
306
+ assert !@options[:if].call(@object)
307
+
308
+ @if_condition_result = false
309
+ @unless_condition_result = true
310
+ assert !@options[:if].call(@object)
311
+ end
312
+
313
+ def test_should_be_false_if_unless_condition_is_true
314
+ @if_condition_result = false
315
+ @unless_condition_result = true
316
+ assert !@options[:if].call(@object)
317
+
318
+ @if_condition_result = true
319
+ @unless_condition_result = true
320
+ assert !@options[:if].call(@object)
321
+ end
322
+
323
+ def test_should_be_true_if_if_condition_is_true_and_unless_condition_is_false
324
+ @if_condition_result = true
325
+ @unless_condition_result = false
326
+ assert @options[:if].call(@object)
327
+ end
328
+ end
@@ -0,0 +1,120 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class EvalHelpersBaseTest < Test::Unit::TestCase
4
+ include StateMachine::EvalHelpers
5
+
6
+ def default_test
7
+ end
8
+ end
9
+
10
+ class EvalHelpersTest < EvalHelpersBaseTest
11
+ def setup
12
+ @object = Object.new
13
+ end
14
+
15
+ def test_should_raise_exception_if_method_is_not_symbol_string_or_proc
16
+ exception = assert_raise(ArgumentError) { evaluate_method(@object, 1) }
17
+ assert_match /Methods must/, exception.message
18
+ end
19
+ end
20
+
21
+ class EvalHelpersSymbolTest < EvalHelpersBaseTest
22
+ def setup
23
+ class << (@object = Object.new)
24
+ def callback
25
+ true
26
+ end
27
+ end
28
+ end
29
+
30
+ def test_should_call_method_on_object_with_no_arguments
31
+ assert evaluate_method(@object, :callback, 1, 2, 3)
32
+ end
33
+ end
34
+
35
+ class EvalHelpersSymbolWithArgumentsTest < EvalHelpersBaseTest
36
+ def setup
37
+ class << (@object = Object.new)
38
+ def callback(*args)
39
+ args
40
+ end
41
+ end
42
+ end
43
+
44
+ def test_should_call_method_with_all_arguments
45
+ assert_equal [1, 2, 3], evaluate_method(@object, :callback, 1, 2, 3)
46
+ end
47
+ end
48
+
49
+ class EvalHelpersSymbolTaintedMethodTest < EvalHelpersBaseTest
50
+ def setup
51
+ class << (@object = Object.new)
52
+ def callback
53
+ true
54
+ end
55
+
56
+ taint
57
+ end
58
+ end
59
+
60
+ def test_should_not_raise_security_error
61
+ assert_nothing_raised { evaluate_method(@object, :callback, 1, 2, 3) }
62
+ end
63
+ end
64
+
65
+ class EvalHelpersStringTest < EvalHelpersBaseTest
66
+ def setup
67
+ @object = Object.new
68
+ end
69
+
70
+ def test_should_evaluate_string
71
+ assert_equal 1, evaluate_method(@object, '1')
72
+ end
73
+
74
+ def test_should_evaluate_string_within_object_context
75
+ @object.instance_variable_set('@value', 1)
76
+ assert_equal 1, evaluate_method(@object, '@value')
77
+ end
78
+
79
+ def test_should_ignore_additional_arguments
80
+ assert_equal 1, evaluate_method(@object, '1', 2, 3, 4)
81
+ end
82
+ end
83
+
84
+ class EvalHelpersProcTest < EvalHelpersBaseTest
85
+ def setup
86
+ @object = Object.new
87
+ @proc = lambda {|obj| obj}
88
+ end
89
+
90
+ def test_should_call_proc_with_object_as_argument
91
+ assert_equal @object, evaluate_method(@object, @proc, 1, 2, 3)
92
+ end
93
+ end
94
+
95
+ class EvalHelpersProcWithoutArgumentsTest < EvalHelpersBaseTest
96
+ def setup
97
+ @object = Object.new
98
+ @proc = lambda {|*args| args}
99
+ class << @proc
100
+ def arity
101
+ 0
102
+ end
103
+ end
104
+ end
105
+
106
+ def test_should_call_proc_with_no_arguments
107
+ assert_equal [], evaluate_method(@object, @proc, 1, 2, 3)
108
+ end
109
+ end
110
+
111
+ class EvalHelpersProcWithArgumentsTest < EvalHelpersBaseTest
112
+ def setup
113
+ @object = Object.new
114
+ @proc = lambda {|*args| args}
115
+ end
116
+
117
+ def test_should_call_method_with_all_arguments
118
+ assert_equal [@object, 1, 2, 3], evaluate_method(@object, @proc, 1, 2, 3)
119
+ end
120
+ end
@@ -0,0 +1,326 @@
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
+ end
33
+
34
+ def test_should_index_by_name
35
+ assert_equal @open, @events[:enable, :name]
36
+ end
37
+
38
+ def test_should_index_by_name_by_default
39
+ assert_equal @open, @events[:enable]
40
+ end
41
+
42
+ def test_should_index_by_qualified_name
43
+ assert_equal @open, @events[:enable_alarm, :qualified_name]
44
+ end
45
+ end
46
+
47
+ class EventCollectionWithEventsWithTransitionsTest < Test::Unit::TestCase
48
+ def setup
49
+ @klass = Class.new
50
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
51
+ @events = StateMachine::EventCollection.new(@machine)
52
+
53
+ @machine.state :idling, :stalled
54
+ @machine.event :ignite
55
+
56
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
57
+ @ignite.transition :parked => :idling
58
+ @ignite.transition :stalled => :idling
59
+ end
60
+
61
+ def test_should_only_include_valid_events_for_an_object
62
+ object = @klass.new
63
+ object.state = 'parked'
64
+ assert_equal [@ignite], @events.valid_for(object)
65
+
66
+ object.state = 'stalled'
67
+ assert_equal [@ignite], @events.valid_for(object)
68
+
69
+ object.state = 'idling'
70
+ assert_equal [], @events.valid_for(object)
71
+ end
72
+
73
+ def test_should_only_include_valid_transitions_for_an_object
74
+ object = @klass.new
75
+ object.state = 'parked'
76
+ assert_equal [{:object => object, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}], @events.transitions_for(object).map {|transition| transition.attributes}
77
+
78
+ object.state = 'stalled'
79
+ assert_equal [{:object => object, :attribute => :state, :event => :ignite, :from => 'stalled', :to => 'idling'}], @events.transitions_for(object).map {|transition| transition.attributes}
80
+
81
+ object.state = 'idling'
82
+ assert_equal [], @events.transitions_for(object)
83
+ end
84
+
85
+ def test_should_filter_valid_transitions_for_an_object_if_requirements_specified
86
+ object = @klass.new
87
+ assert_equal [{:object => object, :attribute => :state, :event => :ignite, :from => 'stalled', :to => 'idling'}], @events.transitions_for(object, :from => :stalled).map {|transition| transition.attributes}
88
+ assert_equal [], @events.transitions_for(object, :from => :idling).map {|transition| transition.attributes}
89
+ end
90
+ end
91
+
92
+ class EventCollectionWithMultipleEventsTest < Test::Unit::TestCase
93
+ def setup
94
+ @klass = Class.new
95
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
96
+ @events = StateMachine::EventCollection.new(@machine)
97
+
98
+ @machine.state :first_gear
99
+ @machine.event :park, :shift_down
100
+
101
+ @events << @park = StateMachine::Event.new(@machine, :park)
102
+ @park.transition :first_gear => :parked
103
+
104
+ @events << @shift_down = StateMachine::Event.new(@machine, :shift_down)
105
+ @shift_down.transition :first_gear => :parked
106
+ end
107
+
108
+ def test_should_only_include_all_valid_events_for_an_object
109
+ object = @klass.new
110
+ object.state = 'first_gear'
111
+ assert_equal [@park, @shift_down], @events.valid_for(object)
112
+ end
113
+ end
114
+
115
+ class EventCollectionWithoutMachineActionTest < Test::Unit::TestCase
116
+ def setup
117
+ @klass = Class.new
118
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
119
+ @events = StateMachine::EventCollection.new(@machine)
120
+
121
+ @machine.event :ignite
122
+ @events << StateMachine::Event.new(@machine, :ignite)
123
+
124
+ @object = @klass.new
125
+ end
126
+
127
+ def test_should_not_have_an_attribute_transition
128
+ assert_nil @events.attribute_transition_for(@object)
129
+ end
130
+ end
131
+
132
+ class EventCollectionAttributeWithMachineActionTest < Test::Unit::TestCase
133
+ def setup
134
+ @klass = Class.new do
135
+ def save
136
+ end
137
+ end
138
+
139
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save)
140
+ @events = StateMachine::EventCollection.new(@machine)
141
+
142
+ @machine.event :ignite
143
+ @machine.state :parked, :idling
144
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
145
+
146
+ @object = @klass.new
147
+ end
148
+
149
+ def test_should_not_have_transition_if_nil
150
+ @object.state_event = nil
151
+ assert_nil @events.attribute_transition_for(@object)
152
+ end
153
+
154
+ def test_should_not_have_transition_if_empty
155
+ @object.state_event = ''
156
+ assert_nil @events.attribute_transition_for(@object)
157
+ end
158
+
159
+ def test_should_have_invalid_transition_if_invalid_event_specified
160
+ @object.state_event = 'invalid'
161
+ assert_equal false, @events.attribute_transition_for(@object)
162
+ end
163
+
164
+ def test_should_have_invalid_transition_if_event_cannot_be_fired
165
+ @object.state_event = 'ignite'
166
+ assert_equal false, @events.attribute_transition_for(@object)
167
+ end
168
+
169
+ def test_should_have_valid_transition_if_event_can_be_fired
170
+ @ignite.transition :parked => :idling
171
+ @object.state_event = 'ignite'
172
+
173
+ assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
174
+ end
175
+
176
+ def test_should_have_valid_transition_if_already_defined_in_transition_cache
177
+ @ignite.transition :parked => :idling
178
+ @object.state_event = nil
179
+ @object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
180
+
181
+ assert_equal transition, @events.attribute_transition_for(@object)
182
+ end
183
+
184
+ def test_should_use_transition_cache_if_both_event_and_transition_are_present
185
+ @ignite.transition :parked => :idling
186
+ @object.state_event = 'ignite'
187
+ @object.send(:state_event_transition=, transition = @ignite.transition_for(@object))
188
+
189
+ assert_equal transition, @events.attribute_transition_for(@object)
190
+ end
191
+ end
192
+
193
+ class EventCollectionAttributeWithNamespacedMachineTest < Test::Unit::TestCase
194
+ def setup
195
+ @klass = Class.new do
196
+ def save
197
+ end
198
+ end
199
+
200
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active, :action => :save)
201
+ @events = StateMachine::EventCollection.new(@machine)
202
+
203
+ @machine.event :disable
204
+ @machine.state :active, :off
205
+ @events << @disable = StateMachine::Event.new(@machine, :disable)
206
+
207
+ @object = @klass.new
208
+ end
209
+
210
+ def test_should_not_have_transition_if_nil
211
+ @object.state_event = nil
212
+ assert_nil @events.attribute_transition_for(@object)
213
+ end
214
+
215
+ def test_should_have_invalid_transition_if_event_cannot_be_fired
216
+ @object.state_event = 'disable'
217
+ assert_equal false, @events.attribute_transition_for(@object)
218
+ end
219
+
220
+ def test_should_have_valid_transition_if_event_can_be_fired
221
+ @disable.transition :active => :off
222
+ @object.state_event = 'disable'
223
+
224
+ assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
225
+ end
226
+ end
227
+
228
+ class EventCollectionWithValidationsTest < Test::Unit::TestCase
229
+ def setup
230
+ StateMachine::Integrations.const_set('Custom', Module.new do
231
+ def invalidate(object, attribute, message, values = [])
232
+ (object.errors ||= []) << generate_message(message, values)
233
+ end
234
+
235
+ def reset(object)
236
+ object.errors = []
237
+ end
238
+ end)
239
+
240
+ @klass = Class.new do
241
+ attr_accessor :errors
242
+
243
+ def initialize
244
+ @errors = []
245
+ super
246
+ end
247
+ end
248
+
249
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save, :integration => :custom)
250
+ @events = StateMachine::EventCollection.new(@machine)
251
+
252
+ @machine.event :ignite
253
+ @machine.state :parked, :idling
254
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
255
+
256
+ @object = @klass.new
257
+ end
258
+
259
+ def test_should_invalidate_if_invalid_event_specified
260
+ @object.state_event = 'invalid'
261
+ @events.attribute_transition_for(@object, true)
262
+
263
+ assert_equal ['is invalid'], @object.errors
264
+ end
265
+
266
+ def test_should_invalidate_if_event_cannot_be_fired
267
+ @object.state = 'idling'
268
+ @object.state_event = 'ignite'
269
+ @events.attribute_transition_for(@object, true)
270
+
271
+ assert_equal ['cannot transition when idling'], @object.errors
272
+ end
273
+
274
+ def test_should_invalidate_with_friendly_name_if_invalid_event_specified
275
+ # Add a valid nil state
276
+ @machine.state nil
277
+
278
+ @object.state = nil
279
+ @object.state_event = 'ignite'
280
+ @events.attribute_transition_for(@object, true)
281
+
282
+ assert_equal ['cannot transition when nil'], @object.errors
283
+ end
284
+
285
+ def test_should_not_invalidate_event_can_be_fired
286
+ @ignite.transition :parked => :idling
287
+ @object.state_event = 'ignite'
288
+ @events.attribute_transition_for(@object, true)
289
+
290
+ assert_equal [], @object.errors
291
+ end
292
+
293
+ def teardown
294
+ StateMachine::Integrations.send(:remove_const, 'Custom')
295
+ end
296
+ end
297
+
298
+ class EventCollectionWithCustomMachineAttributeTest < Test::Unit::TestCase
299
+ def setup
300
+ @klass = Class.new do
301
+ def save
302
+ end
303
+ end
304
+
305
+ @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :parked, :action => :save)
306
+ @events = StateMachine::EventCollection.new(@machine)
307
+
308
+ @machine.event :ignite
309
+ @machine.state :parked, :idling
310
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
311
+
312
+ @object = @klass.new
313
+ end
314
+
315
+ def test_should_not_have_transition_if_nil
316
+ @object.state_event = nil
317
+ assert_nil @events.attribute_transition_for(@object)
318
+ end
319
+
320
+ def test_should_have_valid_transition_if_event_can_be_fired
321
+ @ignite.transition :parked => :idling
322
+ @object.state_event = 'ignite'
323
+
324
+ assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
325
+ end
326
+ end