require_relative '../../spec_helper'
require_relative 'fixtures/classes'

describe "Class.new with a block given" do
  it "yields the new class as self in the block" do
    self_in_block = nil
    klass = Class.new do
      self_in_block = self
    end
    self_in_block.should.equal? klass
  end

  it "uses the given block as the class' body" do
    klass = Class.new do
      def self.message
        "text"
      end

      def hello
        "hello again"
      end
    end

    klass.message.should == "text"
    klass.new.hello.should == "hello again"
  end

  it "creates a subclass of the given superclass" do
    sc = Class.new do
      def self.body
        @body
      end
      @body = self
      def message; "text"; end
    end
    klass = Class.new(sc) do
      def self.body
        @body
      end
      @body = self
      def message2; "hello"; end
    end

    klass.body.should == klass
    sc.body.should == sc
    klass.superclass.should == sc
    klass.new.message.should == "text"
    klass.new.message2.should == "hello"
  end

  it "runs the inherited hook after yielding the block" do
    ScratchPad.record []
    klass = Class.new(CoreClassSpecs::Inherited::D) do
      ScratchPad << self
    end

    ScratchPad.recorded.should == [CoreClassSpecs::Inherited::D, klass]
  end
end

describe "Class.new" do
  it "creates a new anonymous class" do
    klass = Class.new
    klass.is_a?(Class).should == true

    klass_instance = klass.new
    klass_instance.is_a?(klass).should == true
  end

  it "raises a TypeError if passed a metaclass" do
    obj = mock("Class.new metaclass")
    meta = obj.singleton_class
    -> { Class.new meta }.should.raise(TypeError)
  end

  it "creates a class without a name" do
    Class.new.name.should == nil
  end

  it "creates a class that can be given a name by assigning it to a constant" do
    ::MyClass = Class.new
    ::MyClass.name.should == "MyClass"
    a = Class.new
    MyClass::NestedClass = a
    MyClass::NestedClass.name.should == "MyClass::NestedClass"
  ensure
    Object.send(:remove_const, :MyClass)
  end

  it "sets the new class' superclass to the given class" do
    top = Class.new
    Class.new(top).superclass.should == top
  end

  it "sets the new class' superclass to Object when no class given" do
    Class.new.superclass.should == Object
  end

  it "raises a TypeError when given a non-Class" do
    error_msg = /superclass must be a.*Class/
    -> { Class.new("")              }.should.raise(TypeError, error_msg)
    -> { Class.new(1)               }.should.raise(TypeError, error_msg)
    -> { Class.new(:symbol)         }.should.raise(TypeError, error_msg)
    -> { Class.new(mock('o'))       }.should.raise(TypeError, error_msg)
    -> { Class.new(Module.new)      }.should.raise(TypeError, error_msg)
    -> { Class.new(BasicObject.new) }.should.raise(TypeError, error_msg)
  end
end

describe "Class#new" do
  it "returns a new instance of self" do
    klass = Class.new
    klass.new.is_a?(klass).should == true
  end

  it "invokes #initialize on the new instance with the given args" do
    klass = Class.new do
      def initialize(*args)
        @initialized = true
        @args = args
      end

      def args
        @args
      end

      def initialized?
        @initialized || false
      end
    end

    klass.new.should.initialized?
    klass.new(1, 2, 3).args.should == [1, 2, 3]
  end

  it "uses the internal allocator and does not call #allocate" do
    klass = Class.new do
      def self.allocate
        raise "allocate should not be called"
      end
    end

    instance = klass.new
    instance.should.is_a? klass
    instance.class.should.equal? klass
  end

  it "passes the block to #initialize" do
    klass = Class.new do
      def initialize
        yield
      end
    end

    klass.new { break 42 }.should == 42
  end
end
