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

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

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

  it "raises an ArgumentError if a String with different identity is thrown" do
    -> { 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

  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
    -> { 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
