require_relative "../spec_helper"
begin
require 'tilt/erb'
require 'tilt/string'
rescue LoadError
warn "tilt not installed, skipping render plugin test"
else
describe "render plugin" do
before do
app(:bare) do
plugin :render, :views=>"./spec/views", :check_paths=>true
route do |r|
r.on "home" do
view("home", :locals=>{:name => "Agent Smith", :title => "Home"}, :layout_opts=>{:locals=>{:title=>"Home"}})
end
r.on "about" do
render("about", :locals=>{:title => "About Roda"})
end
r.on "inline" do
view(:inline=>"Hello <%= name %>", :locals=>{:name => "Agent Smith"}, :layout=>nil)
end
r.on "path" do
render(:path=>"./spec/views/about.erb", :locals=>{:title => "Path"}, :layout_opts=>{:locals=>{:title=>"Home"}})
end
r.on "content" do
view(:content=>'bar', :layout_opts=>{:locals=>{:title=>"Home"}})
end
end
end
end
it "default actions" do
body("/about").strip.must_equal "
About Roda
"
body("/home").strip.must_equal "Roda: Home\nHome
\nHello Agent Smith
"
body("/inline").strip.must_equal "Hello Agent Smith"
body("/path").strip.must_equal "Path
"
body("/content").strip.must_equal "Roda: Home\nbar"
end
it "with str as engine" do
app.plugin :render, :engine => "str"
body("/about").strip.must_equal "About Roda
"
body("/home").strip.must_equal "Roda: Home\nHome
\nHello Agent Smith
"
body("/inline").strip.must_equal "Hello <%= name %>"
end
it "custom default layout support" do
app.plugin :render, :layout => "layout-alternative"
body("/home").strip.must_equal "Alternative Layout: Home\nHome
\nHello Agent Smith
"
end
it "using hash for :layout" do
app.plugin :render, :layout => {:inline=> 'a<%= yield %>b'}
body("/home").strip.must_equal "aHome
\nHello Agent Smith
\nb"
end
end
describe "render plugin" do
it "simple layout support" do
app(:bare) do
plugin :render
route do |r|
render(:path=>"spec/views/layout-yield.erb") do
render(:path=>"spec/views/content-yield.erb")
end
end
end
body.gsub(/\n+/, "\n").must_equal "Header\nThis is the actual content.\nFooter\n"
end
it "should have :layout_opts=>:views plugin option respect :root app option" do
app(:bare) do
self.opts[:root] = 'spec'
plugin :render, :layout_opts=>{:views=>"views"}, :allowed_paths=>["spec/views"]
route do |r|
view(:content=>'a', :layout_opts=>{:locals=>{:title=>"Home"}})
end
end
body.strip.must_equal "Roda: Home\na"
end
it "views without default layouts" do
app(:bare) do
plugin :render, :views=>"./spec/views", :layout=>false
route do |r|
view("home", :locals=>{:name=>"Agent Smith", :title=>"Home"})
end
end
body.strip.must_equal "Home
\nHello Agent Smith
"
end
it "layout overrides" do
app(:bare) do
plugin :render, :views=>"./spec/views"
route do |r|
view("home", :locals=>{:name=>"Agent Smith", :title=>"Home" }, :layout=>"layout-alternative", :layout_opts=>{:locals=>{:title=>"Home"}})
end
end
body.strip.must_equal "Alternative Layout: Home\nHome
\nHello Agent Smith
"
end
it ":layout=>true/false/string/hash/not-present respects plugin layout switch and template" do
app(:bare) do
plugin :render, :views=>"./spec/views", :layout_opts=>{:template=>'layout-yield'}
route do |r|
opts = {:content=>'bar'}
opts[:layout] = true if r.path == '/'
opts[:layout] = false if r.path == '/f'
opts[:layout] = 'layout' if r.path == '/s'
opts[:layout] = {:template=>'layout'} if r.path == '/h'
opts[:layout_opts] = {:locals=>{:title=>'a'}}
view(opts)
end
end
body.gsub("\n", '').must_equal "HeaderbarFooter"
body('/a').gsub("\n", '').must_equal "HeaderbarFooter"
body('/f').gsub("\n", '').must_equal "bar"
body('/s').gsub("\n", '').must_equal "Roda: abar"
body('/h').gsub("\n", '').must_equal "Roda: abar"
app.plugin :render
body.gsub("\n", '').must_equal "HeaderbarFooter"
body('/a').gsub("\n", '').must_equal "HeaderbarFooter"
body('/f').gsub("\n", '').must_equal "bar"
body('/s').gsub("\n", '').must_equal "Roda: abar"
body('/h').gsub("\n", '').must_equal "Roda: abar"
app.plugin :render, :layout=>true
body.gsub("\n", '').must_equal "HeaderbarFooter"
body('/a').gsub("\n", '').must_equal "HeaderbarFooter"
body('/f').gsub("\n", '').must_equal "bar"
body('/s').gsub("\n", '').must_equal "Roda: abar"
body('/h').gsub("\n", '').must_equal "Roda: abar"
app.plugin :render, :layout=>'layout-alternative'
body.gsub("\n", '').must_equal "Alternative Layout: abar"
body('/a').gsub("\n", '').must_equal "Alternative Layout: abar"
body('/f').gsub("\n", '').must_equal "bar"
body('/s').gsub("\n", '').must_equal "Roda: abar"
body('/h').gsub("\n", '').must_equal "Roda: abar"
app.plugin :render, :layout=>nil
body.gsub("\n", '').must_equal "HeaderbarFooter"
body('/a').gsub("\n", '').must_equal "bar"
body('/f').gsub("\n", '').must_equal "bar"
body('/s').gsub("\n", '').must_equal "Roda: abar"
body('/h').gsub("\n", '').must_equal "Roda: abar"
app.plugin :render, :layout=>false
body.gsub("\n", '').must_equal "HeaderbarFooter"
body('/a').gsub("\n", '').must_equal "bar"
body('/f').gsub("\n", '').must_equal "bar"
body('/s').gsub("\n", '').must_equal "Roda: abar"
body('/h').gsub("\n", '').must_equal "Roda: abar"
app.plugin :render, :layout_opts=>{:template=>'layout-alternative', :locals=>{:title=>'a'}}
body.gsub("\n", '').must_equal "Alternative Layout: abar"
body('/a').gsub("\n", '').must_equal "bar"
body('/f').gsub("\n", '').must_equal "bar"
body('/s').gsub("\n", '').must_equal "Roda: abar"
body('/h').gsub("\n", '').must_equal "Roda: abar"
end
it "app :root option affects :views default" do
app
app.plugin :render
app.render_opts[:views].must_equal File.join(Dir.pwd, 'views')
app.opts[:root] = '/foo'
app.plugin :render
# Work around for Windows
app.render_opts[:views].sub(/\A\w:/, '').must_equal '/foo/views'
app.opts[:root] = '/foo/bar'
app.plugin :render
app.render_opts[:views].sub(/\A\w:/, '').must_equal '/foo/bar/views'
app.opts[:root] = nil
app.plugin :render
app.render_opts[:views].must_equal File.join(Dir.pwd, 'views')
app.plugin :render, :views=>'bar'
app.render_opts[:views].must_equal File.join(Dir.pwd, 'bar')
end
it "inline layouts and inline views" do
app(:render) do
view({:inline=>'bar'}, :layout=>{:inline=>'Foo: <%= yield %>'})
end
body.strip.must_equal "Foo: bar"
end
it "inline renders with opts" do
app(:render) do
render({:inline=>'<%= bar %>'}, {:engine=>'str'})
end
body.strip.must_equal '<%= bar %>'
end
it "template renders with :template opts" do
app(:bare) do
plugin :render, :views => "./spec/views"
route do
render(:template=>"about", :locals=>{:title => "About Roda"})
end
end
body.strip.must_equal "About Roda
"
end
it "template renders with :template_class opts" do
app(:render) do
@a = 1
render(:inline=>'i#{@a}', :template_class=>::Tilt[:str])
end
body.must_equal "i1"
end
it "can specify engine-specific options via :engine_opts" do
app(:bare) do
plugin :render, :engine_opts=>{'a.erb'=>{:outvar=>'@a'}}
route do |r|
@a = nil
r.is('a') do
render(:inline=>'<%= @a.class.name %>', :engine=>'a.erb')
end
render(:inline=>'<%= @a.class.name %>')
end
end
body('/a').must_equal "String"
body.must_equal "NilClass"
end
it "template cache respects :template_opts" do
c = Class.new do
def initialize(path, _, opts)
@path = path
@opts = opts
end
def render(*)
"#{@path}-#{@opts[:foo]}"
end
end
app(:render) do |r|
r.is "a" do
render(:inline=>"i", :template_class=>c, :template_opts=>{:foo=>'a'})
end
r.is "b" do
render(:inline=>"i", :template_class=>c, :template_opts=>{:foo=>'b'})
end
end
body('/a').must_equal "i-a"
body('/b').must_equal "i-b"
end
it "template cache respects :template_block" do
c = Class.new do
def initialize(path, *, &block)
@path = path
@block = block
end
def render(*)
"#{@path}-#{@block.call}"
end
end
proca = proc{'a'}
procb = proc{'b'}
app(:render) do |r|
r.is "a" do
render(:path=>"i", :template_class=>c, :template_block=>proca)
end
r.is "b" do
render(:path=>"i", :template_class=>c, :template_block=>procb)
end
end
body('/a').must_equal "i-a"
body('/b').must_equal "i-b"
end
it "template cache respects :locals" do
template = '<%= @a ? b : c %>'
app(:render) do |r|
r.is "a" do
@a = true
render(:inline=>template.dup, :locals=>{:b=>1})
end
r.is "b" do
@a = true
render(:inline=>template.dup, :locals=>{:b=>2, :c=>4})
end
r.is "c" do
@a = nil
render(:inline=>template.dup, :locals=>{:c=>3})
end
end
body('/a').must_equal "1"
body('/b').must_equal "2"
body('/c').must_equal "3"
end
it "Default to :explicit_cache=>true in development mode" do
with_rack_env('development') do
app(:render){}
end
app.render_opts[:explicit_cache].must_equal true
app(:render){}
app.render_opts[:explicit_cache].must_equal false
end
it "Support :cache=>false plugin option to disable template caching by default, except :cache=>true method option is given" do
app(:bare) do
plugin :render, :views=>"./spec/views", :cache=>false
route do |r|
@a = 'a'
r.is('a'){render('iv', :cache=>false, :cache_key=>:a)}
r.is('b'){render('iv', :cache=>true, :cache_key=>:a)}
render('iv', :cache_key=>:a)
end
end
body('/a').strip.must_equal "a"
app.render_opts[:cache][:a].must_be_nil
body('/b').strip.must_equal "a"
app.render_opts[:cache][:a].wont_be_nil
body('/c').strip.must_equal "a"
app.render_opts[:cache][:a].wont_be_nil
end
it "Support :cache=>false option to disable template caching" do
app(:bare) do
plugin :render, :views=>"./spec/views"
route do |r|
@a = 'a'
r.is('a'){render('iv', :cache=>false)}
render('iv')
end
end
body('/a').strip.must_equal "a"
app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].must_be_nil
body('/b').strip.must_equal "a"
app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].wont_equal nil
end
it "Support :cache=>false option to disable template caching even when :cache_key is given" do
app(:bare) do
plugin :render, :views=>"./spec/views"
route do |r|
@a = 'a'
r.is('a'){render('iv', :cache=>false, :cache_key=>:foo)}
render('iv', :cache_key=>:foo)
end
end
body('/a').strip.must_equal "a"
app.render_opts[:cache][:foo].must_be_nil
body('/b').strip.must_equal "a"
app.render_opts[:cache][:foo].wont_equal nil
end
it "Support :explicit_cache option to disable caching by default, but still allow caching on a per-call basis" do
app(:bare) do
plugin :render, :views=>"./spec/views", :explicit_cache=>true
route do |r|
@a = 'a'
r.is('a'){render('iv')}
render('iv', :cache=>true)
end
end
body('/a').strip.must_equal "a"
app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].must_be_nil
body('/b').strip.must_equal "a"
app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].wont_equal nil
end
it "Support :explicit_cache plugin option with :cache_key render option" do
app(:bare) do
plugin :render, :views=>"./spec/views", :explicit_cache=>true
route do |r|
@a = 'a'
r.is('a'){render('iv', :cache_key=>:foo)}
render('iv', :cache=>true, :cache_key=>:foo)
end
end
body('/a').strip.must_equal "a"
app.render_opts[:cache][:foo].must_be_nil
body('/b').strip.must_equal "a"
app.render_opts[:cache][:foo].wont_equal nil
end
it "Support :cache=>true option to enable template caching when :template_block is used" do
c = Class.new do
def initialize(path, *, &block)
@path = path
@block = block
end
def render(*)
"#{@path}-#{@block.call}"
end
end
proca = proc{'a'}
app(:bare) do
plugin :render, :views=>"./spec/views"
route do |r|
@a = 'a'
r.is('a'){render(:path=>'iv', :template_class=>c, :template_block=>proca)}
render(:path=>'iv', :template_class=>c, :template_block=>proca, :cache=>true)
end
end
body('/a').strip.must_equal "iv-a"
app.render_opts[:cache][['iv', c, nil, nil, proca]].must_be_nil
body('/b').strip.must_equal "iv-a"
app.render_opts[:cache][['iv', c, nil, nil, proca]].wont_equal nil
end
it "Support :cache_key option to force the key used when caching, unless :cache=>false option is used" do
app(:bare) do
plugin :render, :views=>"./spec/views"
route do |r|
@a = 'a'
r.is('a'){render('iv', :cache_key=>:a)}
r.is('about'){render('about', :cache_key=>:a, :cache=>false, :locals=>{:title=>'a'})}
render('about', :cache_key=>:a)
end
end
body('/a').strip.must_equal "a"
body('/b').strip.must_equal "a"
body('/about').strip.must_equal "a
"
end
it "Support :scope option to override object in which to evaluate the template" do
app(:bare) do
plugin :render, :views=>"./spec/views"
route do |r|
render(:inline=>'<%= first %>-<%= last %>', :scope=>[1,2])
end
end
body.must_equal "1-2"
end
it "should dup render_opts when subclassing" do
c = Class.new(Roda)
c.plugin :render, :foo=>:bar
sc = Class.new(c)
c.render_opts.wont_be_same_as(sc.render_opts)
c.render_opts[:foo].must_equal :bar
end
it "should use a copy of superclass's cache when subclassing" do
c = Class.new(Roda)
c.plugin :render
c.render_opts[:cache][:foo] = 1
sc = Class.new(c)
c.render_opts.wont_be_same_as(sc.render_opts)
c.render_opts[:cache].wont_be_same_as(sc.render_opts[:cache])
sc.render_opts[:cache][:foo].must_equal 1
end
it "should not modifying existing cache if loading the plugin a separate time" do
c = Class.new(Roda)
c.plugin :render
cache = c.render_opts[:cache]
c.render_opts[:explicit_cache].must_equal false
c.plugin :render
c.render_opts[:cache].must_be_same_as cache
c.render_opts[:explicit_cache].must_equal false
c.plugin :render, :cache=>false
c.render_opts[:cache].must_be_same_as cache
c.render_opts[:explicit_cache].must_equal true
c.plugin :render
c.render_opts[:cache].must_be_same_as cache
c.render_opts[:explicit_cache].must_equal true
end
it "render plugin call should not override existing options" do
c = Class.new(Roda)
c.plugin :render, :layout=>:foo
c.plugin :render
c.render_opts[:layout].must_equal :foo
end
it "should not use cache by default in subclass if not caching by default in superclass" do
app(:bare) do
plugin :render, :views=>"./spec/views", :cache=>false
route do |r|
view(:inline=>"Hello <%= name %>", :cache_key=>:a, :locals=>{:name => "Agent Smith"}, :layout=>nil)
end
end
body("/inline").strip.must_equal "Hello Agent Smith"
Class.new(app).render_opts[:cache][:a].must_be_nil
end
it "with :check_paths=>true plugin option used" do
render_opts = {}
app(:bare) do
plugin :render, :views=>"./spec/views", :check_paths=>true
route do |r|
r.get 'a' do
render("a", render_opts)
end
r.get 'c' do
render("about/_test", :locals=>{:title=>'a'})
end
render("b", render_opts)
end
end
body.strip.must_equal "b"
req("/a")
req("/c")
app.plugin :render, :allowed_paths=>[]
proc{req}.must_raise Roda::RodaError
proc{req("/a")}.must_raise Roda::RodaError
proc{req("/c")}.must_raise Roda::RodaError
app.plugin :render, :allowed_paths=>['spec/views/about']
proc{req}.must_raise Roda::RodaError
proc{req("/a")}.must_raise Roda::RodaError
req("/c")
app.plugin :render, :allowed_paths=>%w'spec/views/about spec/views/b'
body.strip.must_equal "b"
proc{req("/a")}.must_raise Roda::RodaError
req("/c")
render_opts[:check_paths] = true
app.plugin :render, :check_paths=>false
body.strip.must_equal "b"
proc{req("/a")}.must_raise Roda::RodaError
req("/c")
render_opts.delete(:check_paths)
app.plugin :render
body.strip.must_equal "b"
req("/a")
req("/c")
render_opts[:check_paths] = true
body.strip.must_equal "b"
proc{req("/a")}.must_raise Roda::RodaError
req("/c")
end
it "with a cache_class set" do
app(:bare) do
test_cache = Class.new(Roda::RodaCache) do
def [](key)
super
end
def []=(key, value)
super
end
def test_method
end
end
plugin :render, :views=>"./spec/views", :cache=>true, :cache_class=>test_cache
route do |r|
view(:inline=>"foo", :layout=>nil)
end
end
body("/inline").strip.must_equal "foo"
Class.new(app).render_opts[:cache].must_respond_to :test_method
end
end
end
begin
require 'tilt'
require 'tilt/erubi'
rescue LoadError
warn "tilt 2 or erubi not installed, skipping render :escape=>true test"
else
describe ":render plugin :escape option" do
before do
if defined?(Tilt::ErubiTemplate) && ::Tilt['erb'] != Tilt::ErubiTemplate
# Set erubi as default erb template handler
Tilt.register(Tilt::ErubiTemplate, 'erb')
end
end
it "should escape inside <%= %> and not inside <%== %>, and handle postfix conditionals" do
app(:bare) do
plugin :render, :escape=>:erubi
route do |r|
render(:inline=>'<%= "<>" %> <%== "<>" %><%= "<>" if false %>')
end
end
body.must_equal '<> <>'
end
it "should allow for per-branch escaping via set_view options" do
app(:bare) do
plugin :render, :escape=>:erubi
plugin :view_options
route do |r|
set_view_options :template_opts=>{:escape=>false}
r.is 'a' do
set_view_options :template_opts=>{:engine_class=>render_opts[:template_opts][:engine_class]}
render(:inline=>'<%= "<>" %>')
end
render(:inline=>'<%= "<>" %>')
end
end
body('/a').must_equal '<>'
body.must_equal '<>'
end
end
end