inversion 0.1.1 → 0.2.0

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.
Files changed (43) hide show
  1. data.tar.gz.sig +0 -0
  2. data/ChangeLog +157 -28
  3. data/History.rdoc +8 -0
  4. data/Manifest.txt +2 -3
  5. data/Rakefile +1 -3
  6. data/lib/inversion.rb +2 -2
  7. data/lib/inversion/exceptions.rb +1 -1
  8. data/lib/inversion/{template/parser.rb → parser.rb} +5 -5
  9. data/lib/inversion/renderstate.rb +55 -5
  10. data/lib/inversion/template.rb +5 -3
  11. data/lib/inversion/template/attrtag.rb +19 -12
  12. data/lib/inversion/template/begintag.rb +1 -2
  13. data/lib/inversion/template/configtag.rb +7 -1
  14. data/lib/inversion/template/containertag.rb +8 -3
  15. data/lib/inversion/template/elsetag.rb +16 -0
  16. data/lib/inversion/template/elsiftag.rb +16 -0
  17. data/lib/inversion/template/escapetag.rb +1 -1
  18. data/lib/inversion/template/fortag.rb +2 -5
  19. data/lib/inversion/template/iftag.rb +17 -35
  20. data/lib/inversion/template/importtag.rb +2 -1
  21. data/lib/inversion/template/includetag.rb +2 -0
  22. data/lib/inversion/template/node.rb +1 -1
  23. data/lib/inversion/template/tag.rb +5 -2
  24. data/lib/inversion/template/textnode.rb +1 -2
  25. data/lib/inversion/template/unlesstag.rb +16 -26
  26. data/lib/inversion/template/yieldtag.rb +3 -8
  27. data/spec/inversion/{template/parser_spec.rb → parser_spec.rb} +14 -14
  28. data/spec/inversion/renderstate_spec.rb +242 -165
  29. data/spec/inversion/template/attrtag_spec.rb +10 -18
  30. data/spec/inversion/template/begintag_spec.rb +13 -12
  31. data/spec/inversion/template/configtag_spec.rb +5 -7
  32. data/spec/inversion/template/elsetag_spec.rb +5 -5
  33. data/spec/inversion/template/elsiftag_spec.rb +5 -5
  34. data/spec/inversion/template/endtag_spec.rb +1 -1
  35. data/spec/inversion/template/fortag_spec.rb +22 -1
  36. data/spec/inversion/template/iftag_spec.rb +14 -0
  37. data/spec/inversion/template/rescuetag_spec.rb +4 -4
  38. data/spec/inversion/template/tag_spec.rb +6 -4
  39. data/spec/inversion/template/unlesstag_spec.rb +12 -6
  40. data/spec/inversion/template/yieldtag_spec.rb +2 -2
  41. metadata +31 -32
  42. metadata.gz.sig +0 -0
  43. data/lib/inversion/template/conditionaltag.rb +0 -49
@@ -3,7 +3,7 @@
3
3
 
4
4
  BEGIN {
5
5
  require 'pathname'
6
- basedir = Pathname( __FILE__ ).dirname.parent.parent.parent
6
+ basedir = Pathname( __FILE__ ).dirname.parent.parent
7
7
  libdir = basedir + 'lib'
8
8
 
9
9
  $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
@@ -12,9 +12,9 @@ BEGIN {
12
12
 
13
13
  require 'rspec'
14
14
  require 'spec/lib/helpers'
15
- require 'inversion/template/parser'
15
+ require 'inversion/parser'
16
16
 
17
- describe Inversion::Template::Parser do
17
+ describe Inversion::Parser do
18
18
 
19
19
  before( :all ) do
20
20
  setup_logging( :fatal )
@@ -26,7 +26,7 @@ describe Inversion::Template::Parser do
26
26
  end
27
27
 
28
28
  it "parses a string with no PIs as a single text node" do
29
- result = Inversion::Template::Parser.new( @template ).parse( "render unto Caesar" )
29
+ result = Inversion::Parser.new( @template ).parse( "render unto Caesar" )
30
30
 
31
31
  result.should have( 1 ).member
32
32
  result.first.should be_a( Inversion::Template::TextNode )
@@ -34,18 +34,18 @@ describe Inversion::Template::Parser do
34
34
  end
35
35
 
36
36
  it "parses an empty string as a empty tree" do
37
- result = Inversion::Template::Parser.new( @template ).parse( "" )
37
+ result = Inversion::Parser.new( @template ).parse( "" )
38
38
  result.should be_empty
39
39
  end
40
40
 
41
41
  it "raises a ParseError on mismatched tag brackets" do
42
42
  expect {
43
- Inversion::Template::Parser.new( @template ).parse( '[?foo bar ?>' )
43
+ Inversion::Parser.new( @template ).parse( '[?foo bar ?>' )
44
44
  }.to raise_error( Inversion::ParseError, /mismatched start and end brackets/i )
45
45
  end
46
46
 
47
47
  it "parses a string with a single 'attr' tag as a single AttrTag node" do
48
- result = Inversion::Template::Parser.new( @template ).parse( "<?attr foo ?>" )
48
+ result = Inversion::Parser.new( @template ).parse( "<?attr foo ?>" )
49
49
 
50
50
  result.should have( 1 ).member
51
51
  result.first.should be_a( Inversion::Template::AttrTag )
@@ -53,7 +53,7 @@ describe Inversion::Template::Parser do
53
53
  end
54
54
 
55
55
  it "parses a single 'attr' tag surrounded by plain text" do
56
- result = Inversion::Template::Parser.new( @template ).parse( "beginning<?attr foo ?>end" )
56
+ result = Inversion::Parser.new( @template ).parse( "beginning<?attr foo ?>end" )
57
57
 
58
58
  result.should have( 3 ).members
59
59
  result[0].should be_a( Inversion::Template::TextNode )
@@ -63,7 +63,7 @@ describe Inversion::Template::Parser do
63
63
  end
64
64
 
65
65
  it "ignores unknown tags by default" do
66
- result = Inversion::Template::Parser.new( @template ).parse( "Text <?hoooowhat ?>" )
66
+ result = Inversion::Parser.new( @template ).parse( "Text <?hoooowhat ?>" )
67
67
 
68
68
  result.should have( 2 ).members
69
69
  result[0].should be_a( Inversion::Template::TextNode )
@@ -73,28 +73,28 @@ describe Inversion::Template::Parser do
73
73
 
74
74
  it "can raise exceptions on unknown tags" do
75
75
  expect {
76
- Inversion::Template::Parser.new( @template, :ignore_unknown_tags => false ).
76
+ Inversion::Parser.new( @template, :ignore_unknown_tags => false ).
77
77
  parse( "Text <?hoooowhat ?>" )
78
78
  }.to raise_exception( Inversion::ParseError, /unknown tag/i )
79
79
  end
80
80
 
81
81
  it "can raise exceptions on unclosed (nested) tags" do
82
82
  expect {
83
- Inversion::Template::Parser.new( @template ).parse( "Text <?attr something <?attr something_else ?>" )
83
+ Inversion::Parser.new( @template ).parse( "Text <?attr something <?attr something_else ?>" )
84
84
  }.to raise_exception( Inversion::ParseError, /unclosed or nested tag/i )
85
85
  end
86
86
 
87
87
  it "can raise exceptions on unclosed (eof) tags" do
88
88
  expect {
89
- Inversion::Template::Parser.new( @template ).parse( "Text <?hoooowhat" )
89
+ Inversion::Parser.new( @template ).parse( "Text <?hoooowhat" )
90
90
  }.to raise_exception( Inversion::ParseError, /unclosed tag/i )
91
91
  end
92
92
 
93
93
 
94
- describe Inversion::Template::Parser::State do
94
+ describe Inversion::Parser::State do
95
95
 
96
96
  before( :each ) do
97
- @state = Inversion::Template::Parser::State.new( @template )
97
+ @state = Inversion::Parser::State.new( @template )
98
98
  end
99
99
 
100
100
  it "returns the node tree if it's well-formed" do
@@ -23,245 +23,287 @@ describe Inversion::RenderState do
23
23
  setup_logging( :fatal )
24
24
  end
25
25
 
26
- it "copies its initial attributes" do
27
- attributes = { :foot => "in mouth", :bear => "in woods" }
28
-
29
- state = Inversion::RenderState.new( attributes )
30
-
31
- state.attributes.should_not equal( attributes )
32
- state.attributes[:foot].should == "in mouth"
33
- state.attributes[:foot].should_not equal( attributes[:foot] )
34
- state.attributes[:bear].should == "in woods"
35
- state.attributes[:bear].should_not equal( attributes[:bear] )
26
+ after( :all ) do
27
+ reset_logging()
36
28
  end
37
29
 
38
30
 
39
- it "preserves tainted status when copying its attributes" do
40
- attributes = { :danger => "in pants" }
41
- attributes[:danger].taint
31
+ it "provides access to the block it was constructed with if there was one" do
32
+ block = Proc.new {}
33
+ state = Inversion::RenderState.new( &block )
34
+ state.block.should equal( block )
35
+ end
42
36
 
37
+ it "can evaluate code in the context of itself" do
38
+ attributes = { :foot => "in mouth", :bear => "in woods" }
43
39
  state = Inversion::RenderState.new( attributes )
44
-
45
- state.attributes[:danger].should be_tainted()
40
+ state.eval( "foot" ).should == 'in mouth'
46
41
  end
47
42
 
48
43
 
49
- it "preserves frozen status when copying its attributes" do
50
- attributes = { :danger => "in pants" }
51
- attributes[:danger].freeze
44
+ describe "overridable attributes" do
52
45
 
53
- state = Inversion::RenderState.new( attributes )
46
+ it "copies its initial attributes" do
47
+ attributes = { :foot => "in mouth", :bear => "in woods" }
54
48
 
55
- state.attributes[:danger].should be_frozen()
56
- end
49
+ state = Inversion::RenderState.new( attributes )
57
50
 
51
+ state.attributes.should_not equal( attributes )
52
+ state.attributes[:foot].should == "in mouth"
53
+ state.attributes[:foot].should_not equal( attributes[:foot] )
54
+ state.attributes[:bear].should == "in woods"
55
+ state.attributes[:bear].should_not equal( attributes[:bear] )
56
+ end
58
57
 
59
- it "preserves singleton methods on attribute objects when copying" do
60
- obj = Object.new
61
- def obj.foo; "foo!"; end
58
+ it "preserves tainted status when copying its attributes" do
59
+ attributes = { :danger => "in pants" }
60
+ attributes[:danger].taint
62
61
 
63
- state = Inversion::RenderState.new( :foo => obj )
62
+ state = Inversion::RenderState.new( attributes )
64
63
 
65
- state.attributes[:foo].singleton_methods.map( &:to_sym ).should include( :foo )
66
- end
64
+ state.attributes[:danger].should be_tainted()
65
+ end
67
66
 
67
+ it "preserves singleton methods on attribute objects when copying" do
68
+ obj = Object.new
69
+ def obj.foo; "foo!"; end
68
70
 
69
- it "provides access to the block it was constructed with if there was one" do
70
- block = Proc.new {}
71
- state = Inversion::RenderState.new( &block )
72
- state.block.should equal( block )
73
- end
71
+ state = Inversion::RenderState.new( :foo => obj )
74
72
 
73
+ state.attributes[:foo].singleton_methods.map( &:to_sym ).should include( :foo )
74
+ end
75
75
 
76
- it "can evaluate code in the context of itself" do
77
- attributes = { :foot => "in mouth", :bear => "in woods" }
76
+ it "preserves frozen status when copying its attributes" do
77
+ attributes = { :danger => "in pants" }
78
+ attributes[:danger].freeze
78
79
 
79
- state = Inversion::RenderState.new( attributes )
80
+ state = Inversion::RenderState.new( attributes )
80
81
 
81
- state.eval( "foot" ).should == 'in mouth'
82
- end
82
+ state.attributes[:danger].should be_frozen()
83
+ end
83
84
 
84
- it "can override its attributes for the duration of a block" do
85
- attributes = { :foot => "in mouth", :bear => "in woods" }
85
+ it "can override its attributes for the duration of a block" do
86
+ attributes = { :foot => "in mouth", :bear => "in woods" }
86
87
 
87
- state = Inversion::RenderState.new( attributes )
88
+ state = Inversion::RenderState.new( attributes )
88
89
 
89
- state.with_attributes( :foot => 'ball' ) do
90
- state.foot.should == 'ball'
91
- state.bear.should == 'in woods'
92
- end
90
+ state.with_attributes( :foot => 'ball' ) do
91
+ state.foot.should == 'ball'
92
+ state.bear.should == 'in woods'
93
+ end
93
94
 
94
- state.attributes[:foot].should == 'in mouth'
95
- end
95
+ state.attributes[:foot].should == 'in mouth'
96
+ end
96
97
 
97
98
 
98
- it "restores the original attributes if the block raises an exception" do
99
- attributes = { :foot => "in mouth", :bear => "in woods" }
99
+ it "restores the original attributes if the block raises an exception" do
100
+ attributes = { :foot => "in mouth", :bear => "in woods" }
100
101
 
101
- state = Inversion::RenderState.new( attributes )
102
+ state = Inversion::RenderState.new( attributes )
102
103
 
103
- expect {
104
- state.with_attributes( {} ) do
105
- raise "Charlie dooo!"
106
- end
107
- }.to raise_error()
104
+ expect {
105
+ state.with_attributes( {} ) do
106
+ raise "Charlie dooo!"
107
+ end
108
+ }.to raise_error()
108
109
 
109
- state.attributes[:foot].should == 'in mouth'
110
- end
110
+ state.attributes[:foot].should == 'in mouth'
111
+ end
111
112
 
112
113
 
113
- it "raises an error if #with_attributes is called without a block" do
114
- expect {
115
- Inversion::RenderState.new.with_attributes( {} )
116
- }.to raise_error( LocalJumpError, /no block/i )
117
- end
114
+ it "raises an error if #with_attributes is called without a block" do
115
+ expect {
116
+ Inversion::RenderState.new.with_attributes( {} )
117
+ }.to raise_error( LocalJumpError, /no block/i )
118
+ end
118
119
 
120
+ it "provides accessor methods for its attributes" do
121
+ state = Inversion::RenderState.new( :bar => :the_attribute_value )
122
+ state.bar.should == :the_attribute_value
123
+ end
119
124
 
120
- it "can override the render destination for the duration of a block" do
121
- state = Inversion::RenderState.new
125
+ it "doesn't error if an accessor for a non-existant attribute is called" do
126
+ state = Inversion::RenderState.new( :bar => :the_attribute_value )
127
+ state.foo.should be_nil()
128
+ end
122
129
 
123
- original_dest = state.destination
124
- newdest = []
125
- node = Inversion::Template::TextNode.new( "New!" )
126
- rval = state.with_destination( newdest ) do
127
- state << node
130
+ it "can be merged with another RenderState" do
131
+ state = Inversion::RenderState.new(
132
+ {:bar => :the_bar_value},
133
+ {:debugging_comments => false} )
134
+ anotherstate = Inversion::RenderState.new(
135
+ {:foo => :the_foo_value},
136
+ {:debugging_comments => true, :on_render_error => :propagate} )
137
+
138
+ thirdstate = state.merge( anotherstate )
139
+
140
+ thirdstate.attributes.should == {
141
+ :bar => :the_bar_value,
142
+ :foo => :the_foo_value
143
+ }
144
+ thirdstate.options.should include(
145
+ :debugging_comments => true,
146
+ :on_render_error => :propagate
147
+ )
128
148
  end
129
- rval.should equal( newdest )
130
149
 
131
- newdest.should have( 1 ).member
132
- newdest.should include( 'New!' )
133
- state.destination.should equal( original_dest )
134
150
  end
135
151
 
136
- it "restores the original destination if the block raises an exception" do
137
- state = Inversion::RenderState.new
138
152
 
139
- original_dest = state.destination
153
+ describe "context-aware tag state" do
140
154
 
141
- expect {
142
- state.with_destination( [] ) do
143
- raise "New!"
144
- end
145
- }.to raise_error()
155
+ before( :each ) do
156
+ @renderstate = Inversion::RenderState.new
157
+ end
146
158
 
147
- state.destination.should equal( original_dest )
148
- end
159
+ it "provides a mechanism for storing tag state for the current render" do
160
+ @renderstate.tag_data.should be_a( Hash )
161
+ end
149
162
 
150
- it "raises an error if #with_destination is called without a block" do
151
- expect {
152
- Inversion::RenderState.new.with_destination( [] )
153
- }.to raise_error( LocalJumpError, /no block/i )
154
- end
163
+ it "can override tag state for the duration of a block" do
164
+ @renderstate.tag_data[ :montana ] = 'excellent fishing'
165
+ @renderstate.tag_data[ :colorado ] = 'fine fishing'
155
166
 
156
- it "adds a debugging comment when appending a node if debugging comments are enabled" do
157
- node = Inversion::Template::AttrTag.new( 'foo' )
158
- state = Inversion::RenderState.new( {}, :debugging_comments => true )
167
+ @renderstate.with_tag_data( :alaska => 'good fishing' ) do
168
+ @renderstate.tag_data[:alaska].should == 'good fishing'
169
+ @renderstate.tag_data[:alaska] = 'blueberry bear poop'
170
+ @renderstate.tag_data[:colorado] = 'Boulder has hippies'
171
+ end
159
172
 
160
- state << node
173
+ @renderstate.tag_data.should_not have_key( :alaska )
174
+ @renderstate.tag_data[:montana].should == 'excellent fishing'
175
+ @renderstate.tag_data[:colorado].should == 'fine fishing'
176
+ end
161
177
 
162
- state.to_s.should == '<!-- Attr: { template.foo } -->'
163
178
  end
164
179
 
165
- it "doesn't add a debugging comment when appending a node if debugging comments are disabled" do
166
- node = Inversion::Template::AttrTag.new( 'foo' )
167
- state = Inversion::RenderState.new( {}, :debugging_comments => false )
180
+ describe "render destinations" do
168
181
 
169
- state << node
182
+ it "can override the render destination for the duration of a block" do
183
+ state = Inversion::RenderState.new
170
184
 
171
- state.to_s.should == ''
172
- end
185
+ original_dest = state.destination
186
+ newdest = []
187
+ node = Inversion::Template::TextNode.new( "New!" )
188
+ rval = state.with_destination( newdest ) do
189
+ state << node
190
+ end
191
+ rval.should equal( newdest )
173
192
 
174
- it "ignores errors while rendering appended nodes in 'ignore' mode" do
175
- node = Inversion::Template::AttrTag.new( 'boom.klang' )
176
- state = Inversion::RenderState.new( {}, :on_render_error => :ignore )
193
+ newdest.should have( 1 ).member
194
+ newdest.should include( 'New!' )
195
+ state.destination.should equal( original_dest )
196
+ end
177
197
 
178
- state << node
198
+ it "restores the original destination if the block raises an exception" do
199
+ state = Inversion::RenderState.new
179
200
 
180
- state.to_s.should == ''
181
- end
201
+ original_dest = state.destination
182
202
 
183
- it "adds a comment for errors while rendering appended nodes in 'comment' mode" do
184
- node = Inversion::Template::AttrTag.new( 'boom.klang' )
185
- state = Inversion::RenderState.new( {}, :on_render_error => :comment )
203
+ expect {
204
+ state.with_destination( [] ) do
205
+ raise "New!"
206
+ end
207
+ }.to raise_error()
186
208
 
187
- state << node
209
+ state.destination.should equal( original_dest )
210
+ end
211
+
212
+ it "raises an error if #with_destination is called without a block" do
213
+ expect {
214
+ Inversion::RenderState.new.with_destination( [] )
215
+ }.to raise_error( LocalJumpError, /no block/i )
216
+ end
188
217
 
189
- state.to_s.should == "<!-- NoMethodError: undefined method `klang' for nil:NilClass -->"
190
218
  end
191
219
 
192
- it "re-raises errors while rendering appended nodes in 'propagate' mode" do
193
- node = Inversion::Template::AttrTag.new( 'boom.klang' )
194
- state = Inversion::RenderState.new( {}, :on_render_error => :propagate )
195
220
 
196
- expect {
221
+ describe "debugging comments" do
222
+
223
+ it "adds a debugging comment when appending a node if debugging comments are enabled" do
224
+ node = Inversion::Template::AttrTag.new( 'foo' )
225
+ state = Inversion::RenderState.new( {}, :debugging_comments => true )
226
+
197
227
  state << node
198
- }.to raise_error( NoMethodError, /undefined method/ )
199
- end
200
228
 
201
- it "calls the provided handler if an exception is raised while the error handler has been " +
202
- "overridden" do
203
- handler = Proc.new do |state, node, err|
204
- "Yum, I eat %p from %p! Tasting good!" % [err.class, node.class]
229
+ state.to_s.should == '<!-- Attr: { template.foo } -->'
205
230
  end
206
- node = Inversion::Template::AttrTag.new( 'boom.klang' )
207
- state = Inversion::RenderState.new( {}, :on_render_error => :propagate )
208
- defhandler = state.errhandler
209
231
 
210
- expect {
211
- state.with_error_handler( handler ) do
212
- state << node
213
- end
214
- }.to_not raise_error()
232
+ it "doesn't add a debugging comment when appending a node if debugging comments are disabled" do
233
+ node = Inversion::Template::AttrTag.new( 'foo' )
234
+ state = Inversion::RenderState.new( {}, :debugging_comments => false )
215
235
 
216
- state.to_s.should =~ /yum, i eat nomethoderror/i
217
- state.errhandler.should equal( defhandler )
218
- end
236
+ state << node
237
+
238
+ state.to_s.should == ''
239
+ end
219
240
 
220
- it "raises an exception if the error handler is set to something that doesn't respond to #call" do
221
- state = Inversion::RenderState.new
222
- expect {
223
- state.with_error_handler( :foo )
224
- }.to raise_error( ArgumentError, /doesn't respond_to #call/i )
225
241
  end
226
242
 
227
- it "re-raises errors while rendering appended nodes in 'propagate' mode" do
228
- node = Inversion::Template::AttrTag.new( 'boom.klang' )
229
- state = Inversion::RenderState.new( {}, :on_render_error => :propagate )
230
243
 
231
- expect {
244
+ describe "error-handling" do
245
+
246
+ it "ignores errors while rendering appended nodes in 'ignore' mode" do
247
+ node = Inversion::Template::AttrTag.new( 'boom.klang' )
248
+ state = Inversion::RenderState.new( {}, :on_render_error => :ignore )
249
+
232
250
  state << node
233
- }.to raise_error( NoMethodError, /undefined method/ )
234
- end
235
251
 
236
- it "provides accessor methods for its attributes" do
237
- state = Inversion::RenderState.new( :bar => :the_attribute_value )
238
- state.bar.should == :the_attribute_value
239
- end
252
+ state.to_s.should == ''
253
+ end
240
254
 
241
- it "doesn't error if an accessor for a non-existant attribute is called" do
242
- state = Inversion::RenderState.new( :bar => :the_attribute_value )
243
- state.foo.should be_nil()
244
- end
255
+ it "adds a comment for errors while rendering appended nodes in 'comment' mode" do
256
+ node = Inversion::Template::AttrTag.new( 'boom.klang' )
257
+ state = Inversion::RenderState.new( {}, :on_render_error => :comment )
258
+
259
+ state << node
245
260
 
261
+ state.to_s.should == "<!-- NoMethodError: undefined method `klang' for nil:NilClass -->"
262
+ end
246
263
 
247
- it "can be merged with another RenderState" do
248
- state = Inversion::RenderState.new(
249
- {:bar => :the_bar_value},
250
- {:debugging_comments => false} )
251
- anotherstate = Inversion::RenderState.new(
252
- {:foo => :the_foo_value},
253
- {:debugging_comments => true, :on_render_error => :propagate} )
264
+ it "re-raises errors while rendering appended nodes in 'propagate' mode" do
265
+ node = Inversion::Template::AttrTag.new( 'boom.klang' )
266
+ state = Inversion::RenderState.new( {}, :on_render_error => :propagate )
254
267
 
255
- thirdstate = state.merge( anotherstate )
268
+ expect {
269
+ state << node
270
+ }.to raise_error( NoMethodError, /undefined method/ )
271
+ end
256
272
 
257
- thirdstate.attributes.should == {
258
- :bar => :the_bar_value,
259
- :foo => :the_foo_value
260
- }
261
- thirdstate.options.should include(
262
- :debugging_comments => true,
263
- :on_render_error => :propagate
264
- )
273
+ it "calls the provided handler if an exception is raised while the error handler has been " +
274
+ "overridden" do
275
+ handler = Proc.new do |state, node, err|
276
+ "Yum, I eat %p from %p! Tasting good!" % [err.class, node.class]
277
+ end
278
+ node = Inversion::Template::AttrTag.new( 'boom.klang' )
279
+ state = Inversion::RenderState.new( {}, :on_render_error => :propagate )
280
+ defhandler = state.errhandler
281
+
282
+ expect {
283
+ state.with_error_handler( handler ) do
284
+ state << node
285
+ end
286
+ }.to_not raise_error()
287
+
288
+ state.to_s.should =~ /yum, i eat nomethoderror/i
289
+ state.errhandler.should equal( defhandler )
290
+ end
291
+
292
+ it "raises an exception if the error handler is set to something that doesn't respond to #call" do
293
+ state = Inversion::RenderState.new
294
+ expect {
295
+ state.with_error_handler( :foo )
296
+ }.to raise_error( ArgumentError, /doesn't respond_to #call/i )
297
+ end
298
+
299
+ it "re-raises errors while rendering appended nodes in 'propagate' mode" do
300
+ node = Inversion::Template::AttrTag.new( 'boom.klang' )
301
+ state = Inversion::RenderState.new( {}, :on_render_error => :propagate )
302
+
303
+ expect {
304
+ state << node
305
+ }.to raise_error( NoMethodError, /undefined method/ )
306
+ end
265
307
 
266
308
  end
267
309
 
@@ -287,5 +329,40 @@ describe Inversion::RenderState do
287
329
 
288
330
  end
289
331
 
332
+
333
+ describe "conditional rendering" do
334
+
335
+ before( :each ) do
336
+ @state = Inversion::RenderState.new
337
+ end
338
+
339
+ it "allows rendering to be explicitly enabled and disabled" do
340
+ @state.rendering_enabled?.should be_true()
341
+ @state.disable_rendering
342
+ @state.rendering_enabled?.should be_false()
343
+ @state.enable_rendering
344
+ @state.rendering_enabled?.should be_true()
345
+ end
346
+
347
+ it "allows rendering to be toggled" do
348
+ @state.rendering_enabled?.should be_true()
349
+ @state.toggle_rendering
350
+ @state.rendering_enabled?.should be_false()
351
+ @state.toggle_rendering
352
+ @state.rendering_enabled?.should be_true()
353
+ end
354
+
355
+ it "doesn't render nodes that are appended to it if rendering is disabled" do
356
+ @state << Inversion::Template::TextNode.new( "before" )
357
+ @state.disable_rendering
358
+ @state << Inversion::Template::TextNode.new( "during" )
359
+ @state.enable_rendering
360
+ @state << Inversion::Template::TextNode.new( "after" )
361
+
362
+ @state.to_s.should == 'beforeafter'
363
+ end
364
+
365
+ end
366
+
290
367
  end
291
368