# encoding: UTF-8

require File.expand_path('../../test_helper', __FILE__)
require 'ostruct'

describe "Assets" do
  include RackTestMethods

  let(:app) {Spontaneous::Rack::Back.application(site)}

  module LiveSimulation
    # simulate a production + publishing environment
    def live?
      true
    end

    # usually set as part of the render process
    def revision
      99
    end
  end

  def new_context(live, content = @page, format = :html, params = {})
    renderer = if live
                 Spontaneous::Output::Template::PublishRenderer.new(site)
               else
                 Spontaneous::Output::Template::PreviewRenderer.new(site)
               end
    output = content.output(format)
    context = renderer.context(output, params, nil)
    context.extend LiveSimulation if live
    context.class_eval do
      # Force us into production environment
      # which is where most of the magic has to happen
      def development?
        false
      end
    end
    context
  end

  def live_context(content = @page, format = :html, params = {})
    new_context(true, content, format, params)
  end

  def preview_context(content = @page, format = :html, params = {})
    new_context(false, content, format, params)
  end

  def development_context(content = @page, format = :html, params = {})
    new_context(false, content, format, params).tap do |context|
      context.class_eval do
        def development?
          true
        end
      end
    end
  end

  def asset_digest(asset_relative_path)
    digest = context.asset_environment.environment.digest
    digest.update(File.read(File.join(fixture_root, asset_relative_path)))
    digest.hexdigest
  end

  let(:y_png_digest) { asset_digest('public2/i/y.png') }

  start do
    fixture_root = File.expand_path("../../fixtures/assets", __FILE__)
    site = setup_site
    site.paths.add :assets, fixture_root / "public1", fixture_root / "public2"
    site.config.tap do |c|
      c.auto_login = 'root'
    end
    site.output_store(:Memory)
    Spontaneous::Permissions::User.delete
    user = Spontaneous::Permissions::User.create(:email => "root@example.com", :login => "root", :name => "root name", :password => "rootpass")
    user.update(:level => Spontaneous::Permissions[:editor])
    user.save.reload
    key = user.generate_access_key("127.0.0.1")

    Spontaneous::Permissions::User.stubs(:[]).with(:login => 'root').returns(user)
    Spontaneous::Permissions::User.stubs(:[]).with(user.id).returns(user)
    Spontaneous::Permissions::AccessKey.stubs(:authenticate).with(key.key_id).returns(key)

    let(:site) { site }
    let(:fixture_root) { fixture_root }
    let(:user) { user }
    let(:key) { key }
  end

  finish do
    teardown_site
  end

  before do
    @page = Page.create
  end

  after do
    tmp = site.path('assets/tmp')
    FileUtils.rm_r(tmp) if tmp.exist?
    Content.delete
  end

  describe "Preview context" do
    it "should not be flagged as publishing" do
      refute preview_context.publishing?
    end
    it "should not have the development? flag set" do
      refute preview_context.development?
    end
  end

  describe "Development context" do
    it "should not be flagged as publishing" do
      refute development_context.publishing?
    end

    it "should have the development? flag set" do
      assert development_context.development?
    end
  end

  describe "Publishing context" do
    it "be flagged as publishing" do
      Spontaneous.stubs(:production?).returns(true)
      assert live_context.publishing?
    end

    it "be flagged as live" do
      Spontaneous.stubs(:production?).returns(true)
      assert live_context.live?
    end

    it "be flagged as publishing" do
      assert live_context.publishing?
    end
  end

  describe "development" do
    let(:context) { development_context }

    let(:a_js_digest) { asset_digest('public1/js/a.js') }
    let(:b_js_digest) { asset_digest('public2/js/b.js') }
    let(:c_js_digest) { asset_digest('public2/js/c.js') }
    let(:x_js_digest) { asset_digest('public1/x.js') }
    # these are compiled so fairly complex to calculate their digests
    # not impossible, but annoying
    let(:n_js_digest) { '74f175e03a4bdc8c807aba4ae0314938' }
    let(:m_js_digest) { 'dd35b142dc75b6ec15b2138e9e91c0c3' }
    let(:all_js_digest) { 'd406fc3c21d90828a2f0a718c89e8d99' }

    let(:a_css_digest) { '7b04d295476986c24d8c77245943e5b9' }
    let(:b_css_digest) { '266643993e14da14f2473d45f003bd2c' }
    let(:c_css_digest) { 'fc8ba0d0aae64081dc00b8444a198fb8' }
    let(:x_css_digest) { '2560aec2891794825eba770bf84823fb' }
    let(:all_css_digest) { 'cf61c624b91b9ea126804291ac55bd5d' }

    it "includes all js dependencies" do
      result = context.scripts('js/all', 'js/m', 'js/c', 'x')
      result.must_equal [
        %|<script type="text/javascript" src="/assets/js/a.js?body=1&#{a_js_digest}"></script>|,
        %|<script type="text/javascript" src="/assets/js/b.js?body=1&#{b_js_digest}"></script>|,
        %|<script type="text/javascript" src="/assets/js/n.js?body=1&#{n_js_digest}"></script>|,
        %|<script type="text/javascript" src="/assets/js/all.js?body=1&#{all_js_digest}"></script>|,
        %|<script type="text/javascript" src="/assets/js/m.js?body=1&#{m_js_digest}"></script>|,
        %|<script type="text/javascript" src="/assets/js/c.js?body=1&#{c_js_digest}"></script>|,
        %|<script type="text/javascript" src="/assets/x.js?body=1&#{x_js_digest}"></script>|
      ].join("\n")
    end

    it "doesn't bundle js files" do
      get "/assets/js/all.js?body=1"
      result = last_response.body
      result.wont_match /elvis/
    end

    it "includes all css dependencies" do
      result = context.stylesheets('css/all', 'css/c', 'x')
      result.must_equal [
        %|<link rel="stylesheet" href="/assets/css/b.css?body=1&#{b_css_digest}" />|,
        %|<link rel="stylesheet" href="/assets/css/a.css?body=1&#{a_css_digest}" />|,
        %|<link rel="stylesheet" href="/assets/css/all.css?body=1&#{all_css_digest}" />|,
        %|<link rel="stylesheet" href="/assets/css/c.css?body=1&#{c_css_digest}" />|,
        %|<link rel="stylesheet" href="/assets/x.css?body=1&#{x_css_digest}" />|
      ].join("\n")
    end

    it "doesn't bundle js files" do
      get "/assets/css/all.css?body=1"
      result = last_response.body
      result.must_match %r(/\*\s+\*/)
    end

    it "allows for protocol agnostic absolute script urls" do
      result = context.scripts('//use.typekit.com/abcde')
      result.must_equal '<script type="text/javascript" src="//use.typekit.com/abcde"></script>'
    end

  end

  describe "preview" do
    let(:app) { Spontaneous::Rack::Back.application(site) }
    let(:context) { preview_context }

    let(:c_js_digest) { 'f669550dd7e10e9646ad781f44756950' }
    let(:x_js_digest) { '6b4c9176b2838a4949a18284543fc19c' }
    let(:n_js_digest) { '74f175e03a4bdc8c807aba4ae0314938' }
    let(:m_js_digest) { 'dd35b142dc75b6ec15b2138e9e91c0c3' }
    let(:all_js_digest) { 'cd1f681752f5038421be0bc5ea0e855d' }

    let(:c_css_digest) { 'fc8ba0d0aae64081dc00b8444a198fb8' }
    let(:x_css_digest) { '2560aec2891794825eba770bf84823fb' }
    let(:all_css_digest) { 'bb2c289a27b3d5d4467dde6d60722fd3' }

    describe "javascript" do
      it "include scripts as separate files with finger prints" do
        result = context.scripts('js/all', 'js/m.js', 'js/c.js', 'x')
        result.must_equal [
          %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
          %|<script type="text/javascript" src="/assets/js/m.js?#{m_js_digest}"></script>|,
          %|<script type="text/javascript" src="/assets/js/c.js?#{c_js_digest}"></script>|,
          %|<script type="text/javascript" src="/assets/x.js?#{x_js_digest}"></script>|
        ].join("\n")
      end



      it "handles urls passed as an array" do
        result = context.scripts(['js/all', 'js/m.js'])
        result.must_equal [
          %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
          %|<script type="text/javascript" src="/assets/js/m.js?#{m_js_digest}"></script>|
        ].join("\n")
      end

      it "should ignore missing files" do
        result = context.scripts('js/all', 'js/missing')
        result.must_equal [
          %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
          '<script type="text/javascript" src="js/missing.js"></script>'
        ].join("\n")
      end

      it "should pass through absolute urls" do
        result = context.scripts('/js/all.js')
        result.must_equal '<script type="text/javascript" src="/js/all.js"></script>'
      end

      it "should bundle assets" do
        get "/assets/js/all.js"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match /var a = 1/
        result.must_match /var b = 2/
        result.must_match %r{alert\("I knew it!"\);}
      end

      it "should preprocess coffeescript" do
        get "/assets/js/m.js"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match /square = function\(x\)/
      end

      it "should allow access to straight js" do
        get "/assets/x.js"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{var x = 1;}
      end

      it "should use absolute URLs when encountered" do
        context = preview_context
        result = context.scripts('js/all', '//use.typekit.com/abcde', 'http://cdn.google.com/jquery.js', 'https://cdn.google.com/jquery.js')
        result.must_equal [
          %|<script type="text/javascript" src="/assets/js/all.js?#{all_js_digest}"></script>|,
          '<script type="text/javascript" src="//use.typekit.com/abcde"></script>',
          '<script type="text/javascript" src="http://cdn.google.com/jquery.js"></script>',
          '<script type="text/javascript" src="https://cdn.google.com/jquery.js"></script>'
        ].join("\n")
      end
    end

    describe "css" do
      it "include css files as separate links" do
        result = context.stylesheets('css/all', 'css/c', 'x')
        result.must_equal [
          %|<link rel="stylesheet" href="/assets/css/all.css?#{all_css_digest}" />|,
          %|<link rel="stylesheet" href="/assets/css/c.css?#{c_css_digest}" />|,
          %|<link rel="stylesheet" href="/assets/x.css?#{x_css_digest}" />|
        ].join("\n")
      end

      it "allows passing scripts as an array" do
        result = context.stylesheets(['css/all', 'css/c', 'x'])
        result.must_equal [
          %|<link rel="stylesheet" href="/assets/css/all.css?#{all_css_digest}" />|,
          %|<link rel="stylesheet" href="/assets/css/c.css?#{c_css_digest}" />|,
          %|<link rel="stylesheet" href="/assets/x.css?#{x_css_digest}" />|
        ].join("\n")
      end

      it "should bundle dependencies" do
        get "/assets/css/all.css"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{height: 42px;}
        result.must_match %r{width: 8px;}
      end

      it "should compile sass" do
        get "/assets/css/b.css"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{height: 42px;}
      end

      it "links to images" do
        get "/assets/css/image1.css"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{background: url\(/assets/i/y\.png\)}
      end

      it "passes through non-existant images" do
        get "/assets/css/missing.css"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{background: url\(i\/missing\.png\)}
      end

      it "can understand urls with hashes" do
        get "/assets/css/urlhash.css"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{background: url\(/assets/i/y\.png\?query=true#hash\)}
      end

      it "embeds image data" do
        get "/assets/css/data.css"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{background-image: url\(data:image\/png;base64,}
      end

      it "can include other assets" do
        get "/assets/css/import.css"
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{width: 8px;}
      end
    end

    describe "templates" do
      let(:renderer)  { Spontaneous::Output::Template::PreviewRenderer.new(site) }

      it "should allow for embedding asset images into templates" do
        result = renderer.render_string("${ asset_path 'i/y.png' }", @page.output(:html))
        result.must_equal "/assets/i/y.png?#{y_png_digest}"
      end
      it "should allow for embedding asset urls into templates" do
        result = renderer.render_string("${ asset_url 'i/y.png' }", @page.output(:html))
        result.must_equal "url(/assets/i/y.png?#{y_png_digest})"
      end
    end
  end

  describe "publishing" do
    let(:app) { Spontaneous::Rack::Front.application(site) }
    let(:context) { live_context }
    let(:revision) { site.revision(context.revision) }
    let(:progress) { Spontaneous::Publishing::Progress::Silent.new }

    def publish_assets(revision)
      context.asset_environment.manifest.compile!
      Spontaneous::Publishing::Steps::CopyAssets.new(site, revision, [], progress).call
    end

    before do
      FileUtils.rm_f(Spontaneous.revision_dir) if File.exist?(Spontaneous.revision_dir)
      system "ln -nfs #{revision.root} #{Spontaneous.revision_dir}"
      publish_assets(context.revision)
    end

    after do
      revision.path("assets").rmtree if revision.path("assets").exist?
    end

    describe "javascript" do
      let(:all_sha) { "ed62549e8edc1f61a1e27136602f01d9" }
      let(:x_sha) { "66e92be1e412458f6ff02f4c5dd9beb1" }
      it "bundles & fingerprints local scripts" do
        result = context.scripts('js/all', 'js/m.js', 'js/c.js', 'x')
        result.must_equal [
          %(<script type="text/javascript" src="/assets/js/all-#{all_sha}.js"></script>),
          '<script type="text/javascript" src="/assets/js/m-a5be7324bc314d5cf470a59c3732ef10.js"></script>',
          '<script type="text/javascript" src="/assets/js/c-c24bcbb4f9647b078cc919746aa7fc3a.js"></script>',
          %(<script type="text/javascript" src="/assets/x-#{x_sha}.js"></script>)
        ].join("\n")
      end

      it "writes bundled assets to the revision directory" do
        result = context.scripts('js/all')
        asset_path = revision.path("assets/js/all-#{all_sha}.js")
        assert asset_path.exist?
      end

      it "compresses local scripts" do
        result = context.scripts('js/all')
        asset_path = revision.path("assets/js/all-#{all_sha}.js")
        js = asset_path.read
        js.index("\n").must_be_nil
      end

      it "bundles locals scripts and includes remote ones" do
        result = context.scripts('js/all', '//use.typekit.com/abcde', 'http://cdn.google.com/jquery.js', 'x')
        result.must_equal [
          %(<script type="text/javascript" src="/assets/js/all-#{all_sha}.js"></script>),
          '<script type="text/javascript" src="//use.typekit.com/abcde"></script>',
          '<script type="text/javascript" src="http://cdn.google.com/jquery.js"></script>',
          %(<script type="text/javascript" src="/assets/x-#{x_sha}.js"></script>)
        ].join("\n")
      end

      it "makes bundled scripts available under /assets" do
        context.scripts('js/all')
        get "/assets/js/all-#{all_sha}.js"
        asset_path = revision.path("assets/js/all-#{all_sha}.js")
        last_response.body.must_equal asset_path.read
      end

      it "only bundles & compresses once" do
        context.scripts('js/all')
        asset_path = revision.path("assets/js/all-#{all_sha}.js")
        assert asset_path.exist?
        asset_path.open("w") do |file|
          file.write("var cached = true;")
        end
        context.scripts('js/all')
        asset_path.read.must_equal "var cached = true;"
      end
      describe "re-use" do
        before do
          @result = context.scripts('js/all', 'x')
        end

        it "uses assets from a previous publish if present" do
          context = live_context
          def context.revision; 100 end
          revision = site.revision(context.revision)
          publish_assets(context.revision)
          manifest = Spontaneous::JSON.parse File.read(site.path("assets/tmp") + "manifest.json")
          compiled = manifest[:assets][:"js/all.js"]
          ::File.open(site.path("assets/tmp")+compiled, 'w') do |file|
            file.write("var reused = true;")
          end
          result = context.scripts('js/all', 'x')
          rev = revision.path("assets") + compiled
          File.read(rev).must_equal "var reused = true;"
        end
      end
    end

    describe "css" do
      let(:all_sha) { "2e17f25ddeba996223a6cd1e28e7a319" }
      let(:x_sha)   { "2560aec2891794825eba770bf84823fb" }

      it "bundles & fingerprints local stylesheets" do
        result = context.stylesheets('css/all', 'css/a.css', 'x')
        result.must_equal [
          %(<link rel="stylesheet" href="/assets/css/all-#{all_sha}.css" />),
          '<link rel="stylesheet" href="/assets/css/a-0164c6d5b696ec2f2c5e70cade040da8.css" />',
          %(<link rel="stylesheet" href="/assets/x-#{x_sha}.css" />)
        ].join("\n")
      end

      it "ignores missing stylesheets" do
        result = context.stylesheets('css/all', '/css/notfound', 'css/notfound')
        result.must_equal [
          %(<link rel="stylesheet" href="/assets/css/all-#{all_sha}.css" />),
          '<link rel="stylesheet" href="/css/notfound" />',
          '<link rel="stylesheet" href="css/notfound" />'
        ].join("\n")
      end

      it "bundles locals scripts and includes remote ones" do
        result = context.stylesheets('css/all.css', '//stylesheet.com/responsive', 'http://cdn.google.com/normalize.css', 'x')
        result.must_equal [
          %(<link rel="stylesheet" href="/assets/css/all-#{all_sha}.css" />),
          '<link rel="stylesheet" href="//stylesheet.com/responsive" />',
          '<link rel="stylesheet" href="http://cdn.google.com/normalize.css" />',
          %(<link rel="stylesheet" href="/assets/x-#{x_sha}.css" />)
        ].join("\n")
      end

      it "makes bundled stylesheets available under /assets" do
        path = context.stylesheet_urls('css/all').first
        get path
        asset_path = revision.path(path)
        last_response.body.must_equal asset_path.read
      end

      it "compresses local styles" do
        path = context.stylesheet_urls('css/all').first
        asset_path = revision.path(path)
        css = asset_path.read
        css.index(" ").must_be_nil
      end

      it "only bundles & compresses once" do
        path = context.stylesheet_urls('css/all').first
        asset_path = revision.path(path)
        assert asset_path.exist?
        asset_path.open("w") do |file|
          file.write(".cached { }")
        end
        context.stylesheets('css/all')
        asset_path.read.must_equal ".cached { }"
      end

      it "passes through non-existant images" do
        path = context.stylesheet_urls('css/missing.css').first
        get path
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match /background:url\(i\/missing\.png\)/
      end

      it "can include other assets" do
        path = context.stylesheet_urls('css/import').first
        get path
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match /width:8px/
      end
    end

    describe "images" do
      it "bundles images and links using fingerprinted asset url" do
        path = context.stylesheet_urls('css/image1').first
        get path
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{background:url\(/assets/i/y-#{y_png_digest}\.png\)}

        asset_path = revision.path("/assets/i/y-#{y_png_digest}.png")
        assert asset_path.exist?
      end

      it "can insert data urls for assets" do
        path = context.stylesheet_urls('css/data').first
        get path
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{background-image:url\(data:image/png;base64}
      end

      it "can understand urls with hashes" do
        path = context.stylesheet_urls('css/urlhash').first
        get path
        assert last_response.ok?, "Recieved #{last_response.status} not 200"
        result = last_response.body
        result.must_match %r{background:url\(/assets/i/y-#{y_png_digest}\.png\?query=true#hash\)}
        asset_path = revision.path("/assets/i/y-#{y_png_digest}.png")
        assert asset_path.exist?
      end
    end

    describe "templates" do
      let(:renderer)  { Spontaneous::Output::Template::PublishRenderer.new(site) }

      it "should allow for embedding asset images into templates" do
        result = renderer.render_string("${ asset_path 'i/y.png' }", @page.output(:html))
        result.must_equal "/assets/i/y-#{y_png_digest}.png"
      end
      it "should allow for embedding asset urls into templates" do
        result = renderer.render_string("${ asset_url 'i/y.png' }", @page.output(:html))
        result.must_equal "url(/assets/i/y-#{y_png_digest}.png)"
      end
    end
  end
end