require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper")

module RR
  module DoubleDefinitions
    describe DoubleDefinitionCreate do
      subject { Object.new }

      attr_reader :double_definition_create, :strategy_method_name

      include_examples "Swapped Space"

      before(:each) do
        @double_definition_create = DoubleDefinitionCreate.new
      end

      describe "#root_subject" do
        it "returns #subject" do
          double_definition_create.stub(subject).foobar
          expect(double_definition_create.root_subject).to eq subject
        end
      end

      describe "StrategySetupMethods" do
        describe "normal strategy definitions" do
          def call_strategy(*args, &block)
            double_definition_create.__send__(strategy_method_name, *args, &block)
          end

          describe "#mock" do
            before do
              @strategy_method_name = :mock
            end

            context "when passing no args" do
              it "returns self" do
                expect(call_strategy).to equal double_definition_create
              end
            end

            context "when passed a subject and a method_name argument" do
              it "creates a mock Double for method" do
                double_definition = double_definition_create.mock(subject, :foobar).returns {:baz}
                expect(double_definition.times_matcher).to eq RR::TimesCalledMatchers::IntegerMatcher.new(1)
                expect(double_definition.argument_expectation.class).to eq RR::Expectations::ArgumentEqualityExpectation
                expect(double_definition.argument_expectation.expected_arguments).to eq []
                expect(subject.foobar).to eq :baz
              end
            end
          end

          describe "#stub" do
            before do
              @strategy_method_name = :stub
            end

            context "when passing no args" do
              it "returns self" do
                expect(call_strategy).to equal double_definition_create
              end
            end

            context "when passed subject and a method_name argument" do
              it "creates a stub Double for method when passed a method_name argument" do
                double_definition = double_definition_create.stub(subject, :foobar).returns {:baz}
                expect(double_definition.times_matcher).to eq RR::TimesCalledMatchers::AnyTimesMatcher.new
                expect(double_definition.argument_expectation.class).to eq RR::Expectations::AnyArgumentExpectation
                expect(subject.foobar).to eq :baz
              end
            end
          end

          describe "#dont_allow" do
            before do
              @strategy_method_name = :dont_allow
            end

            context "when passing no args" do
              it "returns self" do
                expect(call_strategy).to equal double_definition_create
              end
            end

            context "when passed a subject and a method_name argument_expectation" do
              it "creates a mock Double for method" do
                double_definition = double_definition_create.dont_allow(subject, :foobar)
                expect(double_definition.times_matcher).to eq RR::TimesCalledMatchers::NeverMatcher.new
                expect(double_definition.argument_expectation.class).to eq RR::Expectations::AnyArgumentExpectation

                expect {
                  subject.foobar
                }.to raise_error(RR::Errors::TimesCalledError)
                RR.reset
              end
            end
          end
        end

        describe "! strategy definitions" do
          attr_reader :strategy_method_name
          def call_strategy(*args, &definition_eval_block)
            double_definition_create.__send__(strategy_method_name, *args, &definition_eval_block)
          end

          describe "#mock!" do
            before do
              @strategy_method_name = :mock!
            end

            context "when passed a method_name argument" do
              it "sets #verification_strategy to Mock" do
                double_definition_create.mock!(:foobar)
                expect(double_definition_create.verification_strategy.class).to eq Strategies::Verification::Mock
                expect { RR.verify }.to raise_error(::RR::Errors::TimesCalledError)
              end
            end
          end

          describe "#stub!" do
            before do
              @strategy_method_name = :stub!
            end

            context "when passed a method_name argument" do
              it "sets #verification_strategy to Stub" do
                double_definition_create.stub!(:foobar)
                expect(double_definition_create.verification_strategy.class).to eq Strategies::Verification::Stub
              end
            end
          end

          describe "#dont_allow!" do
            before do
              @strategy_method_name = :dont_allow!
            end

            context "when passed a method_name argument" do
              it "sets #verification_strategy to DontAllow" do
                double_definition_create.dont_allow!(:foobar)
                expect(double_definition_create.verification_strategy.class).to eq Strategies::Verification::DontAllow
              end
            end
          end
        end

        describe "#stub.proxy" do
          before do
            class << subject
              def foobar(*args)
                :original_foobar
              end
            end
          end

          context "when passed a method_name argument" do
            it "creates a proxy Double for method" do
              double_definition = double_definition_create.stub.proxy(subject, :foobar).after_call {:baz}
              expect(double_definition.times_matcher).to eq RR::TimesCalledMatchers::AnyTimesMatcher.new
              expect(double_definition.argument_expectation.class).to eq RR::Expectations::AnyArgumentExpectation
              expect(subject.foobar).to eq :baz
            end
          end
        end

        describe "#instance_of" do
          context "when not passed a class" do
            it "raises an ArgumentError" do
              expect {
                double_definition_create.instance_of(Object.new).foobar
              }.to raise_error(ArgumentError, "instance_of only accepts class objects")
            end
          end

          context "when passed a method_name argument" do
            it "creates a proxy Double for method" do
              klass = Class.new
              double_definition = double_definition_create.stub.instance_of(klass, :foobar).returns {:baz}
              expect(double_definition.times_matcher).to eq RR::TimesCalledMatchers::AnyTimesMatcher.new
              expect(double_definition.argument_expectation.class).to eq RR::Expectations::AnyArgumentExpectation
              expect(klass.new.foobar).to eq :baz
            end
          end
        end

        describe "#instance_of.mock" do
          before do
            @klass = Class.new
          end

#          context "when passed no arguments" do
#            it "returns a DoubleDefinitiondouble_definition_create" do
#              expect(instance_of.instance_of).to be_instance_of(DoubleDefinitionCreate)
#            end
#          end

          context "when passed a method_name argument" do
            it "creates a instance_of Double for method" do
              double_definition = instance_of.mock(@klass, :foobar)
              double_definition.with(1, 2) {:baz}
              expect(double_definition.times_matcher).to eq RR::TimesCalledMatchers::IntegerMatcher.new(1)
              expect(double_definition.argument_expectation.class).to eq RR::Expectations::ArgumentEqualityExpectation
              expect(double_definition.argument_expectation.expected_arguments).to eq [1, 2]

              expect(@klass.new.foobar(1, 2)).to eq :baz
            end
          end
        end
      end

      describe "StrategyExecutionMethods" do
        describe "#create" do
          context "when #verification_strategy is a Mock" do
            context "when #implementation_strategy is a Reimplementation" do
              before do
                double_definition_create.mock(subject)
              end

              it "sets expectation on the #subject that it will be sent the method_name once with the passed-in arguments" do
                mock(subject).foobar(1, 2)
                subject.foobar(1, 2)
                expect { subject.foobar(1, 2) }.to raise_error(RR::Errors::TimesCalledError)
                expect { RR.verify }.to raise_error(RR::Errors::TimesCalledError)
              end

              describe "#subject.method_name being called" do
                it "returns the return value of the Double#returns block" do
                  double_definition_create.call(:foobar, [1, 2], {}) {:baz}
                  expect(subject.foobar(1, 2)).to eq :baz
                end
              end
            end

            context "when #implementation_strategy is a Proxy" do
              before do
                double_definition_create.mock
                double_definition_create.proxy(subject)
              end

              it "sets expectation on the #subject that it will be sent the method_name once with the passed-in arguments" do
                def subject.foobar(*args)
                  :baz
                end
                mock(subject).foobar(1, 2)

                subject.foobar(1, 2)
                expect { subject.foobar(1, 2) }.to raise_error(RR::Errors::TimesCalledError)
                expect { RR.verify }.to raise_error(RR::Errors::TimesCalledError)
              end

              describe "#subject.method_name being called" do
                it "calls the original method" do
                  original_method_called = false
                  (class << subject; self; end).class_eval do
                    define_method(:foobar) do |*args|
                      original_method_called = true
                    end
                  end
                  double_definition_create.call(:foobar, [1, 2], {})
                  subject.foobar(1, 2)
                  expect(original_method_called).to be_true
                end

                context "when not passed a block" do
                  it "returns the value of the original method" do
                    def subject.foobar(*args)
                      :baz;
                    end
                    double_definition_create.call(:foobar, [1, 2], {})
                    expect(subject.foobar(1, 2)).to eq :baz
                  end
                end

                context "when passed a block" do
                  attr_reader :real_value
                  before do
                    @real_value = real_value = Object.new
                    (class << subject; self; end).class_eval do
                      define_method(:foobar) {|arg1, arg2| real_value}
                    end
                  end

                  it "calls the block with the return value of the original method" do
                    double_definition_create.call(:foobar, [1, 2], {}) do |value|
                      mock(value).a_method {99}
                      value
                    end
                    subject.foobar(1, 2)
                    expect(real_value.a_method).to eq 99
                  end

                  it "returns the return value of the block" do
                    double_definition_create.call(:foobar, [1, 2], {}) do |value|
                      :something_else
                    end
                    expect(subject.foobar(1, 2)).to eq :something_else
                  end
                end
              end
            end
          end

          context "when #verification_strategy is a Stub" do
            context "when #implementation_strategy is a Reimplementation" do
              before do
                double_definition_create.stub(subject)
              end

              context "when not passed a block" do
                it "returns nil" do
                  double_definition_create.call(:foobar, [], {})
                  expect(subject.foobar).to be_nil
                end
              end

              context "when passed a block" do
                describe "#subject.method_name being called" do
                  it "returns the return value of the block" do
                    double_definition_create.call(:foobar, [], {}) {:baz}
                    expect(subject.foobar).to eq :baz
                  end
                end
              end

              context "when not passed args" do
                describe "#subject.method_name being called with any arguments" do
                  it "invokes the implementation of the Stub" do
                    double_definition_create.call(:foobar, [], {}) {:baz}
                    expect(subject.foobar(1, 2)).to eq :baz
                    expect(subject.foobar()).to eq :baz
                    expect(subject.foobar([])).to eq :baz
                  end
                end
              end

              context "when passed args" do
                describe "#subject.method_name being called with the passed-in arguments" do
                  it "invokes the implementation of the Stub" do
                    double_definition_create.call(:foobar, [1, 2], {}) {:baz}
                    expect(subject.foobar(1, 2)).to eq :baz
                  end
                end

                describe "#subject.method_name being called with different arguments" do
                  it "raises a DoubleNotFoundError" do
                    double_definition_create.call(:foobar, [1, 2], {}) {:baz}
                    expect {
                      subject.foobar
                    }.to raise_error(RR::Errors::DoubleNotFoundError)
                  end
                end
              end
            end

            context "when #implementation_strategy is a Proxy" do
              before do
                def subject.foobar(*args)
                  :original_return_value
                end
                double_definition_create.stub
                double_definition_create.proxy(subject)
              end

              context "when not passed a block" do
                describe "#subject.method_name being called" do
                  it "invokes the original implementanion" do
                    double_definition_create.call(:foobar, [], {})
                    expect(subject.foobar).to eq :original_return_value
                  end
                end
              end

              context "when passed a block" do
                describe "#subject.method_name being called" do
                  it "invokes the original implementanion and invokes the block with the return value of the original implementanion" do
                    passed_in_value = nil
                    double_definition_create.call(:foobar, [], {}) do |original_return_value|
                      passed_in_value = original_return_value
                    end
                    subject.foobar
                    expect(passed_in_value).to eq :original_return_value
                  end

                  it "returns the return value of the block" do
                    double_definition_create.call(:foobar, [], {}) do |original_return_value|
                      :new_return_value
                    end
                    expect(subject.foobar).to eq :new_return_value
                  end
                end
              end

              context "when passed args" do
                describe "#subject.method_name being called with the passed-in arguments" do
                  it "invokes the implementation of the Stub" do
                    double_definition_create.call(:foobar, [1, 2], {}) {:baz}
                    expect(subject.foobar(1, 2)).to eq :baz
                  end
                end

                describe "#subject.method_name being called with different arguments" do
                  it "raises a DoubleNotFoundError" do
                    double_definition_create.call(:foobar, [1, 2], {}) {:baz}
                    expect {
                      subject.foobar
                    }.to raise_error(RR::Errors::DoubleNotFoundError)
                  end
                end
              end
            end
          end

          context "when #verification_strategy is a DontAllow" do
            before do
              double_definition_create.dont_allow(subject)
            end

            context "when not passed args" do
              describe "#subject.method_name being called with any arguments" do
                it "raises a TimesCalledError" do
                  double_definition_create.call(:foobar, [], {})
                  expect { subject.foobar }.to raise_error(RR::Errors::TimesCalledError)
                  expect { subject.foobar(1, 2) }.to raise_error(RR::Errors::TimesCalledError)
                end
              end
            end

            context "when passed args" do
              describe "#subject.method_name being called with the passed-in arguments" do
                it "raises a TimesCalledError" do
                  double_definition_create.call(:foobar, [1, 2], {})
                  expect { subject.foobar(1, 2) }.to raise_error(RR::Errors::TimesCalledError)
                end
              end

              describe "#subject.method_name being called with different arguments" do
                it "raises a DoubleNotFoundError" do
                  double_definition_create.call(:foobar, [1, 2], {})
                  expect { subject.foobar() }.to raise_error(RR::Errors::DoubleNotFoundError)
                end
              end
            end
          end
        end
      end
    end
  end
end
