require 'spec_helper'

module RSpec::Core
  describe MemoizedHelpers do
    before(:each) { RSpec.configuration.configure_expectation_framework }

    def subject_value_for(describe_arg, &block)
      group = ExampleGroup.describe(describe_arg, &block)
      subject_value = nil
      group.example { subject_value = subject }
      group.run
      subject_value
    end

    describe "implicit subject" do
      describe "with a class" do
        it "returns an instance of the class" do
          expect(subject_value_for(Array)).to eq([])
        end
      end

      describe "with a Module" do
        it "returns the Module" do
          expect(subject_value_for(Enumerable)).to eq(Enumerable)
        end
      end

      describe "with a string" do
        it "returns the string" do
          expect(subject_value_for("Foo")).to eq("Foo")
        end
      end

      describe "with a number" do
        it "returns the number" do
          expect(subject_value_for(15)).to eq(15)
        end
      end

      describe "with a symbol" do
        it "returns the string form and issues a deprecation warning" do
          the_subject = :unset
          deprecations = []
          allow(RSpec).to receive(:warn_deprecation) { |msg| deprecations << msg }

          line = __LINE__ + 1
          ExampleGroup.describe :symbol do
            example { the_subject = subject }
          end.run

          expect(the_subject).to eq("symbol")
          expect(deprecations.size).to eq(1)
          deprecation = deprecations.first
          expect(deprecation).to match(/describe <a Symbol>/)
          expect(deprecation).to include("#{__FILE__}:#{line}")
        end
      end

      describe "with a hash" do
        it "returns a blank string and issues a deprecation warning" do
          the_subject = :unset
          deprecations = []
          allow(RSpec).to receive(:warn_deprecation) { |msg| deprecations << msg }

          line = __LINE__ + 1
          ExampleGroup.describe :foo => 3 do
            example { the_subject = subject }
          end.run

          expect(the_subject).to eq("")
          expect(deprecations.size).to eq(1)
          deprecation = deprecations.first
          expect(deprecation).to match(/describe <a Hash>/)
          expect(deprecation).to include("#{__FILE__}:#{line}")
        end
      end

      it "can be overriden and super'd to from a nested group" do
        outer_subject_value = inner_subject_value = nil

        ExampleGroup.describe(Array) do
          subject { super() << :parent_group }
          example { outer_subject_value = subject }

          context "nested" do
            subject { super() << :child_group }
            example { inner_subject_value = subject }
          end
        end.run

        expect(outer_subject_value).to eq([:parent_group])
        expect(inner_subject_value).to eq([:parent_group, :child_group])
      end
    end

    describe "explicit subject" do
      it "yields the example in which it is eval'd" do
        example_yielded_to_subject = nil
        example_yielded_to_example = nil

        group = ExampleGroup.describe
        group.subject { |e| example_yielded_to_subject = e }
        group.example { |e| subject; example_yielded_to_example = e }
        group.run

        expect(example_yielded_to_subject).to eq example_yielded_to_example
      end

      [false, nil].each do |falsy_value|
        context "with a value of #{falsy_value.inspect}" do
          it "is evaluated once per example" do
            group = ExampleGroup.describe(Array)
            group.before do
              Object.should_receive(:this_question?).once.and_return(falsy_value)
            end
            group.subject do
              Object.this_question?
            end
            group.example do
              subject
              subject
            end
            expect(group.run).to be_truthy, "expected subject block to be evaluated only once"
          end
        end
      end

      describe "defined in a top level group" do
        it "replaces the implicit subject in that group" do
          subject_value = subject_value_for(Array) do
            subject { [1, 2, 3] }
          end
          expect(subject_value).to eq([1, 2, 3])
        end
      end

      describe "defined in a top level group" do
        let(:group) do
          ExampleGroup.describe do
            subject{ [4, 5, 6] }
          end
        end

        it "is available in a nested group (subclass)" do
          subject_value = nil
          group.describe("I'm nested!") do
            example { subject_value = subject }
          end.run

          expect(subject_value).to eq([4, 5, 6])
        end

        it "is available in a doubly nested group (subclass)" do
          subject_value = nil
          group.describe("Nesting level 1") do
            describe("Nesting level 2") do
              example { subject_value = subject }
            end
          end.run

          expect(subject_value).to eq([4, 5, 6])
        end

        it "can be overriden and super'd to from a nested group" do
          subject_value = nil
          group.describe("Nested") do
            subject { super() + [:override] }
            example { subject_value = subject }
          end.run

          expect(subject_value).to eq([4, 5, 6, :override])
        end

        context 'when referenced in a `before(:all)` hook' do
          before do
            expect(::RSpec).to respond_to(:warn_deprecation)
            ::RSpec.stub(:warn_deprecation)
          end

          def define_and_run_group
            values = { :reference_lines => [] }

            ExampleGroup.describe do
              subject { [1, 2] }
              let(:list) { %w[ a b ] }

              before(:all) do
                subject << 3; values[:reference_lines] << __LINE__
                values[:final_subject_value_in_before_all] = subject; values[:reference_lines] << __LINE__
              end

              example do
                list << '1'
                values[:list_in_ex_1] = list
                values[:subject_value_in_example] = subject
              end

              example do
                list << '2'
                values[:list_in_ex_2] = list
              end
            end.run

            values
          end

          it 'memoizes the value within the before(:all) hook' do
            values = define_and_run_group
            expect(values.fetch(:final_subject_value_in_before_all)).to eq([1, 2, 3])
          end

          it 'preserves the memoization into the individual examples' do
            values = define_and_run_group
            expect(values.fetch(:subject_value_in_example)).to eq([1, 2, 3])
          end

          it 'does not cause other lets to be shared across examples' do
            values = define_and_run_group
            expect(values.fetch(:list_in_ex_1)).to eq(%w[ a b 1 ])
            expect(values.fetch(:list_in_ex_2)).to eq(%w[ a b 2 ])
          end

          it 'prints a warning since `subject` declarations are not intended to be used in :all hooks' do
            msgs = []
            ::RSpec.stub(:warn_deprecation) { |msg| msgs << msg }

            values = define_and_run_group

            expect(msgs).to include(*values[:reference_lines].map { |line|
              match(/subject accessed.*#{__FILE__}:#{line}/m)
            })
          end
        end
      end

      describe "with a name" do
        it "yields the example in which it is eval'd" do
          example_yielded_to_subject = nil
          example_yielded_to_example = nil

          group = ExampleGroup.describe
          group.subject(:foo) { |e| example_yielded_to_subject = e }
          group.example       { |e| foo; example_yielded_to_example = e }
          group.run

          expect(example_yielded_to_subject).to eq example_yielded_to_example
        end

        it "defines a method that returns the memoized subject" do
          list_value_1 = list_value_2 = subject_value_1 = subject_value_2 = nil

          ExampleGroup.describe do
            subject(:list) { [1, 2, 3] }
            example do
              list_value_1 = list
              list_value_2 = list
              subject_value_1 = subject
              subject_value_2 = subject
            end
          end.run

          expect(list_value_1).to eq([1, 2, 3])
          expect(list_value_1).to equal(list_value_2)

          expect(subject_value_1).to equal(subject_value_2)
          expect(subject_value_1).to equal(list_value_1)
        end

        it "is referred from inside subject by the name" do
          inner_subject_value = nil

          ExampleGroup.describe do
            subject(:list) { [1, 2, 3] }
            describe 'first' do
              subject(:first_element) { list.first }
              example { inner_subject_value = subject }
            end
          end.run

          expect(inner_subject_value).to eq(1)
        end

        it 'can continue to be referenced by the name even when an inner group redefines the subject' do
          named_value = nil

          ExampleGroup.describe do
            subject(:named) { :outer }

            describe "inner" do
              subject { :inner }
              example do
                subject # so the inner subject method is run and memoized
                named_value = self.named
              end
            end
          end.run

          expect(named_value).to eq(:outer)
        end

        it 'can continue to reference an inner subject after the outer subject name is referenced' do
          subject_value = nil

          ExampleGroup.describe do
            subject(:named) { :outer }

            describe "inner" do
              subject { :inner }
              example do
                named # so the outer subject method is run and memoized
                subject_value = self.subject
              end
            end
          end.run

          expect(subject_value).to eq(:inner)
        end

        it 'is not overriden when an inner group defines a new method with the same name' do
          subject_value = nil

          ExampleGroup.describe do
            subject(:named) { :outer_subject }

            describe "inner" do
              let(:named) { :inner_named }
              example { subject_value = self.subject }
            end
          end.run

          expect(subject_value).to be(:outer_subject)
        end

        context 'when `super` is used' do
          def should_raise_not_supported_error(&block)
            ex = nil

            ExampleGroup.describe do
              let(:list) { ["a", "b", "c"] }
              subject { [1, 2, 3] }

              describe 'first' do
                module_eval(&block) if block

                subject(:list) { super().first(2) }
                ex = example { subject }
              end
            end.run

            expect(ex.execution_result[:status]).to eq("failed")
            expect(ex.execution_result[:exception].message).to match(/super.*not supported/)
          end

          it 'raises a "not supported" error' do
            should_raise_not_supported_error
          end

          context 'with a `let` definition before the named subject' do
            it 'raises a "not supported" error' do
              should_raise_not_supported_error do
                # My first pass implementation worked unless there was a `let`
                # declared before the named subject -- this let is in place to
                # ensure that bug doesn't return.
                let(:foo) { 3 }
              end
            end
          end
        end
      end
    end

    context "using 'self' as an explicit subject" do
      it "delegates matcher to the ExampleGroup" do
        group = ExampleGroup.describe("group") do
          subject { self }
          def ok?; true; end
          def not_ok?; false; end

          it { should eq(self) }
          it { should be_ok }
          it { should_not be_not_ok }
        end

        expect(group.run).to be true
      end

      it 'supports a new expect-based syntax' do
        group = ExampleGroup.describe([1, 2, 3]) do
          it { is_expected.to be_an Array }
          it { is_expected.not_to include 4 }
        end

        expect(group.run).to be true
      end
    end

    describe "#its" do
      extend SilenceDSLDeprecations
      silence_dsl_deprecations do
        it "should issue deprecation warning" do
          expect_deprecation_with_call_site(__FILE__, __LINE__+1, '`its` method')
          RSpec::Core::ExampleGroup.its(nil) {}
        end

        subject do
          Class.new do
            def initialize
              @call_count = 0
            end

            def call_count
              @call_count += 1
            end
          end.new
        end

        context "with a call counter" do
          its(:call_count) { should eq(1) }
        end

        context "with nil value" do
          subject do
            Class.new do
              def nil_value
                nil
              end
            end.new
          end
          its(:nil_value) { should be_nil }
        end

        context "with nested attributes" do
          subject do
            Class.new do
              def name
                "John"
              end
            end.new
          end
          its("name")            { should eq("John") }
          its("name.size")       { should eq(4) }
          its("name.size.class") { should eq(Fixnum) }
        end

        context "when it responds to #[]" do
          subject do
            Class.new do
              def [](*objects)
                objects.map do |object|
                  "#{object.class}: #{object.to_s}"
                end.join("; ")
              end

              def name
                "George"
              end
            end.new
          end
          its([:a]) { should eq("Symbol: a") }
          its(['a']) { should eq("String: a") }
          its([:b, 'c', 4]) { should eq("Symbol: b; String: c; Fixnum: 4") }
          its(:name) { should eq("George") }
          context "when referring to an attribute without the proper array syntax" do
            context "it raises an error" do
              its(:age) do
                expect do
                  should eq(64)
                end.to raise_error(NoMethodError)
              end
            end
          end
        end

        context "when it does not respond to #[]" do
          subject { Object.new }

          context "it raises an error" do
            its([:a]) do
              expect do
                should eq("Symbol: a")
              end.to raise_error(NoMethodError)
            end
          end
        end

        context "calling and overriding super" do
          it "calls to the subject defined in the parent group" do
            group = ExampleGroup.describe(Array) do
              subject { [1, 'a'] }

              its(:last) { should eq("a") }

              describe '.first' do
                def subject; super().first; end

                its(:next) { should eq(2) }
              end
            end

            expect(group.run).to be_truthy
          end
        end

        context "with nil subject" do
          subject do
            Class.new do
              def initialize
                @counter = -1
              end
              def nil_if_first_time
                @counter += 1
                @counter == 0 ? nil : true
              end
            end.new
          end
          its(:nil_if_first_time) { should be(nil) }
        end

        context "with false subject" do
          subject do
            Class.new do
              def initialize
                @counter = -1
              end
              def false_if_first_time
                @counter += 1
                @counter > 0
              end
            end.new
          end
          its(:false_if_first_time) { should be(false) }
        end

        describe 'accessing `subject` in `before` and `let`' do
          subject { 'my subject' }
          before { @subject_in_before = subject }
          let(:subject_in_let) { subject }
          let!(:eager_loaded_subject_in_let) { subject }

          # These examples read weird, because we're actually
          # specifying the behaviour of `its` itself
          its(nil) { expect(subject).to eq('my subject') }
          its(nil) { expect(@subject_in_before).to eq('my subject') }
          its(nil) { expect(subject_in_let).to eq('my subject') }
          its(nil) { expect(eager_loaded_subject_in_let).to eq('my subject') }
        end
      end
    end

    describe '#subject!' do
      let(:prepared_array) { [1,2,3] }
      subject! { prepared_array.pop }

      it "evaluates subject before example" do
        expect(prepared_array).to eq([1,2])
      end

      it "returns memoized value from first invocation" do
        expect(subject).to eq(3)
      end
    end
  end

  describe "#let" do
    let(:counter) do
      Class.new do
        def initialize
          @count = 0
        end
        def count
          @count += 1
        end
      end.new
    end

    let(:nil_value) do
      @nil_value_count += 1
      nil
    end

    it "generates an instance method" do
      expect(counter.count).to eq(1)
    end

    it "caches the value" do
      expect(counter.count).to eq(1)
      expect(counter.count).to eq(2)
    end

    it "caches a nil value" do
      @nil_value_count = 0
      nil_value
      nil_value

      expect(@nil_value_count).to eq(1)
    end

    let(:yield_the_example) do |example_yielded_to_let|
      @example_yielded_to_let = example_yielded_to_let
    end

    it "yields the example" do |example_yielded_to_example|
      yield_the_example
      expect(@example_yielded_to_let).to equal example_yielded_to_example
    end

    let(:regex_with_capture) { %r[RegexWithCapture(\d)] }

    it 'does not pass the block up the ancestor chain' do
      # Test for Ruby bug http://bugs.ruby-lang.org/issues/8059
      expect("RegexWithCapture1".match(regex_with_capture)[1]).to eq('1')
    end

    it 'raises a useful error when called without a block' do
      expect do
        ExampleGroup.describe { let(:list) }
      end.to raise_error(/#let or #subject called without a block/)
    end

    let(:a_value) { "a string" }

    context 'when overriding let in a nested context' do
      let(:a_value) { super() + " (modified)" }

      it 'can use `super` to reference the parent context value' do
        expect(a_value).to eq("a string (modified)")
      end
    end

    context 'when the declaration uses `return`' do
      let(:value) do
        return :early_exit if @early_exit
        :late_exit
      end

      it 'can exit the let declaration early' do
        @early_exit = true
        expect(value).to eq(:early_exit)
      end

      it 'can get past a conditional `return` statement' do
        @early_exit = false
        expect(value).to eq(:late_exit)
      end
    end

    context 'when referenced in a `before(:all)` hook' do
      before do
        expect(::RSpec).to respond_to(:warn_deprecation)
        ::RSpec.stub(:warn_deprecation)
      end

      def define_and_run_group
        values = { :reference_lines => [] }

        ExampleGroup.describe do
          let(:list) { [1, 2] }
          subject { %w[ a b ] }

          before(:all) do
            list << 3; values[:reference_lines] << __LINE__
            values[:final_list_value_in_before_all] = list; values[:reference_lines] << __LINE__
          end

          example do
            subject << "1"
            values[:subject_in_ex_1] = subject
            values[:list_value_in_example] = list
          end

          example do
            subject << "2"
            values[:subject_in_ex_2] = subject
          end
        end.run

        values
      end

      it 'memoizes the value within the before(:all) hook' do
        values = define_and_run_group
        expect(values.fetch(:final_list_value_in_before_all)).to eq([1, 2, 3])
      end

      it 'preserves the memoized value into the examples' do
        values = define_and_run_group
        expect(values.fetch(:list_value_in_example)).to eq([1, 2, 3])
      end

      it 'does not cause the subject to be shared across examples' do
        values = define_and_run_group
        expect(values.fetch(:subject_in_ex_1)).to eq(%w[ a b 1 ])
        expect(values.fetch(:subject_in_ex_2)).to eq(%w[ a b 2 ])
      end

      it 'prints a warning since `let` declarations are not intended to be used in :all hooks' do
        msgs = []
        ::RSpec.stub(:warn_deprecation) { |msg| msgs << msg }

        values = define_and_run_group

        expect(msgs).to include(*values[:reference_lines].map { |line|
          match(/let declaration `list` accessed.*#{__FILE__}:#{line}/m)
        })
      end
    end

    context "when included modules have hooks that define memoized helpers" do
      it "allows memoized helpers to override methods in previously included modules" do
        group = ExampleGroup.describe do
          include Module.new {
            def self.included(m); m.let(:unrelated) { :unrelated }; end
          }

          include Module.new {
            def hello_message; "Hello from module"; end
          }

          let(:hello_message) { "Hello from let" }
        end

        expect(group.new.hello_message).to eq("Hello from let")
      end
    end
  end

  describe "#let!" do
    subject { [1,2,3] }
    let!(:popped) { subject.pop }

    it "evaluates the value non-lazily" do
      expect(subject).to eq([1,2])
    end

    it "returns memoized value from first invocation" do
      expect(popped).to eq(3)
    end
  end

  describe 'using subject in before and let blocks' do
    shared_examples_for 'a subject' do
      let(:subject_id_in_let) { subject.object_id }
      before { @subject_id_in_before = subject.object_id }

      it 'should be memoized' do
        expect(subject_id_in_let).to eq(@subject_id_in_before)
      end

      it { is_expected.to eq(subject) }
    end

    describe Object do
      context 'with implicit subject' do
        it_should_behave_like 'a subject'
      end

      context 'with explicit subject' do
        subject { Object.new }
        it_should_behave_like 'a subject'
      end

      context 'with a constant subject'do
        subject { 123 }
        it_should_behave_like 'a subject'
      end
    end
  end

  describe 'Module#define_method' do
    it 'is still a private method' do
      a_module = Module.new
      expect { a_module.define_method(:name) { "implementation" } }.to raise_error NoMethodError
    end
  end
end

