require File.dirname(__FILE__) + '/spec_helper'

describe YARD::Templates::Template do
  def template(path)
    YARD::Templates::Engine.template!(path, '/full/path/' + path.to_s)
  end

  before :each do
    YARD::Templates::ErbCache.clear!
  end

  describe '.include_parent' do
    it "should not include parent directory if parent directory is a template root path" do
      mod = template('q')
      mod.should_not include(template(''))
    end

    it "should include overridden parent directory" do
      Engine.stub!(:template_paths).and_return(['/foo', '/bar'])
      File.should_receive(:directory?).with('/foo/a/b').and_return(true)
      File.should_receive(:directory?).with('/bar/a/b').and_return(false)
      File.should_receive(:directory?).with('/foo/a').at_least(1).times.and_return(true)
      File.should_receive(:directory?).with('/bar/a').at_least(1).times.and_return(true)
      ancestors = Engine.template('a/b').ancestors.map {|c| c.class_name }
      ancestors[0, 3].should == %w( Template__foo_a_b Template__bar_a Template__foo_a )
    end

    it "should include parent directory template if exists" do
      mod1 = template('x')
      mod2 = template('x/y')
      mod2.should include(mod1)
    end
  end

  describe '.full_paths' do
    it "should list full_path" do
      mod = template(:a)
      mod.full_paths.should == ['/full/path/a']
    end

    it "should list paths of included modules" do
      mod = template(:a)
      mod.send(:include, template(:b))
      mod.full_paths.should == ['/full/path/a', '/full/path/b']
    end

    it "should list paths from modules of included modules" do
      mod = template(:c)
      mod.send(:include, template(:d))
      mod.send(:include, template(:a))
      mod.full_paths.should == ['c', 'a', 'b', 'd'].map {|o| '/full/path/' + o }
    end

    it "should only list full paths of modules that respond to full_paths" do
      mod = template(:d)
      mod.send(:include, Enumerable)
      mod.full_paths.should == ['/full/path/d']
    end
  end

  describe '.load_setup_rb' do
    it "should load setup.rb file for module" do
      File.should_receive(:file?).with('/full/path/e/setup.rb').and_return(true)
      File.should_receive(:read).with('/full/path/e/setup.rb').and_return('def success; end')
      template(:e).new.should respond_to(:success)
    end
  end

  describe '.T' do
    it "should load template from absolute path" do
      mod = template(:a)
      Engine.should_receive(:template).with('other')
      mod.T('other')
    end
  end

  describe '.find_file' do
    it "should find file in module's full_path" do
      File.should_receive(:file?).with('/full/path/a/basename').and_return(false)
      File.should_receive(:file?).with('/full/path/b/basename').and_return(true)
      template(:a).find_file('basename').should == '/full/path/b/basename'
    end

    it "should return nil if no file is found" do
      File.should_receive(:file?).with('/full/path/a/basename').and_return(false)
      File.should_receive(:file?).with('/full/path/b/basename').and_return(false)
      template(:a).find_file('basename').should be_nil
    end
  end

  describe '.find_nth_file' do
    it "should find 2nd existing file in template paths" do
      File.should_receive(:file?).with('/full/path/a/basename').and_return(true)
      File.should_receive(:file?).with('/full/path/b/basename').and_return(true)
      template(:a).find_nth_file('basename', 2).should == '/full/path/b/basename'
    end

    it "should return nil if no file is found" do
      File.should_receive(:file?).with('/full/path/a/basename').and_return(true)
      File.should_receive(:file?).with('/full/path/b/basename').and_return(true)
      template(:a).find_nth_file('basename', 3).should be_nil
    end
  end

  describe '.extra_includes' do
    it "should be included when a module is initialized" do
      module MyModule; end
      Template.extra_includes << MyModule
      template(:e).new.should be_kind_of(MyModule)
    end

    it "should support lambdas in list" do
      module MyModule2; end
      Template.extra_includes << lambda {|opts| MyModule2 if opts.format == :html }
      template(:f).new(:format => :html).should be_kind_of(MyModule2)
      metaclass = (class << template(:g).new(:format => :text); self end)
      metaclass.ancestors.should_not include(MyModule2)
    end
  end

  describe '.is_a?' do
    it "should be kind of Template" do
      template(:e).is_a?(Template).should == true
    end
  end

  describe '#T' do
    it "should delegate to class method" do
      template(:e).should_receive(:T).with('test')
      template(:e).new.T('test')
    end
  end

  describe '#init' do
    it "should be called during initialization" do
      module YARD::Templates::Engine::Template__full_path_e
        def init; sections 1, 2, 3 end
      end
      template(:e).new.sections.should == Section.new(nil, 1, 2, 3)
    end
  end

  describe '#file' do
    it "should read the file if it exists" do
      File.should_receive(:file?).with('/full/path/e/abc').and_return(true)
      IO.should_receive(:read).with('/full/path/e/abc').and_return('hello world')
      template(:e).new.file('abc').should == 'hello world'
    end

    it "should raise ArgumentError if the file does not exist" do
      File.should_receive(:file?).with('/full/path/e/abc').and_return(false)
      lambda { template(:e).new.file('abc') }.should raise_error(ArgumentError)
    end

    it "should replace {{{__super__}}} with inherited template contents if allow_inherited=true" do
      File.should_receive(:file?).with('/full/path/a/abc').twice.and_return(true)
      File.should_receive(:file?).with('/full/path/b/abc').and_return(true)
      IO.should_receive(:read).with('/full/path/a/abc').and_return('foo {{{__super__}}}')
      IO.should_receive(:read).with('/full/path/b/abc').and_return('bar')
      template(:a).new.file('abc', true).should == "foo bar"
    end

    it "should not replace {{{__super__}}} with inherited template contents if allow_inherited=false" do
      File.should_receive(:file?).with('/full/path/a/abc').and_return(true)
      IO.should_receive(:read).with('/full/path/a/abc').and_return('foo {{{__super__}}}')
      template(:a).new.file('abc').should == "foo {{{__super__}}}"
    end
  end

  describe '#superb' do
    it "should return the inherited erb template contents" do
      File.should_receive(:file?).with('/full/path/a/test.erb').and_return(true)
      File.should_receive(:file?).with('/full/path/b/test.erb').and_return(true)
      IO.should_receive(:read).with('/full/path/b/test.erb').and_return('bar')
      template = template(:a).new
      template.section = :test
      template.superb.should == "bar"
    end

    it "should work inside an erb template" do
      File.should_receive(:file?).with('/full/path/a/test.erb').twice.and_return(true)
      File.should_receive(:file?).with('/full/path/b/test.erb').and_return(true)
      IO.should_receive(:read).with('/full/path/a/test.erb').and_return('foo<%= superb %>!')
      IO.should_receive(:read).with('/full/path/b/test.erb').and_return('bar')
      template = template(:a).new
      template.section = :test
      template.erb(:test).should == "foobar!"
    end
  end

  describe '#sections' do
    it "should allow sections to be set if arguments are provided" do
      mod = template(:e).new
      mod.sections 1, 2, [3]
      mod.sections.should == Section.new(nil, 1, 2, [3])
    end
  end

  describe '#run' do
    it "should render all sections" do
      mod = template(:e).new
      mod.should_receive(:render_section).with(Section.new(:a)).and_return('a')
      mod.should_receive(:render_section).with(Section.new(:b)).and_return('b')
      mod.should_receive(:render_section).with(Section.new(:c)).and_return('c')
      mod.sections :a, :b, :c
      mod.run.should == 'abc'
    end

    it "should render all sections with options" do
      mod = template(:e).new
      mod.should_receive(:render_section).with(Section.new(:a)).and_return('a')
      mod.should_receive(:add_options).with(:a => 1).and_yield
      mod.sections :a
      mod.run(:a => 1).should == 'a'
    end

    it "should run section list if provided" do
      mod = template(:e).new
      mod.should_receive(:render_section).with(Section.new(:q))
      mod.should_receive(:render_section).with(Section.new(:x))
      mod.run({}, [:q, :x])
    end

    it "should accept a nil section as empty string" do
      mod = template(:e).new
      mod.should_receive(:render_section).with(Section.new(:a))
      mod.sections :a
      mod.run.should == ""
    end
  end

  describe '#add_options' do
    it "should set instance variables in addition to options" do
      mod = template(:f).new
      mod.send(:add_options, {:a => 1, :b => 2})
      mod.options.should == {:a => 1, :b => 2}
      mod.instance_variable_get("@a").should == 1
      mod.instance_variable_get("@b").should == 2
    end

    it "should set instance variables and options only for the block" do
      mod = template(:f).new
      mod.send(:add_options, {:a => 100, :b => 200}) do
        mod.options.should == {:a => 100, :b => 200}
      end
      mod.options.should_not == {:a => 100, :b => 200}
    end
  end

  describe '#render_section' do
    it "should call method if method exists by section name as Symbol" do
      mod = template(:f).new
      mod.should_receive(:respond_to?).with(:a).and_return(true)
      mod.should_receive(:respond_to?).with('a').and_return(true)
      mod.should_receive(:send).with(:a).and_return('a')
      mod.should_receive(:send).with('a').and_return('a')
      mod.run({}, [:a, 'a']).should == 'aa'
    end

    it "should call erb if no method exists by section name" do
      mod = template(:f).new
      mod.should_receive(:respond_to?).with(:a).and_return(false)
      mod.should_receive(:respond_to?).with('a').and_return(false)
      mod.should_receive(:erb).with(:a).and_return('a')
      mod.should_receive(:erb).with('a').and_return('a')
      mod.run({}, [:a, 'a']).should == 'aa'
    end

    it "should run a template if section is one" do
      mod2 = template(:g)
      mod2.should_receive(:run)
      mod = template(:f).new
      mod.sections mod2
      mod.run
    end

    it "should run a template instance if section is one" do
      mod2 = template(:g).new
      mod2.should_receive(:run)
      mod = template(:f).new
      mod.sections mod2
      mod.run
    end
  end

  describe '#yield' do
    it "should yield a subsection" do
      mod = template(:e).new
      mod.sections :a, [:b, :c]
      class << mod
        def a; "(" + yield + ")" end
        def b; "b" end
        def c; "c" end
      end

      mod.run.should == "(b)"
    end

    it "should yield a subsection within a yielded subsection" do
      mod = template(:e).new
      mod.sections :a, [:b, [:c]]
      class << mod
        def a; "(" + yield + ")" end
        def b; yield end
        def c; "c" end
      end

      mod.run.should == "(c)"
    end

    it "should support arbitrary nesting" do
      mod = template(:e).new
      mod.sections :a, [:b, [:c, [:d, [:e]]]]
      class << mod
        def a; "(" + yield + ")" end
        def b; yield end
        def c; yield end
        def d; yield end
        def e; "e" end
      end

      mod.run.should == "(e)"
    end

    it "should yield first two elements if yield is called twice" do
      mod = template(:e).new
      mod.sections :a, [:b, :c, :d]
      class << mod
        def a; "(" + yield + yield + ")" end
        def b; 'b' end
        def c; "c" end
      end

      mod.run.should == "(bc)"
    end

    it "should ignore any subsections inside subsection yields" do
      mod = template(:e).new
      mod.sections :a, [:b, [:c], :d]
      class << mod
        def a; "(" + yield + yield + ")" end
        def b; 'b' end
        def d; "d" end
      end

      mod.run.should == "(bd)"
    end

    it "should allow extra options passed via yield" do
      mod = template(:e).new
      mod.sections :a, [:b]
      class << mod
        def a; "(" + yield(:x => "a") + ")" end
        def b; options.x + @x end
      end

      mod.run.should == "(aa)"
    end
  end

  describe '#yieldall' do
    it "should yield all subsections" do
      mod = template(:e).new
      mod.sections :a, [:b, [:d, [:e]], :c]
      class << mod
        def a; "(" + yieldall + ")" end
        def b; "b" + yieldall end
        def c; "c" end
        def d; 'd' + yieldall end
        def e; 'e' end
      end

      mod.run.should == "(bdec)"
    end

    it "should yield options to all subsections" do
      mod = template(:e).new
      mod.sections :a, [:b, :c]
      class << mod
        def a; "(" + yieldall(:x => "2") + ")" end
        def b; @x end
        def c; @x end
      end
      mod.run.should == "(22)"
    end

    it "should yield all subsections more than once" do
      mod = template(:e).new
      mod.sections :a, [:b]
      class << mod
        def a; "(" + yieldall + yieldall + ")" end
        def b; "b" end
      end

      mod.run.should == "(bb)"
    end

    it "should not yield if no yieldall is called" do
      mod = template(:e).new
      mod.sections :a, [:b]
      class << mod
        def a; "()" end
        def b; "b" end
      end

      mod.run.should == "()"
    end
  end
end
