require 'contest'
require 'tilt'
require 'tempfile'

class TiltTemplateTest < Test::Unit::TestCase

  class MockTemplate < Tilt::Template
    def prepare
    end
  end

  test "needs a file or block" do
    assert_raise(ArgumentError) { Tilt::Template.new }
  end

  test "initializing with a file" do
    inst = MockTemplate.new('foo.erb') {}
    assert_equal 'foo.erb', inst.file
  end

  test "initializing with a file and line" do
    inst = MockTemplate.new('foo.erb', 55) {}
    assert_equal 'foo.erb', inst.file
    assert_equal 55, inst.line
  end

  test "initializing with a tempfile" do
    tempfile = Tempfile.new('tilt_template_test')
    inst = MockTemplate.new(tempfile)
    assert_equal File.basename(tempfile.path), inst.basename
  end

  class SillyHash < Hash
    def path(arg)
    end
  end

  test "initialize with hash that implements #path" do
    options = SillyHash[:key => :value]
    inst = MockTemplate.new(options) {}
    assert_equal :value, inst.options[:key]
  end

  test "uses correct eval_file" do
    inst = MockTemplate.new('foo.erb', 55) {}
    assert_equal 'foo.erb', inst.eval_file
  end

  test "uses a default filename for #eval_file when no file provided" do
    inst = MockTemplate.new { 'Hi' }
    assert_not_nil inst.eval_file
    assert !inst.eval_file.include?("\n")
  end

  test "calculating template's #basename" do
    inst = MockTemplate.new('/tmp/templates/foo.html.erb') {}
    assert_equal 'foo.html.erb', inst.basename
  end

  test "calculating the template's #name" do
    inst = MockTemplate.new('/tmp/templates/foo.html.erb') {}
    assert_equal 'foo', inst.name
  end

  test "initializing with a data loading block" do
    MockTemplate.new { |template| "Hello World!" }
  end

  class InitializingMockTemplate < Tilt::Template
    @@initialized_count = 0
    def self.initialized_count
      @@initialized_count
    end

    def initialize_engine
      @@initialized_count += 1
    end

    def prepare
    end
  end

  test "one-time template engine initialization" do
    assert_nil InitializingMockTemplate.engine_initialized
    assert_equal 0, InitializingMockTemplate.initialized_count

    InitializingMockTemplate.new { "Hello World!" }
    assert InitializingMockTemplate.engine_initialized
    assert_equal 1, InitializingMockTemplate.initialized_count

    InitializingMockTemplate.new { "Hello World!" }
    assert_equal 1, InitializingMockTemplate.initialized_count
  end

  class PreparingMockTemplate < Tilt::Template
    include Test::Unit::Assertions
    def prepare
      assert !data.nil?
      @prepared = true
    end
    def prepared? ; @prepared ; end
  end

  test "raises NotImplementedError when #prepare not defined" do
    assert_raise(NotImplementedError) { Tilt::Template.new { |template| "Hello World!" } }
  end

  test "raises NotImplementedError when #evaluate or #template_source not defined" do
    inst = PreparingMockTemplate.new { |t| "Hello World!" }
    assert_raise(NotImplementedError) { inst.render }
    assert inst.prepared?
  end

  class SimpleMockTemplate < PreparingMockTemplate
    include Test::Unit::Assertions
    def evaluate(scope, locals, &block)
      assert prepared?
      assert !scope.nil?
      assert !locals.nil?
      "<em>#{@data}</em>"
    end
  end

  test "prepares and evaluates the template on #render" do
    inst = SimpleMockTemplate.new { |t| "Hello World!" }
    assert_equal "<em>Hello World!</em>", inst.render
    assert inst.prepared?
  end

  class SourceGeneratingMockTemplate < PreparingMockTemplate
    def precompiled_template(locals)
      "foo = [] ; foo << %Q{#{data}} ; foo.join"
    end
  end

  test "template_source with locals" do
    inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{name}!' }
    assert_equal "Hey Joe!", inst.render(Object.new, :name => 'Joe')
    assert inst.prepared?
  end

  test "template_source with locals of strings" do
    inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{name}!' }
    assert_equal "Hey Joe!", inst.render(Object.new, 'name' => 'Joe')
    assert inst.prepared?
  end

  test "template_source with locals having non-variable keys raises error" do
    inst = SourceGeneratingMockTemplate.new { |t| '1 + 2 = #{_answer}' }
    err = assert_raise(RuntimeError) { inst.render(Object.new, 'ANSWER' => 3) }
    assert_equal "invalid locals key: \"ANSWER\" (keys must be variable names)", err.message
    assert_equal "1 + 2 = 3", inst.render(Object.new, '_answer' => 3)
  end

  class Person
    CONSTANT = "Bob"

    attr_accessor :name
    def initialize(name)
      @name = name
    end
  end

  test "template_source with an object scope" do
    inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{@name}!' }
    scope = Person.new('Joe')
    assert_equal "Hey Joe!", inst.render(scope)
  end

  test "template_source with a block for yield" do
    inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{yield}!' }
    assert_equal "Hey Joe!", inst.render(Object.new){ 'Joe' }
  end

  test "template which accesses a constant" do
    inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{CONSTANT}!' }
    assert_equal "Hey Bob!", inst.render(Person.new("Joe"))
  end
end
