state_machines_activerecord_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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +52 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +8 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/lib/matchers/events/handle_event.rb +35 -0
- data/lib/matchers/events/matcher.rb +71 -0
- data/lib/matchers/events/reject_event.rb +35 -0
- data/lib/matchers/states/have_state.rb +46 -0
- data/lib/matchers/states/matcher.rb +56 -0
- data/lib/matchers/states/reject_state.rb +34 -0
- data/lib/state_machines_activerecord_rspec/state_machine_introspector.rb +70 -0
- data/lib/state_machines_activerecord_rspec/version.rb +3 -0
- data/lib/state_machines_activerecord_rspec.rb +11 -0
- data/spec/integration/integration_spec.rb +327 -0
- data/spec/integration/models/vehicle.rb +119 -0
- data/spec/matchers/events/handle_event_spec.rb +133 -0
- data/spec/matchers/events/reject_event_spec.rb +133 -0
- data/spec/matchers/states/have_state_spec.rb +136 -0
- data/spec/matchers/states/reject_state_spec.rb +97 -0
- data/spec/spec_helper.rb +12 -0
- data/state_machines_activerecord_rspec.gemspec +32 -0
- metadata +217 -0
@@ -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?) { is_expected.to be_falsey }
|
9
|
+
|
10
|
+
shared_examples 'crashable' do
|
11
|
+
describe 'crash' do
|
12
|
+
context 'having passed inspection' do
|
13
|
+
before { allow(vehicle).to receive_messages(:passed_inspection => true) }
|
14
|
+
pending 'keeps running' do
|
15
|
+
initial_state = vehicle.state
|
16
|
+
vehicle.crash!
|
17
|
+
|
18
|
+
expect(vehicle.state).to eq initial_state
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'not having passed inspection' do
|
23
|
+
before { allow(vehicle).to receive_messages(:passed_inspection => false) }
|
24
|
+
it 'stalls' do
|
25
|
+
vehicle.crash!
|
26
|
+
expect(vehicle.state).to 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 StateMachines::InvalidContext
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#initialize' do
|
39
|
+
its(:seatbelt_on) { is_expected.to be_falsey }
|
40
|
+
its(:time_used) { is_expected.to eq 0 }
|
41
|
+
its(:auto_shop_busy) { is_expected.to be_truthy }
|
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
|
+
expect(vehicle.seatbelt_on).to be_truthy
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'state machine' do
|
54
|
+
it { is_expected.to have_states :parked, :idling, :stalled, :first_gear,
|
55
|
+
:second_gear, :third_gear }
|
56
|
+
it { is_expected.to reject_state :flying }
|
57
|
+
|
58
|
+
it { is_expected.to handle_event :ignite, when: :parked }
|
59
|
+
it { is_expected.to reject_events :park, :idle, :shift_up,
|
60
|
+
:shift_down, :crash, :repair,
|
61
|
+
when: :parked }
|
62
|
+
|
63
|
+
it { is_expected.to handle_events :park, :shift_up, :crash, when: :idling }
|
64
|
+
it { is_expected.to reject_events :ignite, :idle, :shift_down, :repair,
|
65
|
+
when: :idling }
|
66
|
+
|
67
|
+
it { is_expected.to handle_events :ignite, :repair, when: :stalled }
|
68
|
+
it { is_expected.to reject_events :park, :idle, :shift_up, :shift_down, :crash,
|
69
|
+
when: :stalled }
|
70
|
+
|
71
|
+
it { is_expected.to handle_events :park, :idle, :shift_up, :crash,
|
72
|
+
when: :first_gear }
|
73
|
+
it { is_expected.to reject_events :ignite, :shift_down, :repair,
|
74
|
+
when: :first_gear }
|
75
|
+
|
76
|
+
it { is_expected.to handle_events :shift_up, :shift_down, :crash,
|
77
|
+
when: :second_gear }
|
78
|
+
it { is_expected.to reject_events :park, :ignite, :idle, :repair,
|
79
|
+
when: :second_gear }
|
80
|
+
|
81
|
+
it { is_expected.to handle_events :shift_down, :crash, when: :third_gear }
|
82
|
+
it { is_expected.to reject_events :park, :ignite, :idle, :shift_up, :repair,
|
83
|
+
when: :third_gear }
|
84
|
+
|
85
|
+
it 'has an initial state of "parked"' do
|
86
|
+
expect(vehicle).to be_parked
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'has an initial alarm state of "active"' do
|
90
|
+
expect(vehicle.alarm_active?).to be_truthy
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'around transitions' do
|
94
|
+
it 'updates the time used' do
|
95
|
+
expect(vehicle).to 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) { is_expected.to be_zero }
|
104
|
+
it { is_expected.not_to be_moving }
|
105
|
+
|
106
|
+
describe 'before transitions' do
|
107
|
+
it 'puts on a seatbelt' do
|
108
|
+
expect(vehicle).to 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
|
+
expect(vehicle).to 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
|
+
expect(vehicle).to 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) { is_expected.to eq 10 }
|
133
|
+
it { is_expected.not_to be_moving }
|
134
|
+
|
135
|
+
describe 'park' do
|
136
|
+
it 'should transition to a parked state' do
|
137
|
+
vehicle.park!
|
138
|
+
expect(vehicle).to 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
|
+
expect(vehicle).to 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 { is_expected.not_to be_moving }
|
156
|
+
it_behaves_like 'speedless'
|
157
|
+
|
158
|
+
describe 'ignite' do
|
159
|
+
it 'remains stalled' do
|
160
|
+
vehicle.ignite!
|
161
|
+
expect(vehicle).to be_stalled
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe 'repair' do
|
166
|
+
context 'the auto shop is busy' do
|
167
|
+
before { allow(vehicle).to receive_messages(:auto_shop_busy => true) }
|
168
|
+
it 'remains stalled' do
|
169
|
+
vehicle.repair!
|
170
|
+
expect(vehicle).to be_stalled
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'the auto shop is not busy' do
|
175
|
+
before { allow(vehicle).to receive_messages(:auto_shop_busy => false) }
|
176
|
+
it 'is parked' do
|
177
|
+
vehicle.repair!
|
178
|
+
expect(vehicle).to 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) { is_expected.to eq 10 }
|
188
|
+
it { is_expected.to be_moving }
|
189
|
+
|
190
|
+
describe 'park' do
|
191
|
+
it 'parks' do
|
192
|
+
vehicle.park!
|
193
|
+
expect(vehicle).to be_parked
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'idle' do
|
198
|
+
it 'idles' do
|
199
|
+
vehicle.idle!
|
200
|
+
expect(vehicle).to be_idling
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe 'shift up' do
|
205
|
+
it 'shift into second gear' do
|
206
|
+
vehicle.shift_up!
|
207
|
+
expect(vehicle).to 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 { is_expected.to 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
|
+
expect(vehicle).to 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
|
+
expect(vehicle).to 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 { is_expected.to 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
|
+
expect(vehicle).to 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 { allow(vehicle).to receive_messages(:ignite => false) }
|
256
|
+
pending 'logs the failure' do
|
257
|
+
expect(vehicle).to 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
|
+
expect(vehicle).to 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
|
+
expect(vehicle).to receive(:fix)
|
275
|
+
vehicle.repair!
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
describe 'alarm state machine' do
|
281
|
+
it { is_expected.to have_state :active, on: :alarm_state, value: 1 }
|
282
|
+
it { is_expected.to have_state :off, on: :alarm_state, value: 0 }
|
283
|
+
it { is_expected.to reject_states :broken, :ringing, on: :alarm_state }
|
284
|
+
|
285
|
+
it { is_expected.to handle_events :enable_alarm, :disable_alarm,
|
286
|
+
when: :active, on: :alarm_state }
|
287
|
+
it { is_expected.to handle_events :enable_alarm, :disable_alarm,
|
288
|
+
when: :off, on: :alarm_state }
|
289
|
+
|
290
|
+
it 'has an initial state of activated' do
|
291
|
+
expect(vehicle.alarm_active?).to be_truthy
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'when active' do
|
295
|
+
describe 'enable' do
|
296
|
+
it 'becomes active' do
|
297
|
+
vehicle.enable_alarm!
|
298
|
+
expect(vehicle.alarm_active?).to be_truthy
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe 'disable' do
|
303
|
+
it 'turns the alarm off' do
|
304
|
+
vehicle.disable_alarm!
|
305
|
+
expect(vehicle.alarm_off?).to be_truthy
|
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
|
+
expect(vehicle.alarm_active?).to be_truthy
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe 'disable' do
|
320
|
+
it 'turns the alarm off' do
|
321
|
+
vehicle.disable_alarm!
|
322
|
+
expect(vehicle.alarm_off?).to be_truthy
|
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,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StateMachinesActiverecordRspec::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
|
+
expect(@matcher_subject.state).to 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 :mathiness, 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, on: :mathiness])
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'does not set a failure message' do
|
51
|
+
@matcher.matches? @matcher_subject
|
52
|
+
expect(@matcher.failure_message).to be_nil
|
53
|
+
end
|
54
|
+
it 'returns true' do
|
55
|
+
expect(@matcher.matches?(@matcher_subject)).to be_truthy
|
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
|
+
expect(@matcher.failure_message).to eq('Expected to be able to handle events: algebraify, trigonomalize ' +
|
81
|
+
'in state: mathy')
|
82
|
+
end
|
83
|
+
it 'returns false' do
|
84
|
+
expect(@matcher.matches?(@matcher_subject)).to be_falsey
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'because no such events exist' do
|
89
|
+
before do
|
90
|
+
@matcher = described_class.new([:polynomialize, :eulerasterize])
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'does not raise' do
|
94
|
+
expect { @matcher.matches?(@matcher_subject) }.not_to raise_error
|
95
|
+
end
|
96
|
+
it 'sets a failure message' do
|
97
|
+
@matcher.matches? @matcher_subject
|
98
|
+
expect(@matcher.failure_message).to eq('state_machine: state does not ' +
|
99
|
+
'define events: polynomialize, eulerasterize')
|
100
|
+
end
|
101
|
+
it 'returns false' do
|
102
|
+
expect(@matcher.matches?(@matcher_subject)).to be_falsey
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#description' do
|
109
|
+
context 'with no options' do
|
110
|
+
let(:matcher) { described_class.new([:placate, :mollify]) }
|
111
|
+
|
112
|
+
it 'returns a string description' do
|
113
|
+
expect(matcher.description).to eq('handle :placate, :mollify')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'when :when state is specified' do
|
118
|
+
let(:matcher) { described_class.new([:destroy_food, when: :hangry]) }
|
119
|
+
|
120
|
+
it 'mentions the requisite state' do
|
121
|
+
expect(matcher.description).to eq('handle :destroy_food when :hangry')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'when :on is specified' do
|
126
|
+
let(:matcher) { described_class.new([:ensmarmify, on: :tired_investors]) }
|
127
|
+
|
128
|
+
it 'mentions the state machine variable' do
|
129
|
+
expect(matcher.description).to eq('handle :ensmarmify on :tired_investors')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StateMachinesActiverecordRspec::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
|
+
expect(@matcher_subject.state).to 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) }.not_to raise_error
|
52
|
+
end
|
53
|
+
it 'sets a failure message' do
|
54
|
+
@matcher.matches? @matcher_subject
|
55
|
+
expect(@matcher.failure_message).to eq('state_machine: state does not ' +
|
56
|
+
'define events: martinilunchitize')
|
57
|
+
end
|
58
|
+
it 'returns false' do
|
59
|
+
expect(@matcher.matches?(@matcher_subject)).to be_falsey
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when subject cannot perform any of the specified events' do
|
64
|
+
before do
|
65
|
+
matcher_class = Class.new do
|
66
|
+
state_machine :state, initial: :snarky do
|
67
|
+
state :haughty
|
68
|
+
event(:primmadonnalize) { transition :haughty => same }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
@matcher_subject = matcher_class.new
|
72
|
+
@matcher = described_class.new([:primmadonnalize])
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'does not set a failure message' do
|
76
|
+
@matcher.matches? @matcher_subject
|
77
|
+
expect(@matcher.failure_message).to be_nil
|
78
|
+
end
|
79
|
+
it 'returns true' do
|
80
|
+
expect(@matcher.matches?(@matcher_subject)).to be_truthy
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when subject can perform any one of the specified events' do
|
85
|
+
before do
|
86
|
+
matcher_class = Class.new do
|
87
|
+
state_machine :state, initial: :snarky do
|
88
|
+
state :haughty
|
89
|
+
event(:primmadonnalize) { transition :haughty => same }
|
90
|
+
event(:defer_to_management) { transition any => same }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@matcher_subject = matcher_class.new
|
94
|
+
@matcher = described_class.new([:primmadonnalize, :defer_to_management])
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'sets a failure message' do
|
98
|
+
@matcher.matches? @matcher_subject
|
99
|
+
expect(@matcher.failure_message).to eq('Did not expect to be able to handle events: defer_to_management ' +
|
100
|
+
'in state: snarky')
|
101
|
+
end
|
102
|
+
it 'returns false' do
|
103
|
+
expect(@matcher.matches?(@matcher_subject)).to be_falsey
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#description' do
|
109
|
+
context 'with no options' do
|
110
|
+
let(:matcher) { described_class.new([:makeadealify, :hustlinate]) }
|
111
|
+
|
112
|
+
it 'returns a string description' do
|
113
|
+
expect(matcher.description).to eq('reject :makeadealify, :hustlinate')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'when :when state is specified' do
|
118
|
+
let(:matcher) { described_class.new([:begargle, when: :sleep_encrusted]) }
|
119
|
+
|
120
|
+
it 'mentions the requisite state' do
|
121
|
+
expect(matcher.description).to eq('reject :begargle when :sleep_encrusted')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'when :on is specified' do
|
126
|
+
let(:matcher) { described_class.new([:harrangue, on: :suspicious_crowd]) }
|
127
|
+
|
128
|
+
it 'mentions the state machine variable' do
|
129
|
+
expect(matcher.description).to eq('reject :harrangue on :suspicious_crowd')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|