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

include Parser

describe YARD::Handlers::Base do
  describe "#handles and inheritance" do
    before do
      Handlers::Base.stub!(:inherited)
    end

    it "should keep track of subclasses" do
      Handlers::Base.should_receive(:inherited).once
      class TestHandler < Handlers::Base; end
    end

    it "should raise NotImplementedError if process is called on a class with no #process" do
      class TestNotImplementedHandler < Handlers::Base
      end

      lambda { TestNotImplementedHandler.new(0, 0).process }.should raise_error(NotImplementedError)
    end

    it "should allow multiple handles arguments" do
      Handlers::Base.should_receive(:inherited).once
      class TestHandler1 < Handlers::Base
        handles :a, :b, :c
      end
      TestHandler1.handlers.should == [:a, :b, :c]
    end

    it "should allow multiple handles calls" do
      Handlers::Base.should_receive(:inherited).once
      class TestHandler2 < Handlers::Base
        handles :a
        handles :b
        handles :c
      end
      TestHandler2.handlers.should == [:a, :b, :c]
    end
  end

  describe '#abort! (and HandlerAborted)' do
    it 'should allow HandlerAborted to be raised' do
      class AbortHandler1 < Handlers::Ruby::Base
        process { abort! }
      end
      lambda { AbortHandler1.new(nil, nil).process }.should raise_error(HandlerAborted)
    end
  end

  describe 'transitive tags' do
    it "should add transitive tags to children" do
      Registry.clear
      YARD.parse_string <<-eof
        # @since 1.0
        # @author Foo
        class A
          def foo; end
          # @since 1.1
          def bar; end
        end
      eof
      Registry.at('A').tag(:since).text.should == "1.0"
      Registry.at('A#foo').tag(:since).text.should == "1.0"
      Registry.at('A#bar').tag(:since).text.should == "1.1"
      Registry.at('A#bar').tag(:author).should be_nil
    end
  end

  describe 'sharing global state' do
    it "should allow globals to share global state among handlers" do
      class GlobalStateHandler1 < Handlers::Ruby::Base
        class << self; attr_accessor :state end
        handles :class
        process { self.class.state = globals.foo; globals.foo = :bar }
      end

      class GlobalStateHandler2 < Handlers::Ruby::Base
        class << self; attr_accessor :state end
        handles :def
        process { self.class.state = globals.foo }
      end

      2.times do
        YARD.parse_string 'class Foo; end; def foo; end'
        GlobalStateHandler1.state.should == nil
        GlobalStateHandler2.state.should == :bar
      end
    end
  end if HAVE_RIPPER

  describe '#push_state' do
    def process(klass)
      state = OpenStruct.new(:namespace => "ROOT", :scope => :instance, :owner => "ROOT")
      klass.new(state, nil).process
    end

    it "should push and return all old state info after block" do
      class PushStateHandler1 < Handlers::Base
        def process
          push_state(:namespace => "FOO", :scope => :class, :owner => "BAR") do
            namespace.should == "FOO"
            scope.should == :class
            owner.should == "BAR"
          end
          namespace.should == "ROOT"
          owner.should == "ROOT"
          scope.should == :instance
        end
      end
      process PushStateHandler1
    end

    it "should allow owner to be pushed individually" do
      class PushStateHandler2 < Handlers::Base
        def process
          push_state(:owner => "BAR") do
            namespace.should == "ROOT"
            scope.should == :instance
            owner.should == "BAR"
          end
          owner.should == "ROOT"
        end
      end
      process PushStateHandler2
    end

    it "should allow scope to be pushed individually" do
      class PushStateHandler3 < Handlers::Base
        def process
          push_state(:scope => :foo) do
            namespace.should == "ROOT"
            scope.should == :foo
            owner.should == "ROOT"
          end
          scope.should == :instance
        end
      end
      process PushStateHandler3
    end
  end

  describe '.in_file' do
    def parse(filename, parser_type, src = "class A; end")
      parser = Parser::SourceParser.new(parser_type)
      parser.instance_variable_set("@file", filename)
      parser.parse(StringIO.new(src))
    end

    def create_handler(stmts, parser_type)
      $handler_counter ||= 0
      sklass = parser_type == :ruby ? "Base" : "Legacy::Base"
      instance_eval(<<-eof)
        class ::InFileHandler#{$handler_counter += 1} < Handlers::Ruby::#{sklass}
          handles /^class/
          #{stmts}
          def process; MethodObject.new(:root, :FOO) end
        end
      eof
    end

    def test_handler(file, stmts, creates = true, parser_type = :ruby)
      Registry.clear
      Registry.at('#FOO').should be_nil
      create_handler(stmts, parser_type)
      parse(file, parser_type)
      Registry.at('#FOO').send(creates ? :should_not : :should, be_nil)
      Handlers::Base.subclasses.delete_if {|k,v| k.to_s =~ /^InFileHandler/ }
    end

    [:ruby, :ruby18].each do |parser_type|
      next if parser_type == :ruby && LEGACY_PARSER
      describe "Parser type = #{parser_type.inspect}" do
        it "should allow handler to be specific to a file" do
          test_handler 'file_a.rb', 'in_file "file_a.rb"', true, parser_type
        end

        it "should ignore handler if filename does not match" do
          test_handler 'file_b.rb', 'in_file "file_a.rb"', false, parser_type
        end

        it "should only test filename part when given a String" do
          test_handler '/path/to/file_a.rb', 'in_file "/to/file_a.rb"', false, parser_type
        end

        it "should test exact match for entire String" do
          test_handler 'file_a.rb', 'in_file "file"', false, parser_type
        end

        it "should allow a Regexp as argument and test against full path" do
          test_handler 'file_a.rbx', 'in_file /\.rbx$/', true, parser_type
          test_handler '/path/to/file_a.rbx', 'in_file /\/to\/file_/', true, parser_type
          test_handler '/path/to/file_a.rbx', 'in_file /^\/path/', true, parser_type
        end

        it "should allow multiple in_file declarations" do
          stmts = 'in_file "x"; in_file /y/; in_file "foo.rb"'
          test_handler 'foo.rb', stmts, true, parser_type
          test_handler 'xyzzy.rb', stmts, true, parser_type
          test_handler 'x', stmts, true, parser_type
        end
      end
    end
  end
end
