joelind-state_machine 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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