state_machine 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGELOG.rdoc +31 -1
  2. data/README.rdoc +33 -21
  3. data/Rakefile +2 -2
  4. data/examples/merb-rest/controller.rb +51 -0
  5. data/examples/merb-rest/model.rb +28 -0
  6. data/examples/merb-rest/view_edit.html.erb +24 -0
  7. data/examples/merb-rest/view_index.html.erb +23 -0
  8. data/examples/merb-rest/view_new.html.erb +13 -0
  9. data/examples/merb-rest/view_show.html.erb +17 -0
  10. data/examples/rails-rest/controller.rb +43 -0
  11. data/examples/rails-rest/migration.rb +11 -0
  12. data/examples/rails-rest/model.rb +23 -0
  13. data/examples/rails-rest/view_edit.html.erb +25 -0
  14. data/examples/rails-rest/view_index.html.erb +23 -0
  15. data/examples/rails-rest/view_new.html.erb +14 -0
  16. data/examples/rails-rest/view_show.html.erb +17 -0
  17. data/lib/state_machine/assertions.rb +2 -2
  18. data/lib/state_machine/callback.rb +14 -8
  19. data/lib/state_machine/condition_proxy.rb +3 -3
  20. data/lib/state_machine/event.rb +19 -21
  21. data/lib/state_machine/event_collection.rb +114 -0
  22. data/lib/state_machine/extensions.rb +127 -11
  23. data/lib/state_machine/guard.rb +1 -1
  24. data/lib/state_machine/integrations/active_record/locale.rb +2 -1
  25. data/lib/state_machine/integrations/active_record.rb +117 -39
  26. data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
  27. data/lib/state_machine/integrations/data_mapper.rb +71 -26
  28. data/lib/state_machine/integrations/sequel.rb +69 -21
  29. data/lib/state_machine/machine.rb +267 -139
  30. data/lib/state_machine/machine_collection.rb +145 -0
  31. data/lib/state_machine/matcher.rb +2 -2
  32. data/lib/state_machine/node_collection.rb +9 -4
  33. data/lib/state_machine/state.rb +22 -32
  34. data/lib/state_machine/state_collection.rb +66 -17
  35. data/lib/state_machine/transition.rb +259 -28
  36. data/lib/state_machine.rb +121 -56
  37. data/tasks/state_machine.rake +1 -0
  38. data/tasks/state_machine.rb +26 -0
  39. data/test/active_record.log +116877 -0
  40. data/test/functional/state_machine_test.rb +118 -12
  41. data/test/sequel.log +28542 -0
  42. data/test/unit/callback_test.rb +46 -1
  43. data/test/unit/condition_proxy_test.rb +55 -28
  44. data/test/unit/event_collection_test.rb +228 -0
  45. data/test/unit/event_test.rb +51 -46
  46. data/test/unit/integrations/active_record_test.rb +128 -70
  47. data/test/unit/integrations/data_mapper_test.rb +150 -58
  48. data/test/unit/integrations/sequel_test.rb +63 -6
  49. data/test/unit/invalid_event_test.rb +7 -0
  50. data/test/unit/machine_collection_test.rb +678 -0
  51. data/test/unit/machine_test.rb +198 -91
  52. data/test/unit/node_collection_test.rb +33 -30
  53. data/test/unit/state_collection_test.rb +112 -5
  54. data/test/unit/state_test.rb +23 -3
  55. data/test/unit/transition_test.rb +750 -89
  56. metadata +28 -3
@@ -2,13 +2,18 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  class NodeCollectionByDefaultTest < Test::Unit::TestCase
4
4
  def setup
5
- @collection = StateMachine::NodeCollection.new
5
+ @machine = StateMachine::Machine.new(Class.new)
6
+ @collection = StateMachine::NodeCollection.new(@machine)
6
7
  end
7
8
 
8
9
  def test_should_not_have_any_nodes
9
10
  assert_equal 0, @collection.length
10
11
  end
11
12
 
13
+ def test_should_have_a_machine
14
+ assert_equal @machine, @collection.machine
15
+ end
16
+
12
17
  def test_should_index_by_name
13
18
  @collection << object = Struct.new(:name).new(:parked)
14
19
  assert_equal object, @collection[:parked]
@@ -17,11 +22,12 @@ end
17
22
 
18
23
  class NodeCollectionTest < Test::Unit::TestCase
19
24
  def setup
20
- @collection = StateMachine::NodeCollection.new
25
+ @machine = StateMachine::Machine.new(Class.new)
26
+ @collection = StateMachine::NodeCollection.new(@machine)
21
27
  end
22
28
 
23
29
  def test_should_raise_exception_if_invalid_option_specified
24
- exception = assert_raise(ArgumentError) { StateMachine::NodeCollection.new(:invalid => true) }
30
+ exception = assert_raise(ArgumentError) { StateMachine::NodeCollection.new(@machine, :invalid => true) }
25
31
  assert_equal 'Invalid key(s): invalid', exception.message
26
32
  end
27
33
 
@@ -38,7 +44,8 @@ end
38
44
 
39
45
  class NodeCollectionAfterBeingCopiedTest < Test::Unit::TestCase
40
46
  def setup
41
- @collection = StateMachine::NodeCollection.new
47
+ machine = StateMachine::Machine.new(Class.new)
48
+ @collection = StateMachine::NodeCollection.new(machine)
42
49
  @collection << @parked = Struct.new(:name).new(:parked)
43
50
 
44
51
  @copied_collection = @collection.dup
@@ -62,7 +69,8 @@ end
62
69
 
63
70
  class NodeCollectionWithoutIndicesTest < Test::Unit::TestCase
64
71
  def setup
65
- @collection = StateMachine::NodeCollection.new(:index => {})
72
+ machine = StateMachine::Machine.new(Class.new)
73
+ @collection = StateMachine::NodeCollection.new(machine, :index => {})
66
74
  end
67
75
 
68
76
  def test_should_allow_adding_node
@@ -90,7 +98,8 @@ end
90
98
 
91
99
  class NodeCollectionWithIndicesTest < Test::Unit::TestCase
92
100
  def setup
93
- @collection = StateMachine::NodeCollection.new(:index => [:name, :value])
101
+ machine = StateMachine::Machine.new(Class.new)
102
+ @collection = StateMachine::NodeCollection.new(machine, :index => [:name, :value])
94
103
 
95
104
  @object = Struct.new(:name, :value).new(:parked, 1)
96
105
  @collection << @object
@@ -116,24 +125,25 @@ class NodeCollectionWithIndicesTest < Test::Unit::TestCase
116
125
 
117
126
  def test_should_use_first_index_by_default_on_fetch
118
127
  assert_equal @object, @collection.fetch(:parked)
119
- exception = assert_raise(ArgumentError) { @collection.fetch(1) }
128
+ exception = assert_raise(IndexError) { @collection.fetch(1) }
120
129
  assert_equal '1 is an invalid name', exception.message
121
130
  end
122
131
 
123
132
  def test_should_allow_customizing_index_on_fetch
124
133
  assert_equal @object, @collection.fetch(1, :value)
125
- exception = assert_raise(ArgumentError) { @collection.fetch(:parked, :value) }
134
+ exception = assert_raise(IndexError) { @collection.fetch(:parked, :value) }
126
135
  assert_equal ':parked is an invalid value', exception.message
127
136
  end
128
137
  end
129
138
 
130
139
  class NodeCollectionWithNodesTest < Test::Unit::TestCase
131
140
  def setup
132
- @collection = StateMachine::NodeCollection.new
141
+ machine = StateMachine::Machine.new(Class.new)
142
+ @collection = StateMachine::NodeCollection.new(machine)
133
143
 
134
- @klass = Struct.new(:name)
135
- @parked = @klass.new(:parked)
136
- @idling = @klass.new(:idling)
144
+ @klass = Struct.new(:name, :machine)
145
+ @parked = @klass.new(:parked, machine)
146
+ @idling = @klass.new(:idling, machine)
137
147
 
138
148
  @collection << @parked
139
149
  @collection << @idling
@@ -150,11 +160,21 @@ class NodeCollectionWithNodesTest < Test::Unit::TestCase
150
160
  assert_equal @parked, @collection.at(0)
151
161
  assert_equal @idling, @collection.at(1)
152
162
  end
163
+
164
+ def test_should_deep_copy_machine_changes
165
+ new_machine = StateMachine::Machine.new(Class.new)
166
+ @collection.machine = new_machine
167
+
168
+ assert_equal new_machine, @collection.machine
169
+ assert_equal new_machine, @parked.machine
170
+ assert_equal new_machine, @idling.machine
171
+ end
153
172
  end
154
173
 
155
174
  class NodeCollectionAfterUpdateTest < Test::Unit::TestCase
156
175
  def setup
157
- @collection = StateMachine::NodeCollection.new(:index => [:name, :value])
176
+ machine = StateMachine::Machine.new(Class.new)
177
+ @collection = StateMachine::NodeCollection.new(machine, :index => [:name, :value])
158
178
 
159
179
  @klass = Struct.new(:name, :value)
160
180
  @parked = @klass.new(:parked, 1)
@@ -185,20 +205,3 @@ class NodeCollectionAfterUpdateTest < Test::Unit::TestCase
185
205
  assert_nil @collection[1, :value]
186
206
  end
187
207
  end
188
-
189
- class NodeCollectionChangingMachineTest < Test::Unit::TestCase
190
- def setup
191
- @collection = StateMachine::NodeCollection.new
192
-
193
- @klass = Struct.new(:name, :machine)
194
- @collection << @parked = @klass.new(:parked)
195
- @collection << @idling = @klass.new(:idling)
196
-
197
- @collection.machine = :machine
198
- end
199
-
200
- def test_should_update_each_node_machine
201
- assert_equal :machine, @parked.machine
202
- assert_equal :machine, @idling.machine
203
- end
204
- end
@@ -2,23 +2,130 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
2
 
3
3
  class StateCollectionByDefaultTest < Test::Unit::TestCase
4
4
  def setup
5
- @states = StateMachine::StateCollection.new
5
+ @machine = StateMachine::Machine.new(Class.new)
6
+ @states = StateMachine::StateCollection.new(@machine)
6
7
  end
7
8
 
8
9
  def test_should_not_have_any_nodes
9
10
  assert_equal 0, @states.length
10
11
  end
11
12
 
13
+ def test_should_have_a_machine
14
+ assert_equal @machine, @states.machine
15
+ end
16
+
12
17
  def test_should_be_empty_by_priority
13
18
  assert_equal [], @states.by_priority
14
19
  end
15
20
  end
16
21
 
22
+ class StateCollectionTest < Test::Unit::TestCase
23
+ def setup
24
+ @klass = Class.new
25
+ @machine = StateMachine::Machine.new(@klass)
26
+ @states = StateMachine::StateCollection.new(@machine)
27
+
28
+ @states << @nil = StateMachine::State.new(@machine, nil)
29
+ @states << @parked = StateMachine::State.new(@machine, :parked)
30
+ @states << @idling = StateMachine::State.new(@machine, :idling)
31
+
32
+ @object = @klass.new
33
+ end
34
+
35
+ def test_should_index_by_name
36
+ assert_equal @parked, @states[:parked, :name]
37
+ end
38
+
39
+ def test_should_index_by_name_by_default
40
+ assert_equal @parked, @states[:parked]
41
+ end
42
+
43
+ def test_should_index_by_value
44
+ assert_equal @parked, @states['parked', :value]
45
+ end
46
+
47
+ def test_should_not_match_if_value_does_not_match
48
+ assert !@states.matches?(@object, :parked)
49
+ assert !@states.matches?(@object, :idling)
50
+ end
51
+
52
+ def test_should_match_if_value_matches
53
+ assert @states.matches?(@object, nil)
54
+ end
55
+
56
+ def test_raise_exception_if_matching_invalid_state
57
+ assert_raise(IndexError) { @states.matches?(@object, :invalid) }
58
+ end
59
+
60
+ def test_should_find_state_for_object_if_value_is_known
61
+ @object.state = 'parked'
62
+ assert_equal @parked, @states.match(@object)
63
+ end
64
+
65
+ def test_should_raise_exception_if_finding_state_for_object_with_unknown_value
66
+ @object.state = 'invalid'
67
+ exception = assert_raise(ArgumentError) { @states.match(@object) }
68
+ assert_equal '"invalid" is not a known state value', exception.message
69
+ end
70
+ end
71
+
72
+ class StateCollectionWithCustomStateValuesTest < Test::Unit::TestCase
73
+ def setup
74
+ @klass = Class.new
75
+ @machine = StateMachine::Machine.new(@klass)
76
+ @states = StateMachine::StateCollection.new(@machine)
77
+
78
+ @states << @state = StateMachine::State.new(@machine, :parked, :value => 1)
79
+
80
+ @object = @klass.new
81
+ @object.state = 1
82
+ end
83
+
84
+ def test_should_match_if_value_matches
85
+ assert @states.matches?(@object, :parked)
86
+ end
87
+
88
+ def test_should_not_match_if_value_does_not_match
89
+ @object.state = 2
90
+ assert !@states.matches?(@object, :parked)
91
+ end
92
+
93
+ def test_should_find_state_for_object_if_value_is_known
94
+ assert_equal @state, @states.match(@object)
95
+ end
96
+ end
97
+
98
+ class StateCollectionWithStateMatchersTest < Test::Unit::TestCase
99
+ def setup
100
+ @klass = Class.new
101
+ @machine = StateMachine::Machine.new(@klass)
102
+ @states = StateMachine::StateCollection.new(@machine)
103
+
104
+ @states << @state = StateMachine::State.new(@machine, :parked, :if => lambda {|value| !value.nil?})
105
+
106
+ @object = @klass.new
107
+ @object.state = 1
108
+ end
109
+
110
+ def test_should_match_if_value_matches
111
+ assert @states.matches?(@object, :parked)
112
+ end
113
+
114
+ def test_should_not_match_if_value_does_not_match
115
+ @object.state = nil
116
+ assert !@states.matches?(@object, :parked)
117
+ end
118
+
119
+ def test_should_find_state_for_object_if_value_is_known
120
+ assert_equal @state, @states.match(@object)
121
+ end
122
+ end
123
+
17
124
  class StateCollectionWithInitialStateTest < Test::Unit::TestCase
18
125
  def setup
19
126
  @machine = StateMachine::Machine.new(Class.new)
127
+ @states = StateMachine::StateCollection.new(@machine)
20
128
 
21
- @states = StateMachine::StateCollection.new
22
129
  @states << @parked = StateMachine::State.new(@machine, :parked)
23
130
  @states << @idling = StateMachine::State.new(@machine, :idling)
24
131
 
@@ -54,8 +161,8 @@ end
54
161
  class StateCollectionWithStateBehaviorsTest < Test::Unit::TestCase
55
162
  def setup
56
163
  @machine = StateMachine::Machine.new(Class.new)
164
+ @states = StateMachine::StateCollection.new(@machine)
57
165
 
58
- @states = StateMachine::StateCollection.new
59
166
  @states << @parked = StateMachine::State.new(@machine, :parked)
60
167
  @states << @idling = StateMachine::State.new(@machine, :idling)
61
168
 
@@ -91,8 +198,8 @@ end
91
198
  class StateCollectionWithEventTransitionsTest < Test::Unit::TestCase
92
199
  def setup
93
200
  @machine = StateMachine::Machine.new(Class.new)
201
+ @states = StateMachine::StateCollection.new(@machine)
94
202
 
95
- @states = StateMachine::StateCollection.new
96
203
  @states << @parked = StateMachine::State.new(@machine, :parked)
97
204
  @states << @idling = StateMachine::State.new(@machine, :idling)
98
205
 
@@ -128,8 +235,8 @@ end
128
235
  class StateCollectionWithTransitionCallbacksTest < Test::Unit::TestCase
129
236
  def setup
130
237
  @machine = StateMachine::Machine.new(Class.new)
238
+ @states = StateMachine::StateCollection.new(@machine)
131
239
 
132
- @states = StateMachine::StateCollection.new
133
240
  @states << @parked = StateMachine::State.new(@machine, :parked)
134
241
  @states << @idling = StateMachine::State.new(@machine, :idling)
135
242
 
@@ -14,6 +14,10 @@ class StateByDefaultTest < Test::Unit::TestCase
14
14
  assert_equal :parked, @state.name
15
15
  end
16
16
 
17
+ def test_should_have_a_qualified_name
18
+ assert_equal :parked, @state.name
19
+ end
20
+
17
21
  def test_should_use_stringify_the_name_as_the_value
18
22
  assert_equal 'parked', @state.value
19
23
  end
@@ -81,6 +85,10 @@ class StateWithoutNameTest < Test::Unit::TestCase
81
85
  assert_nil @state.name
82
86
  end
83
87
 
88
+ def test_should_have_a_nil_qualified_name
89
+ assert_nil @state.qualified_name
90
+ end
91
+
84
92
  def test_should_have_a_nil_value
85
93
  assert_nil @state.value
86
94
  end
@@ -107,6 +115,10 @@ class StateWithNameTest < Test::Unit::TestCase
107
115
  assert_equal :parked, @state.name
108
116
  end
109
117
 
118
+ def test_should_have_a_qualified_name
119
+ assert_equal :parked, @state.name
120
+ end
121
+
110
122
  def test_should_use_stringify_the_name_as_the_value
111
123
  assert_equal 'parked', @state.value
112
124
  end
@@ -362,13 +374,21 @@ end
362
374
  class StateWithNamespaceTest < Test::Unit::TestCase
363
375
  def setup
364
376
  @klass = Class.new
365
- @machine = StateMachine::Machine.new(@klass, :namespace => 'gear')
366
- @state = StateMachine::State.new(@machine, :parked)
377
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
378
+ @state = StateMachine::State.new(@machine, :active)
367
379
  @object = @klass.new
368
380
  end
369
381
 
382
+ def test_should_have_a_name
383
+ assert_equal :active, @state.name
384
+ end
385
+
386
+ def test_should_have_a_qualified_name
387
+ assert_equal :alarm_active, @state.qualified_name
388
+ end
389
+
370
390
  def test_should_namespace_predicate
371
- assert @object.respond_to?(:gear_parked?)
391
+ assert @object.respond_to?(:alarm_active?)
372
392
  end
373
393
  end
374
394
 
@@ -5,6 +5,7 @@ class TransitionTest < Test::Unit::TestCase
5
5
  @klass = Class.new
6
6
  @machine = StateMachine::Machine.new(@klass)
7
7
  @machine.state :parked, :idling
8
+ @machine.event :ignite
8
9
 
9
10
  @object = @klass.new
10
11
  @object.state = 'parked'
@@ -24,6 +25,10 @@ class TransitionTest < Test::Unit::TestCase
24
25
  assert_equal :ignite, @transition.event
25
26
  end
26
27
 
28
+ def test_should_have_a_qualified_event
29
+ assert_equal :ignite, @transition.qualified_event
30
+ end
31
+
27
32
  def test_should_have_a_from_value
28
33
  assert_equal 'parked', @transition.from
29
34
  end
@@ -32,6 +37,10 @@ class TransitionTest < Test::Unit::TestCase
32
37
  assert_equal :parked, @transition.from_name
33
38
  end
34
39
 
40
+ def test_should_have_a_qualified_from_name
41
+ assert_equal :parked, @transition.qualified_from_name
42
+ end
43
+
35
44
  def test_should_have_a_to_value
36
45
  assert_equal 'idling', @transition.to
37
46
  end
@@ -40,26 +49,88 @@ class TransitionTest < Test::Unit::TestCase
40
49
  assert_equal :idling, @transition.to_name
41
50
  end
42
51
 
52
+ def test_should_have_a_qualified_to_name
53
+ assert_equal :idling, @transition.qualified_to_name
54
+ end
55
+
43
56
  def test_should_have_an_attribute
44
57
  assert_equal :state, @transition.attribute
45
58
  end
46
59
 
60
+ def test_should_not_have_an_action
61
+ assert_nil @transition.action
62
+ end
63
+
47
64
  def test_should_generate_attributes
48
65
  expected = {:object => @object, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
49
66
  assert_equal expected, @transition.attributes
50
67
  end
51
68
 
69
+ def test_should_have_empty_args
70
+ assert_equal [], @transition.args
71
+ end
72
+
73
+ def test_should_not_have_a_result
74
+ assert_nil @transition.result
75
+ end
76
+
52
77
  def test_should_use_pretty_inspect
53
78
  assert_equal '#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>', @transition.inspect
54
79
  end
55
80
  end
56
81
 
82
+ class TransitionWithoutEventTest < Test::Unit::TestCase
83
+ def setup
84
+ @klass = Class.new
85
+ @machine = StateMachine::Machine.new(@klass)
86
+ @machine.state :parked, :idling
87
+
88
+ @object = @klass.new
89
+ @object.state = 'parked'
90
+
91
+ @transition = StateMachine::Transition.new(@object, @machine, nil, :parked, :idling)
92
+ end
93
+
94
+ def test_should_not_have_an_event
95
+ assert_nil @transition.event
96
+ end
97
+
98
+ def test_should_not_have_a_qualified_event
99
+ assert_nil @transition.qualified_event
100
+ end
101
+ end
102
+
103
+ class TransitionWithInvalidNodesTest < Test::Unit::TestCase
104
+ def setup
105
+ @klass = Class.new
106
+ @machine = StateMachine::Machine.new(@klass)
107
+ @machine.state :parked, :idling
108
+ @machine.event :ignite
109
+
110
+ @object = @klass.new
111
+ @object.state = 'parked'
112
+ end
113
+
114
+ def test_should_raise_exception_with_invalid_event
115
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :invalid, :parked, :idling) }
116
+ end
117
+
118
+ def test_should_raise_exception_with_invalid_from_state
119
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :invalid, :idling) }
120
+ end
121
+
122
+ def test_should_raise_exception_with_invalid_to_state
123
+ assert_raise(IndexError) { StateMachine::Transition.new(@object, @machine, :ignite, :parked, :invalid) }
124
+ end
125
+ end
126
+
57
127
  class TransitionWithDynamicToValueTest < Test::Unit::TestCase
58
128
  def setup
59
129
  @klass = Class.new
60
130
  @machine = StateMachine::Machine.new(@klass)
61
131
  @machine.state :parked
62
132
  @machine.state :idling, :value => lambda {1}
133
+ @machine.event :ignite
63
134
 
64
135
  @object = @klass.new
65
136
  @object.state = 'parked'
@@ -71,72 +142,167 @@ class TransitionWithDynamicToValueTest < Test::Unit::TestCase
71
142
  end
72
143
  end
73
144
 
74
- class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
145
+ class TransitionLoopbackTest < Test::Unit::TestCase
75
146
  def setup
76
- @klass = Class.new do
77
- attr_reader :saved, :save_state
78
-
79
- def save
80
- @save_state = state
81
- @saved = true
82
- end
83
- end
147
+ @klass = Class.new
148
+ @machine = StateMachine::Machine.new(@klass)
149
+ @machine.state :parked
150
+ @machine.event :park
84
151
 
85
- @machine = StateMachine::Machine.new(@klass, :action => :save)
152
+ @object = @klass.new
153
+ @object.state = 'parked'
154
+ @transition = StateMachine::Transition.new(@object, @machine, :park, :parked, :parked)
155
+ end
156
+
157
+ def test_should_be_loopback
158
+ assert @transition.loopback?
159
+ end
160
+ end
161
+
162
+ class TransitionWithDifferentStatesTest < Test::Unit::TestCase
163
+ def setup
164
+ @klass = Class.new
165
+ @machine = StateMachine::Machine.new(@klass)
86
166
  @machine.state :parked, :idling
167
+ @machine.event :ignite
87
168
 
88
169
  @object = @klass.new
89
170
  @object.state = 'parked'
90
171
  @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
91
- @result = @transition.perform
92
172
  end
93
173
 
94
- def test_should_be_successful
95
- assert_equal true, @result
174
+ def test_should_not_be_loopback
175
+ assert !@transition.loopback?
176
+ end
177
+ end
178
+
179
+ class TransitionWithNamespaceTest < Test::Unit::TestCase
180
+ def setup
181
+ @klass = Class.new
182
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
183
+ @machine.state :off, :active
184
+ @machine.event :activate
185
+
186
+ @object = @klass.new
187
+ @object.state = 'off'
188
+
189
+ @transition = StateMachine::Transition.new(@object, @machine, :activate, :off, :active)
96
190
  end
97
191
 
98
- def test_should_the_current_state
99
- assert_equal 'idling', @object.state
192
+ def test_should_have_an_event
193
+ assert_equal :activate, @transition.event
100
194
  end
101
195
 
102
- def test_should_run_the_action
103
- assert @object.saved
196
+ def test_should_have_a_qualified_event
197
+ assert_equal :activate_alarm, @transition.qualified_event
104
198
  end
105
199
 
106
- def test_should_run_the_action_after_saving_the_state
107
- assert_equal 'idling', @object.save_state
200
+ def test_should_have_a_from_name
201
+ assert_equal :off, @transition.from_name
202
+ end
203
+
204
+ def test_should_have_a_qualified_from_name
205
+ assert_equal :alarm_off, @transition.qualified_from_name
206
+ end
207
+
208
+ def test_should_have_a_to_name
209
+ assert_equal :active, @transition.to_name
210
+ end
211
+
212
+ def test_should_have_a_qualified_to_name
213
+ assert_equal :alarm_active, @transition.qualified_to_name
108
214
  end
109
215
  end
110
216
 
111
- class TransitionWithoutRunningActionTest < Test::Unit::TestCase
217
+ class TransitionWithActionTest < Test::Unit::TestCase
112
218
  def setup
113
219
  @klass = Class.new do
114
- attr_reader :saved
115
-
116
220
  def save
117
- @saved = true
118
221
  end
119
222
  end
120
223
 
121
224
  @machine = StateMachine::Machine.new(@klass, :action => :save)
122
225
  @machine.state :parked, :idling
226
+ @machine.event :ignite
123
227
 
124
228
  @object = @klass.new
125
229
  @object.state = 'parked'
230
+
126
231
  @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
127
- @result = @transition.perform(false)
128
232
  end
129
233
 
130
- def test_should_be_successful
131
- assert_equal true, @result
234
+ def test_should_have_an_action
235
+ assert_equal :save, @transition.action
132
236
  end
133
237
 
134
- def test_should_the_current_state
238
+ def test_should_not_have_a_result
239
+ assert_nil @transition.result
240
+ end
241
+ end
242
+
243
+ class TransitionAfterBeingPersistedTest < Test::Unit::TestCase
244
+ def setup
245
+ @klass = Class.new
246
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
247
+ @machine.state :parked, :idling
248
+ @machine.event :ignite
249
+
250
+ @object = @klass.new
251
+ @object.state = 'parked'
252
+
253
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
254
+ @transition.persist
255
+ end
256
+
257
+ def test_should_update_state_value
135
258
  assert_equal 'idling', @object.state
136
259
  end
137
260
 
138
- def test_should_not_run_the_action
139
- assert !@object.saved
261
+ def test_should_not_change_from_state
262
+ assert_equal 'parked', @transition.from
263
+ end
264
+
265
+ def test_should_not_change_to_state
266
+ assert_equal 'idling', @transition.to
267
+ end
268
+
269
+ def test_should_revert_to_from_state_on_rollback
270
+ @transition.rollback
271
+ assert_equal 'parked', @object.state
272
+ end
273
+ end
274
+
275
+ class TransitionAfterBeingRolledBackTest < Test::Unit::TestCase
276
+ def setup
277
+ @klass = Class.new
278
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
279
+ @machine.state :parked, :idling
280
+ @machine.event :ignite
281
+
282
+ @object = @klass.new
283
+ @object.state = 'parked'
284
+
285
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
286
+ @object.state = 'idling'
287
+
288
+ @transition.rollback
289
+ end
290
+
291
+ def test_should_update_state_value_to_from_state
292
+ assert_equal 'parked', @object.state
293
+ end
294
+
295
+ def test_should_not_change_from_state
296
+ assert_equal 'parked', @transition.from
297
+ end
298
+
299
+ def test_should_not_change_to_state
300
+ assert_equal 'idling', @transition.to
301
+ end
302
+
303
+ def test_should_still_be_able_to_persist
304
+ @transition.persist
305
+ assert_equal 'idling', @object.state
140
306
  end
141
307
  end
142
308
 
@@ -153,40 +319,26 @@ class TransitionWithCallbacksTest < Test::Unit::TestCase
153
319
 
154
320
  @machine = StateMachine::Machine.new(@klass)
155
321
  @machine.state :parked, :idling
322
+ @machine.event :ignite
156
323
 
157
324
  @object = @klass.new
158
325
  @object.state = 'parked'
159
326
  @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
160
327
  end
161
328
 
162
- def test_should_run_before_callbacks_before_changing_the_state
163
- @machine.before_transition(lambda {|object| @state = object.state})
164
- @transition.perform
165
-
166
- assert_equal 'parked', @state
167
- end
168
-
169
- def test_should_run_after_callbacks_after_running_the_action
170
- @machine.after_transition(lambda {|object| @state = object.state})
171
- @transition.perform
329
+ def test_should_run_before_callbacks_on_before
330
+ @machine.before_transition(lambda {|object| @run = true})
331
+ result = @transition.before
172
332
 
173
- assert_equal 'idling', @state
333
+ assert_equal true, result
334
+ assert_equal true, @run
174
335
  end
175
336
 
176
337
  def test_should_run_before_callbacks_in_the_order_they_were_defined
177
338
  @callbacks = []
178
339
  @machine.before_transition(lambda {@callbacks << 1})
179
340
  @machine.before_transition(lambda {@callbacks << 2})
180
- @transition.perform
181
-
182
- assert_equal [1, 2], @callbacks
183
- end
184
-
185
- def test_should_run_after_callbacks_in_the_order_they_were_defined
186
- @callbacks = []
187
- @machine.after_transition(lambda {@callbacks << 1})
188
- @machine.after_transition(lambda {@callbacks << 2})
189
- @transition.perform
341
+ @transition.before
190
342
 
191
343
  assert_equal [1, 2], @callbacks
192
344
  end
@@ -199,11 +351,58 @@ class TransitionWithCallbacksTest < Test::Unit::TestCase
199
351
  @machine.before_transition :from => :parked, :to => :parked, :on => :park, :do => callback
200
352
  @machine.before_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
201
353
  @machine.before_transition :from => :idling, :to => :idling, :on => :park, :do => callback
202
- @transition.perform
354
+ @transition.before
203
355
 
204
356
  assert_equal 1, @count
205
357
  end
206
358
 
359
+ def test_should_pass_transition_to_before_callbacks
360
+ @machine.before_transition(lambda {|*args| @args = args})
361
+ @transition.before
362
+
363
+ assert_equal [@object, @transition], @args
364
+ end
365
+
366
+ def test_should_catch_halted_before_callbacks
367
+ @machine.before_transition(lambda {throw :halt})
368
+
369
+ result = nil
370
+ assert_nothing_thrown { result = @transition.before }
371
+ assert_equal false, result
372
+ end
373
+
374
+ def test_should_run_before_callbacks_on_perform_before_changing_the_state
375
+ @machine.before_transition(lambda {|object| @state = object.state})
376
+ @transition.perform
377
+
378
+ assert_equal 'parked', @state
379
+ end
380
+
381
+ def test_should_run_after_callbacks_on_after
382
+ @machine.after_transition(lambda {|object| @run = true})
383
+ result = @transition.after(true)
384
+
385
+ assert_equal true, result
386
+ assert_equal true, @run
387
+ end
388
+
389
+ def test_should_set_result_on_after
390
+ @transition.after
391
+ assert_nil @transition.result
392
+
393
+ @transition.after(1)
394
+ assert_equal 1, @transition.result
395
+ end
396
+
397
+ def test_should_run_after_callbacks_in_the_order_they_were_defined
398
+ @callbacks = []
399
+ @machine.after_transition(lambda {@callbacks << 1})
400
+ @machine.after_transition(lambda {@callbacks << 2})
401
+ @transition.after(true)
402
+
403
+ assert_equal [1, 2], @callbacks
404
+ end
405
+
207
406
  def test_should_only_run_after_callbacks_that_match_transition_context
208
407
  @count = 0
209
408
  callback = lambda {@count += 1}
@@ -212,90 +411,229 @@ class TransitionWithCallbacksTest < Test::Unit::TestCase
212
411
  @machine.after_transition :from => :parked, :to => :parked, :on => :park, :do => callback
213
412
  @machine.after_transition :from => :parked, :to => :idling, :on => :ignite, :do => callback
214
413
  @machine.after_transition :from => :idling, :to => :idling, :on => :park, :do => callback
215
- @transition.perform
414
+ @transition.after(true)
216
415
 
217
416
  assert_equal 1, @count
218
417
  end
219
418
 
220
- def test_should_pass_transition_to_before_callbacks
221
- @machine.before_transition(lambda {|*args| @args = args})
222
- @transition.perform
419
+ def test_should_pass_transition_to_after_callbacks
420
+ @machine.after_transition(lambda {|*args| @args = args})
421
+
422
+ @transition.after(true)
423
+ assert_equal [@object, @transition], @args
424
+ assert_equal true, @transition.result
223
425
 
426
+ @transition.after(false)
224
427
  assert_equal [@object, @transition], @args
428
+ assert_equal false, @transition.result
225
429
  end
226
430
 
227
- def test_should_pass_transition_and_action_result_to_after_callbacks
228
- @machine.after_transition(lambda {|*args| @args = args})
229
- @transition.perform
431
+ def test_should_catch_halted_after_callbacks
432
+ @machine.after_transition(lambda {throw :halt})
433
+
434
+ result = nil
435
+ assert_nothing_thrown { result = @transition.after(true) }
436
+ assert_equal true, result
437
+ end
438
+
439
+ def test_should_run_after_callbacks_on_perform_after_running_the_action
440
+ @machine.after_transition(lambda {|object| @state = object.state})
441
+ @transition.perform(true)
230
442
 
231
- assert_equal [@object, @transition, true], @args
443
+ assert_equal 'idling', @state
232
444
  end
233
445
  end
234
446
 
235
- class TransitionHaltedDuringBeforeCallbacksTest < Test::Unit::TestCase
447
+ class TransitionAfterBeingPerformedTest < Test::Unit::TestCase
448
+ def setup
449
+ @klass = Class.new do
450
+ attr_reader :saved, :save_state
451
+
452
+ def save
453
+ @save_state = state
454
+ @saved = true
455
+ end
456
+ end
457
+
458
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
459
+ @machine.state :parked, :idling
460
+ @machine.event :ignite
461
+
462
+ @object = @klass.new
463
+ @object.state = 'parked'
464
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
465
+ @result = @transition.perform
466
+ end
467
+
468
+ def test_should_have_empty_args
469
+ assert_equal [], @transition.args
470
+ end
471
+
472
+ def test_should_have_a_result
473
+ assert_equal true, @transition.result
474
+ end
475
+
476
+ def test_should_be_successful
477
+ assert_equal true, @result
478
+ end
479
+
480
+ def test_should_change_the_current_state
481
+ assert_equal 'idling', @object.state
482
+ end
483
+
484
+ def test_should_run_the_action
485
+ assert @object.saved
486
+ end
487
+
488
+ def test_should_run_the_action_after_saving_the_state
489
+ assert_equal 'idling', @object.save_state
490
+ end
491
+ end
492
+
493
+ class TransitionWithPerformArgumentsTest < Test::Unit::TestCase
236
494
  def setup
237
495
  @klass = Class.new do
238
- class << self; attr_accessor :cancelled_transaction; end
239
496
  attr_reader :saved
240
497
 
241
498
  def save
242
499
  @saved = true
243
500
  end
244
501
  end
245
- @before_count = 0
246
- @after_count = 0
247
502
 
248
503
  @machine = StateMachine::Machine.new(@klass, :action => :save)
249
504
  @machine.state :parked, :idling
250
- class << @machine
251
- def within_transaction(object)
252
- owner_class.cancelled_transaction = yield == false
505
+ @machine.event :ignite
506
+
507
+ @object = @klass.new
508
+ @object.state = 'parked'
509
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
510
+ end
511
+
512
+ def test_should_have_arguments
513
+ @transition.perform(1, 2)
514
+
515
+ assert_equal [1, 2], @transition.args
516
+ assert @object.saved
517
+ end
518
+
519
+ def test_should_not_include_run_action_in_arguments
520
+ @transition.perform(1, 2, false)
521
+
522
+ assert_equal [1, 2], @transition.args
523
+ assert !@object.saved
524
+ end
525
+ end
526
+
527
+ class TransitionWithoutRunningActionTest < Test::Unit::TestCase
528
+ def setup
529
+ @klass = Class.new do
530
+ attr_reader :saved
531
+
532
+ def save
533
+ @saved = true
253
534
  end
254
535
  end
255
536
 
256
- @machine.before_transition lambda {@before_count += 1; throw :halt}
257
- @machine.before_transition lambda {@before_count += 1}
258
- @machine.after_transition lambda {@after_count += 1}
537
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
538
+ @machine.state :parked, :idling
539
+ @machine.event :ignite
259
540
 
260
541
  @object = @klass.new
261
542
  @object.state = 'parked'
262
543
  @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
263
- @result = @transition.perform
544
+ @result = @transition.perform(false)
264
545
  end
265
546
 
266
- def test_should_not_be_successful
267
- assert !@result
547
+ def test_should_have_empty_args
548
+ assert_equal [], @transition.args
268
549
  end
269
550
 
270
- def test_should_not_change_current_state
271
- assert_equal 'parked', @object.state
551
+ def test_should_not_have_a_result
552
+ assert_nil @transition.result
272
553
  end
273
554
 
274
- def test_should_not_run_action
555
+ def test_should_be_successful
556
+ assert_equal true, @result
557
+ end
558
+
559
+ def test_should_change_the_current_state
560
+ assert_equal 'idling', @object.state
561
+ end
562
+
563
+ def test_should_not_run_the_action
275
564
  assert !@object.saved
276
565
  end
566
+ end
567
+
568
+ class TransitionWithTransactionsTest < Test::Unit::TestCase
569
+ def setup
570
+ @klass = Class.new do
571
+ class << self
572
+ attr_accessor :running_transaction
573
+ end
574
+
575
+ attr_accessor :result
576
+
577
+ def save
578
+ @result = self.class.running_transaction
579
+ true
580
+ end
581
+ end
582
+
583
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
584
+ @machine.state :parked, :idling
585
+ @machine.event :ignite
586
+
587
+ @object = @klass.new
588
+ @object.state = 'parked'
589
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
590
+
591
+ class << @machine
592
+ def within_transaction(object)
593
+ owner_class.running_transaction = object
594
+ yield
595
+ owner_class.running_transaction = false
596
+ end
597
+ end
598
+ end
277
599
 
278
- def test_should_not_run_further_before_callbacks
279
- assert_equal 1, @before_count
600
+ def test_should_run_blocks_within_transaction_for_object
601
+ @transition.within_transaction do
602
+ @result = @klass.running_transaction
603
+ end
604
+
605
+ assert_equal @object, @result
280
606
  end
281
607
 
282
- def test_should_not_run_after_callbacks
283
- assert_equal 0, @after_count
608
+ def test_should_run_before_callbacks_within_transaction
609
+ @machine.before_transition(lambda {|object| @result = @klass.running_transaction})
610
+ @transition.perform
611
+
612
+ assert_equal @object, @result
284
613
  end
285
614
 
286
- def test_should_cancel_the_transaction
287
- assert @klass.cancelled_transaction
615
+ def test_should_run_action_within_transaction
616
+ @transition.perform
617
+
618
+ assert_equal @object, @object.result
619
+ end
620
+
621
+ def test_should_run_after_callbacks_within_transaction
622
+ @machine.after_transition(lambda {|object| @result = @klass.running_transaction})
623
+ @transition.perform
624
+
625
+ assert_equal @object, @result
288
626
  end
289
627
  end
290
628
 
291
- class TransitionHaltedDuringActionTest < Test::Unit::TestCase
629
+ class TransitionHaltedDuringBeforeCallbacksTest < Test::Unit::TestCase
292
630
  def setup
293
631
  @klass = Class.new do
294
632
  class << self; attr_accessor :cancelled_transaction; end
295
633
  attr_reader :saved
296
634
 
297
635
  def save
298
- throw :halt
636
+ @saved = true
299
637
  end
300
638
  end
301
639
  @before_count = 0
@@ -303,12 +641,14 @@ class TransitionHaltedDuringActionTest < Test::Unit::TestCase
303
641
 
304
642
  @machine = StateMachine::Machine.new(@klass, :action => :save)
305
643
  @machine.state :parked, :idling
644
+ @machine.event :ignite
306
645
  class << @machine
307
646
  def within_transaction(object)
308
647
  owner_class.cancelled_transaction = yield == false
309
648
  end
310
649
  end
311
650
 
651
+ @machine.before_transition lambda {@before_count += 1; throw :halt}
312
652
  @machine.before_transition lambda {@before_count += 1}
313
653
  @machine.after_transition lambda {@after_count += 1}
314
654
 
@@ -322,11 +662,15 @@ class TransitionHaltedDuringActionTest < Test::Unit::TestCase
322
662
  assert !@result
323
663
  end
324
664
 
325
- def test_should_change_current_state
326
- assert_equal 'idling', @object.state
665
+ def test_should_not_change_current_state
666
+ assert_equal 'parked', @object.state
327
667
  end
328
668
 
329
- def test_should_run_before_callbacks
669
+ def test_should_not_run_action
670
+ assert !@object.saved
671
+ end
672
+
673
+ def test_should_not_run_further_before_callbacks
330
674
  assert_equal 1, @before_count
331
675
  end
332
676
 
@@ -354,6 +698,7 @@ class TransitionHaltedAfterCallbackTest < Test::Unit::TestCase
354
698
 
355
699
  @machine = StateMachine::Machine.new(@klass, :action => :save)
356
700
  @machine.state :parked, :idling
701
+ @machine.event :ignite
357
702
  class << @machine
358
703
  def within_transaction(object)
359
704
  owner_class.cancelled_transaction = yield == false
@@ -391,7 +736,7 @@ class TransitionHaltedAfterCallbackTest < Test::Unit::TestCase
391
736
  end
392
737
  end
393
738
 
394
- class TransitionWithFailedActionTest < Test::Unit::TestCase
739
+ class TransitionWithActionFailedTest < Test::Unit::TestCase
395
740
  def setup
396
741
  @klass = Class.new do
397
742
  class << self; attr_accessor :cancelled_transaction; end
@@ -406,6 +751,7 @@ class TransitionWithFailedActionTest < Test::Unit::TestCase
406
751
 
407
752
  @machine = StateMachine::Machine.new(@klass, :action => :save)
408
753
  @machine.state :parked, :idling
754
+ @machine.event :ignite
409
755
  class << @machine
410
756
  def within_transaction(object)
411
757
  owner_class.cancelled_transaction = yield == false
@@ -424,8 +770,8 @@ class TransitionWithFailedActionTest < Test::Unit::TestCase
424
770
  assert !@result
425
771
  end
426
772
 
427
- def test_should_change_current_state
428
- assert_equal 'idling', @object.state
773
+ def test_should_not_change_current_state
774
+ assert_nil @object.state
429
775
  end
430
776
 
431
777
  def test_should_run_before_callbacks
@@ -440,3 +786,318 @@ class TransitionWithFailedActionTest < Test::Unit::TestCase
440
786
  assert @klass.cancelled_transaction
441
787
  end
442
788
  end
789
+
790
+ class TransitionWithActionErrorTest < Test::Unit::TestCase
791
+ def setup
792
+ @klass = Class.new do
793
+ def save
794
+ raise ArgumentError
795
+ end
796
+ end
797
+
798
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
799
+ @machine.state :parked, :idling
800
+ @machine.event :ignite
801
+
802
+ @object = @klass.new
803
+ @object.state = 'parked'
804
+ @transition = StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)
805
+
806
+ @raised = true
807
+ begin
808
+ @transition.perform
809
+ @raised = false
810
+ rescue ArgumentError
811
+ end
812
+ end
813
+
814
+ def test_should_not_catch_exception
815
+ assert @raised
816
+ end
817
+
818
+ def test_should_not_change_current_state
819
+ assert_equal 'parked', @object.state
820
+ end
821
+ end
822
+
823
+ class TransitionsInParallelTest < Test::Unit::TestCase
824
+ def setup
825
+ @klass = Class.new do
826
+ attr_reader :persisted
827
+
828
+ def initialize
829
+ @persisted = []
830
+ @state = 'parked'
831
+ @status = 'first_gear'
832
+ super
833
+ end
834
+
835
+ def state=(value)
836
+ @persisted << value
837
+ @state = value
838
+ end
839
+
840
+ def status=(value)
841
+ @persisted << value
842
+ @status = value
843
+ end
844
+ end
845
+
846
+ @state = StateMachine::Machine.new(@klass, :state)
847
+ @state.event :ignite
848
+ @state.state :parked, :idling
849
+
850
+ @status = StateMachine::Machine.new(@klass, :status)
851
+ @status.event :shift_up
852
+ @status.state :first_gear, :second_gear
853
+
854
+ @object = @klass.new
855
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
856
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
857
+
858
+ @result = StateMachine::Transition.perform([@state_transition, @status_transition])
859
+ end
860
+
861
+ def test_should_raise_exception_if_attempted_on_the_same_state_machine
862
+ exception = assert_raise(ArgumentError) { StateMachine::Transition.perform([@state_transition, @state_transition]) }
863
+ assert_equal 'Cannot perform multiple transitions in parallel for the same state machine attribute', exception.message
864
+ end
865
+
866
+ def test_should_perform
867
+ assert_equal true, @result
868
+ end
869
+
870
+ def test_should_persist_first_state
871
+ assert_equal 'idling', @object.state
872
+ end
873
+
874
+ def test_should_persist_second_state
875
+ assert_equal 'second_gear', @object.status
876
+ end
877
+
878
+ def test_should_persist_in_order
879
+ assert_equal ['idling', 'second_gear'], @object.persisted
880
+ end
881
+
882
+ def test_should_have_args_in_transitions
883
+ assert_equal [], @state_transition.args
884
+ assert_equal [], @status_transition.args
885
+ end
886
+ end
887
+
888
+ class TransitionsInParallelWithCallbacksTest < Test::Unit::TestCase
889
+ def setup
890
+ @klass = Class.new
891
+
892
+ @before_callbacks = []
893
+ @after_callbacks = []
894
+
895
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
896
+ @state.event :ignite
897
+ @state.state :parked, :idling
898
+ @state.before_transition lambda {@before_callbacks << :state}
899
+ @state.after_transition lambda {@after_callbacks << :state}
900
+
901
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
902
+ @status.event :shift_up
903
+ @status.state :first_gear, :second_gear
904
+ @status.before_transition lambda {@before_callbacks << :status}
905
+ @status.after_transition lambda {@after_callbacks << :status}
906
+
907
+ @object = @klass.new
908
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
909
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
910
+ end
911
+
912
+ def test_should_run_before_callbacks_in_order
913
+ perform
914
+ assert_equal [:state, :status], @before_callbacks
915
+ end
916
+
917
+ def test_should_run_after_callbacks_in_order
918
+ perform
919
+ assert_equal [:state, :status], @after_callbacks
920
+ end
921
+
922
+ def test_should_not_run_after_callbacks_if_disabled
923
+ perform(:after => false)
924
+ assert_equal [], @after_callbacks
925
+ end
926
+
927
+ def test_should_halt_if_before_callback_halted_for_first_transition
928
+ @state.before_transition lambda {throw :halt}
929
+
930
+ assert_equal false, perform
931
+ assert_equal [:state], @before_callbacks
932
+ assert_equal 'parked', @object.state
933
+ assert_equal 'first_gear', @object.status
934
+ assert_equal [], @after_callbacks
935
+ end
936
+
937
+ def test_should_halt_if_before_callback_halted_for_second_transition
938
+ @status.before_transition lambda {throw :halt}
939
+
940
+ assert_equal false, perform
941
+ assert_equal [:state, :status], @before_callbacks
942
+ assert_equal 'parked', @object.state
943
+ assert_equal 'first_gear', @object.status
944
+ assert_equal [], @after_callbacks
945
+ end
946
+
947
+ def test_should_perform_if_after_callback_halted_for_first_transition
948
+ @state.after_transition lambda {throw :halt}
949
+ @state.after_transition lambda {@after_callbacks << :invalid}
950
+
951
+ assert_equal true, perform
952
+ assert_equal [:state, :status], @before_callbacks
953
+ assert_equal 'idling', @object.state
954
+ assert_equal 'second_gear', @object.status
955
+ assert_equal [:state, :status], @after_callbacks
956
+ end
957
+
958
+ def test_should_perform_if_after_callback_halted_for_second_transition
959
+ @status.after_transition lambda {throw :halt}
960
+ @status.after_transition lambda {@after_callbacks << :invalid}
961
+
962
+ assert_equal true, perform
963
+ assert_equal [:state, :status], @before_callbacks
964
+ assert_equal 'idling', @object.state
965
+ assert_equal 'second_gear', @object.status
966
+ assert_equal [:state, :status], @after_callbacks
967
+ end
968
+
969
+ private
970
+ def perform(options = {})
971
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
972
+ end
973
+ end
974
+
975
+ class TransitionsInParallelWithActionsTest < Test::Unit::TestCase
976
+ def setup
977
+ @klass = Class.new do
978
+ attr_reader :actions
979
+
980
+ def save_state
981
+ (@actions ||= []) << :save_state
982
+ :save_state
983
+ end
984
+
985
+ def save_status
986
+ (@actions ||= []) << :save_status
987
+ :save_status
988
+ end
989
+ end
990
+
991
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked, :action => :save_state)
992
+ @state.event :ignite
993
+ @state.state :parked, :idling
994
+
995
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear, :action => :save_status)
996
+ @status.event :shift_up
997
+ @status.state :first_gear, :second_gear
998
+
999
+ @object = @klass.new
1000
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1001
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1002
+ end
1003
+
1004
+ def test_should_run_actions_in_order
1005
+ perform
1006
+ assert_equal [:save_state, :save_status], @object.actions
1007
+ end
1008
+
1009
+ def test_should_store_action_specific_results
1010
+ perform
1011
+ assert_equal :save_state, @state_transition.result
1012
+ assert_equal :save_status, @status_transition.result
1013
+ end
1014
+
1015
+ def test_should_not_perform_if_action_fails_for_first_transition
1016
+ @klass.class_eval do
1017
+ def save_state
1018
+ false
1019
+ end
1020
+ end
1021
+
1022
+ assert_equal false, perform
1023
+ assert_equal 'parked', @object.state
1024
+ assert_equal 'first_gear', @object.status
1025
+ end
1026
+
1027
+ def test_should_not_perform_if_action_fails_for_second_transition
1028
+ @klass.class_eval do
1029
+ def save_status
1030
+ false
1031
+ end
1032
+ end
1033
+
1034
+ assert_equal false, perform
1035
+ assert_equal 'parked', @object.state
1036
+ assert_equal 'first_gear', @object.status
1037
+ end
1038
+
1039
+ def test_should_rollback_if_action_errors_for_first_transition
1040
+ @klass.class_eval do
1041
+ def save_state
1042
+ raise ArgumentError
1043
+ end
1044
+ end
1045
+
1046
+ begin; perform; rescue; end
1047
+ assert_equal 'parked', @object.state
1048
+ assert_equal 'first_gear', @object.status
1049
+ end
1050
+
1051
+ def test_should_rollback_if_action_errors_for_second_transition
1052
+ @klass.class_eval do
1053
+ def save_status
1054
+ raise ArgumentError
1055
+ end
1056
+ end
1057
+
1058
+ begin; perform; rescue; end
1059
+ assert_equal 'parked', @object.state
1060
+ assert_equal 'first_gear', @object.status
1061
+ end
1062
+
1063
+ private
1064
+ def perform(options = {})
1065
+ StateMachine::Transition.perform([@state_transition, @status_transition], options)
1066
+ end
1067
+ end
1068
+
1069
+ class TransitionsWithPerformBlockTest < Test::Unit::TestCase
1070
+ def setup
1071
+ @klass = Class.new
1072
+
1073
+ @state = StateMachine::Machine.new(@klass, :state, :initial => :parked)
1074
+ @state.event :ignite
1075
+ @state.state :parked, :idling
1076
+
1077
+ @status = StateMachine::Machine.new(@klass, :status, :initial => :first_gear)
1078
+ @status.event :shift_up
1079
+ @status.state :first_gear, :second_gear
1080
+
1081
+ @object = @klass.new
1082
+ @state_transition = StateMachine::Transition.new(@object, @state, :ignite, :parked, :idling)
1083
+ @status_transition = StateMachine::Transition.new(@object, @status, :shift_up, :first_gear, :second_gear)
1084
+ end
1085
+
1086
+ def test_should_be_perform_if_result_is_not_false
1087
+ assert StateMachine::Transition.perform([@state_transition, @status_transition]) { true }
1088
+ assert_equal 'idling', @object.state
1089
+ assert_equal 'second_gear', @object.status
1090
+ end
1091
+
1092
+ def test_should_not_perform_if_result_is_false
1093
+ assert !StateMachine::Transition.perform([@state_transition, @status_transition]) { false }
1094
+ assert_equal 'parked', @object.state
1095
+ assert_equal 'first_gear', @object.status
1096
+ end
1097
+
1098
+ def test_should_use_result_as_transition_result
1099
+ StateMachine::Transition.perform([@state_transition, @status_transition]) { 1 }
1100
+ assert_equal 1, @state_transition.result
1101
+ assert_equal 1, @status_transition.result
1102
+ end
1103
+ end