state_machines 0.0.1 → 0.0.2

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -2
  3. data/README.md +25 -0
  4. data/Rakefile +10 -1
  5. data/lib/state_machines/branch.rb +0 -4
  6. data/lib/state_machines/core.rb +23 -5
  7. data/lib/state_machines/error.rb +81 -2
  8. data/lib/state_machines/event.rb +2 -20
  9. data/lib/state_machines/event_collection.rb +25 -27
  10. data/lib/state_machines/extensions.rb +34 -34
  11. data/lib/state_machines/integrations.rb +98 -90
  12. data/lib/state_machines/integrations/base.rb +11 -60
  13. data/lib/state_machines/matcher.rb +0 -2
  14. data/lib/state_machines/node_collection.rb +0 -2
  15. data/lib/state_machines/path_collection.rb +0 -2
  16. data/lib/state_machines/state.rb +0 -3
  17. data/lib/state_machines/state_collection.rb +17 -19
  18. data/lib/state_machines/state_context.rb +1 -6
  19. data/lib/state_machines/transition.rb +0 -56
  20. data/lib/state_machines/version.rb +1 -1
  21. data/spec/spec_helper.rb +1 -0
  22. data/spec/state_machines/assertions_spec.rb +31 -0
  23. data/spec/state_machines/branch_spec.rb +827 -0
  24. data/spec/state_machines/callbacks_spec.rb +706 -0
  25. data/spec/state_machines/errors_spec.rb +1 -0
  26. data/spec/state_machines/event_collection_spec.rb +401 -0
  27. data/spec/state_machines/event_spec.rb +1140 -0
  28. data/spec/{helpers → state_machines}/helper_spec.rb +0 -0
  29. data/spec/state_machines/integration_base_spec.rb +12 -0
  30. data/spec/state_machines/integration_spec.rb +132 -0
  31. data/spec/state_machines/invalid_event_spec.rb +19 -0
  32. data/spec/state_machines/invalid_parallel_transition_spec.rb +18 -0
  33. data/spec/state_machines/invalid_transition_spec.rb +114 -0
  34. data/spec/state_machines/machine_collection_spec.rb +606 -0
  35. data/spec/{machine_spec.rb → state_machines/machine_spec.rb} +11 -2
  36. data/spec/{matcher_helpers_spec.rb → state_machines/matcher_helpers_spec.rb} +0 -0
  37. data/spec/{matcher_spec.rb → state_machines/matcher_spec.rb} +0 -0
  38. data/spec/{node_collection_spec.rb → state_machines/node_collection_spec.rb} +0 -0
  39. data/spec/{path_collection_spec.rb → state_machines/path_collection_spec.rb} +0 -0
  40. data/spec/{path_spec.rb → state_machines/path_spec.rb} +0 -0
  41. data/spec/{state_collection_spec.rb → state_machines/state_collection_spec.rb} +0 -0
  42. data/spec/{state_context_spec.rb → state_machines/state_context_spec.rb} +0 -0
  43. data/spec/{state_machine_spec.rb → state_machines/state_machine_spec.rb} +0 -0
  44. data/spec/{state_spec.rb → state_machines/state_spec.rb} +0 -0
  45. data/spec/{transition_collection_spec.rb → state_machines/transition_collection_spec.rb} +0 -0
  46. data/spec/{transition_spec.rb → state_machines/transition_spec.rb} +0 -0
  47. data/spec/support/migration_helpers.rb +9 -0
  48. data/state_machines.gemspec +3 -1
  49. metadata +68 -45
  50. data/lib/state_machines/yard.rb +0 -8
  51. data/spec/errors/default_spec.rb +0 -14
  52. data/spec/errors/with_message_spec.rb +0 -39
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachines::Integrations::Base do
4
+ it { should respond_to(:name) }
5
+ it { should respond_to(:integration_name) }
6
+ it { should respond_to(:available?) }
7
+ it { should respond_to(:matching_ancestors) }
8
+ it { should respond_to(:matches?) }
9
+ it { should respond_to(:matches_ancestors?) }
10
+ it { expect(subject.name).to eq('StateMachines::Integrations::Base') }
11
+ it { expect(subject.integration_name).to eq(:base) }
12
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachines::Integrations do
4
+
5
+ it { expect(StateMachines::Integrations).to respond_to(:register) }
6
+ it { expect(StateMachines::Integrations).to respond_to(:integrations) }
7
+ it { expect(StateMachines::Integrations).to respond_to(:match) }
8
+ it { expect(StateMachines::Integrations).to respond_to(:match_ancestors) }
9
+ it { expect(StateMachines::Integrations).to respond_to(:find_by_name) }
10
+ describe '#register' do
11
+ before(:each) do
12
+ StateMachines::Integrations.const_set('Custom', Module.new do
13
+ include StateMachines::Integrations::Base
14
+ end)
15
+ end
16
+
17
+ it 'should register integration' do
18
+ expect(StateMachines::Integrations.register(StateMachines::Integrations::Custom)).to be_truthy
19
+ end
20
+
21
+ after(:each) do
22
+ StateMachines::Integrations.send(:remove_const, 'Custom')
23
+ StateMachines::Integrations.send(:reset)
24
+ end
25
+
26
+ end
27
+
28
+ describe '#integrations' do
29
+ before(:each) do
30
+ StateMachines::Integrations.const_set('Custom', Module.new do
31
+ include StateMachines::Integrations::Base
32
+ end)
33
+ StateMachines::Integrations.register(StateMachines::Integrations::Custom)
34
+ end
35
+
36
+ it 'should register integration' do
37
+ expect(StateMachines::Integrations.integrations).to include(StateMachines::Integrations::Custom)
38
+ end
39
+
40
+ after(:each) do
41
+ StateMachines::Integrations.send(:remove_const, 'Custom')
42
+ StateMachines::Integrations.send(:reset)
43
+ end
44
+ end
45
+
46
+ context 'do' do
47
+ before(:all) do
48
+ Object.const_set('CustomIntegration', Module.new do
49
+ include StateMachines::Integrations::Base
50
+ class << self
51
+ def matching_ancestors
52
+ %w(Bar)
53
+ end
54
+ end
55
+ end)
56
+ StateMachines::Integrations.const_set('Hogue', Module.new do
57
+ include StateMachines::Integrations::Base
58
+ class << self
59
+ def matching_ancestors
60
+ %w(Hogue Foo)
61
+ end
62
+ end
63
+ end)
64
+ class Bar
65
+ end
66
+ class Hogue
67
+ end
68
+ class Foo
69
+ end
70
+
71
+ StateMachines::Integrations.register(CustomIntegration)
72
+ end
73
+
74
+ describe 'StateMachines::Integrations::Hogue' do
75
+ let(:subject) { StateMachines::Integrations::Hogue }
76
+ it { should respond_to(:name) }
77
+ it { should respond_to(:integration_name) }
78
+ it { should respond_to(:available?) }
79
+ it { should respond_to(:matching_ancestors) }
80
+ it { should respond_to(:matches?) }
81
+ it { should respond_to(:matches_ancestors?) }
82
+ it { expect(subject.name).to eq('StateMachines::Integrations::Hogue') }
83
+ it { expect(subject.matching_ancestors).to eq(%w(Hogue Foo)) }
84
+ end
85
+
86
+ describe 'CustomIntegration' do
87
+ let(:subject) { CustomIntegration }
88
+ it { should respond_to(:name) }
89
+ it { should respond_to(:integration_name) }
90
+ it { should respond_to(:available?) }
91
+ it { should respond_to(:matching_ancestors) }
92
+ it { should respond_to(:matches?) }
93
+ it { should respond_to(:matches_ancestors?) }
94
+ it { expect(subject.name).to eq('CustomIntegration') }
95
+ it { expect(subject.integration_name).to eq(:custom_integration) }
96
+ it { expect(subject.matching_ancestors).to eq(%w(Bar)) }
97
+ end
98
+
99
+ describe '#match' do
100
+
101
+
102
+ it 'should match correct integration' do
103
+
104
+ expect(StateMachines::Integrations.match(Bar)).to eq(CustomIntegration)
105
+ expect(StateMachines::Integrations.match(Hogue)).to eq(StateMachines::Integrations::Hogue)
106
+ expect(StateMachines::Integrations.match(Foo)).to eq(StateMachines::Integrations::Hogue)
107
+ end
108
+
109
+
110
+ end
111
+
112
+ describe '#match_ancestors' do
113
+ it { expect(StateMachines::Integrations.match_ancestors([])).to be_nil }
114
+ it { expect(StateMachines::Integrations.match_ancestors(['Foo'])).to eq(StateMachines::Integrations::Hogue) }
115
+ it { expect(StateMachines::Integrations.match_ancestors(['Hogue'])).to eq(StateMachines::Integrations::Hogue) }
116
+ it { expect(StateMachines::Integrations.match_ancestors(['Foo', 'Hogue'])).to eq(StateMachines::Integrations::Hogue) }
117
+ it { expect(StateMachines::Integrations.match_ancestors(['Bar'])).to eq(CustomIntegration) }
118
+ end
119
+
120
+ describe '#find_by_name' do
121
+ pending
122
+ end
123
+
124
+ after(:all) do
125
+ Object.send(:remove_const, 'CustomIntegration')
126
+ StateMachines::Integrations.send(:remove_const, 'Hogue')
127
+ StateMachines::Integrations.send(:reset)
128
+ end
129
+ end
130
+
131
+
132
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ describe StateMachines::InvalidEvent do
3
+ before(:each) do
4
+ @object = Object.new
5
+ @invalid_event = StateMachines::InvalidEvent.new(@object, :invalid)
6
+ end
7
+
8
+ it 'should_have_an_object' do
9
+ assert_equal @object, @invalid_event.object
10
+ end
11
+
12
+ it 'should_have_an_event' do
13
+ assert_equal :invalid, @invalid_event.event
14
+ end
15
+
16
+ it 'should_generate_a_message' do
17
+ assert_equal ':invalid is an unknown state machine event', @invalid_event.message
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachines::InvalidParallelTransition do
4
+ before(:each) do
5
+ @object = Object.new
6
+ @events = [:ignite, :disable_alarm]
7
+
8
+ @invalid_transition = StateMachines::InvalidParallelTransition.new(@object, @events)
9
+ end
10
+
11
+ it 'should_have_an_object' do
12
+ expect(@invalid_transition.object).to eq(@object)
13
+ end
14
+
15
+ it 'should_have_events' do
16
+ expect(@invalid_transition.events).to eq(@events)
17
+ end
18
+ end
@@ -0,0 +1,114 @@
1
+ context 'Default' do
2
+ before(:each) do
3
+ @klass = Class.new
4
+ @machine = StateMachines::Machine.new(@klass)
5
+ @state = @machine.state :parked
6
+ @machine.event :ignite
7
+
8
+ @object = @klass.new
9
+ @object.state = 'parked'
10
+
11
+ @invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :ignite)
12
+ end
13
+
14
+ it 'should_have_an_object' do
15
+ assert_equal @object, @invalid_transition.object
16
+ end
17
+
18
+ it 'should_have_a_machine' do
19
+ assert_equal @machine, @invalid_transition.machine
20
+ end
21
+
22
+ it 'should_have_an_event' do
23
+ assert_equal :ignite, @invalid_transition.event
24
+ end
25
+
26
+ it 'should_have_a_qualified_event' do
27
+ assert_equal :ignite, @invalid_transition.qualified_event
28
+ end
29
+
30
+ it 'should_have_a_from_value' do
31
+ assert_equal 'parked', @invalid_transition.from
32
+ end
33
+
34
+ it 'should_have_a_from_name' do
35
+ assert_equal :parked, @invalid_transition.from_name
36
+ end
37
+
38
+ it 'should_have_a_qualified_from_name' do
39
+ assert_equal :parked, @invalid_transition.qualified_from_name
40
+ end
41
+
42
+ it 'should_generate_a_message' do
43
+ assert_equal 'Cannot transition state via :ignite from :parked', @invalid_transition.message
44
+ end
45
+ end
46
+
47
+ context 'WithNamespace' do
48
+ before(:each) do
49
+ @klass = Class.new
50
+ @machine = StateMachines::Machine.new(@klass, :namespace => 'alarm')
51
+ @state = @machine.state :active
52
+ @machine.event :disable
53
+
54
+ @object = @klass.new
55
+ @object.state = 'active'
56
+
57
+ @invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :disable)
58
+ end
59
+
60
+ it 'should_have_an_event' do
61
+ assert_equal :disable, @invalid_transition.event
62
+ end
63
+
64
+ it 'should_have_a_qualified_event' do
65
+ assert_equal :disable_alarm, @invalid_transition.qualified_event
66
+ end
67
+
68
+ it 'should_have_a_from_name' do
69
+ assert_equal :active, @invalid_transition.from_name
70
+ end
71
+
72
+ it 'should_have_a_qualified_from_name' do
73
+ assert_equal :alarm_active, @invalid_transition.qualified_from_name
74
+ end
75
+ end
76
+
77
+ context 'WithIntegration' do
78
+ before(:each) do
79
+ StateMachines::Integrations.const_set('Custom', Module.new do
80
+ include StateMachines::Integrations::Base
81
+
82
+ def errors_for(object)
83
+ object.errors
84
+ end
85
+ end)
86
+
87
+ @klass = Class.new do
88
+ attr_accessor :errors
89
+ end
90
+ @machine = StateMachines::Machine.new(@klass, :integration => :custom)
91
+ @machine.state :parked
92
+ @machine.event :ignite
93
+
94
+ @object = @klass.new
95
+ @object.state = 'parked'
96
+ end
97
+
98
+ it 'should_generate_a_message_without_reasons_if_empty' do
99
+ @object.errors = ''
100
+ invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :ignite)
101
+ assert_equal 'Cannot transition state via :ignite from :parked', invalid_transition.message
102
+ end
103
+
104
+ it 'should_generate_a_message_with_error_reasons_if_errors_found' do
105
+ @object.errors = 'Id is invalid, Name is invalid'
106
+ invalid_transition = StateMachines::InvalidTransition.new(@object, @machine, :ignite)
107
+ assert_equal 'Cannot transition state via :ignite from :parked (Reason(s): Id is invalid, Name is invalid)', invalid_transition.message
108
+ end
109
+
110
+ after(:each) do
111
+ StateMachines::Integrations.send(:remove_const, 'Custom')
112
+ StateMachines::Integrations.send(:reset)
113
+ end
114
+ end
@@ -0,0 +1,606 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachines::MachineCollection do
4
+ context 'ByDefault' do
5
+ before(:each) do
6
+ @machines = StateMachines::MachineCollection.new
7
+ end
8
+
9
+ it 'should_not_have_any_machines' do
10
+ assert @machines.empty?
11
+ end
12
+ end
13
+
14
+ context 'StateInitialization' do
15
+ before(:each) do
16
+ @machines = StateMachines::MachineCollection.new
17
+
18
+ @klass = Class.new
19
+
20
+ @machines[:state] = StateMachines::Machine.new(@klass, :state, initial: :parked)
21
+ @machines[:alarm_state] = StateMachines::Machine.new(@klass, :alarm_state, initial: lambda { |object| :active })
22
+ @machines[:alarm_state].state :active, value: lambda { 'active' }
23
+
24
+ # Prevent the auto-initialization hook from firing
25
+ @klass.class_eval do
26
+ def initialize
27
+ end
28
+ end
29
+
30
+ @object = @klass.new
31
+ @object.state = nil
32
+ @object.alarm_state = nil
33
+ end
34
+
35
+ it 'should_raise_exception_if_invalid_option_specified' do
36
+ assert_raise(ArgumentError) { @machines.initialize_states(@object, invalid: true) }
37
+ end
38
+
39
+ it 'should_only_initialize_static_states_prior_to_block' do
40
+ @machines.initialize_states(@object) do
41
+ @state_in_block = @object.state
42
+ @alarm_state_in_block = @object.alarm_state
43
+ end
44
+
45
+ assert_equal 'parked', @state_in_block
46
+ assert_nil @alarm_state_in_block
47
+ end
48
+
49
+ it 'should_only_initialize_dynamic_states_after_block' do
50
+ @machines.initialize_states(@object) do
51
+ @alarm_state_in_block = @object.alarm_state
52
+ end
53
+
54
+ assert_nil @alarm_state_in_block
55
+ assert_equal 'active', @object.alarm_state
56
+ end
57
+
58
+ it 'should_initialize_all_states_without_block' do
59
+ @machines.initialize_states(@object)
60
+
61
+ assert_equal 'parked', @object.state
62
+ assert_equal 'active', @object.alarm_state
63
+ end
64
+
65
+ it 'should_skip_static_states_if_disabled' do
66
+ @machines.initialize_states(@object, static: false)
67
+ assert_nil @object.state
68
+ assert_equal 'active', @object.alarm_state
69
+ end
70
+
71
+ it 'should_not_initialize_existing_static_states_by_default' do
72
+ @object.state = 'idling'
73
+ @machines.initialize_states(@object)
74
+ assert_equal 'idling', @object.state
75
+ end
76
+
77
+ it 'should_initialize_existing_static_states_if_forced' do
78
+ @object.state = 'idling'
79
+ @machines.initialize_states(@object, static: :force)
80
+ assert_equal 'parked', @object.state
81
+ end
82
+
83
+ it 'should_not_initialize_existing_static_states_if_not_forced' do
84
+ @object.state = 'idling'
85
+ @machines.initialize_states(@object, static: true)
86
+ assert_equal 'idling', @object.state
87
+ end
88
+
89
+ it 'should_skip_dynamic_states_if_disabled' do
90
+ @machines.initialize_states(@object, dynamic: false)
91
+ assert_equal 'parked', @object.state
92
+ assert_nil @object.alarm_state
93
+ end
94
+
95
+ it 'should_not_initialize_existing_dynamic_states_by_default' do
96
+ @object.alarm_state = 'inactive'
97
+ @machines.initialize_states(@object)
98
+ assert_equal 'inactive', @object.alarm_state
99
+ end
100
+
101
+ it 'should_initialize_existing_dynamic_states_if_forced' do
102
+ @object.alarm_state = 'inactive'
103
+ @machines.initialize_states(@object, dynamic: :force)
104
+ assert_equal 'active', @object.alarm_state
105
+ end
106
+
107
+ it 'should_not_initialize_existing_dynamic_states_if_not_forced' do
108
+ @object.alarm_state = 'inactive'
109
+ @machines.initialize_states(@object, dynamic: true)
110
+ assert_equal 'inactive', @object.alarm_state
111
+ end
112
+ end
113
+
114
+ context 'Fire' do
115
+ before(:each) do
116
+ @machines = StateMachines::MachineCollection.new
117
+
118
+ @klass = Class.new do
119
+ attr_reader :saved
120
+
121
+ def save
122
+ @saved = true
123
+ end
124
+ end
125
+
126
+ # First machine
127
+ @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
128
+ @state.event :ignite do
129
+ transition parked: :idling
130
+ end
131
+ @state.event :park do
132
+ transition idling: :parked
133
+ end
134
+
135
+ # Second machine
136
+ @machines[:alarm_state] = @alarm_state = StateMachines::Machine.new(@klass, :alarm_state, initial: :active, action: :save, namespace: 'alarm')
137
+ @alarm_state.event :enable do
138
+ transition off: :active
139
+ end
140
+ @alarm_state.event :disable do
141
+ transition active: :off
142
+ end
143
+
144
+ @object = @klass.new
145
+ end
146
+
147
+ it 'should_raise_exception_if_invalid_event_specified' do
148
+ assert_raise(StateMachines::InvalidEvent) { @machines.fire_events(@object, :invalid) }
149
+
150
+ assert_raise(StateMachines::InvalidEvent) { @machines.fire_events(@object, :ignite, :invalid) }
151
+ end
152
+
153
+ it 'should_fail_if_any_event_cannot_transition' do
154
+ assert !@machines.fire_events(@object, :park, :disable_alarm)
155
+ assert_equal 'parked', @object.state
156
+ assert_equal 'active', @object.alarm_state
157
+ assert !@object.saved
158
+
159
+ assert !@machines.fire_events(@object, :ignite, :enable_alarm)
160
+ assert_equal 'parked', @object.state
161
+ assert_equal 'active', @object.alarm_state
162
+ assert !@object.saved
163
+ end
164
+
165
+ it 'should_run_failure_callbacks_if_any_event_cannot_transition' do
166
+ @state_failure_run = @alarm_state_failure_run = false
167
+
168
+ @machines[:state].after_failure { @state_failure_run = true }
169
+ @machines[:alarm_state].after_failure { @alarm_state_failure_run = true }
170
+
171
+ assert !@machines.fire_events(@object, :park, :disable_alarm)
172
+ assert @state_failure_run
173
+ assert !@alarm_state_failure_run
174
+ end
175
+
176
+ it 'should_be_successful_if_all_events_transition' do
177
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
178
+ assert_equal 'idling', @object.state
179
+ assert_equal 'off', @object.alarm_state
180
+ assert @object.saved
181
+ end
182
+
183
+ it 'should_not_save_if_skipping_action' do
184
+ assert @machines.fire_events(@object, :ignite, :disable_alarm, false)
185
+ assert_equal 'idling', @object.state
186
+ assert_equal 'off', @object.alarm_state
187
+ assert !@object.saved
188
+ end
189
+ end
190
+
191
+ context 'FireWithTransactions' do
192
+ before(:each) do
193
+ @machines = StateMachines::MachineCollection.new
194
+
195
+ @klass = Class.new do
196
+ attr_accessor :allow_save
197
+
198
+ def save
199
+ @allow_save
200
+ end
201
+ end
202
+
203
+ StateMachines::Integrations.const_set('Custom', Module.new do
204
+ include StateMachines::Integrations::Base
205
+
206
+ attr_reader :rolled_back
207
+
208
+ def transaction(object)
209
+ @rolled_back = yield
210
+ end
211
+ end)
212
+
213
+ # First machine
214
+ @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save, integration: :custom)
215
+ @state.event :ignite do
216
+ transition parked: :idling
217
+ end
218
+
219
+ # Second machine
220
+ @machines[:alarm_state] = @alarm_state = StateMachines::Machine.new(@klass, :alarm_state, initial: :active, action: :save, namespace: 'alarm', integration: :custom)
221
+ @alarm_state.event :disable do
222
+ transition active: :off
223
+ end
224
+
225
+ @object = @klass.new
226
+ end
227
+
228
+ it 'should_not_rollback_if_successful' do
229
+ @object.allow_save = true
230
+
231
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
232
+ assert_equal true, @state.rolled_back
233
+ assert_nil @alarm_state.rolled_back
234
+ assert_equal 'idling', @object.state
235
+ assert_equal 'off', @object.alarm_state
236
+ end
237
+
238
+ it 'should_rollback_if_not_successful' do
239
+ @object.allow_save = false
240
+
241
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
242
+ assert_equal false, @state.rolled_back
243
+ assert_nil @alarm_state.rolled_back
244
+ assert_equal 'parked', @object.state
245
+ assert_equal 'active', @object.alarm_state
246
+ end
247
+
248
+ it 'should_run_failure_callbacks_if_not_successful' do
249
+ @object.allow_save = false
250
+ @state_failure_run = @alarm_state_failure_run = false
251
+
252
+ @machines[:state].after_failure { @state_failure_run = true }
253
+ @machines[:alarm_state].after_failure { @alarm_state_failure_run = true }
254
+
255
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
256
+ assert @state_failure_run
257
+ assert @alarm_state_failure_run
258
+ end
259
+
260
+ after(:each) do
261
+ StateMachines::Integrations.send(:remove_const, 'Custom')
262
+ StateMachines::Integrations.send(:reset)
263
+ end
264
+ end
265
+
266
+ context 'FireWithValidations' do
267
+ before(:each) do
268
+ StateMachines::Integrations.const_set('Custom', Module.new do
269
+ include StateMachines::Integrations::Base
270
+
271
+ def invalidate(object, attribute, message, values = [])
272
+ (object.errors ||= []) << generate_message(message, values)
273
+ end
274
+
275
+ def reset(object)
276
+ object.errors = []
277
+ end
278
+ end)
279
+
280
+ @klass = Class.new do
281
+ attr_accessor :errors
282
+
283
+ def initialize
284
+ @errors = []
285
+ super
286
+ end
287
+ end
288
+
289
+ @machines = StateMachines::MachineCollection.new
290
+ @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, integration: :custom)
291
+ @state.event :ignite do
292
+ transition parked: :idling
293
+ end
294
+
295
+ @machines[:alarm_state] = @alarm_state = StateMachines::Machine.new(@klass, :alarm_state, initial: :active, namespace: 'alarm', integration: :custom)
296
+ @alarm_state.event :disable do
297
+ transition active: :off
298
+ end
299
+
300
+ @object = @klass.new
301
+ end
302
+
303
+ it 'should_not_invalidate_if_transitions_exist' do
304
+ assert @machines.fire_events(@object, :ignite, :disable_alarm)
305
+ assert_equal [], @object.errors
306
+ end
307
+
308
+ it 'should_invalidate_if_no_transitions_exist' do
309
+ @object.state = 'idling'
310
+ @object.alarm_state = 'off'
311
+
312
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
313
+ assert_equal ['cannot transition via "ignite"', 'cannot transition via "disable"'], @object.errors
314
+ end
315
+
316
+ it 'should_run_failure_callbacks_if_no_transitions_exist' do
317
+ @object.state = 'idling'
318
+ @object.alarm_state = 'off'
319
+ @state_failure_run = @alarm_state_failure_run = false
320
+
321
+ @machines[:state].after_failure { @state_failure_run = true }
322
+ @machines[:alarm_state].after_failure { @alarm_state_failure_run = true }
323
+
324
+ assert !@machines.fire_events(@object, :ignite, :disable_alarm)
325
+ assert @state_failure_run
326
+ assert @alarm_state_failure_run
327
+ end
328
+
329
+ after(:each) do
330
+ StateMachines::Integrations.send(:remove_const, 'Custom')
331
+ StateMachines::Integrations.send(:reset)
332
+ end
333
+ end
334
+
335
+ context 'TransitionsWithoutEvents' do
336
+ before(:each) do
337
+ @klass = Class.new
338
+
339
+ @machines = StateMachines::MachineCollection.new
340
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
341
+ @machine.event :ignite do
342
+ transition parked: :idling
343
+ end
344
+
345
+ @object = @klass.new
346
+ @object.state_event = nil
347
+ @transitions = @machines.transitions(@object, :save)
348
+ end
349
+
350
+ it 'should_be_empty' do
351
+ assert @transitions.empty?
352
+ end
353
+
354
+ it 'should_perform' do
355
+ assert_equal true, @transitions.perform
356
+ end
357
+ end
358
+
359
+ context 'TransitionsWithBlankEvents' do
360
+ before(:each) do
361
+ @klass = Class.new
362
+
363
+ @machines = StateMachines::MachineCollection.new
364
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
365
+ @machine.event :ignite do
366
+ transition parked: :idling
367
+ end
368
+
369
+ @object = @klass.new
370
+ @object.state_event = ''
371
+ @transitions = @machines.transitions(@object, :save)
372
+ end
373
+
374
+ it 'should_be_empty' do
375
+ assert @transitions.empty?
376
+ end
377
+
378
+ it 'should_perform' do
379
+ assert_equal true, @transitions.perform
380
+ end
381
+ end
382
+
383
+ context 'TransitionsWithInvalidEvents' do
384
+ before(:each) do
385
+ @klass = Class.new
386
+
387
+ @machines = StateMachines::MachineCollection.new
388
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
389
+ @machine.event :ignite do
390
+ transition parked: :idling
391
+ end
392
+
393
+ @object = @klass.new
394
+ @object.state_event = 'invalid'
395
+ @transitions = @machines.transitions(@object, :save)
396
+ end
397
+
398
+ it 'should_be_empty' do
399
+ assert @transitions.empty?
400
+ end
401
+
402
+ it 'should_not_perform' do
403
+ assert_equal false, @transitions.perform
404
+ end
405
+ end
406
+
407
+ context 'TransitionsWithoutTransition' do
408
+ before(:each) do
409
+ @klass = Class.new
410
+
411
+ @machines = StateMachines::MachineCollection.new
412
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
413
+ @machine.event :ignite do
414
+ transition parked: :idling
415
+ end
416
+
417
+ @object = @klass.new
418
+ @object.state = 'idling'
419
+ @object.state_event = 'ignite'
420
+ @transitions = @machines.transitions(@object, :save)
421
+ end
422
+
423
+ it 'should_be_empty' do
424
+ assert @transitions.empty?
425
+ end
426
+
427
+ it 'should_not_perform' do
428
+ assert_equal false, @transitions.perform
429
+ end
430
+ end
431
+
432
+ context 'TransitionsWithTransition' do
433
+ before(:each) do
434
+ @klass = Class.new
435
+
436
+ @machines = StateMachines::MachineCollection.new
437
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
438
+ @machine.event :ignite do
439
+ transition parked: :idling
440
+ end
441
+
442
+ @object = @klass.new
443
+ @object.state_event = 'ignite'
444
+ @transitions = @machines.transitions(@object, :save)
445
+ end
446
+
447
+ it 'should_not_be_empty' do
448
+ assert_equal 1, @transitions.length
449
+ end
450
+
451
+ it 'should_perform' do
452
+ assert_equal true, @transitions.perform
453
+ end
454
+ end
455
+
456
+ context 'TransitionsWithSameActions' do
457
+ before(:each) do
458
+ @klass = Class.new
459
+
460
+ @machines = StateMachines::MachineCollection.new
461
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
462
+ @machine.event :ignite do
463
+ transition parked: :idling
464
+ end
465
+ @machines[:status] = @machine = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :save)
466
+ @machine.event :shift_up do
467
+ transition first_gear: :second_gear
468
+ end
469
+
470
+ @object = @klass.new
471
+ @object.state_event = 'ignite'
472
+ @object.status_event = 'shift_up'
473
+ @transitions = @machines.transitions(@object, :save)
474
+ end
475
+
476
+ it 'should_not_be_empty' do
477
+ assert_equal 2, @transitions.length
478
+ end
479
+
480
+ it 'should_perform' do
481
+ assert_equal true, @transitions.perform
482
+ end
483
+ end
484
+
485
+ context 'TransitionsWithDifferentActions' do
486
+ before(:each) do
487
+ @klass = Class.new
488
+
489
+ @machines = StateMachines::MachineCollection.new
490
+ @machines[:state] = @state = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
491
+ @state.event :ignite do
492
+ transition parked: :idling
493
+ end
494
+ @machines[:status] = @status = StateMachines::Machine.new(@klass, :status, initial: :first_gear, action: :persist)
495
+ @status.event :shift_up do
496
+ transition first_gear: :second_gear
497
+ end
498
+
499
+ @object = @klass.new
500
+ @object.state_event = 'ignite'
501
+ @object.status_event = 'shift_up'
502
+ @transitions = @machines.transitions(@object, :save)
503
+ end
504
+
505
+ it 'should_only_select_matching_actions' do
506
+ assert_equal 1, @transitions.length
507
+ end
508
+ end
509
+
510
+ context 'TransitionsWithExisitingTransitions' do
511
+ before(:each) do
512
+ @klass = Class.new
513
+
514
+ @machines = StateMachines::MachineCollection.new
515
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
516
+ @machine.event :ignite do
517
+ transition parked: :idling
518
+ end
519
+
520
+ @object = @klass.new
521
+ @object.send(:state_event_transition=, StateMachines::Transition.new(@object, @machine, :ignite, :parked, :idling))
522
+ @transitions = @machines.transitions(@object, :save)
523
+ end
524
+
525
+ it 'should_not_be_empty' do
526
+ assert_equal 1, @transitions.length
527
+ end
528
+
529
+ it 'should_perform' do
530
+ assert_equal true, @transitions.perform
531
+ end
532
+ end
533
+
534
+ context 'TransitionsWithCustomOptions' do
535
+ before(:each) do
536
+ @klass = Class.new
537
+
538
+ @machines = StateMachines::MachineCollection.new
539
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
540
+ @machine.event :ignite do
541
+ transition parked: :idling
542
+ end
543
+
544
+ @object = @klass.new
545
+ @transitions = @machines.transitions(@object, :save, after: false)
546
+ end
547
+
548
+ it 'should_use_custom_options' do
549
+ assert @transitions.skip_after
550
+ end
551
+ end
552
+
553
+ context 'FireAttributesWithValidations' do
554
+ before(:each) do
555
+ @klass = Class.new do
556
+ attr_accessor :errors
557
+
558
+ def initialize
559
+ @errors = []
560
+ super
561
+ end
562
+ end
563
+
564
+ @machines = StateMachines::MachineCollection.new
565
+ @machines[:state] = @machine = StateMachines::Machine.new(@klass, :state, initial: :parked, action: :save)
566
+ @machine.event :ignite do
567
+ transition parked: :idling
568
+ end
569
+
570
+ class << @machine
571
+ def invalidate(object, attribute, message, values = [])
572
+ (object.errors ||= []) << generate_message(message, values)
573
+ end
574
+
575
+ def reset(object)
576
+ object.errors = []
577
+ end
578
+ end
579
+
580
+ @object = @klass.new
581
+ end
582
+
583
+ it 'should_invalidate_if_event_is_invalid' do
584
+ @object.state_event = 'invalid'
585
+ @machines.transitions(@object, :save)
586
+
587
+ assert !@object.errors.empty?
588
+ end
589
+
590
+ it 'should_invalidate_if_no_transition_exists' do
591
+ @object.state = 'idling'
592
+ @object.state_event = 'ignite'
593
+ @machines.transitions(@object, :save)
594
+
595
+ assert !@object.errors.empty?
596
+ end
597
+
598
+ it 'should_not_invalidate_if_transition_exists' do
599
+ @object.state_event = 'ignite'
600
+ @machines.transitions(@object, :save)
601
+
602
+ assert @object.errors.empty?
603
+ end
604
+ end
605
+
606
+ end