davidlee-state-fu 0.0.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/LICENSE +40 -0
- data/README.textile +174 -0
- data/Rakefile +87 -0
- data/lib/no_stdout.rb +32 -0
- data/lib/state-fu.rb +93 -0
- data/lib/state_fu/binding.rb +262 -0
- data/lib/state_fu/core_ext.rb +23 -0
- data/lib/state_fu/event.rb +98 -0
- data/lib/state_fu/exceptions.rb +42 -0
- data/lib/state_fu/fu_space.rb +50 -0
- data/lib/state_fu/helper.rb +189 -0
- data/lib/state_fu/hooks.rb +28 -0
- data/lib/state_fu/interface.rb +139 -0
- data/lib/state_fu/lathe.rb +247 -0
- data/lib/state_fu/logger.rb +10 -0
- data/lib/state_fu/machine.rb +159 -0
- data/lib/state_fu/method_factory.rb +95 -0
- data/lib/state_fu/persistence/active_record.rb +27 -0
- data/lib/state_fu/persistence/attribute.rb +46 -0
- data/lib/state_fu/persistence/base.rb +98 -0
- data/lib/state_fu/persistence/session.rb +7 -0
- data/lib/state_fu/persistence.rb +50 -0
- data/lib/state_fu/sprocket.rb +27 -0
- data/lib/state_fu/state.rb +45 -0
- data/lib/state_fu/transition.rb +213 -0
- data/spec/helper.rb +86 -0
- data/spec/integration/active_record_persistence_spec.rb +189 -0
- data/spec/integration/class_accessor_spec.rb +127 -0
- data/spec/integration/event_definition_spec.rb +74 -0
- data/spec/integration/ex_machine_for_accounts_spec.rb +79 -0
- data/spec/integration/example_01_document_spec.rb +127 -0
- data/spec/integration/example_02_string_spec.rb +87 -0
- data/spec/integration/instance_accessor_spec.rb +100 -0
- data/spec/integration/machine_duplication_spec.rb +95 -0
- data/spec/integration/requirement_reflection_spec.rb +201 -0
- data/spec/integration/sanity_spec.rb +31 -0
- data/spec/integration/state_definition_spec.rb +177 -0
- data/spec/integration/transition_spec.rb +1060 -0
- data/spec/spec.opts +7 -0
- data/spec/units/binding_spec.rb +145 -0
- data/spec/units/event_spec.rb +232 -0
- data/spec/units/exceptions_spec.rb +75 -0
- data/spec/units/fu_space_spec.rb +95 -0
- data/spec/units/lathe_spec.rb +567 -0
- data/spec/units/machine_spec.rb +237 -0
- data/spec/units/method_factory_spec.rb +359 -0
- data/spec/units/sprocket_spec.rb +71 -0
- data/spec/units/state_spec.rb +50 -0
- metadata +122 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
StateFu::FuSpace.reset!
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
##
|
|
7
|
+
##
|
|
8
|
+
|
|
9
|
+
describe "A pristine class Klass with StateFu included:" do
|
|
10
|
+
include MySpecHelper
|
|
11
|
+
before(:each) do
|
|
12
|
+
make_pristine_class 'Klass'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should return a new Machine bound to the class given Klass.machine()" do
|
|
16
|
+
Klass.should respond_to(:machine)
|
|
17
|
+
Klass.machine.should be_kind_of(StateFu::Machine)
|
|
18
|
+
machine = Klass.machine
|
|
19
|
+
Klass.machine.should == machine
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "should return {} given Klass.machines()" do
|
|
23
|
+
Klass.should respond_to(:machines)
|
|
24
|
+
Klass.machines.should == {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should return [] given Klass.machine_names()" do
|
|
28
|
+
Klass.should respond_to(:machine_names)
|
|
29
|
+
Klass.machine_names.should == []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
##
|
|
34
|
+
##
|
|
35
|
+
|
|
36
|
+
describe "Having called Klass.machine() with an empty block:" do
|
|
37
|
+
before(:each) do
|
|
38
|
+
Klass.machine do
|
|
39
|
+
end
|
|
40
|
+
StateFu::DEFAULT_MACHINE.should == :state_fu
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should return a StateFu::Machine given Klass.machine()" do
|
|
44
|
+
Klass.should respond_to(:machine)
|
|
45
|
+
Klass.machine.should_not be_nil
|
|
46
|
+
Klass.machine.should be_kind_of( StateFu::Machine )
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should return { :state_fu => <StateFu::Machine> } given Klass.machines()" do
|
|
50
|
+
Klass.should respond_to(:machines)
|
|
51
|
+
machines = Klass.machines()
|
|
52
|
+
machines.should be_kind_of(Hash)
|
|
53
|
+
machines.should_not be_empty
|
|
54
|
+
machines.length.should == 1
|
|
55
|
+
machines.keys.should == [:state_fu]
|
|
56
|
+
machines.values.first.should be_kind_of( StateFu::Machine )
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should returns [:state_fu] given Klass.machine_names()" do
|
|
60
|
+
Klass.should respond_to(:machine_names)
|
|
61
|
+
Klass.machine_names.should == [:state_fu]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "Having called Klass.machine(:two) with an empty block:" do
|
|
65
|
+
before(:each) do
|
|
66
|
+
Klass.machine(:two) do
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "should return a StateFu::Machine given Klass.machine(:two)" do
|
|
71
|
+
Klass.should respond_to(:machine)
|
|
72
|
+
Klass.machine(:two).should_not be_nil
|
|
73
|
+
Klass.machine(:two).should be_kind_of( StateFu::Machine )
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "should return a new Machine given Klass.machine(:three)" do
|
|
77
|
+
Klass.should respond_to(:machine)
|
|
78
|
+
Klass.machine(:three).should be_kind_of( StateFu::Machine )
|
|
79
|
+
three = Klass.machine(:three)
|
|
80
|
+
Klass.machine(:three).should == three
|
|
81
|
+
# StateFu::FuSpace.class_machines[Klass][:three].should == :three
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should return { :state_fu => <StateFu::Machine>, :two => <StateFu::Machine> } given Klass.machines()" do
|
|
85
|
+
Klass.should respond_to(:machines)
|
|
86
|
+
machines = Klass.machines()
|
|
87
|
+
machines.should be_kind_of(Hash)
|
|
88
|
+
machines.should_not be_empty
|
|
89
|
+
machines.length.should == 2
|
|
90
|
+
machines.keys.should include :state_fu
|
|
91
|
+
machines.keys.should include :two
|
|
92
|
+
machines.values.length.should == 2
|
|
93
|
+
machines.values.each { |v| v.should be_kind_of( StateFu::Machine ) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "should return [:state_fu, :two] give Klass.machine_names (unordered)" do
|
|
97
|
+
Klass.should respond_to(:machine_names)
|
|
98
|
+
Klass.machine_names.length.should == 2
|
|
99
|
+
Klass.machine_names.should include :state_fu
|
|
100
|
+
Klass.machine_names.should include :two
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe "An empty class Child which inherits from Klass" do
|
|
105
|
+
before() do
|
|
106
|
+
Object.send(:remove_const, 'Child' ) if Object.const_defined?( 'Child' )
|
|
107
|
+
class Child < Klass
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# sorry, Lamarckism not supported
|
|
112
|
+
it "does NOT inherit it's parent class' Machines !!" do
|
|
113
|
+
Child.machine.should_not == Klass.machine
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "should know the Machine after calling Klass.machine.bind!( Child )" do
|
|
117
|
+
Child.machine.should_not == Klass.machine
|
|
118
|
+
Klass.machine.bind!( Child )
|
|
119
|
+
Child.machine.should == Klass.machine
|
|
120
|
+
Klass.machine.bind!( Child, :snoo )
|
|
121
|
+
Child.machine(:snoo).should == Klass.machine
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
##
|
|
5
|
+
##
|
|
6
|
+
|
|
7
|
+
describe "Adding events to a Machine outside a state block" do
|
|
8
|
+
|
|
9
|
+
include MySpecHelper
|
|
10
|
+
|
|
11
|
+
describe "When there is an empty machine" do
|
|
12
|
+
before do
|
|
13
|
+
reset!
|
|
14
|
+
make_pristine_class 'Klass'
|
|
15
|
+
Klass.machine() { }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "calling Klass.machine().events" do
|
|
19
|
+
it "should return []" do
|
|
20
|
+
Klass.machine().events.should == []
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "calling event(:die){ from :dead, :to => :alive } in a Klass.machine()" do
|
|
25
|
+
before do
|
|
26
|
+
Klass.machine do
|
|
27
|
+
event :die do # arity == 0
|
|
28
|
+
from :dead, :to => :alive
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should require a name when calling machine.event()" do
|
|
34
|
+
lambda { Klass.machine(){ event {} } }.should raise_error(ArgumentError)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "should add 2 states to the machine called: [:dead, :alive] " do
|
|
38
|
+
Klass.machine.state_names.should == [:dead, :alive]
|
|
39
|
+
Klass.machine.states.length.should == 2
|
|
40
|
+
Klass.machine.states.each { |s| s.should be_kind_of(StateFu::State) }
|
|
41
|
+
Klass.machine.states.map(&:name).sort.should == [:alive, :dead]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "the <StateFu::Event> created" do
|
|
45
|
+
it "should be accessible through Klass.machine.events" do
|
|
46
|
+
Klass.machine.events.should be_kind_of(Array)
|
|
47
|
+
Klass.machine.events.length.should == 1
|
|
48
|
+
Klass.machine.events.first.should be_kind_of( StateFu::Event )
|
|
49
|
+
Klass.machine.events.first.name.should == :die
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# arity of blocks is optional, thanks to magic fairy dust ;)
|
|
56
|
+
describe "calling event(:die){ |s| s.from :dead, :to => :alive } in a Klass.machine()" do
|
|
57
|
+
before do
|
|
58
|
+
Klass.machine do
|
|
59
|
+
event :die do |s|
|
|
60
|
+
s.from :dead, :to => :alive
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should add 2 states to the machine called [:dead, :alive] " do
|
|
66
|
+
Klass.machine.state_names.should == [:dead, :alive]
|
|
67
|
+
Klass.machine.states.length.should == 2
|
|
68
|
+
Klass.machine.states.each { |s| s.should be_kind_of( StateFu::State ) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
##
|
|
5
|
+
##
|
|
6
|
+
|
|
7
|
+
describe "A simple Machine definition" do
|
|
8
|
+
|
|
9
|
+
include MySpecHelper
|
|
10
|
+
|
|
11
|
+
describe "When there is a machine describing user logins" do
|
|
12
|
+
before(:all) do
|
|
13
|
+
reset!
|
|
14
|
+
make_pristine_class 'Klass'
|
|
15
|
+
|
|
16
|
+
# TODO
|
|
17
|
+
#
|
|
18
|
+
# Method proxy:
|
|
19
|
+
# @obj.om.helpers[:method_name] => proc
|
|
20
|
+
# look for methods first on obj
|
|
21
|
+
# then in on the helper
|
|
22
|
+
# before raising a method_missing error.
|
|
23
|
+
#
|
|
24
|
+
# when executing methods from the helper,
|
|
25
|
+
# use method(mname) to convert them into something
|
|
26
|
+
# we can instance_eval in the context of our machinist instance :)
|
|
27
|
+
#
|
|
28
|
+
# maybe. or maybe that's just sick.
|
|
29
|
+
|
|
30
|
+
# @machine_spec = lambda do
|
|
31
|
+
Klass.machine( :method_proxy => true ) do
|
|
32
|
+
|
|
33
|
+
states :new, :active, :limbo, :expired, :deleted
|
|
34
|
+
|
|
35
|
+
event :confirm do
|
|
36
|
+
from :new, :to => :confirmed, :auto => true do
|
|
37
|
+
needs :email_confirmation
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
state :confirmed do
|
|
42
|
+
on_entry :send_welcome_email
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
event :login, :from => [:confirmed, :active], :to => :active do
|
|
46
|
+
execute :handle_login do
|
|
47
|
+
halt_unless :password_correct?
|
|
48
|
+
halt_if :dodgy_user_agent?
|
|
49
|
+
obj.generate_new_cookie!
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
state :active do
|
|
54
|
+
on_entry :popup_banner_ads_everywhere
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
event :delete do
|
|
58
|
+
from :ALL, :except => :deleted
|
|
59
|
+
to :deleted
|
|
60
|
+
after do
|
|
61
|
+
obj.destroy!
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
states :ALL do
|
|
66
|
+
accepted(:save!)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end # machine
|
|
71
|
+
# end # before
|
|
72
|
+
|
|
73
|
+
# it "parsing it should not throw an error"
|
|
74
|
+
# @machine_spec.should_not raise_error()
|
|
75
|
+
# @machine_spec.call()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
end # describe_1
|
|
79
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
require 'activerecord'
|
|
4
|
+
|
|
5
|
+
describe "Document" do
|
|
6
|
+
include MySpecHelper
|
|
7
|
+
before do
|
|
8
|
+
reset!
|
|
9
|
+
prepare_active_record do
|
|
10
|
+
def self.up
|
|
11
|
+
create_table :documents do |t|
|
|
12
|
+
t.string :name
|
|
13
|
+
t.string :author
|
|
14
|
+
t.string :status_field
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
make_pristine_class('Document', ActiveRecord::Base )
|
|
19
|
+
Document.class_eval do
|
|
20
|
+
include StateFu
|
|
21
|
+
|
|
22
|
+
attr_accessor :author
|
|
23
|
+
|
|
24
|
+
def update_rss
|
|
25
|
+
# puts "new feed!"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
machine( :status ) do
|
|
29
|
+
state :draft do
|
|
30
|
+
event :publish, :to => :published
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
state :published do
|
|
34
|
+
on_entry :update_rss
|
|
35
|
+
requires :author
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
event :delete, :from => :ALL, :to => :deleted do
|
|
39
|
+
execute :destroy
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
events do
|
|
43
|
+
after :save!
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@doc = Document.new
|
|
49
|
+
# @doc.status
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "a new document with no attributes" do
|
|
53
|
+
|
|
54
|
+
it "should have a status.name of :draft" do
|
|
55
|
+
@doc.status.name.should == :draft
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should have no author" do
|
|
59
|
+
@doc.author.should be_nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should raise a RequirementError when publish! is called" do
|
|
63
|
+
lambda { @doc.status.publish! }.should raise_error( StateFu::RequirementError )
|
|
64
|
+
begin
|
|
65
|
+
@doc.status.publish!
|
|
66
|
+
rescue StateFu::RequirementError => e
|
|
67
|
+
e.message.should =~ /[:author]/
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "a new document with an author" do
|
|
73
|
+
before do
|
|
74
|
+
@doc.author = "Susan"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "should have a status.name of :draft" do
|
|
78
|
+
@doc.status.name.should == :draft
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should have an author" do
|
|
82
|
+
@doc.author.should_not be_nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "should not raise an error when publish! is called" do
|
|
86
|
+
@doc.status.evaluate_requirement(:author).should == "Susan"
|
|
87
|
+
lambda { @doc.status.publish! }.should_not raise_error( )
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "should call update_rss when publish! is called" do
|
|
91
|
+
mock( @doc ).update_rss() {}
|
|
92
|
+
@doc.status.publish!
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should have the state name :published after .publish! is called" do
|
|
96
|
+
@doc.status.publish!
|
|
97
|
+
@doc.status.current_state_name.should == :published
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "status_field attribute" do
|
|
101
|
+
it "should be private in ruby 1.8 and 1.9"
|
|
102
|
+
|
|
103
|
+
it "should be defined before state_fu is called" do
|
|
104
|
+
@doc.send( :status_field ).should == 'draft'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "should have an initial value of 'draft'" do
|
|
108
|
+
@doc.instance_eval { status_field }.should == "draft"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "should be set to 'published' after publish! is called successfully" do
|
|
112
|
+
@doc.status.publish!
|
|
113
|
+
@doc.instance_eval { status_field }.should == "published"
|
|
114
|
+
end
|
|
115
|
+
end # status_field
|
|
116
|
+
end # with author
|
|
117
|
+
|
|
118
|
+
describe "delete!" do
|
|
119
|
+
|
|
120
|
+
it "should execute destroy()" do
|
|
121
|
+
mock( @doc ).destroy() {}
|
|
122
|
+
@doc.status.delete!
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
describe String do
|
|
4
|
+
include MySpecHelper
|
|
5
|
+
before do
|
|
6
|
+
reset!
|
|
7
|
+
String.class_eval do
|
|
8
|
+
include StateFu
|
|
9
|
+
|
|
10
|
+
def sanitize_for_shell!
|
|
11
|
+
gsub!(/([\\\t\| &`<>)('"])/) { |s| '\\' << s }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def dirty?
|
|
15
|
+
shell.name == :dirty
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def clean?
|
|
19
|
+
shell.name == :clean
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def shell_escape!
|
|
23
|
+
shell.escape!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def shell_escape
|
|
27
|
+
klone = clone
|
|
28
|
+
begin
|
|
29
|
+
klone.shell.escape!
|
|
30
|
+
rescue StateFu::InvalidTransition
|
|
31
|
+
end
|
|
32
|
+
klone
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
machine (:shell) do
|
|
36
|
+
event(:escape, :from => {:dirty => :clean}) do
|
|
37
|
+
execute :sanitize_for_shell!
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end # String
|
|
41
|
+
@str = "; nohup 'rm -rf /opt' &"
|
|
42
|
+
end # before
|
|
43
|
+
|
|
44
|
+
it "should initially be dirty" do
|
|
45
|
+
@str.dirty?.should be_true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "should call sanitize_for_shell! when shell.escape! is called, and be clean afterwards " do
|
|
49
|
+
@str.should be_dirty
|
|
50
|
+
@str.should_not be_clean
|
|
51
|
+
mock( @str ).sanitize_for_shell! {}
|
|
52
|
+
@str.shell.escape!
|
|
53
|
+
@str.should_not be_dirty
|
|
54
|
+
@str.should be_clean
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "should raise an InvalidTransition if shell.escape! is called more than once" do
|
|
58
|
+
@str.shell.escape!
|
|
59
|
+
@str.shell.state_name.should == :clean
|
|
60
|
+
|
|
61
|
+
lambda { @str.shell.escape! }.should raise_error( StateFu::InvalidTransition )
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should modify the string when shell.escape is called" do
|
|
65
|
+
original = @str.dup
|
|
66
|
+
original.should == @str
|
|
67
|
+
@str.shell.escape!
|
|
68
|
+
original.should_not == @str
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should modify the string when shell.escape! is called" do
|
|
72
|
+
original = @str.dup
|
|
73
|
+
original.should == @str
|
|
74
|
+
@str.shell.escape!
|
|
75
|
+
original.should_not == @str
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "should not modify the original string when shell_escape() is called" do
|
|
79
|
+
original = @str.dup
|
|
80
|
+
original.should == @str
|
|
81
|
+
clean_copy = @str.shell_escape()
|
|
82
|
+
clean_copy.should be_clean
|
|
83
|
+
@str.should be_dirty
|
|
84
|
+
original.should == @str
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
StateFu::FuSpace.reset!
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
##
|
|
7
|
+
##
|
|
8
|
+
|
|
9
|
+
describe "An instance of Klass with StateFu included:" do
|
|
10
|
+
include MySpecHelper
|
|
11
|
+
before(:each) do
|
|
12
|
+
make_pristine_class 'Klass'
|
|
13
|
+
@k = Klass.new()
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "when no machine is defined" do
|
|
17
|
+
it "should return nil given .state_fu()" do
|
|
18
|
+
@k.state_fu().should be_nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should return {} given .bindings()" do
|
|
22
|
+
@k.bindings().should == {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should return [] given .state_fu!()" do
|
|
26
|
+
@k.state_fu!.should == []
|
|
27
|
+
end
|
|
28
|
+
end # no machine
|
|
29
|
+
|
|
30
|
+
describe "when an empty machine is defined for the class with the default name:" do
|
|
31
|
+
before(:each) do
|
|
32
|
+
Klass.machine() {}
|
|
33
|
+
StateFu::DEFAULT_MACHINE.should == :state_fu
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "should return a StateFu::Binding given .state_fu()" do
|
|
37
|
+
@k.state_fu().should be_kind_of( StateFu::Binding )
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "before a binding is instantiated by calling .state_fu() or .state_fu!" do
|
|
41
|
+
it "should return {} given .bindings()" do
|
|
42
|
+
@k.bindings().should == {}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "after a binding is instantiated with .state_fu()" do
|
|
47
|
+
before do
|
|
48
|
+
@k.state_fu()
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "should return { :state_fu => <StateFu::Binding>} given .bindings()" do
|
|
52
|
+
@k.bindings().length.should == 1
|
|
53
|
+
@k.bindings().keys.should == [:state_fu]
|
|
54
|
+
@k.bindings().values.first.should be_kind_of( StateFu::Binding )
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "after .state_fu!()" do
|
|
59
|
+
it "should return { :state_fu => <StateFu::Binding>} given .bindings()" do
|
|
60
|
+
@k.state_fu!()
|
|
61
|
+
@k.bindings().length.should == 1
|
|
62
|
+
@k.bindings().keys.should == [:state_fu]
|
|
63
|
+
@k.bindings().values.first.should be_kind_of( StateFu::Binding )
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should return [<StateFu::Binding>] given .state_fu!()" do
|
|
68
|
+
@k.state_fu!.length.should == 1
|
|
69
|
+
@k.state_fu!.first.should be_kind_of( StateFu::Binding )
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "when there is an empty machine called :two for the class" do
|
|
73
|
+
before(:each) do
|
|
74
|
+
Klass.machine(:two) {}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "should return the same Binding given .state_fu() and .state_fu(:state_fu)" do
|
|
78
|
+
@k.state_fu().should be_kind_of( StateFu::Binding )
|
|
79
|
+
@k.state_fu().should == @k.state_fu(:state_fu)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "should return a StateFu::Binding for the machine called :two given .state_fu(:two)" do
|
|
83
|
+
@k.state_fu(:two).should be_kind_of( StateFu::Binding )
|
|
84
|
+
@k.state_fu(:two).should_not == @k.state_fu(:state_fu)
|
|
85
|
+
@k.state_fu(:two).machine.should == Klass.machine(:two)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "should return nil when .state_fu() is called with the name of a machine which doesn't exist" do
|
|
89
|
+
@k.state_fu(:hibiscus).should be_nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "should return an array of the two StateFu::Bindings given .state_fu!" do
|
|
93
|
+
@k.state_fu!.should be_kind_of( Array )
|
|
94
|
+
@k.state_fu!.length.should == 2
|
|
95
|
+
@k.state_fu!.each { |m| m.should be_kind_of( StateFu::Binding ) }
|
|
96
|
+
@k.state_fu!.map(&:method_name).sort_by(&:to_s).should == [:state_fu, :two]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../helper")
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
##
|
|
5
|
+
##
|
|
6
|
+
|
|
7
|
+
describe "Copying / cloning a Machine" do
|
|
8
|
+
|
|
9
|
+
include MySpecHelper
|
|
10
|
+
|
|
11
|
+
describe "a shallow copy" do
|
|
12
|
+
before do
|
|
13
|
+
reset!
|
|
14
|
+
make_pristine_class("Klass")
|
|
15
|
+
@original = Klass.machine do
|
|
16
|
+
state :a do
|
|
17
|
+
event :goto_b, :to => :b
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
@copy = @original.clone
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should update any event in the original when it's changed in the copy" do
|
|
24
|
+
@original.events[:goto_b].should == @copy.events[:goto_b]
|
|
25
|
+
@copy.lathe do
|
|
26
|
+
event :goto_b do |e|
|
|
27
|
+
e.options[:wibble] = :updated
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
@copy. events[:goto_b].options[:wibble].should == :updated
|
|
31
|
+
@original.events[:goto_b].options[:wibble].should == :updated
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "should update any state in the original when it's changed in the copy" do
|
|
35
|
+
@original.states[:a].should == @copy.states[:a]
|
|
36
|
+
@copy.lathe do
|
|
37
|
+
state :a do |s|
|
|
38
|
+
s.options[:wibble] = :updated
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
@copy. states[:a].options[:wibble].should == :updated
|
|
42
|
+
@original.states[:a].options[:wibble].should == :updated
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "should update the original with any changes to options"
|
|
46
|
+
it "should update the original with any changes to helpers"
|
|
47
|
+
it "should update the original with any changes to named_procs"
|
|
48
|
+
it "should update the original with any changes to requirement_messages"
|
|
49
|
+
|
|
50
|
+
end # shallow
|
|
51
|
+
|
|
52
|
+
describe "a deep copy" do
|
|
53
|
+
before do
|
|
54
|
+
reset!
|
|
55
|
+
make_pristine_class("Klass")
|
|
56
|
+
@original = Klass.machine do
|
|
57
|
+
state :a do
|
|
58
|
+
event :goto_b, :to => :b
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
@copy = @original.deep_clone
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should NOT update any event in the original when it's changed in the copy" do
|
|
65
|
+
pending
|
|
66
|
+
@original.events[:goto_b].should == @copy.events[:goto_b]
|
|
67
|
+
@copy.lathe do
|
|
68
|
+
event :goto_b do |e|
|
|
69
|
+
e.options[:wibble] = :updated
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
@copy. events[:goto_b].options[:wibble].should == :updated
|
|
73
|
+
@original.events[:goto_b].options[:wibble].should == nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "should NOT update any state in the original when it's changed in the copy" do
|
|
77
|
+
pending
|
|
78
|
+
@original.states[:a].should == @copy.states[:a]
|
|
79
|
+
@copy.lathe do
|
|
80
|
+
state :a do |s|
|
|
81
|
+
s.options[:wibble] = :updated
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
@copy. states[:a].options[:wibble].should == :updated
|
|
85
|
+
@original.states[:a].options[:wibble].should == nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "should NOT update the original with any changes to options"
|
|
89
|
+
it "should NOT update the original with any changes to helpers"
|
|
90
|
+
it "should NOT update the original with any changes to named_procs"
|
|
91
|
+
it "should NOT update the original with any changes to requirement_messages"
|
|
92
|
+
|
|
93
|
+
end # deep
|
|
94
|
+
|
|
95
|
+
end
|