state_machines 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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