require File.expand_path('../../../spec_helper', __FILE__)
require File.expand_path('../fixtures/classes', __FILE__)

describe "Module#module_function" do
  it "is a private method" do
    Module.should have_private_instance_method(:module_function)
  end

  describe "on Class" do
    it "is undefined" do
      Class.should_not have_private_instance_method(:module_function, true)
    end

    it "raises a TypeError if calling after rebinded to Class" do
      lambda {
        Module.instance_method(:module_function).bind(Class.new).call
      }.should raise_error(TypeError)

      lambda {
        Module.instance_method(:module_function).bind(Class.new).call :foo
      }.should raise_error(TypeError)
    end
  end
end

describe "Module#module_function with specific method names" do
  it "creates duplicates of the given instance methods on the Module object" do
    m = Module.new do
      def test()  end
      def test2() end
      def test3() end

      module_function :test, :test2
    end

    m.respond_to?(:test).should  == true
    m.respond_to?(:test2).should == true
    m.respond_to?(:test3).should == false
  end

  it "returns the current module" do
    x = nil
    m = Module.new do
      def test()  end
      x = module_function :test
    end

    x.should == m
  end

  it "creates an independent copy of the method, not a redirect" do
    module Mixin
      def test
        "hello"
      end
      module_function :test
    end

    class BaseClass
      include Mixin
      def call_test
        test
      end
    end

    Mixin.test.should == "hello"
    c = BaseClass.new
    c.call_test.should == "hello"

    module Mixin
      def test
        "goodbye"
      end
    end

    Mixin.test.should == "hello"
    c.call_test.should == "goodbye"
  end

  it "makes the instance methods private" do
    m = Module.new do
      def test() "hello" end
      module_function :test
    end

    (o = mock('x')).extend(m)
    o.respond_to?(:test).should == false
    m.should have_private_instance_method(:test)
    o.send(:test).should == "hello"
    lambda { o.test }.should raise_error(NoMethodError)
  end

  it "makes the new Module methods public" do
    m = Module.new do
      def test() "hello" end
      module_function :test
    end

    m.public_methods.map {|m| m.to_s }.include?('test').should == true
  end

  it "tries to convert the given names to strings using to_str" do
    (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test")
    (o2 = mock('test2')).should_receive(:to_str).any_number_of_times.and_return("test2")

    m = Module.new do
      def test() end
      def test2() end
      module_function o, o2
    end

    m.respond_to?(:test).should  == true
    m.respond_to?(:test2).should == true
  end

  it "raises a TypeError when the given names can't be converted to string using to_str" do
    o = mock('123')

    lambda { Module.new { module_function(o) } }.should raise_error(TypeError)

    o.should_receive(:to_str).and_return(123)
    lambda { Module.new { module_function(o) } }.should raise_error(TypeError)
  end

  it "can make accessible private methods" do # JRUBY-4214
    m = Module.new do
      module_function :require
    end
    m.respond_to?(:require).should be_true
  end
end

describe "Module#module_function as a toggle (no arguments) in a Module body" do
  it "makes any subsequently defined methods module functions with the normal semantics" do
    m = Module.new {
          module_function
            def test1() end
            def test2() end
        }

    m.respond_to?(:test1).should == true
    m.respond_to?(:test2).should == true
  end

  it "returns the current module" do
    x = nil
    m = Module.new {
      x = module_function
    }

    x.should == m
  end

  it "stops creating module functions if the body encounters another toggle " \
     "like public/protected/private without arguments" do
    m = Module.new {
          module_function
            def test1() end
            def test2() end
          public
            def test3() end
        }

    m.respond_to?(:test1).should == true
    m.respond_to?(:test2).should == true
    m.respond_to?(:test3).should == false
  end

  it "does not stop creating module functions if the body encounters " \
     "public/protected/private WITH arguments" do
    m = Module.new {
          def foo() end

          module_function
            def test1() end
            def test2() end

            public :foo

            def test3() end
        }

    m.respond_to?(:test1).should == true
    m.respond_to?(:test2).should == true
    m.respond_to?(:test3).should == true
  end

  it "does not affect module_evaled method definitions also if outside the eval itself" do
    m = Module.new {
          module_function

          module_eval { def test1() end }
          module_eval " def test2() end "
        }

    c = Class.new { include m }

    m.respond_to?(:test1).should == false
    m.respond_to?(:test2).should == false
  end

  it "has no effect if inside a module_eval if the definitions are outside of it" do
    m = Module.new {
          module_eval { module_function }

          def test1() end
          def test2() end
        }

    m.respond_to?(:test1).should == false
    m.respond_to?(:test2).should == false
  end

  it "functions normally if both toggle and definitions inside a module_eval" do
    m = Module.new {
          module_eval {
            module_function

            def test1() end
            def test2() end
          }
        }

    m.respond_to?(:test1).should == true
    m.respond_to?(:test2).should == true
  end

  it "affects evaled method definitions also even when outside the eval itself" do
    m = Module.new {
          module_function

          eval "def test1() end"
        }

    m.respond_to?(:test1).should == true
  end

  it "doesn't affect definitions when inside an eval even if the definitions are outside of it" do
    m = Module.new {
      eval "module_function"

      def test1() end
    }

    m.respond_to?(:test1).should == false
  end

  it "functions normally if both toggle and definitions inside a eval" do
    m = Module.new {
          eval <<-CODE
            module_function

            def test1() end
            def test2() end
          CODE
        }

    m.respond_to?(:test1).should == true
    m.respond_to?(:test2).should == true
  end
end
