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

describe "Kernel.catch" do
  before :each do
    ScratchPad.clear
  end

  it "executes its block and catches a thrown value matching its argument" do
    catch :thrown_key do
      ScratchPad.record :catch_block
      throw :thrown_key
      ScratchPad.record :throw_failed
    end
    ScratchPad.recorded.should == :catch_block
  end

  it "returns the second value passed to throw" do
    catch(:thrown_key) { throw :thrown_key, :catch_value }.should == :catch_value
  end

  it "returns the last expression evaluated if throw was not called" do
    catch(:thrown_key) { 1; :catch_block }.should == :catch_block
  end

  it "passes the given symbol to its block" do
    catch :thrown_key do |tag|
      ScratchPad.record tag
    end
    ScratchPad.recorded.should == :thrown_key
  end

  ruby_version_is ""..."1.9" do
    it "matches strings as symbols" do
      catch "thrown_key" do
        ScratchPad.record :catch_block
        throw :thrown_key
      end
      ScratchPad.recorded.should == :catch_block
    end

    it "matches strings that are the same value" do
      catch "thrown_key" do
        ScratchPad.record :catch_block
        throw "thrown_key"
      end
      ScratchPad.recorded.should == :catch_block
    end

    it "raises a TypeError if the argument is an Object" do
      lambda { catch(Object.new) {} }.should raise_error(TypeError)
    end

    it "raises an ArgumentError if called without an argument" do
      lambda { catch {} }.should raise_error(ArgumentError)
    end

    it "passes a symbol converted from the given string to its block" do
      catch("thrown_key") { |tag| tag }.should == :thrown_key
    end
  end

  ruby_version_is "1.9" do
    it "raises an ArgumentError if a Symbol is thrown for a String catch value" do
      lambda { catch("exit") { throw :exit } }.should raise_error(ArgumentError)
    end

    it "raises an ArgumentError if a String with different identity is thrown" do
      lambda { catch("exit") { throw "exit" } }.should raise_error(ArgumentError)
    end

    it "catches a Symbol when thrown a matching Symbol" do
      catch :thrown_key do
        ScratchPad.record :catch_block
        throw :thrown_key
      end
      ScratchPad.recorded.should == :catch_block
    end

    it "catches a String when thrown a String with the same identity" do
      key = "thrown_key"
      catch key do
        ScratchPad.record :catch_block
        throw key
      end
      ScratchPad.recorded.should == :catch_block
    end

    it "accepts an object as an argument" do
      catch(Object.new) { :catch_block }.should == :catch_block
    end

    it "yields an object when called without arguments" do
      catch { |tag| tag }.should be_an_instance_of(Object)
    end
  end

  it "can be used even in a method different from where throw is called" do
    class CatchSpecs
      def self.throwing_method
        throw :blah, :thrown_value
      end
      def self.catching_method
        catch :blah do
          throwing_method
        end
      end
    end
    CatchSpecs.catching_method.should == :thrown_value
  end

  describe "when nested" do
    before :each do
      ScratchPad.record []
    end

    it "catches across invocation boundaries" do
      catch :one do
        ScratchPad << 1
        catch :two do
          ScratchPad << 2
          catch :three do
            ScratchPad << 3
            throw :one
            ScratchPad << 4
          end
          ScratchPad << 5
        end
        ScratchPad << 6
      end

      ScratchPad.recorded.should == [1, 2, 3]
    end

    it "catches in the nested invocation with the same key object" do
      catch :thrown_key do
        ScratchPad << 1
        catch :thrown_key do
          ScratchPad << 2
          throw :thrown_key
          ScratchPad << 3
        end
        ScratchPad << 4
      end

      ScratchPad.recorded.should == [1, 2, 4]
    end
  end

  it "raises LocalJumpError if no block is given" do
    lambda { catch :blah }.should raise_error(LocalJumpError)
  end
end

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