statesman 9.0.0 → 13.0.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/.devcontainer/devcontainer.json +31 -0
- data/.devcontainer/docker-compose.yml +39 -0
- data/.github/workflows/tests.yml +130 -0
- data/.gitignore +65 -15
- data/.rspec +2 -0
- data/.rubocop.yml +11 -1
- data/.rubocop_todo.yml +23 -38
- data/.ruby-version +1 -1
- data/CHANGELOG.md +229 -43
- data/CONTRIBUTING.md +14 -13
- data/Gemfile +18 -3
- data/README.md +203 -74
- data/docs/COMPATIBILITY.md +3 -3
- data/lib/generators/statesman/active_record_transition_generator.rb +1 -1
- data/lib/generators/statesman/generator_helpers.rb +2 -2
- data/lib/statesman/adapters/active_record.rb +69 -52
- data/lib/statesman/adapters/active_record_queries.rb +15 -7
- data/lib/statesman/adapters/active_record_transition.rb +5 -1
- data/lib/statesman/adapters/memory.rb +1 -1
- data/lib/statesman/adapters/type_safe_active_record_queries.rb +21 -0
- data/lib/statesman/callback.rb +2 -2
- data/lib/statesman/config.rb +3 -10
- data/lib/statesman/exceptions.rb +9 -7
- data/lib/statesman/guard.rb +1 -1
- data/lib/statesman/machine.rb +60 -0
- data/lib/statesman/version.rb +1 -1
- data/lib/statesman.rb +5 -5
- data/lib/tasks/statesman.rake +5 -5
- data/spec/generators/statesman/active_record_transition_generator_spec.rb +7 -1
- data/spec/generators/statesman/migration_generator_spec.rb +5 -1
- data/spec/spec_helper.rb +44 -7
- data/spec/statesman/adapters/active_record_queries_spec.rb +8 -10
- data/spec/statesman/adapters/active_record_spec.rb +144 -55
- data/spec/statesman/adapters/active_record_transition_spec.rb +5 -2
- data/spec/statesman/adapters/memory_spec.rb +0 -1
- data/spec/statesman/adapters/memory_transition_spec.rb +0 -1
- data/spec/statesman/adapters/shared_examples.rb +6 -7
- data/spec/statesman/adapters/type_safe_active_record_queries_spec.rb +206 -0
- data/spec/statesman/callback_spec.rb +0 -2
- data/spec/statesman/config_spec.rb +0 -2
- data/spec/statesman/exceptions_spec.rb +8 -4
- data/spec/statesman/guard_spec.rb +0 -2
- data/spec/statesman/machine_spec.rb +231 -19
- data/spec/statesman/utils_spec.rb +0 -2
- data/spec/support/active_record.rb +156 -29
- data/spec/support/exactly_query_databases.rb +35 -0
- data/statesman.gemspec +2 -17
- metadata +14 -238
- data/.circleci/config.yml +0 -127
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe Statesman::Adapters::TypeSafeActiveRecordQueries, :active_record do
|
|
4
|
+
def configure(klass, transition_class)
|
|
5
|
+
klass.send(:extend, described_class)
|
|
6
|
+
klass.configure_state_machine(
|
|
7
|
+
transition_class: transition_class,
|
|
8
|
+
initial_state: :initial,
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
prepare_model_table
|
|
14
|
+
prepare_transitions_table
|
|
15
|
+
prepare_other_model_table
|
|
16
|
+
prepare_other_transitions_table
|
|
17
|
+
|
|
18
|
+
Statesman.configure do
|
|
19
|
+
storage_adapter(Statesman::Adapters::ActiveRecord)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after { Statesman.configure { storage_adapter(Statesman::Adapters::Memory) } }
|
|
24
|
+
|
|
25
|
+
let!(:model) do
|
|
26
|
+
model = MyActiveRecordModel.create
|
|
27
|
+
model.state_machine.transition_to(:succeeded)
|
|
28
|
+
model
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
let!(:other_model) do
|
|
32
|
+
model = MyActiveRecordModel.create
|
|
33
|
+
model.state_machine.transition_to(:failed)
|
|
34
|
+
model
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
let!(:initial_state_model) { MyActiveRecordModel.create }
|
|
38
|
+
|
|
39
|
+
let!(:returned_to_initial_model) do
|
|
40
|
+
model = MyActiveRecordModel.create
|
|
41
|
+
model.state_machine.transition_to(:failed)
|
|
42
|
+
model.state_machine.transition_to(:initial)
|
|
43
|
+
model
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
shared_examples "testing methods" do
|
|
47
|
+
before do
|
|
48
|
+
configure(MyActiveRecordModel, MyActiveRecordModelTransition)
|
|
49
|
+
configure(OtherActiveRecordModel, OtherActiveRecordModelTransition)
|
|
50
|
+
|
|
51
|
+
MyActiveRecordModel.send(:has_one, :other_active_record_model)
|
|
52
|
+
OtherActiveRecordModel.send(:belongs_to, :my_active_record_model)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe ".in_state" do
|
|
56
|
+
context "given a single state" do
|
|
57
|
+
subject { MyActiveRecordModel.in_state(:succeeded) }
|
|
58
|
+
|
|
59
|
+
it { is_expected.to include model }
|
|
60
|
+
it { is_expected.to_not include other_model }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context "given multiple states" do
|
|
64
|
+
subject { MyActiveRecordModel.in_state(:succeeded, :failed) }
|
|
65
|
+
|
|
66
|
+
it { is_expected.to include model }
|
|
67
|
+
it { is_expected.to include other_model }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context "given the initial state" do
|
|
71
|
+
subject { MyActiveRecordModel.in_state(:initial) }
|
|
72
|
+
|
|
73
|
+
it { is_expected.to include initial_state_model }
|
|
74
|
+
it { is_expected.to include returned_to_initial_model }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context "given an array of states" do
|
|
78
|
+
subject { MyActiveRecordModel.in_state(%i[succeeded failed]) }
|
|
79
|
+
|
|
80
|
+
it { is_expected.to include model }
|
|
81
|
+
it { is_expected.to include other_model }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context "merging two queries" do
|
|
85
|
+
subject do
|
|
86
|
+
MyActiveRecordModel.in_state(:succeeded).
|
|
87
|
+
joins(:other_active_record_model).
|
|
88
|
+
merge(OtherActiveRecordModel.in_state(:initial))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it { is_expected.to be_empty }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe ".not_in_state" do
|
|
96
|
+
context "given a single state" do
|
|
97
|
+
subject { MyActiveRecordModel.not_in_state(:failed) }
|
|
98
|
+
|
|
99
|
+
it { is_expected.to include model }
|
|
100
|
+
it { is_expected.to_not include other_model }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context "given multiple states" do
|
|
104
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(:succeeded, :failed) }
|
|
105
|
+
|
|
106
|
+
it do
|
|
107
|
+
expect(not_in_state).to contain_exactly(initial_state_model,
|
|
108
|
+
returned_to_initial_model)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
context "given an array of states" do
|
|
113
|
+
subject(:not_in_state) { MyActiveRecordModel.not_in_state(%i[succeeded failed]) }
|
|
114
|
+
|
|
115
|
+
it do
|
|
116
|
+
expect(not_in_state).to contain_exactly(initial_state_model,
|
|
117
|
+
returned_to_initial_model)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context "with a custom name for the transition association" do
|
|
123
|
+
before do
|
|
124
|
+
# Switch to using OtherActiveRecordModelTransition, so the existing
|
|
125
|
+
# relation with MyActiveRecordModelTransition doesn't interfere with
|
|
126
|
+
# this spec.
|
|
127
|
+
MyActiveRecordModel.send(:has_many,
|
|
128
|
+
:custom_name,
|
|
129
|
+
class_name: "OtherActiveRecordModelTransition")
|
|
130
|
+
|
|
131
|
+
MyActiveRecordModel.class_eval do
|
|
132
|
+
def self.transition_class
|
|
133
|
+
OtherActiveRecordModelTransition
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe ".in_state" do
|
|
139
|
+
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
|
140
|
+
|
|
141
|
+
specify { expect { query }.to_not raise_error }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context "with a custom primary key for the model" do
|
|
146
|
+
before do
|
|
147
|
+
# Switch to using OtherActiveRecordModelTransition, so the existing
|
|
148
|
+
# relation with MyActiveRecordModelTransition doesn't interfere with
|
|
149
|
+
# this spec.
|
|
150
|
+
# Configure the relationship to use a different primary key,
|
|
151
|
+
MyActiveRecordModel.send(:has_many,
|
|
152
|
+
:custom_name,
|
|
153
|
+
class_name: "OtherActiveRecordModelTransition",
|
|
154
|
+
primary_key: :external_id)
|
|
155
|
+
|
|
156
|
+
MyActiveRecordModel.class_eval do
|
|
157
|
+
def self.transition_class
|
|
158
|
+
OtherActiveRecordModelTransition
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe ".in_state" do
|
|
164
|
+
subject(:query) { MyActiveRecordModel.in_state(:succeeded) }
|
|
165
|
+
|
|
166
|
+
specify { expect { query }.to_not raise_error }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context "after_commit transactional integrity" do
|
|
171
|
+
before do
|
|
172
|
+
MyStateMachine.class_eval do
|
|
173
|
+
cattr_accessor(:after_commit_callback_executed) { false }
|
|
174
|
+
|
|
175
|
+
after_transition(from: :initial, to: :succeeded, after_commit: true) do
|
|
176
|
+
# This leaks state in a testable way if transactional integrity is broken.
|
|
177
|
+
MyStateMachine.after_commit_callback_executed = true
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
after do
|
|
183
|
+
MyStateMachine.class_eval do
|
|
184
|
+
callbacks[:after_commit] = []
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
let!(:model) do
|
|
189
|
+
MyActiveRecordModel.create
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it do
|
|
193
|
+
expect do
|
|
194
|
+
ActiveRecord::Base.transaction do
|
|
195
|
+
model.state_machine.transition_to!(:succeeded)
|
|
196
|
+
raise ActiveRecord::Rollback
|
|
197
|
+
end
|
|
198
|
+
end.to_not change(MyStateMachine, :after_commit_callback_executed)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
context "using configuration method" do
|
|
204
|
+
it_behaves_like "testing methods"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
describe Statesman do
|
|
3
|
+
describe "Exceptions" do
|
|
6
4
|
describe "InvalidStateError" do
|
|
7
5
|
subject(:error) { Statesman::InvalidStateError.new }
|
|
8
6
|
|
|
@@ -64,12 +62,18 @@ describe Statesman do
|
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
describe "GuardFailedError" do
|
|
67
|
-
subject(:error) { Statesman::GuardFailedError.new("from", "to") }
|
|
65
|
+
subject(:error) { Statesman::GuardFailedError.new("from", "to", callback) }
|
|
66
|
+
|
|
67
|
+
let(:callback) { -> { "hello" } }
|
|
68
68
|
|
|
69
69
|
its(:message) do
|
|
70
70
|
is_expected.to eq("Guard on transition from: 'from' to 'to' returned false")
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
its(:backtrace) do
|
|
74
|
+
is_expected.to eq([callback.source_location.join(":")])
|
|
75
|
+
end
|
|
76
|
+
|
|
73
77
|
its "string matches its message" do
|
|
74
78
|
expect(error.to_s).to eq(error.message)
|
|
75
79
|
end
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
3
|
describe Statesman::Machine do
|
|
6
4
|
let(:machine) { Class.new { include Statesman::Machine } }
|
|
7
5
|
let(:my_model) { Class.new { attr_accessor :current_state }.new }
|
|
8
6
|
|
|
9
7
|
describe ".state" do
|
|
10
|
-
before
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
before do
|
|
9
|
+
machine.state(:x)
|
|
10
|
+
machine.state(:y)
|
|
11
|
+
end
|
|
13
12
|
|
|
14
13
|
specify { expect(machine.states).to eq(%w[x y]) }
|
|
15
14
|
|
|
@@ -27,6 +26,112 @@ describe Statesman::Machine do
|
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
28
|
|
|
29
|
+
describe ".remove_state" do
|
|
30
|
+
subject(:remove_state) { machine.remove_state(:x) }
|
|
31
|
+
|
|
32
|
+
before do
|
|
33
|
+
machine.class_eval do
|
|
34
|
+
state :x
|
|
35
|
+
state :y
|
|
36
|
+
state :z
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "removes the state" do
|
|
41
|
+
expect { remove_state }.
|
|
42
|
+
to change(machine, :states).
|
|
43
|
+
from(match_array(%w[x y z])).
|
|
44
|
+
to(%w[y z])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context "with a transition from the removed state" do
|
|
48
|
+
before { machine.transition from: :x, to: :y }
|
|
49
|
+
|
|
50
|
+
it "removes the transition" do
|
|
51
|
+
expect { remove_state }.
|
|
52
|
+
to change(machine, :successors).
|
|
53
|
+
from({ "x" => ["y"] }).
|
|
54
|
+
to({})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "with multiple transitions" do
|
|
58
|
+
before { machine.transition from: :x, to: :z }
|
|
59
|
+
|
|
60
|
+
it "removes all transitions" do
|
|
61
|
+
expect { remove_state }.
|
|
62
|
+
to change(machine, :successors).
|
|
63
|
+
from({ "x" => %w[y z] }).
|
|
64
|
+
to({})
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "with a transition to the removed state" do
|
|
70
|
+
before { machine.transition from: :y, to: :x }
|
|
71
|
+
|
|
72
|
+
it "removes the transition" do
|
|
73
|
+
expect { remove_state }.
|
|
74
|
+
to change(machine, :successors).
|
|
75
|
+
from({ "y" => ["x"] }).
|
|
76
|
+
to({})
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context "with multiple transitions" do
|
|
80
|
+
before { machine.transition from: :z, to: :x }
|
|
81
|
+
|
|
82
|
+
it "removes all transitions" do
|
|
83
|
+
expect { remove_state }.
|
|
84
|
+
to change(machine, :successors).
|
|
85
|
+
from({ "y" => ["x"], "z" => ["x"] }).
|
|
86
|
+
to({})
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context "with a callback from the removed state" do
|
|
92
|
+
before do
|
|
93
|
+
machine.class_eval do
|
|
94
|
+
transition from: :x, to: :y
|
|
95
|
+
transition from: :x, to: :z
|
|
96
|
+
guard_transition(from: :x) { return false }
|
|
97
|
+
guard_transition(from: :x, to: :z) { return true }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
let(:guards) do
|
|
102
|
+
[having_attributes(from: "x", to: []), having_attributes(from: "x", to: ["z"])]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "removes the guard" do
|
|
106
|
+
expect { remove_state }.
|
|
107
|
+
to change(machine, :callbacks).
|
|
108
|
+
from(a_hash_including(guards: match_array(guards))).
|
|
109
|
+
to(a_hash_including(guards: []))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context "with a callback to the removed state" do
|
|
114
|
+
before do
|
|
115
|
+
machine.class_eval do
|
|
116
|
+
transition from: :y, to: :x
|
|
117
|
+
guard_transition(to: :x) { return false }
|
|
118
|
+
guard_transition(from: :y, to: :x) { return true }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
let(:guards) do
|
|
123
|
+
[having_attributes(from: nil, to: ["x"]), having_attributes(from: "y", to: ["x"])]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "removes the guard" do
|
|
127
|
+
expect { remove_state }.
|
|
128
|
+
to change(machine, :callbacks).
|
|
129
|
+
from(a_hash_including(guards: match_array(guards))).
|
|
130
|
+
to(a_hash_including(guards: []))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
30
135
|
describe ".retry_conflicts" do
|
|
31
136
|
subject(:transition_state) do
|
|
32
137
|
described_class.retry_conflicts(retry_attempts) do
|
|
@@ -170,6 +275,42 @@ describe Statesman::Machine do
|
|
|
170
275
|
end
|
|
171
276
|
end
|
|
172
277
|
|
|
278
|
+
describe ".remove_transitions" do
|
|
279
|
+
before do
|
|
280
|
+
machine.class_eval do
|
|
281
|
+
state :x
|
|
282
|
+
state :y
|
|
283
|
+
state :z
|
|
284
|
+
transition from: :x, to: :y
|
|
285
|
+
transition from: :x, to: :z
|
|
286
|
+
transition from: :y, to: :z
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
let(:initial_successors) { { "x" => %w[y z], "y" => ["z"] } }
|
|
291
|
+
|
|
292
|
+
it "removes the correct transitions when given a from state" do
|
|
293
|
+
expect { machine.remove_transitions(from: :x) }.
|
|
294
|
+
to change(machine, :successors).
|
|
295
|
+
from(initial_successors).
|
|
296
|
+
to({ "y" => ["z"] })
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it "removes the correct transitions when given a to state" do
|
|
300
|
+
expect { machine.remove_transitions(to: :z) }.
|
|
301
|
+
to change(machine, :successors).
|
|
302
|
+
from(initial_successors).
|
|
303
|
+
to({ "x" => ["y"] })
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "removes the correct transitions when given a from and to state" do
|
|
307
|
+
expect { machine.remove_transitions(from: :x, to: :z) }.
|
|
308
|
+
to change(machine, :successors).
|
|
309
|
+
from(initial_successors).
|
|
310
|
+
to({ "x" => ["y"], "y" => ["z"] })
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
173
314
|
describe ".validate_callback_condition" do
|
|
174
315
|
before do
|
|
175
316
|
machine.class_eval do
|
|
@@ -338,12 +479,83 @@ describe Statesman::Machine do
|
|
|
338
479
|
it_behaves_like "a callback store", :after_guard_failure, :after_guard_failure
|
|
339
480
|
end
|
|
340
481
|
|
|
482
|
+
shared_examples "initial transition is not created" do
|
|
483
|
+
it "doesn't call .create on storage adapter" do
|
|
484
|
+
expect_any_instance_of(Statesman.storage_adapter).to_not receive(:create)
|
|
485
|
+
machine.new(my_model, options)
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
shared_examples "initial transition is created" do
|
|
490
|
+
it "calls .create on storage adapter" do
|
|
491
|
+
expect_any_instance_of(Statesman.storage_adapter).to receive(:create).with(nil, "x")
|
|
492
|
+
machine.new(my_model, options)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
it "creates a new transition object" do
|
|
496
|
+
instance = machine.new(my_model, options)
|
|
497
|
+
|
|
498
|
+
expect(instance.history.count).to eq(1)
|
|
499
|
+
expect(instance.history.first.to_state).to eq("x")
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
341
503
|
describe "#initialize" do
|
|
342
504
|
it "accepts an object to manipulate" do
|
|
343
505
|
machine_instance = machine.new(my_model)
|
|
344
506
|
expect(machine_instance.object).to be(my_model)
|
|
345
507
|
end
|
|
346
508
|
|
|
509
|
+
context "initial_transition is not provided" do
|
|
510
|
+
let(:options) { {} }
|
|
511
|
+
|
|
512
|
+
it_behaves_like "initial transition is not created"
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
context "initial_transition is provided" do
|
|
516
|
+
context "initial_transition is true" do
|
|
517
|
+
let(:options) do
|
|
518
|
+
{ initial_transition: true,
|
|
519
|
+
transition_class: Statesman::Adapters::MemoryTransition }
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
context "history is empty" do
|
|
523
|
+
context "initial state is defined" do
|
|
524
|
+
before { machine.state(:x, initial: true) }
|
|
525
|
+
|
|
526
|
+
it_behaves_like "initial transition is created"
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
context "initial state is not defined" do
|
|
530
|
+
it_behaves_like "initial transition is not created"
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
context "history is not empty" do
|
|
535
|
+
before do
|
|
536
|
+
allow_any_instance_of(Statesman.storage_adapter).to receive(:history).
|
|
537
|
+
and_return([{}])
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
context "initial state is defined" do
|
|
541
|
+
before { machine.state(:x, initial: true) }
|
|
542
|
+
|
|
543
|
+
it_behaves_like "initial transition is not created"
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
context "initial state is not defined" do
|
|
547
|
+
it_behaves_like "initial transition is not created"
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
context "initial_transition is false" do
|
|
553
|
+
let(:options) { { initial_transition: false } }
|
|
554
|
+
|
|
555
|
+
it_behaves_like "initial transition is not created"
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
347
559
|
context "transition class" do
|
|
348
560
|
it "sets a default" do
|
|
349
561
|
expect(Statesman.storage_adapter).to receive(:new).once.
|
|
@@ -399,9 +611,10 @@ describe Statesman::Machine do
|
|
|
399
611
|
end
|
|
400
612
|
|
|
401
613
|
context "with multiple transitions" do
|
|
402
|
-
before
|
|
403
|
-
|
|
404
|
-
|
|
614
|
+
before do
|
|
615
|
+
instance.transition_to!(:y)
|
|
616
|
+
instance.transition_to!(:z)
|
|
617
|
+
end
|
|
405
618
|
|
|
406
619
|
it { is_expected.to eq("z") }
|
|
407
620
|
end
|
|
@@ -416,12 +629,11 @@ describe Statesman::Machine do
|
|
|
416
629
|
state :y
|
|
417
630
|
transition from: :x, to: :y
|
|
418
631
|
end
|
|
632
|
+
instance.transition_to!(:y)
|
|
419
633
|
end
|
|
420
634
|
|
|
421
635
|
let(:instance) { machine.new(my_model) }
|
|
422
636
|
|
|
423
|
-
before { instance.transition_to!(:y) }
|
|
424
|
-
|
|
425
637
|
context "when machine is in given state" do
|
|
426
638
|
let(:state) { "y" }
|
|
427
639
|
|
|
@@ -784,7 +996,7 @@ describe Statesman::Machine do
|
|
|
784
996
|
let(:instance) { machine.new(my_model) }
|
|
785
997
|
let(:metadata) { { some: :metadata } }
|
|
786
998
|
|
|
787
|
-
context "when it is
|
|
999
|
+
context "when it is successful" do
|
|
788
1000
|
before do
|
|
789
1001
|
expect(instance).to receive(:transition_to!).once.
|
|
790
1002
|
with(:some_state, metadata).and_return(:some_state)
|
|
@@ -793,10 +1005,10 @@ describe Statesman::Machine do
|
|
|
793
1005
|
it { is_expected.to be(:some_state) }
|
|
794
1006
|
end
|
|
795
1007
|
|
|
796
|
-
context "when it is
|
|
1008
|
+
context "when it is unsuccessful" do
|
|
797
1009
|
before do
|
|
798
1010
|
allow(instance).to receive(:transition_to!).
|
|
799
|
-
and_raise(Statesman::GuardFailedError.new(:x, :some_state))
|
|
1011
|
+
and_raise(Statesman::GuardFailedError.new(:x, :some_state, nil))
|
|
800
1012
|
end
|
|
801
1013
|
|
|
802
1014
|
it { is_expected.to be_falsey }
|
|
@@ -834,20 +1046,20 @@ describe Statesman::Machine do
|
|
|
834
1046
|
end
|
|
835
1047
|
|
|
836
1048
|
context "with defined callbacks" do
|
|
837
|
-
let(:
|
|
838
|
-
let(:
|
|
1049
|
+
let(:callback_one) { -> { "Hi" } }
|
|
1050
|
+
let(:callback_two) { -> { "Bye" } }
|
|
839
1051
|
|
|
840
1052
|
before do
|
|
841
|
-
machine.send(definer, from: :x, to: :y, &
|
|
842
|
-
machine.send(definer, from: :y, to: :z, &
|
|
1053
|
+
machine.send(definer, from: :x, to: :y, &callback_one)
|
|
1054
|
+
machine.send(definer, from: :y, to: :z, &callback_two)
|
|
843
1055
|
end
|
|
844
1056
|
|
|
845
1057
|
it "contains the relevant callback" do
|
|
846
|
-
expect(callbacks.map(&:callback)).to include(
|
|
1058
|
+
expect(callbacks.map(&:callback)).to include(callback_one)
|
|
847
1059
|
end
|
|
848
1060
|
|
|
849
1061
|
it "does not contain the irrelevant callback" do
|
|
850
|
-
expect(callbacks.map(&:callback)).to_not include(
|
|
1062
|
+
expect(callbacks.map(&:callback)).to_not include(callback_two)
|
|
851
1063
|
end
|
|
852
1064
|
end
|
|
853
1065
|
end
|