statesman 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -2
- data/CHANGELOG.md +6 -0
- data/Guardfile +1 -1
- data/README.md +75 -1
- data/circle.yml +1 -1
- data/lib/statesman/adapters/mongoid.rb +1 -1
- data/lib/statesman/event_transitions.rb +15 -0
- data/lib/statesman/machine.rb +55 -1
- data/lib/statesman/version.rb +1 -1
- data/spec/generators/statesman/active_record_transition_generator_spec.rb +7 -7
- data/spec/generators/statesman/migration_generator_spec.rb +4 -4
- data/spec/generators/statesman/mongoid_transition_generator_spec.rb +6 -6
- data/spec/spec_helper.rb +3 -3
- data/spec/statesman/adapters/active_record_model_spec.rb +6 -6
- data/spec/statesman/adapters/active_record_spec.rb +14 -13
- data/spec/statesman/adapters/active_record_transition_spec.rb +2 -2
- data/spec/statesman/adapters/mongoid_spec.rb +5 -4
- data/spec/statesman/adapters/shared_examples.rb +27 -25
- data/spec/statesman/callback_spec.rb +11 -11
- data/spec/statesman/config_spec.rb +1 -1
- data/spec/statesman/machine_spec.rb +222 -25
- data/spec/support/generators_shared_examples.rb +3 -3
- data/statesman.gemspec +6 -5
- metadata +27 -12
@@ -2,11 +2,11 @@ require "spec_helper"
|
|
2
2
|
require "json"
|
3
3
|
|
4
4
|
describe Statesman::Adapters::ActiveRecordTransition do
|
5
|
-
let(:transition_class) { Class.new }
|
5
|
+
let(:transition_class) { Class.new { def self.serialize(*_args); end } }
|
6
6
|
|
7
7
|
describe "including behaviour" do
|
8
8
|
it "calls Class.serialize" do
|
9
|
-
transition_class.
|
9
|
+
expect(transition_class).to receive(:serialize).with(:metadata, JSON).once
|
10
10
|
transition_class.send(:include, described_class)
|
11
11
|
end
|
12
12
|
end
|
@@ -11,7 +11,7 @@ describe Statesman::Adapters::Mongoid, mongo: true do
|
|
11
11
|
end
|
12
12
|
let(:observer) do
|
13
13
|
result = double(Statesman::Machine)
|
14
|
-
result.
|
14
|
+
allow(result).to receive(:execute)
|
15
15
|
result
|
16
16
|
end
|
17
17
|
let(:model) { MyMongoidModel.create(current_state: :pending) }
|
@@ -20,7 +20,8 @@ describe Statesman::Adapters::Mongoid, mongo: true do
|
|
20
20
|
describe "#initialize" do
|
21
21
|
context "with unserialized metadata" do
|
22
22
|
before do
|
23
|
-
described_class
|
23
|
+
allow_any_instance_of(described_class)
|
24
|
+
.to receive_messages(transition_class_hash_fields: [])
|
24
25
|
end
|
25
26
|
|
26
27
|
it "raises an exception if metadata is not serialized" do
|
@@ -44,8 +45,8 @@ describe Statesman::Adapters::Mongoid, mongo: true do
|
|
44
45
|
end
|
45
46
|
|
46
47
|
it "caches the transition" do
|
47
|
-
MyMongoidModel
|
48
|
-
.
|
48
|
+
expect_any_instance_of(MyMongoidModel)
|
49
|
+
.to receive(:my_mongoid_model_transitions).never
|
49
50
|
adapter.last
|
50
51
|
end
|
51
52
|
|
@@ -14,16 +14,16 @@ require "spec_helper"
|
|
14
14
|
shared_examples_for "an adapter" do |adapter_class, transition_class|
|
15
15
|
let(:observer) do
|
16
16
|
result = double(Statesman::Machine)
|
17
|
-
result.
|
17
|
+
allow(result).to receive(:execute)
|
18
18
|
result
|
19
19
|
end
|
20
20
|
let(:adapter) { adapter_class.new(transition_class, model, observer) }
|
21
21
|
|
22
22
|
describe "#initialize" do
|
23
23
|
subject { adapter }
|
24
|
-
its(:transition_class) {
|
25
|
-
its(:parent_model) {
|
26
|
-
its(:history) {
|
24
|
+
its(:transition_class) { is_expected.to be(transition_class) }
|
25
|
+
its(:parent_model) { is_expected.to be(model) }
|
26
|
+
its(:history) { is_expected.to eq([]) }
|
27
27
|
end
|
28
28
|
|
29
29
|
describe "#create" do
|
@@ -33,32 +33,32 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
33
33
|
let(:create) { adapter.create(from, to) }
|
34
34
|
subject { -> { create } }
|
35
35
|
|
36
|
-
it {
|
36
|
+
it { is_expected.to change(adapter.history, :count).by(1) }
|
37
37
|
|
38
38
|
context "the new transition" do
|
39
39
|
subject { create }
|
40
|
-
it {
|
40
|
+
it { is_expected.to be_a(transition_class) }
|
41
41
|
|
42
42
|
it "should have the initial state" do
|
43
43
|
expect(subject.to_state.to_sym).to eq(to)
|
44
44
|
end
|
45
45
|
|
46
46
|
context "with no previous transition" do
|
47
|
-
its(:sort_key) {
|
47
|
+
its(:sort_key) { is_expected.to be(0) }
|
48
48
|
end
|
49
49
|
|
50
50
|
context "with a previous transition" do
|
51
51
|
before { adapter.create(from, to) }
|
52
|
-
its(:sort_key) {
|
52
|
+
its(:sort_key) { is_expected.to be(10) }
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
context "with before callbacks" do
|
57
57
|
it "is called before the state transition" do
|
58
|
-
observer.
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
expect(observer).to receive(:execute)
|
59
|
+
.with(:before, anything, anything, anything) {
|
60
|
+
expect(adapter.history.length).to eq(0)
|
61
|
+
}.once
|
62
62
|
adapter.create(from, to)
|
63
63
|
expect(adapter.history.length).to eq(1)
|
64
64
|
end
|
@@ -66,20 +66,22 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
66
66
|
|
67
67
|
context "with after callbacks" do
|
68
68
|
it "is called after the state transition" do
|
69
|
-
observer.
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
expect(observer).to receive(:execute)
|
70
|
+
.with(:after, anything, anything, anything) {
|
71
|
+
|_phase, _from_state, _to_state, transition|
|
72
|
+
expect(adapter.last).to eq(transition)
|
73
|
+
}.once
|
73
74
|
adapter.create(from, to)
|
74
75
|
end
|
75
76
|
|
76
77
|
it "exposes the new transition for subsequent transitions" do
|
77
78
|
adapter.create(from, to)
|
78
79
|
|
79
|
-
observer.
|
80
|
-
|
81
|
-
|
82
|
-
|
80
|
+
expect(observer).to receive(:execute)
|
81
|
+
.with(:after, anything, anything, anything) {
|
82
|
+
|_phase, _from_state, _to_state, transition|
|
83
|
+
expect(adapter.last).to eq(transition)
|
84
|
+
}.once
|
83
85
|
adapter.create(to, there)
|
84
86
|
end
|
85
87
|
end
|
@@ -87,22 +89,22 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
87
89
|
context "with metadata" do
|
88
90
|
let(:metadata) { { "some" => "hash" } }
|
89
91
|
subject { adapter.create(from, to, metadata) }
|
90
|
-
its(:metadata) {
|
92
|
+
its(:metadata) { is_expected.to eq(metadata) }
|
91
93
|
end
|
92
94
|
end
|
93
95
|
|
94
96
|
describe "#history" do
|
95
97
|
subject { adapter.history }
|
96
|
-
it {
|
98
|
+
it { is_expected.to eq([]) }
|
97
99
|
|
98
100
|
context "with transitions" do
|
99
101
|
let!(:transition) { adapter.create(:x, :y) }
|
100
|
-
it {
|
102
|
+
it { is_expected.to eq([transition]) }
|
101
103
|
|
102
104
|
context "sorting" do
|
103
105
|
let!(:transition2) { adapter.create(:x, :y) }
|
104
106
|
subject { adapter.history }
|
105
|
-
it {
|
107
|
+
it { is_expected.to eq(adapter.history.sort_by(&:sort_key)) }
|
106
108
|
end
|
107
109
|
end
|
108
110
|
end
|
@@ -114,7 +116,7 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
|
|
114
116
|
end
|
115
117
|
subject { adapter.last }
|
116
118
|
|
117
|
-
it {
|
119
|
+
it { is_expected.to be_a(transition_class) }
|
118
120
|
specify { expect(adapter.last.to_state.to_sym).to eq(:z) }
|
119
121
|
end
|
120
122
|
end
|
@@ -37,12 +37,12 @@ describe Statesman::Callback do
|
|
37
37
|
|
38
38
|
context "and an allowed to value" do
|
39
39
|
let(:to) { :y }
|
40
|
-
it {
|
40
|
+
it { is_expected.to be_truthy }
|
41
41
|
end
|
42
42
|
|
43
43
|
context "and a disallowed to value" do
|
44
44
|
let(:to) { :a }
|
45
|
-
it {
|
45
|
+
it { is_expected.to be_falsey }
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -51,12 +51,12 @@ describe Statesman::Callback do
|
|
51
51
|
|
52
52
|
context "and an allowed 'from' value" do
|
53
53
|
let(:from) { :x }
|
54
|
-
it {
|
54
|
+
it { is_expected.to be_truthy }
|
55
55
|
end
|
56
56
|
|
57
57
|
context "and a disallowed 'from' value" do
|
58
58
|
let(:from) { :a }
|
59
|
-
it {
|
59
|
+
it { is_expected.to be_falsey }
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -67,7 +67,7 @@ describe Statesman::Callback do
|
|
67
67
|
let(:from) { :x }
|
68
68
|
let(:to) { :y }
|
69
69
|
|
70
|
-
it {
|
70
|
+
it { is_expected.to be_truthy }
|
71
71
|
end
|
72
72
|
|
73
73
|
context "with any from value on the callback" do
|
@@ -78,12 +78,12 @@ describe Statesman::Callback do
|
|
78
78
|
|
79
79
|
context "and an allowed to value" do
|
80
80
|
let(:to) { :y }
|
81
|
-
it {
|
81
|
+
it { is_expected.to be_truthy }
|
82
82
|
end
|
83
83
|
|
84
84
|
context "and a disallowed to value" do
|
85
85
|
let(:to) { :a }
|
86
|
-
it {
|
86
|
+
it { is_expected.to be_falsey }
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
@@ -95,25 +95,25 @@ describe Statesman::Callback do
|
|
95
95
|
|
96
96
|
context "and an allowed to value" do
|
97
97
|
let(:from) { :x }
|
98
|
-
it {
|
98
|
+
it { is_expected.to be_truthy }
|
99
99
|
end
|
100
100
|
|
101
101
|
context "and a disallowed to value" do
|
102
102
|
let(:from) { :a }
|
103
|
-
it {
|
103
|
+
it { is_expected.to be_falsey }
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
107
|
context "with allowed 'from' and 'to' values" do
|
108
108
|
let(:from) { :x }
|
109
109
|
let(:to) { :y }
|
110
|
-
it {
|
110
|
+
it { is_expected.to be_truthy }
|
111
111
|
end
|
112
112
|
|
113
113
|
context "with disallowed 'from' and 'to' values" do
|
114
114
|
let(:from) { :a }
|
115
115
|
let(:to) { :b }
|
116
|
-
it {
|
116
|
+
it { is_expected.to be_falsey }
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
@@ -12,7 +12,7 @@ describe Statesman::Config do
|
|
12
12
|
let(:adapter) { Class.new }
|
13
13
|
before { instance.storage_adapter(adapter) }
|
14
14
|
subject { instance.adapter_class }
|
15
|
-
it {
|
15
|
+
it { is_expected.to be(adapter) }
|
16
16
|
|
17
17
|
it "is DSL configurable" do
|
18
18
|
new_adapter = adapter
|
@@ -23,6 +23,80 @@ describe Statesman::Machine do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
describe ".retry_conflicts" do
|
27
|
+
before do
|
28
|
+
machine.class_eval do
|
29
|
+
state :x, initial: true
|
30
|
+
state :y
|
31
|
+
state :z
|
32
|
+
transition from: :x, to: :y
|
33
|
+
transition from: :y, to: :z
|
34
|
+
end
|
35
|
+
end
|
36
|
+
let(:instance) { machine.new(my_model) }
|
37
|
+
let(:retry_attempts) { 2 }
|
38
|
+
|
39
|
+
subject(:transition_state) do
|
40
|
+
Statesman::Machine.retry_conflicts(retry_attempts) do
|
41
|
+
instance.transition_to(:y)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when no exception occurs" do
|
46
|
+
it "runs the transition once" do
|
47
|
+
expect(instance).to receive(:transition_to).once
|
48
|
+
transition_state
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "when an irrelevant exception occurs" do
|
53
|
+
it "runs the transition once" do
|
54
|
+
expect(instance)
|
55
|
+
.to receive(:transition_to).once
|
56
|
+
.and_raise(StandardError)
|
57
|
+
transition_state rescue nil # rubocop:disable RescueModifier
|
58
|
+
end
|
59
|
+
|
60
|
+
it "re-raises the exception" do
|
61
|
+
allow(instance).to receive(:transition_to).once
|
62
|
+
.and_raise(StandardError)
|
63
|
+
expect { transition_state }.to raise_error(StandardError)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when a TransitionConflictError occurs" do
|
68
|
+
context "and is resolved on the second attempt" do
|
69
|
+
it "runs the transition twice" do
|
70
|
+
expect(instance)
|
71
|
+
.to receive(:transition_to).once
|
72
|
+
.and_raise(Statesman::TransitionConflictError)
|
73
|
+
.ordered
|
74
|
+
expect(instance)
|
75
|
+
.to receive(:transition_to).once.ordered.and_call_original
|
76
|
+
transition_state
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "and keeps occurring" do
|
81
|
+
it "runs the transition `retry_attempts + 1` times" do
|
82
|
+
expect(instance)
|
83
|
+
.to receive(:transition_to)
|
84
|
+
.exactly(retry_attempts + 1).times
|
85
|
+
.and_raise(Statesman::TransitionConflictError)
|
86
|
+
transition_state rescue nil # rubocop:disable RescueModifier
|
87
|
+
end
|
88
|
+
|
89
|
+
it "re-raises the conflict" do
|
90
|
+
allow(instance)
|
91
|
+
.to receive(:transition_to)
|
92
|
+
.and_raise(Statesman::TransitionConflictError)
|
93
|
+
expect { transition_state }
|
94
|
+
.to raise_error(Statesman::TransitionConflictError)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
26
100
|
describe ".transition" do
|
27
101
|
before do
|
28
102
|
machine.class_eval do
|
@@ -192,21 +266,21 @@ describe Statesman::Machine do
|
|
192
266
|
|
193
267
|
context "transition class" do
|
194
268
|
it "sets a default" do
|
195
|
-
Statesman.storage_adapter.
|
269
|
+
expect(Statesman.storage_adapter).to receive(:new).once
|
196
270
|
.with(Statesman::Adapters::MemoryTransition, my_model, anything)
|
197
271
|
machine.new(my_model)
|
198
272
|
end
|
199
273
|
|
200
274
|
it "sets the passed class" do
|
201
275
|
my_transition_class = Class.new
|
202
|
-
Statesman.storage_adapter.
|
276
|
+
expect(Statesman.storage_adapter).to receive(:new).once
|
203
277
|
.with(my_transition_class, my_model, anything)
|
204
278
|
machine.new(my_model, transition_class: my_transition_class)
|
205
279
|
end
|
206
280
|
|
207
281
|
it "falls back to Memory without transaction_class" do
|
208
|
-
Statesman.
|
209
|
-
Statesman::Adapters::Memory.
|
282
|
+
allow(Statesman).to receive(:storage_adapter).and_return(Class.new)
|
283
|
+
expect(Statesman::Adapters::Memory).to receive(:new).once
|
210
284
|
.with(Statesman::Adapters::MemoryTransition, my_model, anything)
|
211
285
|
machine.new(my_model)
|
212
286
|
end
|
@@ -238,7 +312,7 @@ describe Statesman::Machine do
|
|
238
312
|
subject { instance.current_state }
|
239
313
|
|
240
314
|
context "with no transitions" do
|
241
|
-
it {
|
315
|
+
it { is_expected.to eq(machine.initial_state) }
|
242
316
|
end
|
243
317
|
|
244
318
|
context "with multiple transitions" do
|
@@ -247,7 +321,7 @@ describe Statesman::Machine do
|
|
247
321
|
instance.transition_to!(:z)
|
248
322
|
end
|
249
323
|
|
250
|
-
it {
|
324
|
+
it { is_expected.to eq("z") }
|
251
325
|
end
|
252
326
|
end
|
253
327
|
|
@@ -266,7 +340,7 @@ describe Statesman::Machine do
|
|
266
340
|
subject { instance.allowed_transitions }
|
267
341
|
|
268
342
|
context "with multiple possible states" do
|
269
|
-
it {
|
343
|
+
it { is_expected.to eq(%w(y z)) }
|
270
344
|
end
|
271
345
|
|
272
346
|
context "with one possible state" do
|
@@ -274,7 +348,7 @@ describe Statesman::Machine do
|
|
274
348
|
instance.transition_to!(:y)
|
275
349
|
end
|
276
350
|
|
277
|
-
it {
|
351
|
+
it { is_expected.to eq(['z']) }
|
278
352
|
end
|
279
353
|
|
280
354
|
context "with no possible transitions" do
|
@@ -282,7 +356,7 @@ describe Statesman::Machine do
|
|
282
356
|
instance.transition_to!(:z)
|
283
357
|
end
|
284
358
|
|
285
|
-
it {
|
359
|
+
it { is_expected.to eq([]) }
|
286
360
|
end
|
287
361
|
end
|
288
362
|
|
@@ -291,7 +365,7 @@ describe Statesman::Machine do
|
|
291
365
|
let(:last_action) { "Whatever" }
|
292
366
|
|
293
367
|
it "delegates to the storage adapter" do
|
294
|
-
Statesman.storage_adapter.
|
368
|
+
expect_any_instance_of(Statesman.storage_adapter).to receive(:last).once
|
295
369
|
.and_return(last_action)
|
296
370
|
expect(instance.last_transition).to be(last_action)
|
297
371
|
end
|
@@ -314,13 +388,13 @@ describe Statesman::Machine do
|
|
314
388
|
context "when the transition is invalid" do
|
315
389
|
context "with an initial to state" do
|
316
390
|
let(:new_state) { :x }
|
317
|
-
it {
|
391
|
+
it { is_expected.to be_falsey }
|
318
392
|
end
|
319
393
|
|
320
394
|
context "with a terminal from state" do
|
321
395
|
before { instance.transition_to!(:y) }
|
322
396
|
let(:new_state) { :y }
|
323
|
-
it {
|
397
|
+
it { is_expected.to be_falsey }
|
324
398
|
end
|
325
399
|
|
326
400
|
context "and is guarded" do
|
@@ -329,19 +403,19 @@ describe Statesman::Machine do
|
|
329
403
|
before { machine.guard_transition(to: new_state, &guard_cb) }
|
330
404
|
|
331
405
|
it "does not fire guard" do
|
332
|
-
guard_cb.
|
333
|
-
|
406
|
+
expect(guard_cb).not_to receive(:call)
|
407
|
+
is_expected.to be_falsey
|
334
408
|
end
|
335
409
|
end
|
336
410
|
end
|
337
411
|
|
338
412
|
context "when the transition valid" do
|
339
413
|
let(:new_state) { :y }
|
340
|
-
it {
|
414
|
+
it { is_expected.to be_truthy }
|
341
415
|
|
342
416
|
context "but it has a failing guard" do
|
343
417
|
before { machine.guard_transition(to: :y) { false } }
|
344
|
-
it {
|
418
|
+
it { is_expected.to be_falsey }
|
345
419
|
end
|
346
420
|
end
|
347
421
|
end
|
@@ -390,19 +464,19 @@ describe Statesman::Machine do
|
|
390
464
|
end
|
391
465
|
|
392
466
|
it "returns true" do
|
393
|
-
expect(instance.transition_to!(:y)).to
|
467
|
+
expect(instance.transition_to!(:y)).to be_truthy
|
394
468
|
end
|
395
469
|
|
396
470
|
context "with a guard" do
|
397
471
|
let(:result) { true }
|
398
|
-
let(:guard_cb) { ->(*
|
472
|
+
let(:guard_cb) { ->(*_args) { result } }
|
399
473
|
before { machine.guard_transition(from: :x, to: :y, &guard_cb) }
|
400
474
|
|
401
475
|
context "and an object to act on" do
|
402
476
|
let(:instance) { machine.new(my_model) }
|
403
477
|
|
404
478
|
it "passes the object to the guard" do
|
405
|
-
guard_cb.
|
479
|
+
expect(guard_cb).to receive(:call).once
|
406
480
|
.with(my_model, instance.last_transition, nil).and_return(true)
|
407
481
|
instance.transition_to!(:y)
|
408
482
|
end
|
@@ -435,23 +509,24 @@ describe Statesman::Machine do
|
|
435
509
|
|
436
510
|
context "when it is succesful" do
|
437
511
|
before do
|
438
|
-
instance.
|
512
|
+
expect(instance).to receive(:transition_to!).once
|
439
513
|
.with(:some_state, metadata).and_return(:some_state)
|
440
514
|
end
|
441
|
-
it {
|
515
|
+
it { is_expected.to be(:some_state) }
|
442
516
|
end
|
443
517
|
|
444
518
|
context "when it is unsuccesful" do
|
445
519
|
before do
|
446
|
-
instance.
|
520
|
+
allow(instance).to receive(:transition_to!)
|
521
|
+
.and_raise(Statesman::GuardFailedError)
|
447
522
|
end
|
448
|
-
it {
|
523
|
+
it { is_expected.to be_falsey }
|
449
524
|
end
|
450
525
|
|
451
526
|
context "when a non statesman exception is raised" do
|
452
527
|
before do
|
453
|
-
instance.
|
454
|
-
|
528
|
+
allow(instance).to receive(:transition_to!)
|
529
|
+
.and_raise(RuntimeError, 'user defined exception')
|
455
530
|
end
|
456
531
|
|
457
532
|
it "should not rescue the exception" do
|
@@ -514,4 +589,126 @@ describe Statesman::Machine do
|
|
514
589
|
it_behaves_like "a callback filter", :after_transition,
|
515
590
|
:after
|
516
591
|
end
|
592
|
+
|
593
|
+
describe "#event" do
|
594
|
+
before do
|
595
|
+
machine.class_eval do
|
596
|
+
state :x, initial: true
|
597
|
+
state :y
|
598
|
+
state :z
|
599
|
+
|
600
|
+
event :event_1 do
|
601
|
+
transition from: :x, to: :y
|
602
|
+
end
|
603
|
+
|
604
|
+
event :event_2 do
|
605
|
+
transition from: :y, to: :z
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
let(:instance) { machine.new(my_model) }
|
611
|
+
|
612
|
+
context "when the state cannot be transitioned to" do
|
613
|
+
it "raises an error" do
|
614
|
+
expect do
|
615
|
+
instance.trigger!(:event_2)
|
616
|
+
end.to raise_error(Statesman::TransitionFailedError)
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
context "when the state can be transitioned to" do
|
621
|
+
it "changes state" do
|
622
|
+
instance.trigger!(:event_1)
|
623
|
+
expect(instance.current_state).to eq("y")
|
624
|
+
end
|
625
|
+
|
626
|
+
it "creates a new transition object" do
|
627
|
+
expect do
|
628
|
+
instance.trigger!(:event_1)
|
629
|
+
end.to change(instance.history, :count).by(1)
|
630
|
+
|
631
|
+
expect(instance.history.first)
|
632
|
+
.to be_a(Statesman::Adapters::MemoryTransition)
|
633
|
+
expect(instance.history.first.to_state).to eq("y")
|
634
|
+
end
|
635
|
+
|
636
|
+
it "sends metadata to the transition object" do
|
637
|
+
meta = { "my" => "hash" }
|
638
|
+
instance.trigger!(:event_1, meta)
|
639
|
+
expect(instance.history.first.metadata).to eq(meta)
|
640
|
+
end
|
641
|
+
|
642
|
+
it "returns true" do
|
643
|
+
expect(instance.trigger!(:event_1)).to eq(true)
|
644
|
+
end
|
645
|
+
|
646
|
+
context "with a guard" do
|
647
|
+
let(:result) { true }
|
648
|
+
# rubocop:disable UnusedBlockArgument
|
649
|
+
let(:guard_cb) { ->(*args) { result } }
|
650
|
+
# rubocop:enable UnusedBlockArgument
|
651
|
+
before { machine.guard_transition(from: :x, to: :y, &guard_cb) }
|
652
|
+
|
653
|
+
context "and an object to act on" do
|
654
|
+
let(:instance) { machine.new(my_model) }
|
655
|
+
|
656
|
+
it "passes the object to the guard" do
|
657
|
+
expect(guard_cb).to receive(:call).once
|
658
|
+
.with(my_model, instance.last_transition, nil).and_return(true)
|
659
|
+
instance.trigger!(:event_1)
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
context "which passes" do
|
664
|
+
it "changes state" do
|
665
|
+
instance.trigger!(:event_1)
|
666
|
+
expect(instance.current_state).to eq("y")
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
context "which fails" do
|
671
|
+
let(:result) { false }
|
672
|
+
|
673
|
+
it "raises an exception" do
|
674
|
+
expect do
|
675
|
+
instance.trigger!(:event_1)
|
676
|
+
end.to raise_error(Statesman::GuardFailedError)
|
677
|
+
end
|
678
|
+
end
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
end
|
683
|
+
|
684
|
+
describe "#available_events" do
|
685
|
+
before do
|
686
|
+
machine.class_eval do
|
687
|
+
state :x, initial: true
|
688
|
+
state :y
|
689
|
+
state :z
|
690
|
+
|
691
|
+
event :event_1 do
|
692
|
+
transition from: :x, to: :y
|
693
|
+
end
|
694
|
+
|
695
|
+
event :event_2 do
|
696
|
+
transition from: :y, to: :z
|
697
|
+
end
|
698
|
+
|
699
|
+
event :event_3 do
|
700
|
+
transition from: :x, to: :y
|
701
|
+
transition from: :y, to: :x
|
702
|
+
end
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
let(:instance) { machine.new(my_model) }
|
707
|
+
it "should return list of available events for the current state" do
|
708
|
+
expect(instance.available_events).to eq([:event_1, :event_3])
|
709
|
+
instance.trigger!(:event_1)
|
710
|
+
expect(instance.available_events).to eq([:event_2, :event_3])
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
517
714
|
end
|