finite_machine 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile +1 -1
  4. data/README.md +73 -35
  5. data/assets/finite_machine_logo.png +0 -0
  6. data/lib/finite_machine.rb +0 -7
  7. data/lib/finite_machine/async_proxy.rb +1 -2
  8. data/lib/finite_machine/dsl.rb +13 -14
  9. data/lib/finite_machine/event_definition.rb +32 -35
  10. data/lib/finite_machine/events_chain.rb +183 -37
  11. data/lib/finite_machine/hook_event.rb +47 -42
  12. data/lib/finite_machine/logger.rb +3 -4
  13. data/lib/finite_machine/observer.rb +27 -11
  14. data/lib/finite_machine/state_definition.rb +66 -0
  15. data/lib/finite_machine/state_machine.rb +177 -99
  16. data/lib/finite_machine/subscribers.rb +17 -6
  17. data/lib/finite_machine/thread_context.rb +1 -1
  18. data/lib/finite_machine/transition.rb +45 -173
  19. data/lib/finite_machine/transition_builder.rb +24 -6
  20. data/lib/finite_machine/transition_event.rb +5 -4
  21. data/lib/finite_machine/undefined_transition.rb +32 -0
  22. data/lib/finite_machine/version.rb +1 -1
  23. data/spec/spec_helper.rb +1 -0
  24. data/spec/unit/async_events_spec.rb +24 -18
  25. data/spec/unit/callbacks_spec.rb +0 -19
  26. data/spec/unit/event_names_spec.rb +19 -0
  27. data/spec/unit/events_chain/add_spec.rb +25 -0
  28. data/spec/unit/events_chain/cancel_transitions_spec.rb +22 -0
  29. data/spec/unit/events_chain/choice_transition_spec.rb +28 -0
  30. data/spec/unit/events_chain/clear_spec.rb +7 -18
  31. data/spec/unit/events_chain/events_spec.rb +18 -0
  32. data/spec/unit/events_chain/inspect_spec.rb +14 -17
  33. data/spec/unit/events_chain/match_transition_spec.rb +37 -0
  34. data/spec/unit/events_chain/move_to_spec.rb +48 -0
  35. data/spec/unit/events_chain/states_for_spec.rb +17 -0
  36. data/spec/unit/events_spec.rb +119 -27
  37. data/spec/unit/hook_event/build_spec.rb +15 -0
  38. data/spec/unit/hook_event/eql_spec.rb +3 -4
  39. data/spec/unit/hook_event/initialize_spec.rb +14 -11
  40. data/spec/unit/hook_event/notify_spec.rb +14 -0
  41. data/spec/unit/{initialize_spec.rb → initial_spec.rb} +1 -1
  42. data/spec/unit/inspect_spec.rb +1 -1
  43. data/spec/unit/logger_spec.rb +4 -5
  44. data/spec/unit/subscribers_spec.rb +20 -9
  45. data/spec/unit/transition/check_conditions_spec.rb +54 -0
  46. data/spec/unit/transition/inspect_spec.rb +2 -2
  47. data/spec/unit/transition/matches_spec.rb +23 -0
  48. data/spec/unit/transition/states_spec.rb +31 -0
  49. data/spec/unit/transition/to_state_spec.rb +27 -0
  50. data/spec/unit/trigger_spec.rb +22 -0
  51. data/spec/unit/undefined_transition/eql_spec.rb +17 -0
  52. data/tasks/console.rake +1 -0
  53. metadata +39 -23
  54. data/lib/finite_machine/event.rb +0 -146
  55. data/spec/unit/event/add_spec.rb +0 -16
  56. data/spec/unit/event/eql_spec.rb +0 -37
  57. data/spec/unit/event/initialize_spec.rb +0 -38
  58. data/spec/unit/event/inspect_spec.rb +0 -21
  59. data/spec/unit/event/next_transition_spec.rb +0 -35
  60. data/spec/unit/events_chain/check_choice_conditions_spec.rb +0 -20
  61. data/spec/unit/events_chain/insert_spec.rb +0 -26
  62. data/spec/unit/events_chain/select_transition_spec.rb +0 -23
  63. data/spec/unit/transition/parse_states_spec.rb +0 -42
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module FiniteMachine
4
- VERSION = "0.10.2"
4
+ VERSION = "0.11.0"
5
5
  end
data/spec/spec_helper.rb CHANGED
@@ -16,6 +16,7 @@ if RUBY_VERSION > '1.9' and (ENV['COVERAGE'] || ENV['TRAVIS'])
16
16
  end
17
17
 
18
18
  require 'finite_machine'
19
+ require 'thwait'
19
20
 
20
21
  RSpec.configure do |config|
21
22
  config.run_all_when_everything_filtered = true
@@ -72,26 +72,32 @@ RSpec.describe FiniteMachine, 'async_events' do
72
72
 
73
73
  it "ensure queue per thread" do
74
74
  called = []
75
- fsmFoo = FiniteMachine.define do
76
- initial :green
77
- events { event :slow, :green => :yellow }
75
+ fsmFoo = nil
76
+ fsmBar = nil
77
+ foo_thread = Thread.new {
78
+ fsmFoo = FiniteMachine.define do
79
+ initial :green
80
+ events { event :slow, :green => :yellow }
78
81
 
79
- callbacks {
80
- on_enter :yellow do |event, a| called << "(foo)on_enter_yellow_#{a}" end
81
- }
82
- end
83
- fsmBar = FiniteMachine.define do
84
- initial :green
85
- events { event :slow, :green => :yellow }
82
+ callbacks {
83
+ on_enter :yellow do |event, a| called << "(foo)on_enter_yellow_#{a}" end
84
+ }
85
+ end
86
+ fsmFoo.async.slow(:foo)
87
+ }
88
+ bar_thread = Thread.new {
89
+ fsmBar = FiniteMachine.define do
90
+ initial :green
91
+ events { event :slow, :green => :yellow }
86
92
 
87
- callbacks {
88
- on_enter :yellow do |event, a| called << "(bar)on_enter_yellow_#{a}" end
89
- }
90
- end
91
- foo_thread = Thread.new { fsmFoo.async.slow(:foo) }
92
- bar_thread = Thread.new { fsmBar.async.slow(:bar) }
93
- [foo_thread, bar_thread].each(&:join)
94
- [fsmFoo, fsmBar].each { |fsm| fsm.event_queue.join 0.02 }
93
+ callbacks {
94
+ on_enter :yellow do |event, a| called << "(bar)on_enter_yellow_#{a}" end
95
+ }
96
+ end
97
+ fsmBar.async.slow(:bar)
98
+ }
99
+ ThreadsWait.all_waits(foo_thread, bar_thread)
100
+ sleep 0.01
95
101
  expect(called).to match_array([
96
102
  '(foo)on_enter_yellow_foo',
97
103
  '(bar)on_enter_yellow_bar'
@@ -687,25 +687,6 @@ RSpec.describe FiniteMachine, 'callbacks' do
687
687
  ])
688
688
  end
689
689
 
690
- it "cancels transition on state callback" do
691
- fsm = FiniteMachine.define do
692
- initial :green
693
-
694
- events {
695
- event :slow, :green => :yellow
696
- event :go, :yellow => :green
697
- }
698
-
699
- callbacks {
700
- on_exit :green do |event| FiniteMachine::CANCELLED end
701
- }
702
- end
703
-
704
- expect(fsm.current).to eql(:green)
705
- fsm.slow
706
- expect(fsm.current).to eql(:green)
707
- end
708
-
709
690
  it "cancels transition on event callback" do
710
691
  fsm = FiniteMachine.define do
711
692
  initial :green
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe FiniteMachine, '.event_names' do
6
+ it "retrieves all event names" do
7
+ fsm = FiniteMachine.define do
8
+ initial :green
9
+
10
+ events {
11
+ event :start, :red => :green
12
+ event :stop, :green => :red
13
+ }
14
+ end
15
+
16
+ expect(fsm.current).to eql(:green)
17
+ expect(fsm.event_names).to eql([:init, :start, :stop])
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe FiniteMachine::EventsChain, '.add' do
6
+ it "adds transitions" do
7
+ transition = double(:transition)
8
+ events_chain = described_class.new
9
+
10
+ events_chain.add(:validated, transition)
11
+ expect(events_chain[:validated]).to eq([transition])
12
+
13
+ events_chain.add(:validated, transition)
14
+ expect(events_chain[:validated]).to eq([transition, transition])
15
+ end
16
+
17
+ it "allows to chain add operations" do
18
+ events_chain = described_class.new
19
+ transition = double(:transition)
20
+
21
+ events_chain.add(:go, transition).add(:start, transition)
22
+
23
+ expect(events_chain.size).to eq(2)
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe FiniteMachine::EventsChain, '.cancel_transitions' do
6
+ it "sets cancel status for chosen transitions" do
7
+ events_chain = described_class.new
8
+ transition_a = spy(:transition_a, cancelled: false)
9
+ transition_b = spy(:transition_b, cancelled: false)
10
+ transition_c = spy(:transition_c, cancelled: false)
11
+
12
+ events_chain.add(:start, transition_a)
13
+ events_chain.add(:start, transition_b)
14
+ events_chain.add(:finish, transition_c)
15
+
16
+ events_chain.cancel_transitions(:start, true)
17
+
18
+ expect(transition_a).to have_received(:cancelled=).with(true)
19
+ expect(transition_b).to have_received(:cancelled=).with(true)
20
+ expect(transition_c).not_to have_received(:cancelled=)
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe FiniteMachine::EventsChain, '.choice_transition?' do
6
+
7
+ it "checks if transition has many branches" do
8
+ transition_a = double(:transition_a, matches?: true)
9
+ transition_b = double(:transition_b, matches?: true)
10
+
11
+ events_chain = described_class.new
12
+ events_chain.add(:go, transition_a)
13
+ events_chain.add(:go, transition_b)
14
+
15
+ expect(events_chain.choice_transition?(:go, :green)).to eq(true)
16
+ end
17
+
18
+ it "checks that transition has no branches" do
19
+ transition_a = double(:transition_a, matches?: false)
20
+ transition_b = double(:transition_b, matches?: true)
21
+
22
+ events_chain = described_class.new
23
+ events_chain.add(:go, transition_a)
24
+ events_chain.add(:go, transition_b)
25
+
26
+ expect(events_chain.choice_transition?(:go, :green)).to eq(false)
27
+ end
28
+ end
@@ -2,25 +2,14 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- RSpec.describe FiniteMachine::EventsChain, '#insert' do
6
- let(:object) { described_class }
7
-
8
- let(:machine) { double(:machine) }
9
-
10
- let(:transition) { double(:transition) }
11
-
12
- subject(:chain) { object.new(machine) }
13
-
14
- it "inserts transition" do
5
+ RSpec.describe FiniteMachine::EventsChain, '#clear' do
6
+ it "clears chain events" do
15
7
  event = double(:event)
16
- chain.add(:validated, event)
17
- expect(chain[:validated]).to eq(event)
18
-
19
- expect(event).to receive(:<<).with(transition)
20
- chain.insert(:validated, transition)
21
- end
8
+ events_chain = described_class.new
9
+ events_chain.add(:validated, event)
10
+ expect(events_chain.empty?).to be(false)
22
11
 
23
- it "fails to insert transition" do
24
- expect(chain.insert(:validated, transition)).to be(false)
12
+ events_chain.clear
13
+ expect(events_chain.empty?).to be(true)
25
14
  end
26
15
  end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe FiniteMachine::EventsChain, '.events' do
6
+ it "has no event names" do
7
+ events_chain = described_class.new
8
+ expect(events_chain.events).to eq([])
9
+ end
10
+
11
+ it "returns all event names" do
12
+ events_chain = described_class.new
13
+ transition = double(:transition)
14
+ events_chain.add(:ready, transition)
15
+ events_chain.add(:go, transition)
16
+ expect(events_chain.events).to match_array([:ready, :go])
17
+ end
18
+ end
@@ -2,26 +2,23 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- RSpec.describe FiniteMachine::EventsChain, '#insert' do
6
- let(:object) { described_class }
7
-
8
- let(:machine) { double(:machine) }
9
-
10
- subject(:chain) { object.new(machine) }
11
-
12
- it "inspects empty chain" do
13
- expect(chain.inspect).to eq("<#FiniteMachine::EventsChain @chain={}>")
5
+ RSpec.describe FiniteMachine::EventsChain, '#inspect' do
6
+ it "inspects empty events chain" do
7
+ events_chain = described_class.new
8
+ expect(events_chain.inspect).to eq("<#FiniteMachine::EventsChain @chain={}>")
14
9
  end
15
10
 
16
- it "inspect chain" do
17
- event = double(:event)
18
- chain.add(:validated, event)
19
- expect(chain.inspect).to eq("<#FiniteMachine::EventsChain @chain=#{{validated: event}}>")
11
+ it "inspect events chain" do
12
+ transition = double(:transition)
13
+ events_chain = described_class.new
14
+ events_chain.add(:validated, transition)
15
+ expect(events_chain.inspect).to eq("<#FiniteMachine::EventsChain @chain=#{{validated: [transition]}}>")
20
16
  end
21
17
 
22
- it "prints chain" do
23
- event = double(:event)
24
- chain.add(:validated, event)
25
- expect(chain.to_s).to eq("#{{validated: event}}")
18
+ it "prints events chain" do
19
+ transition = double(:transition)
20
+ events_chain = described_class.new
21
+ events_chain.add(:validated, transition)
22
+ expect(events_chain.to_s).to eq("#{{validated: [transition]}}")
26
23
  end
27
24
  end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe FiniteMachine::EventsChain, '.match_transition' do
6
+ it "matches transition without conditions" do
7
+ transition_a = double(:transition_a, matches?: false)
8
+ transition_b = double(:transition_b, matches?: true)
9
+ events_chain = described_class.new
10
+
11
+ events_chain.add(:a, transition_a)
12
+ events_chain.add(:a, transition_b)
13
+
14
+ expect(events_chain.match_transition(:a, :green)).to eq(transition_b)
15
+ end
16
+
17
+ it "fails to match any transition" do
18
+ events_chain = described_class.new
19
+
20
+ expect(events_chain.match_transition(:a, :green)).to eq(nil)
21
+ end
22
+
23
+ it "matches transition with conditions" do
24
+ transition_a = double(:transition_a, matches?: true)
25
+ transition_b = double(:transition_b, matches?: true)
26
+ events_chain = described_class.new
27
+
28
+ events_chain.add(:a, transition_a)
29
+ events_chain.add(:a, transition_b)
30
+
31
+ allow(transition_a).to receive(:check_conditions).and_return(false)
32
+ allow(transition_b).to receive(:check_conditions).and_return(true)
33
+
34
+ expect(events_chain.match_transition_with(:a, :green, 'Piotr')).to eq(transition_b)
35
+ expect(transition_a).to have_received(:check_conditions).with('Piotr')
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe FiniteMachine::EventsChain, '.move_to' do
6
+
7
+ it "moves to state by matching individual transition" do
8
+ transition_a = double(:transition_a, matches?: false)
9
+ transition_b = double(:transition_b, matches?: true)
10
+
11
+ events_chain = described_class.new
12
+ events_chain.add(:go, transition_a)
13
+ events_chain.add(:go, transition_b)
14
+
15
+ allow(transition_b).to receive(:to_state).with(:yellow).and_return(:red)
16
+
17
+ expect(events_chain.move_to(:go, :yellow)).to eq(:red)
18
+ expect(transition_b).to have_received(:to_state).with(:yellow)
19
+ end
20
+
21
+ it "moves to state by matching choice transition" do
22
+ transition_a = double(:transition_a, matches?: true)
23
+ transition_b = double(:transition_b, matches?: true)
24
+
25
+ events_chain = described_class.new
26
+ events_chain.add(:go, transition_a)
27
+ events_chain.add(:go, transition_b)
28
+
29
+ allow(transition_a).to receive(:check_conditions).and_return(false)
30
+ allow(transition_b).to receive(:check_conditions).and_return(true)
31
+
32
+ allow(transition_b).to receive(:to_state).with(:green).and_return(:red)
33
+
34
+ expect(events_chain.move_to(:go, :green)).to eq(:red)
35
+ expect(transition_b).to have_received(:to_state).with(:green)
36
+ end
37
+
38
+ it "moves to from state if no transition available" do
39
+ transition_a = double(:transition_a, matches?: false)
40
+ transition_b = double(:transition_b, matches?: false)
41
+
42
+ events_chain = described_class.new
43
+ events_chain.add(:go, transition_a)
44
+ events_chain.add(:go, transition_b)
45
+
46
+ expect(events_chain.move_to(:go, :green)).to eq(:green)
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FiniteMachine::EventsChain do
4
+ it "finds current states for event name" do
5
+ transition = spy(:transition, states: {:red => :yellow, :yellow => :green})
6
+ events_chain = described_class.new
7
+ events_chain.add(:start, transition)
8
+
9
+ expect(events_chain.states_for(:start)).to eq([:red, :yellow])
10
+ end
11
+
12
+ it "fails to find any states for event name" do
13
+ events_chain = described_class.new
14
+
15
+ expect(events_chain.states_for(:start)).to eq([])
16
+ end
17
+ end
@@ -169,37 +169,109 @@ RSpec.describe FiniteMachine, 'events' do
169
169
  expect(fsm.current).to eql(:green)
170
170
  end
171
171
 
172
- it "raises error on invalid transition" do
172
+ it "doesn't raise error on invalid transition for non-dangerous version" do
173
+ called = []
173
174
  fsm = FiniteMachine.define do
174
175
  initial :green
175
176
 
176
177
  events {
177
- event :slow, from: :green, to: :yellow
178
- event :stop, from: :yellow, to: :red
178
+ event :stop, from: :yellow, to: :red
179
+ }
180
+ callbacks {
181
+ on_before :stop do |event| called << 'on_before_stop' end
182
+ on_after :stop do |event| called << 'on_before_stop' end
179
183
  }
180
184
  end
181
185
 
182
- expect(fsm.current).to eql(:green)
186
+ expect(fsm.current).to eq(:green)
187
+ expect(fsm.stop).to eq(false)
188
+ expect(fsm.current).to eq(:green)
189
+ expect(called).to match_array(['on_before_stop'])
190
+ end
183
191
 
184
- expect {
185
- fsm.stop
186
- }.to raise_error(FiniteMachine::InvalidStateError,
187
- /inappropriate current state 'green'/)
192
+ context 'for non-dangerous version' do
193
+ it "doesn't raise error on invalid transition and fires callbacks" do
194
+ called = []
195
+ fsm = FiniteMachine.define do
196
+ initial :green
197
+
198
+ events {
199
+ event :stop, from: :yellow, to: :red
200
+ }
201
+ callbacks {
202
+ on_before :stop do |event| called << 'on_before_stop' end
203
+ on_after :stop do |event| called << 'on_before_stop' end
204
+ }
205
+ end
206
+
207
+ expect(fsm.current).to eq(:green)
208
+ expect(fsm.stop).to eq(false)
209
+ expect(fsm.current).to eq(:green)
210
+ expect(called).to match_array(['on_before_stop'])
211
+ end
212
+
213
+ it "raises error on invalid transition for dangerous version" do
214
+ called = []
215
+ fsm = FiniteMachine.define do
216
+ initial :green
217
+
218
+ events {
219
+ event :slow, from: :green, to: :yellow
220
+ event :stop, from: :yellow, to: :red, silent: true
221
+ }
222
+ callbacks {
223
+ on_before :stop do |event| called << 'on_before_stop' end
224
+ on_after :stop do |event| called << 'on_before_stop' end
225
+ }
226
+ end
227
+
228
+ expect(fsm.current).to eql(:green)
229
+ expect(fsm.stop).to eq(false)
230
+ expect(called).to match_array([])
231
+ end
188
232
  end
189
233
 
190
- it "allows to transition to any state" do
191
- fsm = FiniteMachine.define do
192
- initial :green
234
+ context 'for dangerous version' do
235
+ it "raises error on invalid transition without callbacks" do
236
+ called = []
237
+ fsm = FiniteMachine.define do
238
+ initial :green
193
239
 
194
- events {
195
- event :slow, from: :green, to: :yellow
196
- event :stop, from: :yellow, to: :red
197
- }
240
+ events {
241
+ event :start, :red => :yellow, silent: true
242
+ }
243
+ callbacks {
244
+ on_before :start do |event| called << 'on_before_start' end
245
+ on_after :start do |event| called << 'on_after_start' end
246
+ }
247
+ end
248
+
249
+ expect(fsm.current).to eq(:green)
250
+ expect { fsm.start! }.to raise_error(FiniteMachine::InvalidStateError)
251
+ expect(called).to eq([])
252
+ expect(fsm.current).to eq(:green)
253
+ end
254
+
255
+ it "raises error on invalid transition with callbacks fired" do
256
+ called = []
257
+ fsm = FiniteMachine.define do
258
+ initial :green
259
+
260
+ events {
261
+ event :start, :red => :yellow
262
+ }
263
+ callbacks {
264
+ on_before :start do |event| called << 'on_before_start' end
265
+ on_after :start do |event| called << 'on_after_start' end
266
+ }
267
+ end
268
+
269
+ expect(fsm.current).to eq(:green)
270
+ expect { fsm.start! }.to raise_error(FiniteMachine::InvalidStateError,
271
+ /inappropriate current state 'green'/)
272
+ expect(called).to eq(['on_before_start'])
273
+ expect(fsm.current).to eq(:green)
198
274
  end
199
- expect(fsm.current).to eql(:green)
200
- expect(fsm.can?(:stop)).to be false
201
- fsm.stop!
202
- expect(fsm.current).to eql(:red)
203
275
  end
204
276
 
205
277
  context 'when multiple from states' do
@@ -314,16 +386,16 @@ RSpec.describe FiniteMachine, 'events' do
314
386
  }
315
387
 
316
388
  callbacks {
317
- on_before(:drive) { }
389
+ on_before(:drive) { FiniteMachine::CANCELLED }
318
390
  on_after(:stop) { }
319
391
  }
320
392
  end
321
393
 
322
394
  expect(fsm.current).to eql(:neutral)
323
- expect(fsm.start).to eql(FiniteMachine::SUCCEEDED)
324
- expect(fsm.drive).to eql(FiniteMachine::CANCELLED)
325
- expect(fsm.stop).to eql(FiniteMachine::SUCCEEDED)
326
- expect(fsm.stop).to eql(FiniteMachine::NOTRANSITION)
395
+ expect(fsm.start).to eql(true)
396
+ expect(fsm.drive).to eql(false)
397
+ expect(fsm.stop).to eql(true)
398
+ expect(fsm.stop).to eql(true)
327
399
  end
328
400
 
329
401
  it "allows for self transition events" do
@@ -358,10 +430,30 @@ RSpec.describe FiniteMachine, 'events' do
358
430
  end
359
431
 
360
432
  it "detects dangerous event names" do
361
- expect { FiniteMachine.define do
433
+ expect {
434
+ FiniteMachine.define do
435
+ events {
436
+ event :trigger, :a => :b
437
+ }
438
+ end
439
+ }.to raise_error(FiniteMachine::AlreadyDefinedError)
440
+ end
441
+
442
+ it "executes event block" do
443
+ fsm = FiniteMachine.define do
444
+ initial :red
445
+
362
446
  events {
363
- event :transition, :a => :b
447
+ event :start, :red => :green
448
+ event :stop, :green => :red
364
449
  }
365
- end }.to raise_error(FiniteMachine::AlreadyDefinedError)
450
+ end
451
+
452
+ expect(fsm.current).to eq(:red)
453
+ called = []
454
+ fsm.start do |from, to|
455
+ called << "execute_start_#{from}_#{to}"
456
+ end
457
+ expect(called).to eq(['execute_start_red_green'])
366
458
  end
367
459
  end