require_relative "../spec_helper"
require 'fileutils'
run_tests = true
begin
begin
require 'tilt/sass'
rescue LoadError
begin
for lib in %w'tilt sass'
require lib
end
rescue LoadError
warn "#{lib} not installed, skipping assets plugin test"
run_tests = false
end
end
end
if run_tests
metadata_file = File.expand_path('spec/assets/tmp/precompiled.json')
js_file = File.expand_path('spec/assets/js/head/app.js')
css_file = File.expand_path('spec/assets/css/no_access.css')
js_mtime = File.mtime(js_file)
js_atime = File.atime(js_file)
css_mtime = File.mtime(css_file)
css_atime = File.atime(css_file)
describe 'assets plugin' do
before do
app(:bare) do
plugin :assets,
:css => ['app.scss', 'raw.css'],
:js => { :head => ['app.js'] },
:path => 'spec/assets',
:public => 'spec',
:css_opts => {:cache=>false},
:css_compressor => :none,
:js_compressor => :none
route do |r|
r.assets
r.is 'test' do
"#{assets(:css)}\n#{assets([:js, :head])}"
end
r.is 'paths_test' do
css_paths = assets_paths(:css)
js_paths = assets_paths([:js, :head])
empty_paths = assets_paths(:empty)
{ 'css' => css_paths, 'js' => js_paths, 'empty' => empty_paths }.map do |k, a|
"#{k}:#{a.class}:#{a.length}:#{a.join(',')}"
end.join("\n")
end
end
end
end
after do
File.utime(js_atime, js_mtime, js_file)
File.utime(css_atime, css_mtime, css_file)
File.delete(metadata_file) if File.file?(metadata_file)
end
after(:all) do
FileUtils.rm_r('spec/assets/tmp') if File.directory?('spec/assets/tmp')
FileUtils.rm_r('spec/public') if File.directory?('spec/public')
FileUtils.rm(Dir['spec/assets/app.*.{js,css}*'])
end
def gunzip(body)
Zlib::GzipReader.wrap(StringIO.new(body), &:read)
end
it 'assets_opts should use correct paths given options' do
fpaths = [:js_path, :css_path, :compiled_js_path, :compiled_css_path]
rpaths = [:js_prefix, :css_prefix, :compiled_js_prefix, :compiled_css_prefix]
app.assets_opts.values_at(*fpaths).must_equal %w"spec/assets/js/ spec/assets/css/ spec/assets/app spec/assets/app".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"assets/js/ assets/css/ assets/app assets/app"
app.plugin :assets, :path=>'bar/', :public=>'foo/', :prefix=>'as/', :js_dir=>'j/', :css_dir=>'c/', :compiled_name=>'a'
app.assets_opts.values_at(*fpaths).must_equal %w"bar/j/ bar/c/ foo/as/a foo/as/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/j/ as/c/ as/a as/a"
app.plugin :assets, :path=>'bar', :public=>'foo', :prefix=>'as', :js_dir=>'j', :css_dir=>'c', :compiled_name=>'a'
app.assets_opts.values_at(*fpaths).must_equal %w"bar/j/ bar/c/ foo/as/a foo/as/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/j/ as/c/ as/a as/a"
app.plugin :assets, :compiled_js_dir=>'cj', :compiled_css_dir=>'cs', :compiled_path=>'cp'
app.assets_opts.values_at(*fpaths).must_equal %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/j/ as/c/ as/cj/a as/cs/a"
app.plugin :assets, :compiled_js_route=>'cjr', :compiled_css_route=>'ccr', :js_route=>'jr', :css_route=>'cr'
app.assets_opts.values_at(*fpaths).must_equal %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/jr/ as/cr/ as/cjr/a as/ccr/a"
app.plugin :assets, :compiled_js_route=>'cj', :compiled_css_route=>'cs', :js_route=>'j', :css_route=>'c'
app.assets_opts.values_at(*fpaths).must_equal %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/j/ as/c/ as/cj/a as/cs/a"
app.plugin :assets
app.assets_opts.values_at(*fpaths).must_equal %w"bar/j/ bar/c/ foo/cp/cj/a foo/cp/cs/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/j/ as/c/ as/cj/a as/cs/a"
app.plugin :assets, :compiled_js_dir=>'', :compiled_css_dir=>nil, :compiled_js_route=>nil, :compiled_css_route=>nil
app.assets_opts.values_at(*fpaths).must_equal %w"bar/j/ bar/c/ foo/cp/a foo/cp/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/j/ as/c/ as/a as/a"
app.plugin :assets, :js_dir=>'', :css_dir=>nil, :js_route=>nil, :css_route=>nil
app.assets_opts.values_at(*fpaths).must_equal %w"bar/ bar/ foo/cp/a foo/cp/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/ as/ as/a as/a"
app.plugin :assets, :public=>''
app.assets_opts.values_at(*fpaths).must_equal %w"bar/ bar/ cp/a cp/a".map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal %w"as/ as/ as/a as/a"
app.plugin :assets, :path=>'', :compiled_path=>nil
app.assets_opts.values_at(*fpaths).must_equal ['', '', 'a', 'a'].map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal ['as/', 'as/', 'as/a', 'as/a']
app.plugin :assets, :prefix=>''
app.assets_opts.values_at(*fpaths).must_equal ['', '', 'a', 'a'].map{|s| File.join(Dir.pwd, s)}
app.assets_opts.values_at(*rpaths).must_equal ['', '', 'a', 'a']
app.plugin :assets, :compiled_name=>nil
app.assets_opts.values_at(*fpaths).must_equal ['', ''].map{|s| File.join(Dir.pwd, s)} + ['', ''].map{|s| File.join(Dir.pwd, s).chop}
app.assets_opts.values_at(*rpaths).must_equal ['', '', '', '']
end
it 'assets_opts should use headers and dependencies given options' do
keys = [:css_headers, :js_headers, :dependencies]
app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"text/css; charset=UTF-8"}, {'Content-Type'=>"application/javascript; charset=UTF-8"}, {}]
app.plugin :assets, :headers=>{'A'=>'B'}, :dependencies=>{'a'=>'b'}
app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B'}, {'a'=>'b'}]
app.plugin :assets, :css_headers=>{'C'=>'D'}, :js_headers=>{'E'=>'F'}, :dependencies=>{'c'=>'d'}
app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B', 'C'=>'D'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
app.plugin :assets
app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B', 'C'=>'D'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
app.plugin :assets
app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"text/css; charset=UTF-8", 'A'=>'B', 'C'=>'D'}, {'Content-Type'=>"application/javascript; charset=UTF-8", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
app.plugin :assets, :headers=>{'Content-Type'=>'C', 'E'=>'G'}
app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"C", 'A'=>'B', 'C'=>'D', 'E'=>'G'}, {'Content-Type'=>"C", 'A'=>'B', 'E'=>'F'}, {'a'=>'b', 'c'=>'d'}]
app.plugin :assets, :css_headers=>{'A'=>'B1'}, :js_headers=>{'E'=>'F1'}, :dependencies=>{'c'=>'d1'}
app.assets_opts.values_at(*keys).must_equal [{'Content-Type'=>"C", 'A'=>'B1', 'C'=>'D', 'E'=>'G'}, {'Content-Type'=>"C", 'A'=>'B', 'E'=>'F1'}, {'a'=>'b', 'c'=>'d1'}]
end
it 'assets_paths should return arrays of source paths' do
html = body('/paths_test')
html.scan('css:Array:2:/assets/css/app.scss,/assets/css/raw.css').length.must_equal 1
html.scan('js:Array:1:/assets/js/head/app.js').length.must_equal 1
html.scan('empty:Array:0').length.must_equal 1
end
it 'assets_paths should return the compiled path in an array' do
app.compile_assets
html = body('/paths_test')
css_hash = app.assets_opts[:compiled]['css']
js_hash = app.assets_opts[:compiled]['js.head']
html.scan("css:Array:1:/assets/app.#{css_hash}.css").length.must_equal 1
html.scan("js:Array:1:/assets/app.head.#{js_hash}.js").length.must_equal 1
html.scan('empty:Array:0').length.must_equal 1
end
it 'should handle rendering assets, linking to them, and accepting requests for them when not compiling' do
html = body('/test')
html.scan(/true
html = body('/test')
html.scan(/'-'
html = body('/test')
html.scan(/true
eh = []
html = body('/test', 'rack.early_hints'=>proc{|h| eh << h})
eh.must_equal [
{"Link"=>"; rel=preload; as=style\n; rel=preload; as=style"},
{"Link"=>"; rel=preload; as=script"}
]
html.scan(/'/foo')
html.scan(/'spec/', :js_dir=>'assets/js', :css_dir=>'assets/css', :prefix=>'a',
:js_route=>'foo', :css_route=>'bar', :add_suffix=>true, :css_opts=>{:style=>:compressed}
html = body('/test')
html.scan(/'spec', :js_dir=>nil, :css_dir=>nil, :prefix=>nil,
:css=>{:assets=>{:css=>%w'app.scss raw.css'}}, :js=>{:assets=>{:js=>{:head=>'app.js'}}}
app.route do |r|
r.assets
r.is 'test' do
"#{assets([:css, :assets, :css])}\n#{assets([:js, :assets, :js, :head])}"
end
end
html = body('/test')
html.scan(/ false' do
app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :prefix=>nil, :group_subdirs=>false,
:css=>{:assets=>{:css=>%w'assets/css/app.scss assets/css/raw.css'}}, :js=>{:assets=>{:js=>{:head=>'assets/js/head/app.js'}}}
app.route do |r|
r.assets
r.is 'test' do
"#{assets([:css, :assets, :css])}\n#{assets([:js, :assets, :js, :head])}"
end
end
html = body('/test')
html.scan(/css, :js_compressor=>js
begin
app.compile_assets
rescue LoadError, Roda::RodaPlugins::Assets::CompressorNotFound
next
end
File.read("spec/assets/app.#{app.assets_opts[:compiled]['css']}.css").must_match(/color: ?blue/)
File.read("spec/assets/app.head.#{app.assets_opts[:compiled]['js.head']}.js").must_include('console.log')
end
try_compressor.call(nil, nil)
try_compressor.call(:yui, :yui)
try_compressor.call(:none, :closure)
try_compressor.call(:none, :uglifier)
try_compressor.call(:none, :minjs)
end
it 'should handle compiling assets, linking to them, and accepting requests for them' do
app.plugin :assets, :js=>{:head => %w'comment_1.js comment_2.js'}
app.compile_assets
html = body('/test')
html.scan(/'
end
it '#assets should escape attribute values given' do
app.allocate.assets([:js, :head], 'a'=>'b"e').must_equal ''
end
it 'requests for assets should return 304 if the asset has not been modified' do
loc = '/assets/js/head/app.js'
lm = header('Last-Modified', loc)
status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_equal 304
body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_equal ''
end
it 'requests for assets should not return 304 if the asset has been modified' do
loc = '/assets/js/head/app.js'
lm = header('Last-Modified', loc)
File.utime(js_atime, js_mtime+1, js_file)
status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_equal 200
body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_include('console.log')
end
it 'requests for assets should return 304 if the dependency of an asset has not been modified' do
app.plugin :assets, :dependencies=>{js_file=>css_file}
loc = '/assets/js/head/app.js'
lm = header('Last-Modified', loc)
status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_equal 304
body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_equal ''
end
it 'requests for assets should return 200 if the dependency of an asset has been modified' do
app.plugin :assets, :dependencies=>{js_file=>css_file}
loc = '/assets/js/head/app.js'
lm = header('Last-Modified', loc)
File.utime(css_atime, [css_mtime+2, js_mtime+2].max, css_file)
status(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_equal 200
body(loc, 'HTTP_IF_MODIFIED_SINCE'=>lm).must_include('console.log')
end
it 'should do a terminal match for assets' do
status('/assets/css/app.scss/foo').must_equal 404
end
it 'should only allow files that you specify' do
status('/assets/css/no_access.css').must_equal 404
end
it 'should not add routes for empty asset types' do
app.plugin :assets, :css=>nil
a = app::RodaRequest.assets_matchers
a.length.must_equal 1
a.first.length.must_equal 2
a.first.first.must_equal :js
'assets/js/head/app.js'.must_match a.first.last
'assets/js/head/app2.js'.wont_match a.first.last
end
it 'should not add routes if no asset types' do
app.plugin :assets, :js=>nil, :css=>nil
app::RodaRequest.assets_matchers.must_equal []
end
it 'should support :postprocessor option' do
postprocessor = lambda do |file, type, content|
"file=#{file} type=#{type} tc=#{type.class} #{content.sub('color', 'font')}"
end
app.plugin :assets, :path=>'spec', :js_dir=>nil, :css_dir=>nil, :prefix=>nil,
:postprocessor=>postprocessor,
:css=>{:assets=>{:css=>%w'app.scss'}}
app.route do |r|
r.assets
r.is 'test' do
"#{assets([:css, :assets, :css])}"
end
end
html = body('/test')
html.scan(/metadata_file
File.exist?(metadata_file).must_equal false
app.allocate.assets([:js, :head]).must_equal ''
app.compile_assets
File.exist?(metadata_file).must_equal true
app.allocate.assets([:js, :head]).must_match %r{src="(/assets/app\.head\.[a-f0-9]{64}\.js)"}
app.plugin :assets, :compiled=>false, :precompiled=>false
app.allocate.assets([:js, :head]).must_equal ''
app.plugin :assets, :precompiled=>metadata_file
app.allocate.assets([:js, :head]).must_match %r{src="(/assets/app\.head\.[a-f0-9]{64}\.js)"}
end
it 'should work correctly with json plugin when r.assets is the last method called' do
app.plugin :assets
app.plugin :json
app.route do |r|
r.assets
end
status.must_equal 404
end
end
describe 'assets plugin' do
it "app :root option affects :views default" do
app.plugin :assets
app.assets_opts[:path].must_equal File.join(Dir.pwd, 'assets')
app.assets_opts[:js_path].must_equal File.join(Dir.pwd, 'assets/js/')
app.assets_opts[:css_path].must_equal File.join(Dir.pwd, 'assets/css/')
app.opts[:root] = '/foo'
app.plugin :assets
# Work around for Windows
app.assets_opts[:path].sub(/\A\w:/, '').must_equal '/foo/assets'
app.assets_opts[:js_path].sub(/\A\w:/, '').must_equal '/foo/assets/js/'
app.assets_opts[:css_path].sub(/\A\w:/, '').must_equal '/foo/assets/css/'
app.opts[:root] = '/foo/bar'
app.plugin :assets
app.assets_opts[:path].sub(/\A\w:/, '').must_equal '/foo/bar/assets'
app.assets_opts[:js_path].sub(/\A\w:/, '').must_equal '/foo/bar/assets/js/'
app.assets_opts[:css_path].sub(/\A\w:/, '').must_equal '/foo/bar/assets/css/'
app.opts[:root] = nil
app.plugin :assets
app.assets_opts[:path].must_equal File.join(Dir.pwd, 'assets')
app.assets_opts[:js_path].must_equal File.join(Dir.pwd, 'assets/js/')
app.assets_opts[:css_path].must_equal File.join(Dir.pwd, 'assets/css/')
end
end
end