statesman 0.7.0 → 0.8.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 +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
|