state_machine 0.6.3 → 0.7.0

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 (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