/
template_spec.rb
395 lines (290 loc) · 11.4 KB
/
template_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
#!/usr/bin/env rspec -cfd -b
# vim: set noet nosta sw=4 ts=4 :
BEGIN {
require 'pathname'
basedir = Pathname( __FILE__ ).dirname.parent.parent
libdir = basedir + 'lib'
$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
}
require 'rspec'
require 'stringio'
require 'spec/lib/helpers'
require 'inversion/template'
describe Inversion::Template do
before( :all ) do
setup_logging( :fatal )
end
it "can be loaded from a String" do
Inversion::Template.new( "a template" ).source.should == 'a template'
end
it "renders the source as-is if there are no instructions" do
Inversion::Template.new( "a template" ).render.should == 'a template'
end
it "renders when stringified" do
Inversion::Template.new( "a template" ).to_s.should == 'a template'
end
it "calls before and after rendering hooks on all of its nodes when rendered" do
node = double( "fake node" )
parentstate = Inversion::RenderState.new( :foo => 'the merged stuff' )
tmpl = Inversion::Template.new( '' )
tmpl.node_tree << node
node.should_receive( :before_rendering ).with( an_instance_of(Inversion::RenderState) )
node.should_receive( :render ).with( an_instance_of(Inversion::RenderState) )
node.should_receive( :after_rendering ).with( an_instance_of(Inversion::RenderState) )
tmpl.render( parentstate )
end
it "passes the block it was rendered with to its RenderState" do
node = double( "fake node", :before_rendering => nil, :after_rendering => nil )
tmpl = Inversion::Template.new( '' )
tmpl.node_tree << node
renderblock = Proc.new {}
node.should_receive( :render ).and_return do |renderstate|
renderstate.block.should equal( renderblock )
end
tmpl.render( &renderblock )
end
it "carries its global configuration to the parser" do
begin
orig_config = Inversion::Template.config
Inversion::Template.configure( :ignore_unknown_tags => false )
expect {
Inversion::Template.new( '<?rumple an unknown tag ?>' )
}.to raise_error( Inversion::ParseError, /unknown tag/i )
ensure
Inversion::Template.config = orig_config
end
end
it "can make an human-readable string version of itself suitable for debugging" do
IO.should_receive( :read ).with( '/tmp/inspect.tmpl' ).and_return( '<?attr foo ?>' )
tmpl = Inversion::Template.load( '/tmp/inspect.tmpl' )
tmpl.inspect.should =~ /Inversion::Template/
tmpl.inspect.should =~ %r{/tmp/inspect.tmpl}
tmpl.inspect.should =~ /attributes/
tmpl.inspect.should_not =~ /node_tree/
end
it "includes the node tree in the inspected object if debugging is enabled" do
begin
debuglevel = $DEBUG
$DEBUG = true
tmpl = Inversion::Template.new( '<?attr something ?>' )
tmpl.inspect.should =~ /node_tree/
ensure
$DEBUG = debuglevel
end
end
it "provides accessors for attributes that aren't identifiers in the template" do
tmpl = Inversion::Template.new( '' )
tmpl.foo = :bar
tmpl.foo.should == :bar
end
it "can pass an encoding option to IO.open through the template constructor" do
content = 'some stuff'.encode( 'utf-8' )
IO.should_receive( :read ).with( '/a/utf8/template.tmpl', encoding: 'utf-8' ).and_return( content )
template = Inversion::Template.load( '/a/utf8/template.tmpl', encoding: 'utf-8' )
template.render.encoding.should == Encoding::UTF_8
end
context "loaded from a file" do
before( :each ) do
@timestamp = Time.now
content = 'file contents'.taint
IO.stub( :read ).with( '/tmp/hooowat' ).and_return( content )
@template = Inversion::Template.load( '/tmp/hooowat' )
end
it "untaints template content loaded from a file" do
@template.source.should_not be_tainted()
end
it "can be reloaded" do
newcontent = 'changed file contents'.taint
IO.should_receive( :read ).with( '/tmp/hooowat' ).and_return( newcontent )
@template.reload
@template.source.should == newcontent
end
context "that hasn't changed since it was loaded" do
before( :each ) do
@template.source_file.stub!( :mtime ).and_return( @timestamp )
end
it "knows that it hasn't changed" do
@template.should_not be_changed()
end
context "with a stat delay" do
before( :each ) do
@template.options[ :stat_delay ] = 30
end
it "returns unchanged if the delay time hasn't expired" do
@template.instance_variable_set( :@last_checked, @timestamp )
@template.should_not be_changed()
end
it "returns unchanged if the delay time has expired" do
@template.source_file.stub!( :mtime ).and_return( @timestamp - 30 )
@template.instance_variable_set( :@last_checked, @timestamp - 30 )
@template.should_not be_changed()
end
end
end
context "that has changed since it was loaded" do
before( :each ) do
@template.source_file.stub!( :mtime ).and_return( @timestamp + 1 )
end
it "knows that is has changed" do
@template.should be_changed()
end
context "with a stat delay" do
before( :each ) do
@template.options[ :stat_delay ] = 30
end
it "returns unchanged if the delay time hasn't expired" do
@template.instance_variable_set( :@last_checked, @timestamp )
@template.should_not be_changed()
end
it "returns changed if the delay time has expired" do
@template.instance_variable_set( :@last_checked, @timestamp - 60 )
@template.should be_changed()
end
end
end
end
context "loaded from a String" do
before( :each ) do
@template = Inversion::Template.new( 'some stuff' )
end
it "never says it has changed" do
@template.should_not be_changed()
end
it "raises an exception if reloaded" do
expect {
@template.reload
}.to raise_error( Inversion::Error, /not loaded from a file/i )
end
end
context "without template paths set" do
before( :each ) do
Inversion::Template.template_paths.clear
end
it "instances can be loaded from an absolute path" do
IO.should_receive( :read ).with( '/tmp/hooowat' ).and_return( 'file contents' )
Inversion::Template.load( '/tmp/hooowat' ).source.should == 'file contents'
end
it "instances can be loaded from a path relative to the current working directory" do
tmplpath = Pathname.pwd + 'hooowat.tmpl'
FileTest.should_receive( :exist? ).with( tmplpath.to_s ).and_return( true )
IO.should_receive( :read ).with( tmplpath.to_s ).and_return( 'file contents' )
Inversion::Template.load( 'hooowat.tmpl' ).source.should == 'file contents'
end
end
context "with template paths set" do
before( :each ) do
Inversion::Template.template_paths = [ '/tmp', '/fun' ]
end
after( :each ) do
Inversion::Template.template_paths.clear
end
it "instances can be loaded from an absolute path" do
FileTest.should_not_receive( :exist? )
IO.should_receive( :read ).with( '/tmp/hooowat' ).and_return( 'file contents' )
Inversion::Template.load( '/tmp/hooowat' ).source.should == 'file contents'
end
it "raises a runtime exception if unable to locate the template" do
tmplpath = Pathname.pwd + 'sadmanhose.tmpl'
FileTest.should_receive( :exist? ).with( '/tmp/sadmanhose.tmpl' ).and_return( false )
FileTest.should_receive( :exist? ).with( '/fun/sadmanhose.tmpl' ).and_return( false )
FileTest.should_receive( :exist? ).with( tmplpath.to_s ).and_return( false )
expect {
Inversion::Template.load( 'sadmanhose.tmpl' )
}.to raise_error( RuntimeError, /unable to find template ".+" within configured paths/i )
end
it "loads template relative to directories in the template_paths" do
FileTest.should_receive( :exist? ).with( '/tmp/hooowat.tmpl' ).and_return( false )
FileTest.should_receive( :exist? ).with( '/fun/hooowat.tmpl' ).and_return( true )
IO.should_receive( :read ).with( '/fun/hooowat.tmpl' ).and_return( 'file contents' )
Inversion::Template.load( 'hooowat.tmpl' ).source.should == 'file contents'
end
it "falls back to loading the template relative to the current working directory" do
tmplpath = Pathname.pwd + 'hooowat.tmpl'
FileTest.should_receive( :exist? ).with( '/tmp/hooowat.tmpl' ).and_return( false )
FileTest.should_receive( :exist? ).with( '/fun/hooowat.tmpl' ).and_return( false )
FileTest.should_receive( :exist? ).with( tmplpath.to_s ).and_return( true )
IO.should_receive( :read ).with( tmplpath.to_s ).and_return( 'file contents' )
Inversion::Template.load( 'hooowat.tmpl' ).source.should == 'file contents'
end
end
context "with an attribute PI" do
let( :template ) { Inversion::Template.new("<h1><?attr foo ?></h1>") }
it "has a reader for getting the attribute's value" do
template.should respond_to( :foo )
end
it "has an accessor for setting the attribute's value" do
template.should respond_to( :foo= )
end
it "renders scalar values set for the attribute" do
template.foo = "a lion"
template.render.should == "<h1>a lion</h1>"
end
it "renders an non-String value set for the attribute using #to_s" do
template.foo = [ 'a lion', 'a little guy', 'a bad mousie', 'one birdy' ]
template.render.should == %{<h1>a liona little guya bad mousieone birdy</h1>}
end
end
context "with several attribute PIs" do
let( :template ) { Inversion::Template.new("<h1><?attr foo ?> <?attr foo?> RUN!</h1>") }
it "has a reader for getting the attribute's value" do
template.should respond_to( :foo )
end
it "has an accessor for setting the attribute's value" do
template.should respond_to( :foo= )
end
it "renders scalar values set for the attribute(s)" do
template.foo = "lions!!"
template.render.should == "<h1>lions!! lions!! RUN!</h1>"
end
end
describe "Configurability support", :if => defined?( Configurability ) do
after( :each ) do
Inversion::Template.config = Inversion::Template::DEFAULT_CONFIG
end
it "is included in the list of configurable objects" do
Configurability.configurable_objects.should include( Inversion::Template )
end
it "can be configured using a Configurability::Config object" do
config = Configurability::Config.new( %{
---
templates:
ignore_unknown_tags: false
debugging_comments: true
comment_start: "#"
comment_end: ""
}.gsub(/^\t{3}/, '') )
Inversion::Template.configure( config.templates )
Inversion::Template.config[:ignore_unknown_tags].should be_false()
Inversion::Template.config[:debugging_comments].should be_true()
Inversion::Template.config[:comment_start].should == '#'
Inversion::Template.config[:comment_end].should == ''
end
end
describe "exception-handling:" do
before( :each ) do
@source = "Some stuff\n<?call obj.raise_exception ?>\nMore stuff"
@tmpl = Inversion::Template.new( @source )
@obj = Object.new
def @obj.raise_exception
raise "Okay, here's an exception!"
end
@tmpl.obj = @obj
end
it "can be configured to completely ignore exceptions raised while rendering" do
@tmpl.options[:on_render_error] = :ignore
@tmpl.render.should == "Some stuff\nMore stuff"
end
it "can be configured to insert debugging comments for exceptions raised while rendering" do
@tmpl.options[:on_render_error] = :comment
@tmpl.render.should ==
"Some stuff\n<!-- RuntimeError: Okay, here's an exception! -->More stuff"
end
it "can be configured to propagate exceptions raised while rendering" do
@tmpl.options[:on_render_error] = :propagate
expect {
@tmpl.render
}.to raise_exception( RuntimeError, /Okay, here's an exception!/ )
end
end
end