simple_state_machine 0.4.3 → 0.5.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+