simple_state_machine 0.4.3 → 0.5.0.beta

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.
@@ -1,44 +1,195 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- class WithoutEventMethods
4
- extend SimpleStateMachine
3
+ describe SimpleStateMachine::Decorator do
5
4
 
6
- def initialize
7
- self.state = 'unread'
8
- end
9
- event :view, :unread => :read
10
-
11
- end
5
+ context "given a class" do
6
+ before do
7
+ klass = Class.new do
8
+ # TODO remove the need for defining this method
9
+ def self.state_machine_definition
10
+ @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
11
+ end
12
+ end
13
+ decorator = SimpleStateMachine::Decorator.new klass
14
+ decorator.decorate SimpleStateMachine::Transition.new(:event, :state1, :state2)
15
+ @instance = klass.new
16
+ @instance.state = 'state1'
17
+ end
12
18
 
13
- class WithPredefinedStateHelperMethods
14
- extend SimpleStateMachine
19
+ describe "#initialize" do
20
+ it "defines a state_machine method" do
21
+ @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
22
+ end
15
23
 
16
- def initialize
17
- self.state = 'unread'
18
- end
19
- event :view, :unread => :read
24
+ it "defines a state getter method" do
25
+ @instance.should respond_to(:state)
26
+ end
27
+
28
+ it "defines a state setter method" do
29
+ @instance.should respond_to(:state=)
30
+ end
31
+ end
20
32
 
21
- def unread?
22
- raise "blah"
33
+ describe "#decorate" do
34
+ it "defines state_helper_methods for both states" do
35
+ @instance.state1?.should == true
36
+ @instance.state2?.should == false
37
+ end
38
+
39
+ it "defines an event method" do
40
+ @instance.should respond_to(:event)
41
+ end
42
+ end
23
43
  end
24
- end
25
44
 
45
+ context "given a class with predefined public methods" do
46
+ before do
47
+ klass = Class.new do
48
+ # TODO remove the need for defining this method
49
+ def self.state_machine_definition
50
+ @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
51
+ end
52
+ # predefined methods
53
+ def state1?() "state1" end
54
+ def state2?() "state2" end
55
+ def event() "predefined method" end
56
+ end
57
+ transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
58
+ decorator = SimpleStateMachine::Decorator.new klass
59
+ decorator.decorate transition
60
+ klass.state_machine_definition.transitions << transition
61
+ @instance = klass.new
62
+ @instance.state = 'state1'
63
+ end
26
64
 
27
- describe SimpleStateMachine::Decorator do
65
+ describe "#initialize" do
66
+ it "defines a state_machine method" do
67
+ @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
68
+ end
69
+
70
+ it "defines a state getter method" do
71
+ @instance.should respond_to(:state)
72
+ end
73
+
74
+ it "defines a state setter method" do
75
+ @instance.should respond_to(:state=)
76
+ end
77
+ end
28
78
 
29
- it "defines state_helper_methods for all states" do
30
- TrafficLight.new.green?.should == true
31
- TrafficLight.new.orange?.should == false
32
- TrafficLight.new.red?.should == false
79
+ describe "#decorate" do
80
+ it "does not overwrite predefined state_helper_methods" do
81
+ @instance.state1?.should == "state1"
82
+ @instance.state2?.should == "state2"
83
+ end
84
+
85
+ it "does not overwrite predefined event method" do
86
+ @instance.event.should == "predefined method"
87
+ end
88
+ end
33
89
  end
34
90
 
35
- it "does not define a state_helper_method if it already exists" do
36
- l = lambda { WithPredefinedStateHelperMethods.new.unread? }
37
- l.should raise_error(RuntimeError, 'blah')
91
+ context "given a class with predefined protected methods" do
92
+ before do
93
+ klass = Class.new do
94
+ # TODO remove the need for defining this method
95
+ def self.state_machine_definition
96
+ @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
97
+ end
98
+ # predefined methods
99
+ protected
100
+ def state1?() "state1" end
101
+ def state2?() "state2" end
102
+ def event() "predefined method" end
103
+ end
104
+ transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
105
+ decorator = SimpleStateMachine::Decorator.new klass
106
+ decorator.decorate transition
107
+ klass.state_machine_definition.transitions << transition
108
+ @instance = klass.new
109
+ @instance.state = 'state1'
110
+ end
111
+
112
+ describe "#initialize" do
113
+ it "defines a state_machine method" do
114
+ @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
115
+ end
116
+
117
+ it "defines a state getter method" do
118
+ @instance.should respond_to(:state)
119
+ end
120
+
121
+ it "defines a state setter method" do
122
+ @instance.should respond_to(:state=)
123
+ end
124
+ end
125
+
126
+ describe "#decorate" do
127
+ it "does not overwrite predefined protected state_helper_methods" do
128
+ @instance.send(:state1?).should == "state1"
129
+ @instance.send(:state2?).should == "state2"
130
+ end
131
+
132
+ it "keeps predefined protected state_helper_methods protected" do
133
+ expect { @instance.state1? }.to raise_error(NoMethodError)
134
+ expect { @instance.state2? }.to raise_error(NoMethodError)
135
+ end
136
+
137
+ it "does not overwrite predefined protected event method" do
138
+ @instance.event.should == "predefined method"
139
+ end
140
+ end
38
141
  end
39
-
40
- it "defines an event method if it doesn't exist" do
41
- WithoutEventMethods.new.view
142
+
143
+ context "given a class with predefined private methods" do
144
+ before do
145
+ klass = Class.new do
146
+ # TODO the need for defining this method
147
+ def self.state_machine_definition
148
+ @state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
149
+ end
150
+ # predefined methods
151
+ private
152
+ def state1?() "state1" end
153
+ def state2?() "state2" end
154
+ def event() "predefined method" end
155
+ end
156
+ transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
157
+ decorator = SimpleStateMachine::Decorator.new klass
158
+ decorator.decorate transition
159
+ klass.state_machine_definition.transitions << transition
160
+ @instance = klass.new
161
+ @instance.state = 'state1'
162
+ end
163
+
164
+ describe "#initialize" do
165
+ it "defines a state_machine method" do
166
+ @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
167
+ end
168
+
169
+ it "defines a state getter method" do
170
+ @instance.should respond_to(:state)
171
+ end
172
+
173
+ it "defines a state setter method" do
174
+ @instance.should respond_to(:state=)
175
+ end
176
+ end
177
+
178
+ describe "#decorate" do
179
+ it "does not overwrite predefined private state_helper_methods" do
180
+ @instance.send(:state1?).should == "state1"
181
+ @instance.send(:state2?).should == "state2"
182
+ end
183
+
184
+ it "keeps predefined private state_helper_methods private" do
185
+ expect { @instance.state1? }.to raise_error(NoMethodError)
186
+ expect { @instance.state2? }.to raise_error(NoMethodError)
187
+ end
188
+
189
+ it "does not overwrite predefined protected event method" do
190
+ @instance.event.should == "predefined method"
191
+ end
192
+ end
42
193
  end
43
194
 
44
195
  end
@@ -53,6 +53,7 @@ describe "Examples" do
53
53
  conversation.close
54
54
  conversation.should be_closed
55
55
  end
56
+
56
57
  end
57
58
 
58
59
  end
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Mountable" do
4
+ before do
5
+ mountable_class = Class.new(SimpleStateMachine::StateMachineDefinition) do
6
+ def initialize(subject)
7
+ self.lazy_decorator = lambda { SimpleStateMachine::Decorator.new(subject) }
8
+ add_transition(:event, :state1, :state2)
9
+ end
10
+ end
11
+ klass = Class.new do
12
+ extend SimpleStateMachine::Mountable
13
+ self.state_machine_definition = mountable_class.new self
14
+ end
15
+ @instance = klass.new
16
+ @instance.state = 'state1'
17
+ end
18
+
19
+ it "has state_helper methods" do
20
+ @instance.should be_state1
21
+ @instance.should_not be_state2
22
+ end
23
+
24
+ end
@@ -1,85 +1,81 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- class SimpleExample
4
- extend SimpleStateMachine
5
- attr_reader :event2_called
6
- def initialize(state = 'state1')
7
- @state = state
8
- end
9
- event :event1, :state1 => :state2, :state2 => :state3
10
- def event2
11
- @event2_called = true
12
- 'event2'
13
- end
14
- event :event2, :state2 => :state3
15
- event :event_with_multiple_from, [:state1, :state2] => :state3
16
- event :event_from_all, :all => :state3
17
- end
18
-
19
- class SimpleExampleWithCustomStateMethod
20
- extend SimpleStateMachine
21
- state_machine_definition.state_method = :ssm_state
22
-
23
- def initialize(state = 'state1')
24
- @ssm_state = state
25
- end
26
- event :event1, :state1 => :state2
27
- event :event2, :state2 => :state3
28
- end
2
+ require 'cgi'
29
3
 
30
4
  describe SimpleStateMachine do
31
5
 
32
6
  it "has an error that extends RuntimeError" do
33
- SimpleStateMachine::Error.superclass.should == RuntimeError
7
+ SimpleStateMachine::IllegalStateTransitionError.superclass.should == RuntimeError
34
8
  end
35
9
 
36
- it "has a default state" do
37
- SimpleExample.new.state.should == 'state1'
38
- end
10
+ describe ".event" do
39
11
 
40
- describe "events" do
12
+ before do
13
+ @klass = Class.new do
14
+ extend SimpleStateMachine
15
+ def initialize(state = 'state1')
16
+ @state = state
17
+ end
18
+ end
19
+ end
41
20
 
42
21
  it "changes state if event has multiple transitions" do
43
- example = SimpleExample.new
22
+ klass = Class.new(@klass)
23
+ klass.instance_eval do
24
+ event :event, :state1 => :state2, :state2 => :state3
25
+ end
26
+ example = klass.new
44
27
  example.should be_state1
45
- example.event1
28
+ example.event
46
29
  example.should be_state2
47
- example.event1
30
+ example.event
48
31
  example.should be_state3
49
32
  end
50
33
 
51
34
  it "changes state if event has multiple froms" do
52
- example = SimpleExample.new
53
- example.event_with_multiple_from
35
+ klass = Class.new(@klass)
36
+ klass.instance_eval do
37
+ event :event, [:state1, :state2] => :state3
38
+ end
39
+ example = klass.new
40
+ example.event
54
41
  example.should be_state3
55
- example = SimpleExample.new 'state2'
42
+ example = klass.new 'state2'
56
43
  example.should be_state2
57
- example.event_with_multiple_from
44
+ example.event
58
45
  example.should be_state3
59
46
  end
60
47
 
61
48
  it "changes state if event has all as from" do
62
- example = SimpleExample.new
63
- example.event_from_all
49
+ klass = Class.new(@klass)
50
+ klass.instance_eval do
51
+ event :other_event, :state1 => :state2
52
+ event :event, :all => :state3
53
+ end
54
+ example = klass.new
55
+ example.event
64
56
  example.should be_state3
65
- example = SimpleExample.new 'state2'
57
+ example = klass.new 'state2'
66
58
  example.should be_state2
67
- example.event_from_all
59
+ example.event
68
60
  example.should be_state3
69
61
  end
70
62
 
71
63
  it "changes state when state is a symbol instead of a string" do
72
- example = SimpleExample.new :state1
64
+ klass = Class.new(@klass)
65
+ klass.instance_eval do
66
+ event :event, :state1 => :state2
67
+ end
68
+ example = klass.new :state1
73
69
  example.state.should == :state1
74
- example.send(:event1)
70
+ example.send(:event)
75
71
  example.should be_state2
76
72
  end
77
73
 
78
- it "changes state to error_state when error should be caught" do
79
- class_with_error = Class.new(SimpleExample)
74
+ it "changes state to error_state when error can be caught" do
75
+ class_with_error = Class.new(@klass)
80
76
  class_with_error.instance_eval do
81
77
  define_method :raise_error do
82
- raise
78
+ raise RuntimeError.new
83
79
  end
84
80
  event :raise_error, :state1 => :state2, RuntimeError => :failed
85
81
  end
@@ -89,61 +85,45 @@ describe SimpleStateMachine do
89
85
  example.should be_failed
90
86
  end
91
87
 
92
- it "raise an error if an invalid state_transition is called" do
93
- example = SimpleExample.new
94
- lambda { example.event2 }.should raise_error(SimpleStateMachine::Error, "You cannot 'event2' when state is 'state1'")
88
+ it "raises an error if an invalid state_transition is called" do
89
+ klass = Class.new(@klass)
90
+ klass.instance_eval do
91
+ event :event, :state1 => :state2
92
+ event :event2, :state2 => :state3
93
+ end
94
+ example = klass.new
95
+ lambda { example.event2 }.should raise_error(SimpleStateMachine::IllegalStateTransitionError, "You cannot 'event2' when state is 'state1'")
95
96
  end
96
97
 
97
98
  it "returns what the decorated method returns" do
98
- example = SimpleExample.new
99
+ klass = Class.new(@klass)
100
+ klass.instance_eval do
101
+ event :event1, :state1 => :state2
102
+ define_method :event2 do
103
+ 'event2'
104
+ end
105
+ event :event2, :state2 => :state3
106
+ end
107
+ example = klass.new
99
108
  example.event1.should == nil
100
109
  example.event2.should == 'event2'
101
110
  end
102
111
 
103
112
  it "calls existing methods" do
104
- example = SimpleExample.new
105
- example.event1
106
- example.event2
107
- example.event2_called.should == true
108
- end
109
-
110
- end
111
-
112
- describe 'custom state method' do
113
-
114
- it "changes state when calling events" do
115
- example = SimpleExampleWithCustomStateMethod.new
116
- example.should be_state1
117
- example.event1
118
- example.should be_state2
119
- example.event2
120
- example.should be_state3
121
- end
122
-
123
- it "raise an error if an invalid state_transition is called" do
124
- example = SimpleExampleWithCustomStateMethod.new
125
- lambda { example.event2 }.should raise_error(SimpleStateMachine::Error, "You cannot 'event2' when state is 'state1'")
126
- end
127
-
128
- end
129
-
130
- describe "state_machine_definition" do
131
- it "is inherited by subclasses" do
132
- example = Class.new(SimpleExample).new
133
- example.should be_state1
134
- example.event1
135
- example.should be_state2
136
- example.event1
137
- example.should be_state3
113
+ klass = Class.new(@klass)
114
+ klass.instance_eval do
115
+ attr_accessor :event_called
116
+ define_method :event do
117
+ @event_called = true
118
+ end
119
+ event :event, :state1 => :state2
120
+ end
121
+ example = klass.new
122
+ example.event
123
+ example.event_called.should == true
138
124
  end
139
- end
140
125
 
141
- describe "state_machine" do
142
- it "has a next_state method" do
143
- example = SimpleExample.new
144
- example.state_machine.next_state('event1').should == 'state2'
145
- example.state_machine.next_state('event2').should be_nil
146
- end
147
126
  end
148
127
 
149
128
  end
129
+
data/spec/spec_helper.rb CHANGED
@@ -1,13 +1,7 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
3
  require 'simple_state_machine'
4
- require 'spec'
5
- require 'spec/autorun'
6
-
7
4
  require 'examples/conversation'
8
5
  require 'examples/lamp'
9
6
  require 'examples/traffic_light'
10
7
 
11
- Spec::Runner.configure do |config|
12
-
13
- end
@@ -0,0 +1,89 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe SimpleStateMachine::StateMachineDefinition do
4
+
5
+ before do
6
+ @klass = Class.new do
7
+ extend SimpleStateMachine
8
+ def initialize(state = 'state1')
9
+ @state = state
10
+ end
11
+ event :event1, :state1 => :state2, :state2 => :state3
12
+ end
13
+ @smd = @klass.state_machine_definition
14
+ end
15
+
16
+ it "is inherited by subclasses" do
17
+ example = Class.new(@klass).new
18
+ example.should be_state1
19
+ example.event1
20
+ example.should be_state2
21
+ example.event1
22
+ example.should be_state3
23
+ end
24
+
25
+ describe '#state_method' do
26
+ subject do
27
+ klass = Class.new do
28
+ attr_reader :ssm_state
29
+ extend SimpleStateMachine
30
+ state_machine_definition.state_method = :ssm_state
31
+
32
+ def initialize(state = 'state1')
33
+ @ssm_state = state
34
+ end
35
+ event :event1, :state1 => :state2
36
+ event :event2, :state2 => :state3
37
+ end
38
+ klass.new
39
+ end
40
+
41
+ it "is used when changing state" do
42
+ subject.ssm_state.should == 'state1'
43
+ subject.event1
44
+ subject.ssm_state.should == 'state2'
45
+ subject.event2
46
+ subject.ssm_state.should == 'state3'
47
+ end
48
+
49
+ it "works with state helper methods" do
50
+ subject.should be_state1
51
+ subject.event1
52
+ subject.should be_state2
53
+ subject.event2
54
+ subject.should be_state3
55
+ end
56
+
57
+ it "raise an error if an invalid state_transition is called" do
58
+ lambda { subject.event2 }.should raise_error(SimpleStateMachine::IllegalStateTransitionError, "You cannot 'event2' when state is 'state1'")
59
+ end
60
+
61
+ end
62
+
63
+ describe "#transitions" do
64
+ it "has a list of transitions" do
65
+ @smd.transitions.should be_a(Array)
66
+ @smd.transitions.first.should be_a(SimpleStateMachine::Transition)
67
+ end
68
+ end
69
+
70
+ describe "#to_s" do
71
+ it "converts to readable string format" do
72
+ @smd.to_s.should =~ Regexp.new("state1.event1! => state2")
73
+ end
74
+ end
75
+
76
+ describe "#to_graphiz_dot" do
77
+ it "converts to graphiz dot format" do
78
+ @smd.to_graphiz_dot.should == %("state1"->"state2"[label=event1];"state2"->"state3"[label=event1])
79
+ end
80
+ end
81
+
82
+ describe "#google_chart_url" do
83
+ it "shows the state and event dependencies as a Google chart" do
84
+ puts "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape @smd.to_graphiz_dot}}"
85
+ @smd.google_chart_url.should == "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape @smd.to_graphiz_dot}}"
86
+ end
87
+ end
88
+ end
89
+
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe SimpleStateMachine::StateMachine do
4
+
5
+ describe "#next_state" do
6
+ subject do
7
+ klass = Class.new do
8
+ extend SimpleStateMachine
9
+ def initialize() @state = 'state1' end
10
+ event :event1, :state1 => :state2
11
+ event :event2, :state2 => :state3
12
+ end
13
+ klass.new.state_machine
14
+ end
15
+
16
+ it "returns the next state for the event and current state" do
17
+ subject.next_state('event1').should == 'state2'
18
+ end
19
+
20
+ it "returns nil if no next state for the event and current state exists" do
21
+ subject.next_state('event2').should be_nil
22
+ end
23
+ end
24
+
25
+ end
26
+