finite_machine 0.7.1 → 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/CHANGELOG.md +10 -0
- data/README.md +154 -46
- data/lib/finite_machine.rb +1 -0
- data/lib/finite_machine/choice_merger.rb +44 -0
- data/lib/finite_machine/dsl.rb +17 -10
- data/lib/finite_machine/event.rb +36 -5
- data/lib/finite_machine/observer.rb +1 -1
- data/lib/finite_machine/state_machine.rb +22 -5
- data/lib/finite_machine/state_parser.rb +3 -1
- data/lib/finite_machine/transition.rb +43 -10
- data/lib/finite_machine/transition_event.rb +2 -2
- data/lib/finite_machine/version.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/async_events_spec.rb +1 -1
- data/spec/unit/callbacks_spec.rb +29 -8
- data/spec/unit/can_spec.rb +49 -1
- data/spec/unit/choice_spec.rb +137 -0
- data/spec/unit/if_unless_spec.rb +47 -6
- data/spec/unit/initialize_spec.rb +34 -4
- data/spec/unit/states_spec.rb +15 -1
- metadata +5 -2
data/spec/unit/can_spec.rb
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
describe FiniteMachine, 'can?' do
|
6
|
+
before(:each) {
|
7
|
+
Bug = Class.new do
|
8
|
+
def pending?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
}
|
6
13
|
|
7
14
|
it "allows to check if event can be fired" do
|
8
15
|
fsm = FiniteMachine.define do
|
@@ -19,7 +26,7 @@ describe FiniteMachine, 'can?' do
|
|
19
26
|
expect(fsm.current).to eql(:green)
|
20
27
|
|
21
28
|
expect(fsm.can?(:slow)).to be_true
|
22
|
-
expect(fsm.
|
29
|
+
expect(fsm.cannot?(:stop)).to be_true
|
23
30
|
expect(fsm.can?(:ready)).to be_false
|
24
31
|
expect(fsm.can?(:go)).to be_false
|
25
32
|
|
@@ -47,4 +54,45 @@ describe FiniteMachine, 'can?' do
|
|
47
54
|
expect(fsm.can?(:ready)).to be_false
|
48
55
|
expect(fsm.can?(:go)).to be_true
|
49
56
|
end
|
57
|
+
|
58
|
+
context 'with conditionl transition' do
|
59
|
+
it "evalutes condition with parameters" do
|
60
|
+
fsm = FiniteMachine.define do
|
61
|
+
initial :green
|
62
|
+
|
63
|
+
events {
|
64
|
+
event :slow, :green => :yellow
|
65
|
+
event :stop, :yellow => :red, if: proc { |_, state| state }
|
66
|
+
}
|
67
|
+
end
|
68
|
+
expect(fsm.current).to eq(:green)
|
69
|
+
expect(fsm.can?(:slow)).to be_true
|
70
|
+
expect(fsm.can?(:stop)).to be_false
|
71
|
+
|
72
|
+
fsm.slow
|
73
|
+
expect(fsm.current).to eq(:yellow)
|
74
|
+
expect(fsm.can?(:stop, false)).to be_false
|
75
|
+
expect(fsm.can?(:stop, true)).to be_true
|
76
|
+
end
|
77
|
+
|
78
|
+
it "checks against target and grouped events" do
|
79
|
+
bug = Bug.new
|
80
|
+
fsm = FiniteMachine.define do
|
81
|
+
initial :initial
|
82
|
+
|
83
|
+
target bug
|
84
|
+
|
85
|
+
events {
|
86
|
+
event :bump, :initial => :low
|
87
|
+
event :bump, :low => :medium, if: :pending?
|
88
|
+
event :bump, :medium => :high
|
89
|
+
}
|
90
|
+
end
|
91
|
+
expect(fsm.current).to eq(:initial)
|
92
|
+
|
93
|
+
expect(fsm.can?(:bump)).to be_true
|
94
|
+
fsm.bump
|
95
|
+
expect(fsm.can?(:bump)).to be_false
|
96
|
+
end
|
97
|
+
end
|
50
98
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe FiniteMachine, '#choice' do
|
6
|
+
before(:each) {
|
7
|
+
User = Class.new do
|
8
|
+
def promo?(token = false)
|
9
|
+
token == :yes
|
10
|
+
end
|
11
|
+
end
|
12
|
+
}
|
13
|
+
|
14
|
+
it "allows for static choice based on conditional branching" do
|
15
|
+
called = []
|
16
|
+
fsm = FiniteMachine.define do
|
17
|
+
initial :company_form
|
18
|
+
|
19
|
+
events {
|
20
|
+
event :next, from: :company_form do
|
21
|
+
choice :agreement_form, if: -> { false }
|
22
|
+
choice :promo_form, if: -> { false }
|
23
|
+
choice :official_form, if: -> { true }
|
24
|
+
end
|
25
|
+
}
|
26
|
+
|
27
|
+
callbacks {
|
28
|
+
on_exit do |event| called << "on_exit_#{event.from}" end
|
29
|
+
on_enter do |event| called << "on_enter_#{event.to}" end
|
30
|
+
}
|
31
|
+
end
|
32
|
+
expect(fsm.current).to eq(:company_form)
|
33
|
+
fsm.next
|
34
|
+
expect(fsm.current).to eq(:official_form)
|
35
|
+
expect(called).to eq([
|
36
|
+
'on_exit_company_form',
|
37
|
+
'on_enter_official_form'
|
38
|
+
])
|
39
|
+
end
|
40
|
+
|
41
|
+
it "allows for dynamic choice based on conditional branching" do
|
42
|
+
fsm = FiniteMachine.define do
|
43
|
+
initial :company_form
|
44
|
+
|
45
|
+
events {
|
46
|
+
event :next, from: :company_form do
|
47
|
+
choice :agreement_form, if: proc { |_, a| a < 1 }
|
48
|
+
choice :promo_form, if: proc { |_, a| a == 1 }
|
49
|
+
choice :official_form, if: proc { |_, a| a > 1 }
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
expect(fsm.current).to eq(:company_form)
|
54
|
+
fsm.next(0)
|
55
|
+
expect(fsm.current).to eq(:agreement_form)
|
56
|
+
|
57
|
+
fsm.restore!(:company_form)
|
58
|
+
fsm.next(1)
|
59
|
+
expect(fsm.current).to eq(:promo_form)
|
60
|
+
|
61
|
+
fsm.restore!(:company_form)
|
62
|
+
fsm.next(2)
|
63
|
+
expect(fsm.current).to eq(:official_form)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "allows for dynamic choice based on conditional branching and target" do
|
67
|
+
user = User.new
|
68
|
+
fsm = FiniteMachine.define do
|
69
|
+
initial :company_form
|
70
|
+
|
71
|
+
target user
|
72
|
+
|
73
|
+
events {
|
74
|
+
event :next, from: :company_form do
|
75
|
+
choice :agreement_form, if: proc { |_user, token| _user.promo?(token) }
|
76
|
+
choice :promo_form, unless: proc { |_user, token| _user.promo?(token) }
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
expect(fsm.current).to eq(:company_form)
|
81
|
+
fsm.next(:no)
|
82
|
+
expect(fsm.current).to eq(:promo_form)
|
83
|
+
fsm.restore!(:company_form)
|
84
|
+
fsm.next(:yes)
|
85
|
+
expect(fsm.current).to eq(:agreement_form)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "choses state when skipped if/unless" do
|
89
|
+
fsm = FiniteMachine.define do
|
90
|
+
initial :company_form
|
91
|
+
|
92
|
+
events {
|
93
|
+
event :next, from: :company_form do
|
94
|
+
choice :agreement_form, if: -> { false }
|
95
|
+
choice :promo_form
|
96
|
+
choice :official_form, if: -> { true }
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
expect(fsm.current).to eq(:company_form)
|
101
|
+
fsm.next
|
102
|
+
expect(fsm.current).to eq(:promo_form)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "choice default state when branching conditions don't match" do
|
106
|
+
fsm = FiniteMachine.define do
|
107
|
+
initial :company_form
|
108
|
+
|
109
|
+
events {
|
110
|
+
event :next, from: :company_form do
|
111
|
+
choice :agreement_form, if: -> { false }
|
112
|
+
choice :promo_form, if: -> { false }
|
113
|
+
default :official_form
|
114
|
+
end
|
115
|
+
}
|
116
|
+
end
|
117
|
+
expect(fsm.current).to eq(:company_form)
|
118
|
+
fsm.next
|
119
|
+
expect(fsm.current).to eq(:official_form)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "fails to transition when no condition matches without default state" do
|
123
|
+
fsm = FiniteMachine.define do
|
124
|
+
initial :company_form
|
125
|
+
|
126
|
+
events {
|
127
|
+
event :next, from: :company_form do
|
128
|
+
choice :agreement_form, if: -> { false }
|
129
|
+
choice :promo_form, if: -> { false }
|
130
|
+
end
|
131
|
+
}
|
132
|
+
end
|
133
|
+
expect(fsm.current).to eq(:company_form)
|
134
|
+
fsm.next
|
135
|
+
expect(fsm.current).to eq(:company_form)
|
136
|
+
end
|
137
|
+
end
|
data/spec/unit/if_unless_spec.rb
CHANGED
@@ -19,6 +19,12 @@ describe FiniteMachine, ':if, :unless' do
|
|
19
19
|
!!@engine_on
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
class Bug
|
24
|
+
def pending?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
22
28
|
}
|
23
29
|
|
24
30
|
it "allows to cancel event with :if option" do
|
@@ -225,12 +231,6 @@ describe FiniteMachine, ':if, :unless' do
|
|
225
231
|
end
|
226
232
|
|
227
233
|
context 'when same event name' do
|
228
|
-
class Bug
|
229
|
-
def pending?
|
230
|
-
false
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
234
|
it "preservers conditions for the same named event" do
|
235
235
|
bug = Bug.new
|
236
236
|
fsm = FiniteMachine.define do
|
@@ -250,5 +250,46 @@ describe FiniteMachine, ':if, :unless' do
|
|
250
250
|
fsm.bump
|
251
251
|
expect(fsm.current).to eq(:low)
|
252
252
|
end
|
253
|
+
|
254
|
+
it "allows for static choice based on branching condition" do
|
255
|
+
fsm = FiniteMachine.define do
|
256
|
+
initial :company_form
|
257
|
+
|
258
|
+
events {
|
259
|
+
event :next, :company_form => :agreement_form, if: -> { false }
|
260
|
+
event :next, :company_form => :promo_form, if: -> { false }
|
261
|
+
event :next, :company_form => :official_form, if: -> { true }
|
262
|
+
}
|
263
|
+
end
|
264
|
+
expect(fsm.current).to eq(:company_form)
|
265
|
+
fsm.next
|
266
|
+
expect(fsm.current).to eq(:official_form)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "allows for dynamic choice based on branching condition" do
|
270
|
+
fsm = FiniteMachine.define do
|
271
|
+
initial :company_form
|
272
|
+
|
273
|
+
events {
|
274
|
+
event :next, :company_form => :agreement_form, if: proc { |_, a| a < 1 }
|
275
|
+
event :next, :company_form => :promo_form, if: proc { |_, a| a == 1 }
|
276
|
+
event :next, :company_form => :official_form, if: proc { |_, a| a > 1 }
|
277
|
+
}
|
278
|
+
end
|
279
|
+
expect(fsm.current).to eq(:company_form)
|
280
|
+
|
281
|
+
fsm.next(0)
|
282
|
+
expect(fsm.current).to eq(:agreement_form)
|
283
|
+
fsm.restore!(:company_form)
|
284
|
+
expect(fsm.current).to eq(:company_form)
|
285
|
+
|
286
|
+
fsm.next(1)
|
287
|
+
expect(fsm.current).to eq(:promo_form)
|
288
|
+
fsm.restore!(:company_form)
|
289
|
+
expect(fsm.current).to eq(:company_form)
|
290
|
+
|
291
|
+
fsm.next(2)
|
292
|
+
expect(fsm.current).to eq(:official_form)
|
293
|
+
end
|
253
294
|
end
|
254
295
|
end
|
@@ -54,10 +54,7 @@ describe FiniteMachine, 'initialize' do
|
|
54
54
|
}
|
55
55
|
end
|
56
56
|
expect(fsm.current).to eql(:green)
|
57
|
-
expect(called).to
|
58
|
-
'on_exit_none',
|
59
|
-
'on_enter_green'
|
60
|
-
])
|
57
|
+
expect(called).to be_empty
|
61
58
|
end
|
62
59
|
|
63
60
|
it "allows to specify initial state through parameter" do
|
@@ -189,4 +186,37 @@ describe FiniteMachine, 'initialize' do
|
|
189
186
|
expect(fsm.current).to eq(:green)
|
190
187
|
expect(fsm.initial_state).to eq(:green)
|
191
188
|
end
|
189
|
+
|
190
|
+
it "allows to trigger callbacks on initial with :silent option" do
|
191
|
+
called = []
|
192
|
+
fsm = FiniteMachine.define do
|
193
|
+
initial state: :green, silent: false
|
194
|
+
|
195
|
+
events {
|
196
|
+
event :slow, :green => :yellow
|
197
|
+
}
|
198
|
+
callbacks {
|
199
|
+
on_enter :green do |event| called << 'on_enter_green' end
|
200
|
+
}
|
201
|
+
end
|
202
|
+
expect(fsm.current).to eq(:green)
|
203
|
+
expect(called).to eq(['on_enter_green'])
|
204
|
+
end
|
205
|
+
|
206
|
+
it "allows to trigger callbacks on deferred initial state" do
|
207
|
+
called = []
|
208
|
+
fsm = FiniteMachine.define do
|
209
|
+
initial state: :green, silent: false, defer: true
|
210
|
+
|
211
|
+
events {
|
212
|
+
event :slow, :green => :yellow
|
213
|
+
}
|
214
|
+
callbacks {
|
215
|
+
on_enter :green do |event| called << 'on_enter_green' end
|
216
|
+
}
|
217
|
+
end
|
218
|
+
expect(fsm.current).to eq(:none)
|
219
|
+
fsm.init
|
220
|
+
expect(called).to eq(['on_enter_green'])
|
221
|
+
end
|
192
222
|
end
|
data/spec/unit/states_spec.rb
CHANGED
@@ -16,6 +16,20 @@ describe FiniteMachine, 'states' do
|
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
-
expect(fsm.states).to
|
19
|
+
expect(fsm.states).to match_array([:none, :green, :yellow, :red])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "retrieves all unique states for choice transition" do
|
23
|
+
fsm = FiniteMachine.define do
|
24
|
+
initial :green
|
25
|
+
|
26
|
+
events {
|
27
|
+
event :next, from: :green do
|
28
|
+
choice :yellow, if: -> { false }
|
29
|
+
choice :red, if: -> { true }
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
expect(fsm.states).to match_array([:none, :green, :yellow, :red])
|
20
34
|
end
|
21
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: finite_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Murach
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- lib/finite_machine/async_proxy.rb
|
52
52
|
- lib/finite_machine/callable.rb
|
53
53
|
- lib/finite_machine/catchable.rb
|
54
|
+
- lib/finite_machine/choice_merger.rb
|
54
55
|
- lib/finite_machine/dsl.rb
|
55
56
|
- lib/finite_machine/event.rb
|
56
57
|
- lib/finite_machine/event_queue.rb
|
@@ -73,6 +74,7 @@ files:
|
|
73
74
|
- spec/unit/callable/call_spec.rb
|
74
75
|
- spec/unit/callbacks_spec.rb
|
75
76
|
- spec/unit/can_spec.rb
|
77
|
+
- spec/unit/choice_spec.rb
|
76
78
|
- spec/unit/define_spec.rb
|
77
79
|
- spec/unit/event/add_spec.rb
|
78
80
|
- spec/unit/event/inspect_spec.rb
|
@@ -130,6 +132,7 @@ test_files:
|
|
130
132
|
- spec/unit/callable/call_spec.rb
|
131
133
|
- spec/unit/callbacks_spec.rb
|
132
134
|
- spec/unit/can_spec.rb
|
135
|
+
- spec/unit/choice_spec.rb
|
133
136
|
- spec/unit/define_spec.rb
|
134
137
|
- spec/unit/event/add_spec.rb
|
135
138
|
- spec/unit/event/inspect_spec.rb
|