state_machine_rspec 0.1.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.
@@ -0,0 +1,327 @@
1
+ require 'spec_helper'
2
+ require 'integration/models/vehicle'
3
+
4
+ describe Vehicle do
5
+ let(:vehicle) { Vehicle.new }
6
+ subject { vehicle }
7
+
8
+ its(:passed_inspection?) { should be_false }
9
+
10
+ shared_examples 'crashable' do
11
+ describe 'crash' do
12
+ context 'having passed inspection' do
13
+ before { vehicle.stub(:passed_inspection).and_return(true) }
14
+ pending 'keeps running' do
15
+ initial_state = vehicle.state
16
+ vehicle.crash!
17
+
18
+ vehicle.state.should eq initial_state
19
+ end
20
+ end
21
+
22
+ context 'not having passed inspection' do
23
+ before { vehicle.stub(:passed_inspection).and_return(false) }
24
+ it 'stalls' do
25
+ vehicle.crash!
26
+ vehicle.state.should eq :stalled.to_s
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ shared_examples 'speedless' do
33
+ it 'does not respond to speed' do
34
+ expect { vehicle.speed }.to raise_error NoMethodError
35
+ end
36
+ end
37
+
38
+ describe '#initialize' do
39
+ its(:seatbelt_on) { should be_false }
40
+ its(:time_used) { should eq 0 }
41
+ its(:auto_shop_busy) { should be_true }
42
+ end
43
+
44
+ describe '#put_on_seatbelt' do
45
+ it 'sets seatbelt_on to true' do
46
+ vehicle.seatbelt_on = false
47
+ vehicle.put_on_seatbelt
48
+
49
+ vehicle.seatbelt_on.should be_true
50
+ end
51
+ end
52
+
53
+ describe 'state machine' do
54
+ it { should have_states :parked, :idling, :stalled, :first_gear,
55
+ :second_gear, :third_gear }
56
+ it { should reject_state :flying }
57
+
58
+ it { should handle_event :ignite, when: :parked }
59
+ it { should reject_events :park, :idle, :shift_up,
60
+ :shift_down, :crash, :repair,
61
+ when: :parked }
62
+
63
+ it { should handle_events :park, :shift_up, :crash, when: :idling }
64
+ it { should reject_events :ignite, :idle, :shift_down, :repair,
65
+ when: :idling }
66
+
67
+ it { should handle_events :ignite, :repair, when: :stalled }
68
+ it { should reject_events :park, :idle, :shift_up, :shift_down, :crash,
69
+ when: :stalled }
70
+
71
+ it { should handle_events :park, :idle, :shift_up, :crash,
72
+ when: :first_gear }
73
+ it { should reject_events :ignite, :shift_down, :repair,
74
+ when: :first_gear }
75
+
76
+ it { should handle_events :shift_up, :shift_down, :crash,
77
+ when: :second_gear }
78
+ it { should reject_events :park, :ignite, :idle, :repair,
79
+ when: :second_gear }
80
+
81
+ it { should handle_events :shift_down, :crash, when: :third_gear }
82
+ it { should reject_events :park, :ignite, :idle, :shift_up, :repair,
83
+ when: :third_gear }
84
+
85
+ it 'has an initial state of "parked"' do
86
+ vehicle.should be_parked
87
+ end
88
+
89
+ it 'has an initial alarm state of "active"' do
90
+ vehicle.alarm_active?.should be_true
91
+ end
92
+
93
+ describe 'around transitions' do
94
+ it 'updates the time used' do
95
+ vehicle.should_receive(:time_used=).with(0)
96
+ Timecop.freeze { vehicle.ignite! }
97
+ end
98
+ end
99
+
100
+ context 'when parked' do
101
+ before { vehicle.state = :parked.to_s }
102
+
103
+ its(:speed) { should be_zero }
104
+ it { should_not be_moving }
105
+
106
+ describe 'before transitions' do
107
+ it 'puts on a seatbelt' do
108
+ vehicle.should_receive :put_on_seatbelt
109
+ vehicle.ignite!
110
+ end
111
+ end
112
+
113
+ describe 'ignite' do
114
+ it 'should transition to idling' do
115
+ vehicle.ignite!
116
+ vehicle.should be_idling
117
+ end
118
+ end
119
+ end
120
+
121
+ context 'when transitioning to parked' do
122
+ before { vehicle.state = :idling.to_s }
123
+ it 'removes seatbelts' do
124
+ vehicle.should_receive(:seatbelt_on=).with(false)
125
+ vehicle.park!
126
+ end
127
+ end
128
+
129
+ context 'when idling' do
130
+ before { vehicle.state = :idling.to_s }
131
+
132
+ its(:speed) { should eq 10 }
133
+ it { should_not be_moving }
134
+
135
+ describe 'park' do
136
+ it 'should transition to a parked state' do
137
+ vehicle.park!
138
+ vehicle.should be_parked
139
+ end
140
+ end
141
+
142
+ describe 'shift up' do
143
+ it 'should shift into first gear' do
144
+ vehicle.shift_up!
145
+ vehicle.should be_first_gear
146
+ end
147
+ end
148
+
149
+ it_behaves_like 'crashable'
150
+ end
151
+
152
+ context 'when stalled' do
153
+ before { vehicle.state = :stalled.to_s }
154
+
155
+ it { should_not be_moving }
156
+ it_behaves_like 'speedless'
157
+
158
+ describe 'ignite' do
159
+ it 'remains stalled' do
160
+ vehicle.ignite!
161
+ vehicle.should be_stalled
162
+ end
163
+ end
164
+
165
+ describe 'repair' do
166
+ context 'the auto shop is busy' do
167
+ before { vehicle.stub(:auto_shop_busy).and_return(true) }
168
+ it 'remains stalled' do
169
+ vehicle.repair!
170
+ vehicle.should be_stalled
171
+ end
172
+ end
173
+
174
+ context 'the auto shop is not busy' do
175
+ before { vehicle.stub(:auto_shop_busy).and_return(false) }
176
+ it 'is parked' do
177
+ vehicle.repair!
178
+ vehicle.should be_parked
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ context 'when in first gear' do
185
+ before { vehicle.state = :first_gear.to_s }
186
+
187
+ its(:speed) { should eq 10 }
188
+ it { should be_moving }
189
+
190
+ describe 'park' do
191
+ it 'parks' do
192
+ vehicle.park!
193
+ vehicle.should be_parked
194
+ end
195
+ end
196
+
197
+ describe 'idle' do
198
+ it 'idles' do
199
+ vehicle.idle!
200
+ vehicle.should be_idling
201
+ end
202
+ end
203
+
204
+ describe 'shift up' do
205
+ it 'shift into second gear' do
206
+ vehicle.shift_up!
207
+ vehicle.should be_second_gear
208
+ end
209
+ end
210
+
211
+ it_behaves_like 'crashable'
212
+ end
213
+
214
+ context 'when in second gear' do
215
+ before { vehicle.state = :second_gear.to_s }
216
+
217
+ it { should be_moving }
218
+ it_behaves_like 'speedless'
219
+
220
+ describe 'shift up' do
221
+ it 'shifts into third gear' do
222
+ vehicle.shift_up!
223
+ vehicle.should be_third_gear
224
+ end
225
+ end
226
+
227
+ describe 'shift down' do
228
+ it 'shifts back into first gear' do
229
+ vehicle.shift_down!
230
+ vehicle.should be_first_gear
231
+ end
232
+ end
233
+
234
+ it_behaves_like 'crashable'
235
+ end
236
+
237
+ context 'when in third gear' do
238
+ before { vehicle.state = :third_gear.to_s }
239
+
240
+ it { should be_moving }
241
+ it_behaves_like 'speedless'
242
+
243
+ describe 'shift down' do
244
+ it 'shifts back into second gear' do
245
+ vehicle.shift_down!
246
+ vehicle.should be_second_gear
247
+ end
248
+ end
249
+
250
+ it_behaves_like 'crashable'
251
+ end
252
+
253
+ context 'on ignition' do
254
+ context 'when it fails' do
255
+ before { vehicle.stub(:ignite).and_return(false) }
256
+ pending 'logs the failure' do
257
+ vehicle.should_receive(:log_start_failure)
258
+ vehicle.ignite
259
+ end
260
+ end
261
+ end
262
+
263
+ context 'on a crash' do
264
+ before { vehicle.state = :third_gear.to_s }
265
+ it 'gets towed' do
266
+ vehicle.should_receive(:tow)
267
+ vehicle.crash!
268
+ end
269
+ end
270
+
271
+ context 'upon being repaired' do
272
+ before { vehicle.state = :stalled.to_s }
273
+ it 'gets fixed' do
274
+ vehicle.should_receive(:fix)
275
+ vehicle.repair!
276
+ end
277
+ end
278
+ end
279
+
280
+ describe 'alarm state machine' do
281
+ it { should have_state :active, on: :alarm_state, value: 1 }
282
+ it { should have_state :off, on: :alarm_state, value: 0 }
283
+ it { should reject_states :broken, :ringing, on: :alarm_state }
284
+
285
+ it { should handle_events :enable_alarm, :disable_alarm,
286
+ when: :active, state: :alarm_state }
287
+ it { should handle_events :enable_alarm, :disable_alarm,
288
+ when: :off, state: :alarm_state }
289
+
290
+ it 'has an initial state of activated' do
291
+ vehicle.alarm_active?.should be_true
292
+ end
293
+
294
+ context 'when active' do
295
+ describe 'enable' do
296
+ it 'becomes active' do
297
+ vehicle.enable_alarm!
298
+ vehicle.alarm_active?.should be_true
299
+ end
300
+ end
301
+
302
+ describe 'disable' do
303
+ it 'turns the alarm off' do
304
+ vehicle.disable_alarm!
305
+ vehicle.alarm_off?.should be_true
306
+ end
307
+ end
308
+ end
309
+
310
+ context 'when off' do
311
+ before { vehicle.alarm_state = 0 }
312
+ describe 'enable' do
313
+ it 'becomes active' do
314
+ vehicle.enable_alarm!
315
+ vehicle.alarm_active?.should be_true
316
+ end
317
+ end
318
+
319
+ describe 'disable' do
320
+ it 'turns the alarm off' do
321
+ vehicle.disable_alarm!
322
+ vehicle.alarm_off?.should be_true
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ class Vehicle
4
+ attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
5
+
6
+ state_machine :state, :initial => :parked do
7
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
8
+
9
+ after_transition :on => :crash, :do => :tow
10
+ after_transition :on => :repair, :do => :fix
11
+ after_transition any => :parked do |vehicle, transition|
12
+ vehicle.seatbelt_on = false
13
+ end
14
+
15
+ after_failure :on => :ignite, :do => :log_start_failure
16
+
17
+ around_transition do |vehicle, transition, block|
18
+ start = Time.now
19
+ block.call
20
+ vehicle.time_used += Time.now - start
21
+ end
22
+
23
+ event :park do
24
+ transition [:idling, :first_gear] => :parked
25
+ end
26
+
27
+ event :ignite do
28
+ transition :stalled => same, :parked => :idling
29
+ end
30
+
31
+ event :idle do
32
+ transition :first_gear => :idling
33
+ end
34
+
35
+ event :shift_up do
36
+ transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
37
+ end
38
+
39
+ event :shift_down do
40
+ transition :third_gear => :second_gear, :second_gear => :first_gear
41
+ end
42
+
43
+ event :crash do
44
+ transition all - [:parked, :stalled] => :stalled, :if => lambda {|vehicle| !vehicle.passed_inspection?}
45
+ end
46
+
47
+ event :repair do
48
+ # The first transition that matches the state and passes its conditions
49
+ # will be used
50
+ transition :stalled => :parked, :unless => :auto_shop_busy
51
+ transition :stalled => same
52
+ end
53
+
54
+ state :parked do
55
+ def speed
56
+ 0
57
+ end
58
+ end
59
+
60
+ state :idling, :first_gear do
61
+ def speed
62
+ 10
63
+ end
64
+ end
65
+
66
+ state all - [:parked, :stalled, :idling] do
67
+ def moving?
68
+ true
69
+ end
70
+ end
71
+
72
+ state :parked, :stalled, :idling do
73
+ def moving?
74
+ false
75
+ end
76
+ end
77
+ end
78
+
79
+ state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
80
+ event :enable do
81
+ transition all => :active
82
+ end
83
+
84
+ event :disable do
85
+ transition all => :off
86
+ end
87
+
88
+ state :active, :value => 1
89
+ state :off, :value => 0
90
+ end
91
+
92
+ def initialize
93
+ @seatbelt_on = false
94
+ @time_used = 0
95
+ @auto_shop_busy = true
96
+ super() # NOTE: This *must* be called, otherwise states won't get initialized
97
+ end
98
+
99
+ def put_on_seatbelt
100
+ @seatbelt_on = true
101
+ end
102
+
103
+ def passed_inspection?
104
+ false
105
+ end
106
+
107
+ def tow
108
+ # tow the vehicle
109
+ end
110
+
111
+ def fix
112
+ # get the vehicle fixed by a mechanic
113
+ end
114
+
115
+ def log_start_failure
116
+ # log a failed attempt to start the vehicle
117
+ end
118
+ end
119
+
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachineRspec::Matchers::HandleEventMatcher do
4
+ describe '#matches?' do
5
+ context 'when :when state is specified' do
6
+ context 'but the state doesn\'t exist' do
7
+ before do
8
+ matcher_class = Class.new do
9
+ state_machine :state, initial: :mathy
10
+ end
11
+ @matcher_subject = matcher_class.new
12
+ @matcher = described_class.new([when: :artsy])
13
+ end
14
+
15
+ it 'raises' do
16
+ expect { @matcher.matches? @matcher_subject }.
17
+ to raise_error StateMachineIntrospectorError
18
+ end
19
+ end
20
+
21
+ context 'and that state exists' do
22
+ before do
23
+ matcher_class = Class.new do
24
+ state_machine :state, initial: :mathy do
25
+ state :artsy
26
+ end
27
+ end
28
+ @matcher_subject = matcher_class.new
29
+ @matcher = described_class.new([when: :artsy])
30
+ end
31
+
32
+ it 'sets the state' do
33
+ @matcher.matches? @matcher_subject
34
+ @matcher_subject.state.should eq 'artsy'
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'when subject can perform events' do
40
+ before do
41
+ matcher_class = Class.new do
42
+ state_machine :state, initial: :mathy do
43
+ event(:mathematize) { transition any => same }
44
+ end
45
+ end
46
+ @matcher_subject = matcher_class.new
47
+ @matcher = described_class.new([:mathematize])
48
+ end
49
+
50
+ it 'does not set a failure message' do
51
+ @matcher.matches? @matcher_subject
52
+ @matcher.failure_message.should be_nil
53
+ end
54
+ it 'returns true' do
55
+ @matcher.matches?(@matcher_subject).should be_true
56
+ end
57
+ end
58
+
59
+ context 'when subject cannot perform events' do
60
+ before do
61
+ matcher_class = Class.new do
62
+ state_machine :state, initial: :mathy do
63
+ state :polynomial
64
+
65
+ event(:mathematize) { transition any => same }
66
+ event(:algebraify) { transition :polynomial => same }
67
+ event(:trigonomalize) { transition :trigonomalize => same }
68
+ end
69
+ end
70
+ @matcher_subject = matcher_class.new
71
+ end
72
+
73
+ context 'because it cannot perform the transition' do
74
+ before do
75
+ @matcher = described_class.new([:mathematize, :algebraify, :trigonomalize])
76
+ end
77
+
78
+ it 'sets a failure message' do
79
+ @matcher.matches? @matcher_subject
80
+ @matcher.failure_message.
81
+ should eq 'Expected to be able to handle events: algebraify, trigonomalize ' +
82
+ 'in state: mathy'
83
+ end
84
+ it 'returns false' do
85
+ @matcher.matches?(@matcher_subject).should be_false
86
+ end
87
+ end
88
+
89
+ context 'because no such events exist' do
90
+ before do
91
+ @matcher = described_class.new([:polynomialize, :eulerasterize])
92
+ end
93
+
94
+ it 'does not raise' do
95
+ expect { @matcher.matches?(@matcher_subject) }.to_not raise_error
96
+ end
97
+ it 'sets a failure message' do
98
+ @matcher.matches? @matcher_subject
99
+ @matcher.failure_message.
100
+ should eq 'state_machine: state does not ' +
101
+ 'define events: polynomialize, eulerasterize'
102
+ end
103
+ it 'returns false' do
104
+ @matcher.matches?(@matcher_subject).should be_false
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachineRspec::Matchers::RejectEventMatcher do
4
+ describe '#matches?' do
5
+ context 'when :when state is specified' do
6
+ context 'but that state doesn\'t exist' do
7
+ before do
8
+ matcher_class = Class.new do
9
+ state_machine :state, initial: :sleazy
10
+ end
11
+ @matcher_subject = matcher_class.new
12
+ @matcher = described_class.new([when: :sneezy])
13
+ end
14
+
15
+ it 'raises' do
16
+ expect { @matcher.matches? @matcher_subject }.
17
+ to raise_error StateMachineIntrospectorError
18
+ end
19
+ end
20
+
21
+ context 'and that state exists' do
22
+ before do
23
+ matcher_class = Class.new do
24
+ state_machine :state, initial: :sleazy do
25
+ state :sneezy
26
+ end
27
+ end
28
+ @matcher_subject = matcher_class.new
29
+ @matcher = described_class.new([when: :sneezy])
30
+ end
31
+
32
+ it 'sets the state' do
33
+ @matcher.matches? @matcher_subject
34
+ @matcher_subject.state.should eq 'sneezy'
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'when an expectation is made on an event that is undefined' do
40
+ before do
41
+ matcher_class = Class.new do
42
+ state_machine :state, initial: :snarky do
43
+ event(:primmadonnalize) { transition any => same }
44
+ end
45
+ end
46
+ @matcher_subject = matcher_class.new
47
+ @matcher = described_class.new([:primmadonnalize, :martinilunchitize])
48
+ end
49
+
50
+ it 'does not raise' do
51
+ expect { @matcher.matches?(@matcher_subject) }.to_not raise_error
52
+ end
53
+ it 'sets a failure message' do
54
+ @matcher.matches? @matcher_subject
55
+ @matcher.failure_message.
56
+ should eq 'state_machine: state does not ' +
57
+ 'define events: martinilunchitize'
58
+ end
59
+ it 'returns false' do
60
+ @matcher.matches?(@matcher_subject).should be_false
61
+ end
62
+ end
63
+
64
+ context 'when subject cannot perform any of the specified events' do
65
+ before do
66
+ matcher_class = Class.new do
67
+ state_machine :state, initial: :snarky do
68
+ state :haughty
69
+ event(:primmadonnalize) { transition :haughty => same }
70
+ end
71
+ end
72
+ @matcher_subject = matcher_class.new
73
+ @matcher = described_class.new([:primmadonnalize])
74
+ end
75
+
76
+ it 'does not set a failure message' do
77
+ @matcher.matches? @matcher_subject
78
+ @matcher.failure_message.should be_nil
79
+ end
80
+ it 'returns true' do
81
+ @matcher.matches?(@matcher_subject).should be_true
82
+ end
83
+ end
84
+
85
+ context 'when subject can perform any one of the specified events' do
86
+ before do
87
+ matcher_class = Class.new do
88
+ state_machine :state, initial: :snarky do
89
+ state :haughty
90
+ event(:primmadonnalize) { transition :haughty => same }
91
+ event(:defer_to_management) { transition any => same }
92
+ end
93
+ end
94
+ @matcher_subject = matcher_class.new
95
+ @matcher = described_class.new([:primmadonnalize, :defer_to_management])
96
+ end
97
+
98
+ it 'sets a failure message' do
99
+ @matcher.matches? @matcher_subject
100
+ @matcher.failure_message.
101
+ should eq 'Did not expect to be able to handle events: defer_to_management ' +
102
+ 'in state: snarky'
103
+ end
104
+ it 'returns false' do
105
+ @matcher.matches?(@matcher_subject).should be_false
106
+ end
107
+ end
108
+ end
109
+ end