describe :string_each_line, shared: true do
  it "splits using default newline separator when none is specified" do
    a = []
    "one\ntwo\r\nthree".send(@method) { |s| a << s }
    a.should == ["one\n", "two\r\n", "three"]

    b = []
    "hello\n\n\nworld".send(@method) { |s| b << s }
    b.should == ["hello\n", "\n", "\n", "world"]

    c = []
    "\n\n\n\n\n".send(@method) {|s| c << s}
    c.should == ["\n", "\n", "\n", "\n", "\n"]
  end

  it "splits self using the supplied record separator and passes each substring to the block" do
    a = []
    "one\ntwo\r\nthree".send(@method, "\n") { |s| a << s }
    a.should == ["one\n", "two\r\n", "three"]

    b = []
    "hello\nworld".send(@method, 'l') { |s| b << s }
    b.should == [ "hel", "l", "o\nworl", "d" ]

    c = []
    "hello\n\n\nworld".send(@method, "\n") { |s| c << s }
    c.should == ["hello\n", "\n", "\n", "world"]
  end

  it "splits strings containing multibyte characters" do
    s = <<~EOS
      foo
      🤡🤡🤡🤡🤡🤡🤡
      bar
      baz
    EOS

    b = []
    s.send(@method) { |part| b << part }
    b.should == ["foo\n", "🤡🤡🤡🤡🤡🤡🤡\n", "bar\n", "baz\n"]
  end

  it "passes self as a whole to the block if the separator is nil" do
    a = []
    "one\ntwo\r\nthree".send(@method, nil) { |s| a << s }
    a.should == ["one\ntwo\r\nthree"]
  end

  context "when passed '' (paragraph mode, broken by 2 or more successive newlines)" do
    it "replaces multiple newlines with only two ones" do
      a = []
      "hello\nworld\n\n\nand\nuniverse\n\n\n\n\n".send(@method, '') { |s| a << s }
      a.should == ["hello\nworld\n\n", "and\nuniverse\n\n"]

      a = []
      "hello\nworld\n\n\nand\nuniverse\n\n\n\n\ndog".send(@method, '') { |s| a << s }
      a.should == ["hello\nworld\n\n", "and\nuniverse\n\n", "dog"]
    end

    it 'handles \r\n-style newlines' do
      a = []
      "hello\nworld\r\n\r\n\nand\nuniverse\n\r\n\n\n\n".send(@method, '') { |s| a << s }
      a.should == ["hello\nworld\r\n\r\n", "and\nuniverse\n\r\n"]

      a = []
      "hello\r\nworld\n\n\nand\nuniverse\n\n\n\r\n\r\ndog".send(@method, '') { |s| a << s }
      a.should == ["hello\r\nworld\n\n", "and\nuniverse\n\n", "dog"]
    end

    it "removes trailing newlines with `chomp: true`" do
      a = []
      "hello\nworld\n\n\nand\nuniverse\n\n\n\n\n".send(@method, '', chomp: true) { |s| a << s }
      a.should == ["hello\nworld", "and\nuniverse"]

      a = []
      "hello\nworld\n\n\nand\nuniverse\n\n\n\n\ndog".send(@method, '', chomp: true) { |s| a << s }
      a.should == ["hello\nworld", "and\nuniverse", "dog"]
    end
  end

  describe "uses $/" do
    before :each do
      @before_separator = $/
    end

    after :each do
      suppress_warning {$/ = @before_separator}
    end

    it "as the separator when none is given" do
      [
        "", "x", "x\ny", "x\ry", "x\r\ny", "x\n\r\r\ny",
        "hello hullo bello"
      ].each do |str|
        ["", "llo", "\n", "\r", nil].each do |sep|
          expected = []
          str.send(@method, sep) { |x| expected << x }

          suppress_warning {$/ = sep}

          actual = []
          suppress_warning {str.send(@method) { |x| actual << x }}

          actual.should == expected
        end
      end
    end
  end

  it "yields String instances for subclasses" do
    a = []
    StringSpecs::MyString.new("hello\nworld").send(@method) { |s| a << s.class }
    a.should == [String, String]
  end

  it "returns self" do
    s = "hello\nworld"
    (s.send(@method) {}).should.equal?(s)
  end

  it "tries to convert the separator to a string using to_str" do
    separator = mock('l')
    separator.should_receive(:to_str).and_return("l")

    a = []
    "hello\nworld".send(@method, separator) { |s| a << s }
    a.should == [ "hel", "l", "o\nworl", "d" ]
  end

  it "does not care if the string is modified while substituting" do
    str = +"hello\nworld."
    out = []
    str.send(@method){|x| out << x; str[-1] = '!' }.should == "hello\nworld!"
    out.should == ["hello\n", "world."]
  end

  it "returns Strings in the same encoding as self" do
    "one\ntwo\r\nthree".encode("US-ASCII").send(@method) do |s|
      s.encoding.should == Encoding::US_ASCII
    end
  end

  it "raises a TypeError when the separator can't be converted to a string" do
    -> { "hello world".send(@method, false) {}     }.should.raise(TypeError)
    -> { "hello world".send(@method, mock('x')) {} }.should.raise(TypeError)
  end

  it "accepts a string separator" do
    "hello world".send(@method, ?o).to_a.should == ["hello", " wo", "rld"]
  end

  it "raises a TypeError when the separator is a symbol" do
    -> { "hello world".send(@method, :o).to_a }.should.raise(TypeError)
  end

  context "when `chomp` keyword argument is passed" do
    it "removes new line characters when separator is not specified" do
      a = []
      "hello \nworld\n".send(@method, chomp: true) { |s| a << s }
      a.should == ["hello ", "world"]

      a = []
      "hello \r\nworld\r\n".send(@method, chomp: true) { |s| a << s }
      a.should == ["hello ", "world"]
    end

    it "removes only specified separator" do
      a = []
      "hello world".send(@method, ' ', chomp: true) { |s| a << s }
      a.should == ["hello", "world"]
    end

    # https://bugs.ruby-lang.org/issues/14257
    it "ignores new line characters when separator is specified" do
      a = []
      "hello\n world\n".send(@method, ' ', chomp: true) { |s| a << s }
      a.should == ["hello\n", "world\n"]

      a = []
      "hello\r\n world\r\n".send(@method, ' ', chomp: true) { |s| a << s }
      a.should == ["hello\r\n", "world\r\n"]
    end
  end

  it "does not split lines for dummy UTF-16" do
    "a\nb".encode(Encoding::UTF_16).lines.should == [
      "\xFE\xFF\x00\x61\x00\n\x00\x62".dup.force_encoding(Encoding::UTF_16)
    ]

    str = "\x00\n\n\x00".dup.force_encoding(Encoding::UTF_16)
    str.lines.should == [str]
  end

  it "raises Encoding::ConverterNotFoundError for dummy UTF-7" do
    str = "a\nb".dup.force_encoding(Encoding::UTF_7)
    -> { str.lines }.should.raise(Encoding::ConverterNotFoundError)
  end
end
