davidlee-state-fu 0.2.0 → 0.3.1
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.
- data/README.textile +6 -2
- data/Rakefile +24 -3
- data/lib/no_stdout.rb +28 -5
- data/lib/state-fu.rb +25 -21
- data/lib/state_fu/active_support_lite/misc.rb +57 -0
- data/lib/state_fu/binding.rb +51 -41
- data/lib/state_fu/core_ext.rb +5 -4
- data/lib/state_fu/event.rb +51 -16
- data/lib/state_fu/exceptions.rb +5 -0
- data/lib/state_fu/fu_space.rb +5 -4
- data/lib/state_fu/helper.rb +25 -3
- data/lib/state_fu/hooks.rb +4 -1
- data/lib/state_fu/interface.rb +20 -24
- data/lib/state_fu/lathe.rb +38 -2
- data/lib/state_fu/logger.rb +84 -6
- data/lib/state_fu/machine.rb +3 -0
- data/lib/state_fu/method_factory.rb +3 -3
- data/lib/state_fu/persistence/active_record.rb +3 -1
- data/lib/state_fu/persistence/attribute.rb +4 -4
- data/lib/state_fu/persistence/base.rb +3 -3
- data/lib/state_fu/persistence/relaxdb.rb +23 -0
- data/lib/state_fu/persistence.rb +24 -29
- data/lib/state_fu/plotter.rb +63 -0
- data/lib/state_fu/sprocket.rb +12 -0
- data/lib/state_fu/state.rb +22 -0
- data/lib/state_fu/transition.rb +13 -0
- data/lib/vizier.rb +300 -0
- data/spec/BDD/plotter_spec.rb +115 -0
- data/spec/features/binding_and_transition_helper_mixin_spec.rb +111 -0
- data/spec/features/not_requirements_spec.rb +81 -0
- data/spec/features/state_and_array_options_accessor_spec.rb +47 -0
- data/spec/features/transition_boolean_comparison.rb +90 -0
- data/spec/helper.rb +33 -0
- data/spec/integration/active_record_persistence_spec.rb +0 -1
- data/spec/integration/example_01_document_spec.rb +1 -1
- data/spec/integration/relaxdb_persistence_spec.rb +94 -0
- data/spec/integration/requirement_reflection_spec.rb +2 -2
- data/spec/integration/transition_spec.rb +9 -1
- data/spec/units/binding_spec.rb +46 -17
- data/spec/units/lathe_spec.rb +11 -10
- data/spec/units/method_factory_spec.rb +6 -1
- metadata +37 -23
- data/spec/integration/temp_spec.rb +0 -17
data/lib/state_fu/transition.rb
CHANGED
|
@@ -211,5 +211,18 @@ module StateFu
|
|
|
211
211
|
alias_method :pretend?, :testing?
|
|
212
212
|
alias_method :dry_run?, :testing?
|
|
213
213
|
|
|
214
|
+
# an accepted transition == true
|
|
215
|
+
# an unaccepted transition == false
|
|
216
|
+
# same for === (for case equality)
|
|
217
|
+
def == other
|
|
218
|
+
case other
|
|
219
|
+
when true
|
|
220
|
+
accepted?
|
|
221
|
+
when false
|
|
222
|
+
!accepted?
|
|
223
|
+
else
|
|
224
|
+
super( other )
|
|
225
|
+
end
|
|
226
|
+
end
|
|
214
227
|
end
|
|
215
228
|
end
|
data/lib/vizier.rb
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require File.join(File.dirname(__FILE__), '/state_fu/core_ext' )
|
|
5
|
+
rescue LoadError
|
|
6
|
+
require 'activesupport'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# sorry, there's only Heisendocumentation (if I realize anyone's looking for
|
|
10
|
+
# them, I might write some)
|
|
11
|
+
|
|
12
|
+
# temporary dirty hack
|
|
13
|
+
|
|
14
|
+
module Vizier
|
|
15
|
+
|
|
16
|
+
module Support
|
|
17
|
+
LEGAL_CHARS = 'a-zA-Z0-9_'
|
|
18
|
+
|
|
19
|
+
def attributes=( attrs )
|
|
20
|
+
@attributes = attrs.symbolize_keys!.extend( Attributes )
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def attributes
|
|
24
|
+
(@attributes ||= {}).extend( Attributes )
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def legal?( str )
|
|
28
|
+
str =~ /^[#{LEGAL_CHARS}]+$/ && str == str.split
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sanitize(str)
|
|
32
|
+
sanitize( str )
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def quote( str )
|
|
36
|
+
return str if legal?( str )
|
|
37
|
+
'"' + str.to_s.gsub(/"/,'\"') + '"'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.included( klass )
|
|
41
|
+
klass.extend( ClassMethods )
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module Finder
|
|
45
|
+
def []( idx )
|
|
46
|
+
begin
|
|
47
|
+
super( idx )
|
|
48
|
+
rescue TypeError => e
|
|
49
|
+
if idx.is_a?( String ) || idx.is_a?( Symbol )
|
|
50
|
+
self.detect { |i| i.name.to_s == idx.to_s }
|
|
51
|
+
elsif idx.class.respond_to?(:table_name)
|
|
52
|
+
self.detect { |i| i.name.to_s == Vizier::Node.make_name( idx ) }
|
|
53
|
+
else
|
|
54
|
+
raise e
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
module ClassMethods
|
|
61
|
+
def sanitize( str )
|
|
62
|
+
str.to_s.gsub(/[^#{LEGAL_CHARS}]/,'_').gsub(/__+/,'_')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def finder( name )
|
|
66
|
+
class_eval do
|
|
67
|
+
define_method name do
|
|
68
|
+
instance_variable_get( "@#{name}" ).extend( Finder )
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module Attributes
|
|
76
|
+
include Support
|
|
77
|
+
|
|
78
|
+
def to_s
|
|
79
|
+
return '[]' if empty?
|
|
80
|
+
'[ ' + self.map do |k,v|
|
|
81
|
+
"#{quote k} = #{quote v}"
|
|
82
|
+
end.join(" ") + ' ]'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class Base
|
|
88
|
+
def [](k)
|
|
89
|
+
attributes[k.to_sym]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def []=(k,v)
|
|
93
|
+
attributes[k.to_sym] = v
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class Link < Base
|
|
98
|
+
include Support
|
|
99
|
+
attr_accessor :from
|
|
100
|
+
attr_accessor :to
|
|
101
|
+
|
|
102
|
+
def initialize( from, to, attrs={} )
|
|
103
|
+
self.attributes = attrs
|
|
104
|
+
@from = extract_name( from )
|
|
105
|
+
@to = extract_name( to )
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def extract_name( o )
|
|
109
|
+
o.is_a?(String) ? o : o.name
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def to_str
|
|
113
|
+
"#{quote from} -> #{quote to} #{attributes};"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# TODO ..
|
|
118
|
+
module Label
|
|
119
|
+
def []( i )
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class Node < Base
|
|
125
|
+
include Support
|
|
126
|
+
|
|
127
|
+
attr_accessor :object
|
|
128
|
+
attr_accessor :fields
|
|
129
|
+
attr_accessor :name
|
|
130
|
+
|
|
131
|
+
def initialize( name = nil, attrs={} )
|
|
132
|
+
self.attributes = attrs
|
|
133
|
+
if name.is_a?( String )
|
|
134
|
+
self.name = name
|
|
135
|
+
@label = attrs.delete(:label) || name
|
|
136
|
+
else
|
|
137
|
+
@object = name
|
|
138
|
+
self.name = Node.make_name( @object )
|
|
139
|
+
@label = attrs.delete(:label) || Node.first_response( @object, :name, :identifier, :label ) || name
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.make_name( obj )
|
|
144
|
+
sanitize [ obj.class, first_response( obj, :name, :identifier, :id, :hash)].join('_')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.first_response obj, *method_names
|
|
148
|
+
responder = method_names.flatten.detect { |m| obj.respond_to?(m) }
|
|
149
|
+
obj.send( responder ) unless responder.nil?
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def name=( str )
|
|
153
|
+
@name = str.to_s.gsub(/[^a-zA-Z0-9_]/,'_').gsub(/__+/,'_')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def to_str
|
|
157
|
+
"#{quote name} #{attributes.to_s};"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def to_s
|
|
161
|
+
quote( name )
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class SubGraph < Base
|
|
166
|
+
include Support
|
|
167
|
+
|
|
168
|
+
finder :nodes
|
|
169
|
+
|
|
170
|
+
attr_accessor :links
|
|
171
|
+
attr_accessor :name
|
|
172
|
+
|
|
173
|
+
def initialize( name, attrs={} )
|
|
174
|
+
self.attributes = attrs
|
|
175
|
+
@node = {}
|
|
176
|
+
@edge = {}
|
|
177
|
+
|
|
178
|
+
@name = name
|
|
179
|
+
@nodes = []
|
|
180
|
+
@links = []
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def node(attrs={})
|
|
184
|
+
(@node ||= {}).merge!(attrs).extend(Attributes)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def graph(attrs={})
|
|
188
|
+
self.attributes.merge!(attrs).extend(Attributes)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def edge(attrs={})
|
|
192
|
+
(@edge ||= {}).merge!(attrs).extend(Attributes)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def add_node( n, a={} )
|
|
196
|
+
returning Node.new(n,a) do |n|
|
|
197
|
+
@nodes << n
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def add_link(from, to, a={})
|
|
202
|
+
returning Link.new( from, to, a) do |l|
|
|
203
|
+
@links << l
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
alias_method :connect, :add_link
|
|
207
|
+
alias_method :add_edge, :add_link
|
|
208
|
+
|
|
209
|
+
def build(lines = [], indent = 0)
|
|
210
|
+
lines.map do |line|
|
|
211
|
+
if line.is_a?( Array )
|
|
212
|
+
build( line, indent + 1)
|
|
213
|
+
else
|
|
214
|
+
(" " * (indent * 4) ) + line.to_s
|
|
215
|
+
end
|
|
216
|
+
end.join("\n")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def write_comment( str, j = 0 )
|
|
220
|
+
l = 40 - (j * 4)
|
|
221
|
+
i = ' ' * (j * 4)
|
|
222
|
+
"\n#{i}/*#{'*'*(l-2)}\n#{i}** #{ str.ljust((l - (6) - (j*4)),' ') }#{i} **\n#{i}#{'*'*(l-1)}/"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def comment(str)
|
|
226
|
+
write_comment(str, 2)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def to_str
|
|
230
|
+
build( ["subgraph #{quote name} {",
|
|
231
|
+
[ # attributes.map {|k,v| "#{quote k} = #{quote v};" },
|
|
232
|
+
["graph #{attributes};",
|
|
233
|
+
"node #{node};",
|
|
234
|
+
"edge #{edge};"
|
|
235
|
+
],
|
|
236
|
+
nodes.map(&:to_str),
|
|
237
|
+
links.map(&:to_str),
|
|
238
|
+
"}"
|
|
239
|
+
],
|
|
240
|
+
])
|
|
241
|
+
end
|
|
242
|
+
alias_method :generate!, :to_str
|
|
243
|
+
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
class Graph < SubGraph
|
|
247
|
+
finder :subgraphs
|
|
248
|
+
|
|
249
|
+
def comment( str )
|
|
250
|
+
write_comment( str, 1 )
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def to_str
|
|
254
|
+
build(["digraph #{quote name} {",
|
|
255
|
+
[
|
|
256
|
+
comment("global options"),
|
|
257
|
+
"graph #{graph};",
|
|
258
|
+
"node #{node};",
|
|
259
|
+
"edge #{edge};"
|
|
260
|
+
],
|
|
261
|
+
comment("nodes"),
|
|
262
|
+
nodes.map(&:to_str),
|
|
263
|
+
comment("links"),
|
|
264
|
+
links.map(&:to_str),
|
|
265
|
+
comment("subgraphs"),
|
|
266
|
+
subgraphs.map(&:to_str),
|
|
267
|
+
"}"])
|
|
268
|
+
end
|
|
269
|
+
alias_method :generate!, :to_str
|
|
270
|
+
|
|
271
|
+
def publish!( a = {} )
|
|
272
|
+
generate! # -> png
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def subgraph(name, a = {})
|
|
276
|
+
returning( SubGraph.new(name, a)) do |g|
|
|
277
|
+
@subgraphs << g
|
|
278
|
+
yield g if block_given?
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def cluster(name = nil, a = {}, &block)
|
|
283
|
+
if name && name = "cluster_#{name}"
|
|
284
|
+
subgraph( name, a, &block )
|
|
285
|
+
else
|
|
286
|
+
clusters
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def clusters
|
|
291
|
+
@subgraphs.select {|s| s.name =~ /^cluster_/ }.extend( Finder )
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def initialize(name = 'my_graph', attrs = {})
|
|
295
|
+
@subgraphs = []
|
|
296
|
+
super( name, attrs )
|
|
297
|
+
yield self if block_given?
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
##
|
|
5
|
+
##
|
|
6
|
+
|
|
7
|
+
describe StateFu::Plotter do
|
|
8
|
+
include MySpecHelper
|
|
9
|
+
before do
|
|
10
|
+
reset!
|
|
11
|
+
make_pristine_class('Klass')
|
|
12
|
+
@machine = Klass.machine(:drawme) do
|
|
13
|
+
chain 'clean -tarnish-> dirty -fester-> putrid'
|
|
14
|
+
end
|
|
15
|
+
@machine = Klass.machine(:drawme)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "deleteme" do
|
|
19
|
+
@machine.graphviz.save_as('/tmp/1.dot')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "example machine" do
|
|
23
|
+
it "should have 3 states" do
|
|
24
|
+
@machine.states.length.should == 3
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should have 2 simple events" do
|
|
28
|
+
@machine.events.length.should == 2
|
|
29
|
+
@machine.events.each{ |e| e.should be_simple }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe StateFu::Plotter do
|
|
35
|
+
describe "class methods" do
|
|
36
|
+
describe ".new" do
|
|
37
|
+
it "should expect a StateFu::Machine and return a Plotter" do
|
|
38
|
+
@plotter = StateFu::Plotter.new( @machine )
|
|
39
|
+
@plotter.should be_kind_of(StateFu::Plotter)
|
|
40
|
+
@plotter.machine.should == @machine
|
|
41
|
+
lambda { StateFu::Plotter.new( "abracadabra" ) }.should raise_error(RuntimeError)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "a new plotter" do
|
|
46
|
+
before do
|
|
47
|
+
@plotter = StateFu::Plotter.new( @machine )
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "should have an empty hash of states" do
|
|
51
|
+
@plotter = StateFu::Plotter.new( @machine )
|
|
52
|
+
@plotter.states.should == {}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end # class methods
|
|
57
|
+
|
|
58
|
+
describe "instance methods" do
|
|
59
|
+
before do
|
|
60
|
+
@plotter = StateFu::Plotter.new( @machine )
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe ".generate" do
|
|
64
|
+
|
|
65
|
+
it "should call generate_dot!" do
|
|
66
|
+
mock( @plotter ).generate_dot!() { "dot" }
|
|
67
|
+
@plotter.generate
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "should store the result in the dot attribute" do
|
|
71
|
+
mock( @plotter).generate_dot!() { "dot" }
|
|
72
|
+
@plotter.generate
|
|
73
|
+
@plotter.dot.should == "dot"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "should return a Vizier::Graph" # .graph
|
|
77
|
+
|
|
78
|
+
describe ".save_as(filename)" do
|
|
79
|
+
it "should save the string to a file" do
|
|
80
|
+
mock( File).open( 'filename', 'w' ).yields( @fh = Object.new() )
|
|
81
|
+
mock( @fh ).write( @plotter.output )
|
|
82
|
+
@plotter.output.save_as( 'filename' )
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
describe ".save!" do
|
|
86
|
+
it "should save the string in a tempfile and return the path" do
|
|
87
|
+
mock(@tempfile = Object.new).path {"path"}.subject
|
|
88
|
+
mock(Tempfile).new(['state_fu_graph','.dot']).yields( @fh = Object.new() ) { @tempfile }
|
|
89
|
+
mock( @fh ).write( @plotter.output )
|
|
90
|
+
@plotter.output.save!.should == 'path'
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe "output" do
|
|
96
|
+
it "should return the result of .generate" do
|
|
97
|
+
@plotter.output.should == @plotter.generate
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe "generate_dot!" do
|
|
102
|
+
|
|
103
|
+
it "should return a string" do
|
|
104
|
+
@plotter.generate_dot!.should be_kind_of(String)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "should extend the string to respond_to save_as" do
|
|
108
|
+
@plotter.output.should respond_to(:save_as)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
module MySpecHelper
|
|
4
|
+
module BindingExampleHelper
|
|
5
|
+
|
|
6
|
+
attr_accessor :ok
|
|
7
|
+
|
|
8
|
+
def helper_method
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def requirement_satisfier?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def requirement_satisfier_with_arg?( t )
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module OtherExampleHelper
|
|
20
|
+
def other_helper_method
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def other_requirement_satisfier?
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "extending bindings and transitions with Lathe#helper" do
|
|
30
|
+
|
|
31
|
+
include MySpecHelper
|
|
32
|
+
|
|
33
|
+
before(:each) do
|
|
34
|
+
reset!
|
|
35
|
+
make_pristine_class('Klass')
|
|
36
|
+
Klass.class_eval do
|
|
37
|
+
attr_accessor :ok
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
@machine = Klass.machine do
|
|
41
|
+
helper MySpecHelper::BindingExampleHelper
|
|
42
|
+
helper 'my_spec_helper/other_example_helper'
|
|
43
|
+
|
|
44
|
+
chain "a -a2b-> b -b2c-> c"
|
|
45
|
+
|
|
46
|
+
events.each do |e|
|
|
47
|
+
e.requires :requirement_satisfier?
|
|
48
|
+
e.requires :requirement_satisfier_with_arg?
|
|
49
|
+
e.requires :other_requirement_satisfier?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@other_machine = Klass.machine(:other) do
|
|
54
|
+
helper ::MySpecHelper::OtherExampleHelper
|
|
55
|
+
end
|
|
56
|
+
@obj = Klass.new
|
|
57
|
+
@binding = @obj.state_fu
|
|
58
|
+
@other_binding = @obj.other
|
|
59
|
+
@transition = @obj.state_fu.transition(:a2b)
|
|
60
|
+
end # before
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
#
|
|
64
|
+
|
|
65
|
+
describe "binding" do
|
|
66
|
+
describe "instance methods" do
|
|
67
|
+
|
|
68
|
+
it "should respond to helper_method" do
|
|
69
|
+
@binding.should respond_to( :helper_method)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
it "should respond to other_helper_method" do
|
|
74
|
+
@binding.should respond_to( :other_helper_method)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "should respond to requirement_satisfier?" do
|
|
78
|
+
@binding.should respond_to( :requirement_satisfier?)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should respond to other_requirement_satisfier?" do
|
|
82
|
+
@binding.should respond_to( :other_requirement_satisfier?)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "transition" do
|
|
89
|
+
describe "instance methods" do
|
|
90
|
+
|
|
91
|
+
it "should respond to helper_method" do
|
|
92
|
+
@transition.should respond_to( :helper_method)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should respond to other_helper_method" do
|
|
96
|
+
@transition.should respond_to( :other_helper_method)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should respond to requirement_satisfier?" do
|
|
100
|
+
@transition.should respond_to( :requirement_satisfier?)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "should respond to other_requirement_satisfier?" do
|
|
104
|
+
@transition.should respond_to( :other_requirement_satisfier?)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
module RequirementFeatureHelper
|
|
4
|
+
|
|
5
|
+
def account_expired_test
|
|
6
|
+
false
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def valid_password_test
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def account_expired?
|
|
14
|
+
!!account_expired_test
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def valid_password?
|
|
18
|
+
!!valid_password_test
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "requirement objects" do
|
|
23
|
+
include MySpecHelper
|
|
24
|
+
before(:all) do
|
|
25
|
+
reset!
|
|
26
|
+
make_pristine_class('Klass')
|
|
27
|
+
Klass.machine do
|
|
28
|
+
helper RequirementFeatureHelper
|
|
29
|
+
|
|
30
|
+
initial_state :guest
|
|
31
|
+
|
|
32
|
+
event :login_success, :from => :guest, :to => [:logged_in, :expired] do
|
|
33
|
+
requires :valid_password?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
event :login_failure, :from => :guest, :to => :guest do
|
|
37
|
+
execute :show_error
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
state :logged_in do
|
|
41
|
+
requires :not_account_expired?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
state :expired do
|
|
45
|
+
requires :account_expired?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
@obj = Klass.new
|
|
49
|
+
@binding = @obj.state_fu
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "requirements with names beginning with not_" do
|
|
53
|
+
|
|
54
|
+
it "should return the opposite of the requirement name without not_" do
|
|
55
|
+
@binding.respond_to?(:valid_password?).should == true
|
|
56
|
+
@binding.respond_to?(:not_valid_password?).should == false
|
|
57
|
+
@binding.evaluate_named_proc_or_method( :valid_password? ).should == true
|
|
58
|
+
@binding.evaluate_named_proc_or_method( :not_valid_password? ).should == false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "should call the method directly if one exists" do
|
|
62
|
+
mock( @binding ).not_valid_password?() { true }
|
|
63
|
+
@binding.evaluate_named_proc_or_method( :valid_password? ).should == true
|
|
64
|
+
@binding.evaluate_named_proc_or_method( :not_valid_password? ).should == true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should act as the opposite of requirement in guarding a transition" do
|
|
68
|
+
@binding.account_expired?.should == false
|
|
69
|
+
@binding.valid_password?.should == true
|
|
70
|
+
mock( @binding ).valid_password_test { false }
|
|
71
|
+
t = @binding.login_success(:logged_in)
|
|
72
|
+
t.requirements.should == [:not_account_expired?, :valid_password?]
|
|
73
|
+
t.unmet_requirements.should == [:valid_password?]
|
|
74
|
+
mock( @binding ).valid_password_test.times(2) { true }
|
|
75
|
+
t.unmet_requirements.should == []
|
|
76
|
+
@obj.login_success!(:logged_in).should == true
|
|
77
|
+
@binding.should == :logged_in
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
describe "extending bindings and transitions with Lathe#helper" do
|
|
4
|
+
|
|
5
|
+
include MySpecHelper
|
|
6
|
+
|
|
7
|
+
before(:each) do
|
|
8
|
+
reset!
|
|
9
|
+
make_pristine_class('Klass')
|
|
10
|
+
|
|
11
|
+
@machine = Klass.machine do
|
|
12
|
+
state :normal, :colour => 'green'
|
|
13
|
+
state :bad, :colour => 'red'
|
|
14
|
+
event( :worsen, :colour => 'orange' ) { from :normal => :bad }
|
|
15
|
+
end
|
|
16
|
+
@obj = Klass.new
|
|
17
|
+
@binding = @obj.state_fu
|
|
18
|
+
|
|
19
|
+
end # before
|
|
20
|
+
|
|
21
|
+
describe "accessing sprocket options" do
|
|
22
|
+
describe "state#[]" do
|
|
23
|
+
it "should return state.options[key]" do
|
|
24
|
+
@machine.states[:normal][:colour].should == 'green'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
describe "event#[]" do
|
|
28
|
+
it "should return event.options[key]" do
|
|
29
|
+
@machine.events[:worsen][:colour].should == 'orange'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "state#[]=" do
|
|
34
|
+
it "should update state.options" do
|
|
35
|
+
@machine.states[:normal][:flavour] = 'lime'
|
|
36
|
+
@machine.states[:normal][:flavour].should == 'lime'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
describe "event#[]=" do
|
|
40
|
+
it "should update event.options" do
|
|
41
|
+
@machine.events[:worsen][:flavour] = 'orange'
|
|
42
|
+
@machine.events[:worsen][:flavour].should == 'orange'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|