require "examples/example_helper"

module RR
describe ScenarioCreator, :shared => true do
  before(:each) do
    @space = Space.new
    @subject = Object.new
    @creator = ScenarioCreator.new(@space)
  end
end

describe ScenarioCreator, " strategy definition", :shared => true do
  it_should_behave_like "RR::ScenarioCreator"

  it "returns self when passing no args" do
    @creator.__send__(@method_name).should === @creator
  end

  it "returns a ScenarioMethodProxy when passed a subject" do
    scenario = @creator.__send__(@method_name, @subject).foobar
    scenario.should be_instance_of(ScenarioDefinition)
  end

  it "returns a ScenarioMethodProxy when passed Kernel" do
    scenario = @creator.__send__(@method_name, Kernel).foobar
    scenario.should be_instance_of(ScenarioDefinition)
  end

  it "raises error if passed a method name and a block" do
    proc do
      @creator.__send__(@method_name, @subject, :foobar) {}
    end.should raise_error(ArgumentError, "Cannot pass in a method name and a block")
  end

  it "raises error when using mock strategy" do
    @creator.mock
    proc do
      @creator.__send__(@method_name)
    end.should raise_error(
      Errors::ScenarioDefinitionError,
      "This Scenario already has a mock strategy"
    )
  end

  it "raises error when using stub strategy" do
    @creator.stub
    proc do
      @creator.__send__(@method_name)
    end.should raise_error(
      Errors::ScenarioDefinitionError,
      "This Scenario already has a stub strategy"
    )
  end

  it "raises error when using do_not_call strategy" do
    @creator.do_not_call
    proc do
      @creator.__send__(@method_name)
    end.should raise_error(
      Errors::ScenarioDefinitionError,
      "This Scenario already has a do_not_call strategy"
    )
  end
end

describe ScenarioCreator, "#mock" do
  it_should_behave_like "RR::ScenarioCreator strategy definition"

  before do
    @method_name = :mock
  end

  it "sets up the RR mock call chain" do
    should create_mock_call_chain(@creator.mock(@subject))
  end

  it "creates a mock Scenario for method when passed a second argument with rr_mock" do
    should create_scenario_with_method_name(
      @creator.mock(@subject, :foobar)
    )
  end

  def create_scenario_with_method_name(scenario)
    scenario.with(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::IntegerMatcher.new(1)
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    scenario.argument_expectation.expected_arguments.should == [1, 2]

    @subject.foobar(1, 2).should == :baz
  end

  def create_mock_call_chain(creator)
    scenario = creator.foobar(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::IntegerMatcher.new(1)
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    scenario.argument_expectation.expected_arguments.should == [1, 2]

    @subject.foobar(1, 2).should == :baz
  end
end

describe ScenarioCreator, "#stub" do
  it_should_behave_like "RR::ScenarioCreator strategy definition"

  before do
    @method_name = :stub
  end

  it "sets up the RR stub call chain" do
    should create_stub_call_chain(@creator.stub(@subject))
  end

  it "creates a stub Scenario for method when passed a second argument" do
    should create_scenario_with_method_name(@creator.stub(@subject, :foobar))
  end

  def create_scenario_with_method_name(scenario)
    scenario.with(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::AnyTimesMatcher.new
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    @subject.foobar(1, 2).should == :baz
  end

  def create_stub_call_chain(creator)
    scenario = creator.foobar(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::AnyTimesMatcher.new
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    @subject.foobar(1, 2).should == :baz
  end
end

describe ScenarioCreator, "#do_not_call" do
  it_should_behave_like "RR::ScenarioCreator strategy definition"

  before do
    @method_name = :do_not_call
  end

  it "raises error when probed" do
    @creator.probe
    proc do
      @creator.do_not_call
    end.should raise_error(
      Errors::ScenarioDefinitionError,
      "Scenarios cannot be probed when using do_not_call strategy"
    )
  end

  it "sets up the RR do_not_call call chain" do
    should create_do_not_call_call_chain(@creator.do_not_call(@subject))
  end

  it "sets up the RR do_not_call call chain" do
    should create_do_not_call_call_chain(@creator.dont_call(@subject))
  end

  it "sets up the RR do_not_call call chain" do
    should create_do_not_call_call_chain(@creator.do_not_allow(@subject))
  end

  it "sets up the RR do_not_call call chain" do
    should create_do_not_call_call_chain(@creator.dont_allow(@subject))
  end

  it "creates a mock Scenario for method when passed a second argument" do
    should create_scenario_with_method_name(@creator.do_not_call(@subject, :foobar))
  end

  it "creates a mock Scenario for method when passed a second argument" do
    should create_scenario_with_method_name(@creator.dont_call(@subject, :foobar))
  end

  it "creates a mock Scenario for method when passed a second argument" do
    should create_scenario_with_method_name(@creator.do_not_allow(@subject, :foobar))
  end

  it "creates a mock Scenario for method when passed a second argument" do
    should create_scenario_with_method_name(@creator.dont_allow(@subject, :foobar))
  end

  def create_scenario_with_method_name(scenario)
    scenario.with(1, 2)
    scenario.times_matcher.should == TimesCalledMatchers::IntegerMatcher.new(0)
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    scenario.argument_expectation.expected_arguments.should == [1, 2]

    proc do
      @subject.foobar(1, 2)
    end.should raise_error(Errors::TimesCalledError)
    reset
    nil
  end

  def create_do_not_call_call_chain(creator)
    scenario = creator.foobar(1, 2)
    scenario.times_matcher.should == TimesCalledMatchers::IntegerMatcher.new(0)
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    scenario.argument_expectation.expected_arguments.should == [1, 2]

    proc do
      @subject.foobar(1, 2)
    end.should raise_error(Errors::TimesCalledError)
    reset
    nil
  end
end

describe ScenarioCreator, "#probe and #stub" do
  it_should_behave_like "RR::ScenarioCreator"

  before do
    class << @subject
      def foobar(*args)
        :original_foobar
      end
    end
  end

  it "raises error when using do_not_call strategy" do
    @creator.do_not_call
    proc do
      @creator.probe
    end.should raise_error(
      Errors::ScenarioDefinitionError,
      "Scenarios cannot be probed when using do_not_call strategy"
    )
  end

  it "sets up the RR probe call chain" do
    scenario = @creator.stub.probe(@subject).foobar(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::AnyTimesMatcher.new
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    @subject.foobar(1, 2).should == :baz
  end

  it "creates a probe Scenario for method when passed a second argument" do
    scenario = @creator.stub.probe(@subject, :foobar)
    scenario.with(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::AnyTimesMatcher.new
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    @subject.foobar(1, 2).should == :baz
  end
end

describe ScenarioCreator, "#instance_of" do
  it_should_behave_like "RR::ScenarioCreator"

  it "raises an error when not passed a class" do
    proc do
      @creator.instance_of(Object.new)
    end.should raise_error(ArgumentError, "instance_of only accepts class objects")
  end

  it "sets up the RR probe call chain" do
    klass = Class.new
    scenario = @creator.stub.instance_of(klass).foobar(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::AnyTimesMatcher.new
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    klass.new.foobar(1, 2).should == :baz
  end

  it "creates a probe Scenario for method when passed a second argument" do
    klass = Class.new
    scenario = @creator.stub.instance_of(klass, :foobar)
    scenario.with(1, 2) {:baz}
    scenario.times_matcher.should == TimesCalledMatchers::AnyTimesMatcher.new
    scenario.argument_expectation.class.should == RR::Expectations::ArgumentEqualityExpectation
    klass.new.foobar(1, 2).should == :baz
  end
end

describe ScenarioCreator, "#create! using no strategy" do
  it_should_behave_like "RR::ScenarioCreator"

  it "raises error" do
    proc do
      @creator.create!(@subject, :foobar, 1, 2)
    end.should raise_error(
      Errors::ScenarioDefinitionError,
      "This Scenario has no strategy"
    )
  end
end

describe ScenarioCreator, "#create! using mock strategy" do
  it_should_behave_like "RR::ScenarioCreator"
  
  before do
    @creator.mock
  end

  it "sets expectations on the subject" do
    @creator.create!(@subject, :foobar, 1, 2) {:baz}.twice

    @subject.foobar(1, 2).should == :baz
    @subject.foobar(1, 2).should == :baz
    proc {@subject.foobar(1, 2)}.should raise_error(Errors::TimesCalledError)
  end
end

describe ScenarioCreator, "#create! using stub strategy" do
  it_should_behave_like "RR::ScenarioCreator"

  before do
    @creator.stub
  end

  it "stubs the subject without any args" do
    @creator.create!(@subject, :foobar) {:baz}
    @subject.foobar.should == :baz
  end

  it "stubs the subject mapping passed in args with the output" do
    @creator.create!(@subject, :foobar, 1, 2) {:one_two}
    @creator.create!(@subject, :foobar, 1) {:one}
    @creator.create!(@subject, :foobar) {:nothing}
    @subject.foobar.should == :nothing
    @subject.foobar(1).should == :one
    @subject.foobar(1, 2).should == :one_two
  end
end

describe ScenarioCreator, "#create! using do_not_call strategy" do
  it_should_behave_like "RR::ScenarioCreator"

  before do
    @creator.do_not_call
  end

  it "sets expectation for method to never be called with any arguments when on arguments passed in" do
    @creator.create!(@subject, :foobar)
    proc {@subject.foobar}.should raise_error(Errors::TimesCalledError)
    proc {@subject.foobar(1, 2)}.should raise_error(Errors::TimesCalledError)
  end

  it "sets expectation for method to never be called with passed in arguments" do
    @creator.create!(@subject, :foobar, 1, 2)
    proc {@subject.foobar}.should raise_error(Errors::ScenarioNotFoundError)
    proc {@subject.foobar(1, 2)}.should raise_error(Errors::TimesCalledError)
  end

  it "sets expectation for method to never be called with no arguments when with_no_args is set" do
    @creator.create!(@subject, :foobar).with_no_args
    proc {@subject.foobar}.should raise_error(Errors::TimesCalledError)
    proc {@subject.foobar(1, 2)}.should raise_error(Errors::ScenarioNotFoundError)
  end
end

describe ScenarioCreator, "#create! using mock strategy with probe" do
  it_should_behave_like "RR::ScenarioCreator"

  before do
    @creator.mock
    @creator.probe
  end

  it "sets expectations on the subject while calling the original method" do
    def @subject.foobar(*args); :baz; end
    @creator.create!(@subject, :foobar,1, 2).twice
    @subject.foobar(1, 2).should == :baz
    @subject.foobar(1, 2).should == :baz
    proc {@subject.foobar(1, 2)}.should raise_error(Errors::TimesCalledError)
  end

  it "sets after_call on the scenario when passed a block" do
    real_value = Object.new
    (class << @subject; self; end).class_eval do
      define_method(:foobar) {real_value}
    end
    @creator.create!(@subject, :foobar, 1, 2) do |value|
      mock(value).a_method {99}
      value
    end

    return_value = @subject.foobar(1, 2)
    return_value.should === return_value
    return_value.a_method.should == 99
  end
end

describe ScenarioCreator, "#create! using stub strategy with probe" do
  it_should_behave_like "RR::ScenarioCreator"

  before do
    @creator.stub
    @creator.probe
  end

  it "sets up a scenario with passed in arguments" do
    def @subject.foobar(*args); :baz; end
    @creator.create!(@subject, :foobar, 1, 2)
    proc do
      @subject.foobar
    end.should raise_error(Errors::ScenarioNotFoundError)
  end

  it "sets expectations on the subject while calling the original method" do
    def @subject.foobar(*args); :baz; end
    @creator.create!(@subject, :foobar, 1, 2) {:new_value}
    10.times do
      @subject.foobar(1, 2).should == :new_value
    end
  end

  it "sets after_call on the scenario when passed a block" do
    real_value = Object.new
    (class << @subject; self; end).class_eval do
      define_method(:foobar) {real_value}
    end
    @creator.create!(@subject, :foobar, 1, 2) do |value|
      mock(value).a_method {99}
      value
    end

    return_value = @subject.foobar(1, 2)
    return_value.should === return_value
    return_value.a_method.should == 99
  end
end
end