module RSpec
  module Mocks
    RSpec.describe TestDouble do
      shared_examples "reserved_method" do |name, options|
        responds = options.fetch(:responds)
        value = options[:value]

        it "can be overridden with a stub" do
          allow(obj).to receive(name) { :non_nil_value }
          expect(obj.send(name)).to be(:non_nil_value)
        end

        it "responds when overridden" do
          allow(obj).to receive(:to_ary) { :non_nil_value }
          expect(obj).to respond_to(:to_ary)
        end

        if responds === true
          it "does respond to ##{name}" do
            expect(obj).to respond_to(name)
          end

          it "returns nil for ##{name}" do
            expect(obj.send(name)).to eq value
          end
        else
          it "does not respond to ##{name}" do
            expect(obj).to_not respond_to(name)
          end

          if responds == :raises_error
            it "raises a NoMethodError for ##{name}" do
              expect { obj.send(name) }.to raise_error(NoMethodError)
            end
          end
        end
      end

      context "as_null_object" do
        let(:obj) { double('obj').as_null_object }

        it_behaves_like "reserved_method", :to_int, :responds => true, :value => 0
        if (RUBY_VERSION.to_f >= 1.9)
          it_behaves_like "reserved_method", :to_a, :responds => true
        end
        it_behaves_like "reserved_method", :to_ary, :responds => true
        it_behaves_like "reserved_method", :to_h, :responds => true, :value => {}
        it_behaves_like "reserved_method", :to_hash, :responds => true, :value => {}
        it_behaves_like "reserved_method", :to_str, :responds => true, :value => "#[Double \"obj\"]"

        it "supports Array#flatten" do
          expect([obj].flatten).to eq([obj])
        end
      end

      context "without as_null_object" do
        let(:obj) { double('obj') }

        it_behaves_like "reserved_method", :to_int, :responds => false
        if (RUBY_VERSION.to_f >= 1.9)
          it_behaves_like "reserved_method", :to_a, :responds => :raises_error
        end
        it_behaves_like "reserved_method", :to_ary, :responds => :raises_error
        it_behaves_like "reserved_method", :to_h, :responds => false
        it_behaves_like "reserved_method", :to_hash, :responds => false
        it_behaves_like "reserved_method", :to_str, :responds => false

        it "supports Array#flatten" do
          expect([obj].flatten).to eq([obj])
        end
      end

      describe "#freeze" do
        subject { double }

        it "gives a warning" do
          expect(RSpec).to receive(:warn_with).with(/freeze a test double/)
          subject.freeze
        end

        it "gives the correct call site for the warning" do
          expect_warning_with_call_site(__FILE__, __LINE__ + 1)
          subject.freeze
        end

        it "doesn't freeze the object" do
          allow(RSpec).to receive(:warn_with).with(/freeze a test double/)
          double.freeze
          allow(subject).to receive(:hi)

          expect {
            subject.hi
          }.not_to raise_error
        end

        it "returns the instance of the test double" do
          allow(RSpec).to receive(:warn_with).with(/freeze a test double/)
          expect(subject.freeze).to eq subject
        end
      end

      RSpec.shared_examples_for "a copy method" do |method|
        it "copies the `as_null_object` state when #{method}'d" do
          dbl = double.as_null_object
          copy = dbl.__send__(method)
          expect(copy.foo.bar).to be(copy)
        end
      end

      include_examples "a copy method", :dup
      include_examples "a copy method", :clone

      [[:should, :expect], [:expect], [:should]].each do |syntax|
        context "with syntax #{syntax.inspect}" do
          include_context "with syntax", syntax

          it 'stubs the methods passed in the stubs hash' do
            dbl = double("MyDouble", :a => 5, :b => 10)

            expect(dbl.a).to eq(5)
            expect(dbl.b).to eq(10)
          end
        end
      end
    end
  end
end
