require_relative '../../spec_helper'

describe "Range#step" do
  before :each do
    ScratchPad.record []
  end

  it "returns self" do
    r = 1..2
    r.step { }.should equal(r)
  end

  ruby_version_is ""..."3.4" do
    it "calls #to_int to coerce step to an Integer" do
      obj = mock("Range#step")
      obj.should_receive(:to_int).and_return(1)

      (1..2).step(obj) { |x| ScratchPad << x }
      ScratchPad.recorded.should eql([1, 2])
    end

    it "raises a TypeError if step does not respond to #to_int" do
      obj = mock("Range#step non-integer")

      -> { (1..2).step(obj) { } }.should raise_error(TypeError)
    end

    it "raises a TypeError if #to_int does not return an Integer" do
      obj = mock("Range#step non-integer")
      obj.should_receive(:to_int).and_return("1")

      -> { (1..2).step(obj) { } }.should raise_error(TypeError)
    end

    it "raises a TypeError if the first element does not respond to #succ" do
      obj = mock("Range#step non-comparable")
      obj.should_receive(:<=>).with(obj).and_return(1)

      -> { (obj..obj).step { |x| x } }.should raise_error(TypeError)
    end
  end

  ruby_version_is "3.4" do
    it "calls #coerce to coerce step to an Integer" do
      obj = mock("Range#step")
      obj.should_receive(:coerce).at_least(:once).and_return([1, 2])

      (1..3).step(obj) { |x| ScratchPad << x }
      ScratchPad.recorded.should eql([1, 3])
    end

    it "raises a TypeError if step does not respond to #coerce" do
      obj = mock("Range#step non-coercible")

      -> { (1..2).step(obj) { } }.should raise_error(TypeError)
    end
  end

  it "raises an ArgumentError if step is 0" do
    -> { (-1..1).step(0) { |x| x } }.should raise_error(ArgumentError)
  end

  it "raises an ArgumentError if step is 0.0" do
    -> { (-1..1).step(0.0) { |x| x } }.should raise_error(ArgumentError)
  end

  ruby_version_is "3.4" do
    it "does not raise an ArgumentError if step is 0 for non-numeric ranges" do
      t = Time.utc(2023, 2, 24)
      NATFIXME 'Fix Range#step for non-numeric ranges', exception: TypeError, message: "can't iterate from Time" do
        -> { (t..t+1).step(0) { break } }.should_not raise_error(ArgumentError)
      end
    end
  end

  ruby_version_is ""..."3.4" do
    it "raises an ArgumentError if step is negative" do
      -> { (-1..1).step(-2) { |x| x } }.should raise_error(ArgumentError)
    end
  end

  describe "with inclusive end" do
    describe "and Integer values" do
      it "yields Integer values incremented by 1 and less than or equal to end when not passed a step" do
        (-2..2).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
      end

      it "yields Integer values incremented by an Integer step" do
        (-5..5).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5, -3, -1, 1, 3, 5])
      end

      it "yields Float values incremented by a Float step" do
        (-2..2).step(1.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
      end

      ruby_version_is "3.4" do
        it "does not iterate if step is negative for forward range" do
          (-1..1).step(-1) { |x| ScratchPad << x }
          ScratchPad.recorded.should eql([])
        end

        it "iterates backward if step is negative for backward range" do
          (1..-1).step(-1) { |x| ScratchPad << x }
          ScratchPad.recorded.should eql([1, 0, -1])
        end
      end
    end

    describe "and Float values" do
      it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
        (-2.0..2.0).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
      end

      it "yields Float values incremented by an Integer step" do
        (-5.0..5.0).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
      end

      it "yields Float values incremented by a Float step" do
        (-1.0..1.0).step(0.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
      end

      it "returns Float values of 'step * n + begin <= end'" do
        (1.0..6.4).step(1.8) { |x| ScratchPad << x }
        (1.0..12.7).step(1.3) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([1.0, 2.8, 4.6, 6.4, 1.0, 2.3, 3.6,
                                       4.9, 6.2, 7.5, 8.8, 10.1, 11.4, 12.7])
      end

      it "handles infinite values at either end" do
        (-Float::INFINITY..0.0).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
        ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])

        ScratchPad.record []
        (0.0..Float::INFINITY).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
        ScratchPad.recorded.should eql([0.0, 2.0, 4.0])
      end
    end

    describe "and Integer, Float values" do
      it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
        (-2..2.0).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
      end

      it "yields Float values incremented by an Integer step" do
        (-5..5.0).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
      end

      it "yields Float values incremented by a Float step" do
        (-1..1.0).step(0.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
      end
    end

    describe "and Float, Integer values" do
      it "yields Float values incremented by 1 and less than or equal to end when not passed a step" do
        (-2.0..2).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0, 2.0])
      end

      it "yields Float values incremented by an Integer step" do
        (-5.0..5).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0, 5.0])
      end

      it "yields Float values incremented by a Float step" do
        (-1.0..1).step(0.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5, 1.0])
      end
    end

    describe "and String values" do
      it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
        ("A".."E").step { |x| ScratchPad << x }
        ScratchPad.recorded.should == ["A", "B", "C", "D", "E"]
      end

      it "yields String values incremented by #succ called Integer step times" do
        ("A".."G").step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should == ["A", "C", "E", "G"]
      end

      it "raises a TypeError when passed a Float step" do
        -> { ("A".."G").step(2.0) { } }.should raise_error(TypeError)
      end

      ruby_version_is ""..."3.4" do
        it "calls #succ on begin and each element returned by #succ" do
          obj = mock("Range#step String start")
          obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0)
          obj.should_receive(:succ).exactly(2).times.and_return(obj)

          (obj..obj).step { |x| ScratchPad << x }
          ScratchPad.recorded.should == [obj, obj, obj]
        end
      end

      ruby_version_is "3.4" do
        it "yields String values adjusted by step and less than or equal to end" do
          NATFIXME 'it yields String values adjusted by step and less than or equal to end', exception: TypeError, message: 'no implicit conversion of String into Integer' do
            ("A".."AAA").step("A") { |x| ScratchPad << x }
            ScratchPad.recorded.should == ["A", "AA", "AAA"]
          end
        end

        it "raises a TypeError when passed an incompatible type step" do
          -> { ("A".."G").step([]) { } }.should raise_error(TypeError)
        end

        it "calls #+ on begin and each element returned by #+" do
          NATFIXME 'it calls #+ on begin and each element returned by #+', exception: TypeError, message: 'no implicit conversion of MockObject into Integer' do
            start = mock("Range#step String start")
            stop = mock("Range#step String stop")

            mid1 = mock("Range#step String mid1")
            mid2 = mock("Range#step String mid2")

            step = mock("Range#step String step")

            # Deciding on the direction of iteration
            start.should_receive(:<=>).with(stop).at_least(:twice).and_return(-1)
            # Deciding whether the step moves iteration in the right direction
            start.should_receive(:<=>).with(mid1).and_return(-1)
            # Iteration 1
            start.should_receive(:+).at_least(:once).with(step).and_return(mid1)
            # Iteration 2
            mid1.should_receive(:<=>).with(stop).and_return(-1)
            mid1.should_receive(:+).with(step).and_return(mid2)
            # Iteration 3
            mid2.should_receive(:<=>).with(stop).and_return(0)

            (start..stop).step(step) { |x| ScratchPad << x }
            ScratchPad.recorded.should == [start, mid1, mid2]
          end
        end

        it "iterates backward if the step is decreasing values, and the range is backward" do
          NATFIXME 'it iterates backward if the step is decreasing values, and the range is backward', exception: TypeError, message: 'no implicit conversion of MockObject into Integer' do
            start = mock("Range#step String start")
            stop = mock("Range#step String stop")

            mid1 = mock("Range#step String mid1")
            mid2 = mock("Range#step String mid2")

            step = mock("Range#step String step")

            # Deciding on the direction of iteration
            start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1)
            # Deciding whether the step moves iteration in the right direction
            start.should_receive(:<=>).with(mid1).and_return(1)
            # Iteration 1
            start.should_receive(:+).at_least(:once).with(step).and_return(mid1)
            # Iteration 2
            mid1.should_receive(:<=>).with(stop).and_return(1)
            mid1.should_receive(:+).with(step).and_return(mid2)
            # Iteration 3
            mid2.should_receive(:<=>).with(stop).and_return(0)

            (start..stop).step(step) { |x| ScratchPad << x }
            ScratchPad.recorded.should == [start, mid1, mid2]
          end
        end

        it "does no iteration of the direction of the range and of the step don't match" do
          NATFIXME "it does no iteration of the direction of the range and of the step don't match", exception: TypeError, message: 'no implicit conversion of MockObject into Integer' do
            start = mock("Range#step String start")
            stop = mock("Range#step String stop")

            mid1 = mock("Range#step String mid1")
            mid2 = mock("Range#step String mid2")

            step = mock("Range#step String step")

            # Deciding on the direction of iteration: stop > start
            start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1)
            # Deciding whether the step moves iteration in the right direction
            # start + step < start, the direction is opposite to the range's
            start.should_receive(:+).with(step).and_return(mid1)
            start.should_receive(:<=>).with(mid1).and_return(-1)

            (start..stop).step(step) { |x| ScratchPad << x }
            ScratchPad.recorded.should == []
          end
        end
      end
    end
  end

  describe "with exclusive end" do
    describe "and Integer values" do
      it "yields Integer values incremented by 1 and less than end when not passed a step" do
        (-2...2).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2, -1, 0, 1])
      end

      it "yields Integer values incremented by an Integer step" do
        (-5...5).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
      end

      it "yields Float values incremented by a Float step" do
        (-2...2).step(1.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
      end
    end

    describe "and Float values" do
      it "yields Float values incremented by 1 and less than end when not passed a step" do
        (-2.0...2.0).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
      end

      it "yields Float values incremented by an Integer step" do
        (-5.0...5.0).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
      end

      it "yields Float values incremented by a Float step" do
        (-1.0...1.0).step(0.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
      end

      it "returns Float values of 'step * n + begin < end'" do
        (1.0...6.4).step(1.8) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([1.0, 2.8, 4.6])
      end

      it "correctly handles values near the upper limit" do # https://bugs.ruby-lang.org/issues/16612
        (1.0...55.6).step(18.2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([1.0, 19.2, 37.4, 55.599999999999994])

        (1.0...55.6).step(18.2).size.should == 4
      end

      it "handles infinite values at either end" do
        (-Float::INFINITY...0.0).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
        ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])

        ScratchPad.record []
        (0.0...Float::INFINITY).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
        ScratchPad.recorded.should eql([0.0, 2.0, 4.0])
      end
    end

    describe "and Integer, Float values" do
      it "yields Float values incremented by 1 and less than end when not passed a step" do
        (-2...2.0).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
      end

      it "yields Float values incremented by an Integer step" do
        (-5...5.0).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
      end

      it "yields an Float and then Float values incremented by a Float step" do
        (-1...1.0).step(0.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
      end
    end

    describe "and Float, Integer values" do
      it "yields Float values incremented by 1 and less than end when not passed a step" do
        (-2.0...2).step { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
      end

      it "yields Float values incremented by an Integer step" do
        (-5.0...5).step(2) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
      end

      it "yields Float values incremented by a Float step" do
        (-1.0...1).step(0.5) { |x| ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
      end
    end

    describe "and String values" do
      ruby_version_is ""..."3.4" do
        it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
          ("A"..."E").step { |x| ScratchPad << x }
          ScratchPad.recorded.should == ["A", "B", "C", "D"]
        end

        it "yields String values incremented by #succ called Integer step times" do
          ("A"..."G").step(2) { |x| ScratchPad << x }
          ScratchPad.recorded.should == ["A", "C", "E"]
        end

        it "raises a TypeError when passed a Float step" do
          -> { ("A"..."G").step(2.0) { } }.should raise_error(TypeError)
        end
      end

      ruby_version_is "3.4" do
        it "yields String values adjusted by step and less than or equal to end" do
          NATFIXME 'it yields String values adjusted by step and less than or equal to end', exception: TypeError, message: 'no implicit conversion of String into Integer' do
            ("A"..."AAA").step("A") { |x| ScratchPad << x }
            ScratchPad.recorded.should == ["A", "AA"]
          end
        end

        it "raises a TypeError when passed an incompatible type step" do
          -> { ("A".."G").step([]) { } }.should raise_error(TypeError)
        end
      end
    end
  end

  describe "with an endless range" do
    describe "and Integer values" do
      it "yield Integer values incremented by 1 when not passed a step" do
        (-2..).step { |x| break if x > 2; ScratchPad << x }
        ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])

        ScratchPad.record []
        (-2...).step { |x| break if x > 2; ScratchPad << x }
        ScratchPad.recorded.should eql([-2, -1, 0, 1, 2])
      end

      it "yields Integer values incremented by an Integer step" do
        (-5..).step(2) { |x| break if x > 3; ScratchPad << x }
        ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])

        ScratchPad.record []
        (-5...).step(2) { |x| break if x > 3; ScratchPad << x }
        ScratchPad.recorded.should eql([-5, -3, -1, 1, 3])
      end

      it "yields Float values incremented by a Float step" do
        (-2..).step(1.5) { |x| break if x > 1.0; ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])

        ScratchPad.record []
        (-2..).step(1.5) { |x| break if x > 1.0; ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -0.5, 1.0])
      end
    end

    describe "and Float values" do
      it "yields Float values incremented by 1 and less than end when not passed a step" do
        (-2.0..).step { |x| break if x > 1.5; ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])

        ScratchPad.record []
        (-2.0...).step { |x| break if x > 1.5; ScratchPad << x }
        ScratchPad.recorded.should eql([-2.0, -1.0, 0.0, 1.0])
      end

      it "yields Float values incremented by an Integer step" do
        (-5.0..).step(2) { |x| break if x > 3.5; ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])

        ScratchPad.record []
        (-5.0...).step(2) { |x| break if x > 3.5; ScratchPad << x }
        ScratchPad.recorded.should eql([-5.0, -3.0, -1.0, 1.0, 3.0])
      end

      it "yields Float values incremented by a Float step" do
        (-1.0..).step(0.5) { |x| break if x > 0.6; ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])

        ScratchPad.record []
        (-1.0...).step(0.5) { |x| break if x > 0.6; ScratchPad << x }
        ScratchPad.recorded.should eql([-1.0, -0.5, 0.0, 0.5])
      end

      it "handles infinite values at the start" do
        (-Float::INFINITY..).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
        ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])

        ScratchPad.record []
        (-Float::INFINITY...).step(2) { |x| ScratchPad << x; break if ScratchPad.recorded.size == 3 }
        ScratchPad.recorded.should eql([-Float::INFINITY, -Float::INFINITY, -Float::INFINITY])
      end
    end

    describe "and String values" do
      it "yields String values incremented by #succ and less than or equal to end when not passed a step" do
        ('A'..).step { |x| break if x > "D"; ScratchPad << x }
        ScratchPad.recorded.should == ["A", "B", "C", "D"]

        ScratchPad.record []
        ('A'...).step { |x| break if x > "D"; ScratchPad << x }
        ScratchPad.recorded.should == ["A", "B", "C", "D"]
      end

      it "yields String values incremented by #succ called Integer step times" do
        ('A'..).step(2) { |x| break if x > "F"; ScratchPad << x }
        ScratchPad.recorded.should == ["A", "C", "E"]

        ScratchPad.record []
        ('A'...).step(2) { |x| break if x > "F"; ScratchPad << x }
        ScratchPad.recorded.should == ["A", "C", "E"]
      end

      it "raises a TypeError when passed a Float step" do
        -> { ('A'..).step(2.0) { } }.should raise_error(TypeError)
        -> { ('A'...).step(2.0) { } }.should raise_error(TypeError)
      end

      ruby_version_is "3.4" do
        it "yields String values adjusted by step" do
          NATFIXME 'it yields String values adjusted by step', exception: TypeError, message: 'no implicit conversion of String into Integer' do
            eval("('A'..)").step("A") { |x| break if x > "AAA"; ScratchPad << x }
            ScratchPad.recorded.should == ["A", "AA", "AAA"]

            ScratchPad.record []
            ('A'...).step("A") { |x| break if x > "AAA"; ScratchPad << x }
            ScratchPad.recorded.should == ["A", "AA", "AAA"]
          end
        end

        it "raises a TypeError when passed an incompatible type step" do
          -> { ('A'..).step([]) { } }.should raise_error(TypeError)
          -> { ('A'...).step([]) { } }.should raise_error(TypeError)
        end
      end
    end
  end

  describe "when no block is given" do
    it "raises an ArgumentError if step is 0" do
      -> { (-1..1).step(0) }.should raise_error(ArgumentError)
    end

    describe "returned Enumerator" do
      describe "size" do
        ruby_version_is ""..."3.4" do
          it "raises a TypeError if step does not respond to #to_int" do
            obj = mock("Range#step non-integer")
            -> { (1..2).step(obj) }.should raise_error(TypeError)
          end

          it "raises a TypeError if #to_int does not return an Integer" do
            obj = mock("Range#step non-integer")
            obj.should_receive(:to_int).and_return("1")
            -> { (1..2).step(obj) }.should raise_error(TypeError)
          end
        end

        ruby_version_is "3.4" do
          it "does not raise if step is incompatible" do
            obj = mock("Range#step non-integer")
            NATFIXME 'it does not raise if step is incompatible', exception: SpecFailedException do
              -> { (1..2).step(obj) }.should_not raise_error
            end
          end
        end

        it "returns the ceil of range size divided by the number of steps" do
          (1..10).step(4).size.should == 3
          (1..10).step(3).size.should == 4
          (1..10).step(2).size.should == 5
          (1..10).step(1).size.should == 10
          (-5..5).step(2).size.should == 6
          (1...10).step(4).size.should == 3
          (1...10).step(3).size.should == 3
          (1...10).step(2).size.should == 5
          (1...10).step(1).size.should == 9
          (-5...5).step(2).size.should == 5
        end

        it "returns the ceil of range size divided by the number of steps even if step is negative" do
          (-1..1).step(-1).size.should == 0
          (1..-1).step(-1).size.should == 3
        end

        it "returns the correct number of steps when one of the arguments is a float" do
          (-1..1.0).step(0.5).size.should == 5
          (-1.0...1.0).step(0.5).size.should == 4
        end

        it "returns the range size when there's no step_size" do
          (-2..2).step.size.should == 5
          (-2.0..2.0).step.size.should == 5
          (-2..2.0).step.size.should == 5
          (-2.0..2).step.size.should == 5
          (1.0..6.4).step(1.8).size.should == 4
          (1.0..12.7).step(1.3).size.should == 10
          (-2...2).step.size.should == 4
          (-2.0...2.0).step.size.should == 4
          (-2...2.0).step.size.should == 4
          (-2.0...2).step.size.should == 4
          (1.0...6.4).step(1.8).size.should == 3
        end

        ruby_version_is ""..."3.4" do
          it "returns nil with begin and end are String" do
            ("A".."E").step(2).size.should == nil
            ("A"..."E").step(2).size.should == nil
            ("A".."E").step.size.should == nil
            ("A"..."E").step.size.should == nil
          end

          it "return nil and not raises a TypeError if the first element does not respond to #succ" do
            obj = mock("Range#step non-comparable")
            obj.should_receive(:<=>).with(obj).and_return(1)
            enum = (obj..obj).step
            -> { enum.size }.should_not raise_error
            enum.size.should == nil
          end
        end

        ruby_version_is "3.4" do
          it "returns nil with begin and end are String" do
            NATFIXME 'it returns nil with begin and end are String', exception: TypeError, message: 'no implicit conversion of String into Integer' do
              ("A".."E").step("A").size.should == nil
              ("A"..."E").step("A").size.should == nil
            end
          end

          it "return nil and not raises a TypeError if the first element is not of compatible type" do
            obj = mock("Range#step non-comparable")
            NATFIXME 'it return nil and not raises a TypeError if the first element is not of compatible type', exception: TypeError, message: 'no implicit conversion of MockObject into Integer' do
              obj.should_receive(:<=>).with(obj).and_return(1)
              enum = (obj..obj).step(obj)
              -> { enum.size }.should_not raise_error
              enum.size.should == nil
            end
          end
        end
      end

      # We use .take below to ensure the enumerator works
      # because that's an Enumerable method and so it uses the Enumerator behavior
      # not just a method overridden in Enumerator::ArithmeticSequence.
      describe "type" do
        context "when both begin and end are numerics" do
          it "returns an instance of Enumerator::ArithmeticSequence" do
            (1..10).step.class.should == Enumerator::ArithmeticSequence
            (1..10).step(3).take(4).should == [1, 4, 7, 10]
          end
        end

        context "when begin is not defined and end is numeric" do
          it "returns an instance of Enumerator::ArithmeticSequence" do
            (..10).step.class.should == Enumerator::ArithmeticSequence
          end
        end

        context "when range is endless" do
          it "returns an instance of Enumerator::ArithmeticSequence when begin is numeric" do
            (1..).step.class.should == Enumerator::ArithmeticSequence
            (1..).step(2).take(3).should == [1, 3, 5]
          end

          ruby_version_is ""..."3.4" do
            it "returns an instance of Enumerator when begin is not numeric" do
              ("a"..).step.class.should == Enumerator
              ("a"..).step(2).take(3).should == %w[a c e]
            end
          end

          ruby_version_is "3.4" do
            it "returns an instance of Enumerator when begin is not numeric" do
              NATFIXME 'it returns an instance of Enumerator when begin is not numeric', exception: TypeError, message: 'no implicit conversion of String into Integer' do
                ("a"..).step("a").class.should == Enumerator
                ("a"..).step("a").take(3).should == %w[a aa aaa]
              end
            end
          end
        end

        context "when range is beginless and endless" do
          ruby_version_is ""..."3.4" do
            it "returns an instance of Enumerator" do
              Range.new(nil, nil).step.class.should == Enumerator
            end
          end

          ruby_version_is "3.4" do
            it "raises an ArgumentError" do
              NATFIXME 'it raises an ArgumentError', exception: SpecFailedException do
                -> { Range.new(nil, nil).step(1) }.should raise_error(ArgumentError)
              end
            end
          end
        end

        context "when begin and end are not numerics" do
          ruby_version_is ""..."3.4" do
            it "returns an instance of Enumerator" do
              ("a".."z").step.class.should == Enumerator
              ("a".."z").step(3).take(4).should == %w[a d g j]
            end
          end

          ruby_version_is "3.4" do
            it "returns an instance of Enumerator" do
              NATFIXME 'it returns an instance of Enumerator', exception: TypeError, message: 'no implicit conversion of String into Integer' do
                ("a".."z").step("a").class.should == Enumerator
                ("a".."z").step("a").take(4).should == %w[a aa aaa aaaa]
              end
            end
          end
        end
      end
    end
  end
end
