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

describe YARD::CodeObjects::ClassObject do
  describe '#inheritance_tree' do
    before(:all) do
      Registry.clear
      @mixin = ModuleObject.new(:root, :SomeMixin)
      @mixin2 = ModuleObject.new(:root, :SomeMixin2)
      @mixin2.instance_mixins << @mixin
      @mixin3 = ModuleObject.new(:root, :SomeMixin3)
      @mixin4 = ModuleObject.new(:root, :SomeMixin4)
      @mixin2.instance_mixins << @mixin3
      @superyard = ClassObject.new(:root, :SuperYard)
      @superyard.superclass = P("String")
      @superyard.instance_mixins << @mixin2
      @superyard.class_mixins << @mixin4
      @yard = ClassObject.new(:root, :YARD)
      @yard.superclass = @superyard
      @yard.instance_mixins << @mixin
    end

    it "should show the proper inheritance tree" do
      @yard.inheritance_tree.should == [@yard, @superyard, P(:String)]
    end

    it "should show proper inheritance tree when mixins are included" do
      @yard.inheritance_tree(true).should == [@yard, @mixin, @superyard, @mixin4, @mixin2, @mixin3, P(:String)]
    end

    it "should not modify the object's mixin list when mixins are included" do
      @class1 = ClassObject.new(:root, :A)
      @class2 = ClassObject.new(:root, :B)
      @class2.superclass = @class1

      @class2.inheritance_tree(true)
      @class2.mixins.should == []
    end

    it "should list class mixins in inheritance tree" do
      mod = ModuleObject.new(:root, :ClassMethods)
      klass = ClassObject.new(:root, :ReceivingClass)
      klass.class_mixins << mod
      klass.inheritance_tree(true).should == [klass, mod]
    end
  end

  describe "#meths / #inherited_meths" do
    before(:all) do
      Registry.clear

      YARD.parse_string <<-eof
        class SuperYard < String
          def foo; end
          def foo2; end
          def bar; end
          def middle; end
          protected :foo2
          private
          def self.bar; end
        end

        class MiddleYard < SuperYard
          def middle; end
        end

        class YARD < MiddleYard
          def mymethod; end
          def bar; end
        end

        module IncludedYard
          def foo; end
        end

        class FinalYard < SuperYard
          include IncludedYard
        end
      eof
    end

    it "should show inherited methods by default" do
      meths = P(:YARD).meths
      meths.should include(P("YARD#mymethod"))
      meths.should include(P("SuperYard#foo"))
      meths.should include(P("SuperYard#foo2"))
      meths.should include(P("SuperYard.bar"))
    end

    it "should allow :inherited to be set to false" do
      meths = P(:YARD).meths(:inherited => false)
      meths.should include(P("YARD#mymethod"))
      meths.should_not include(P("SuperYard#foo"))
      meths.should_not include(P("SuperYard#foo2"))
      meths.should_not include(P("SuperYard.bar"))
    end

    it "should not show overridden methods" do
      meths = P(:YARD).meths
      meths.should include(P("YARD#bar"))
      meths.should_not include(P("SuperYard#bar"))

      meths = P(:YARD).inherited_meths
      meths.should_not include(P("YARD#bar"))
      meths.should_not include(P("YARD#mymethod"))
      meths.should include(P("SuperYard#foo"))
      meths.should include(P("SuperYard#foo2"))
      meths.should include(P("SuperYard.bar"))
    end

    it "should not show inherited methods overridden by other subclasses" do
      meths = P(:YARD).inherited_meths
      meths.should include(P('MiddleYard#middle'))
      meths.should_not include(P('SuperYard#middle'))
    end

    it "should show mixed in methods before superclass method" do
      meths = P(:FinalYard).meths
      meths.should include(P('IncludedYard#foo'))
      meths.should_not include(P('SuperYard#foo'))
    end
  end

  describe "#constants / #inherited_constants" do
    before(:all) do
      Registry.clear

      Parser::SourceParser.parse_string <<-eof
        class YARD
          CONST1 = 1
          CONST2 = "hello"
          CONST4 = 0
        end

        class SUPERYARD < YARD
          CONST4 = 5
        end

        class SubYard < SUPERYARD
          CONST2 = "hi"
          CONST3 = "foo"
        end
      eof
    end

    it "should list inherited constants by default" do
      consts = P(:SubYard).constants
      consts.should include(P("YARD::CONST1"))
      consts.should include(P("SubYard::CONST3"))

      consts = P(:SubYard).inherited_constants
      consts.should include(P("YARD::CONST1"))
      consts.should_not include(P("YARD::CONST2"))
      consts.should_not include(P("SubYard::CONST2"))
      consts.should_not include(P("SubYard::CONST3"))
    end

    it "should not list inherited constants if turned off" do
      consts = P(:SubYard).constants(:inherited => false)
      consts.should_not include(P("YARD::CONST1"))
      consts.should include(P("SubYard::CONST3"))
    end

    it "should not include an inherited constant if it is overridden by the object" do
      consts = P(:SubYard).constants
      consts.should include(P("SubYard::CONST2"))
      consts.should_not include(P("YARD::CONST2"))
    end

    it "should not include an inherited constant if it is overridden by another subclass" do
      consts = P(:SubYard).inherited_constants
      consts.should include(P("SUPERYARD::CONST4"))
      consts.should_not include(P("YARD::CONST4"))
    end

    it "should not set a superclass on BasicObject class" do
      o = ClassObject.new(:root, :Object)
      o.superclass.should == P(:BasicObject)
    end

    it "should set superclass of Object to BasicObject" do
      o = ClassObject.new(:root, :BasicObject)
      o.superclass.should be_nil
    end

    it "should raise ArgumentError if superclass == self" do
      lambda do
        o = ClassObject.new(:root, :Object) do |o|
          o.superclass = :Object
        end
      end.should raise_error(ArgumentError)
    end

    it "should tell the world if it is an exception class" do
      o = ClassObject.new(:root, :MyClass)
      o2 = ClassObject.new(:root, :OtherClass)
      o2.superclass = :SystemCallError
      o3 = ClassObject.new(:root, :StandardError)
      o3.superclass = :Object
      o4 = ClassObject.new(:root, :Object)

      o.superclass = :Object
      o.is_exception?.should == false

      o.superclass = :Exception
      o.is_exception?.should == true

      o.superclass = :NoMethodError
      o.is_exception?.should == true

      o.superclass = o2
      o.is_exception?.should == true

      o.superclass = o3
      o.is_exception?.should == true
    end

    it "should not raise ArgumentError if superclass is proxy in different namespace" do
      lambda do
        o = ClassObject.new(:root, :X) do |o|
          o.superclass = P('OTHER::X')
        end
      end.should_not raise_error(ArgumentError)
    end
  end
end