summaryrefslogtreecommitdiff
path: root/spec/ruby/core/string/shared
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/string/shared')
-rw-r--r--spec/ruby/core/string/shared/byte_index_common.rb63
-rw-r--r--spec/ruby/core/string/shared/chars.rb66
-rw-r--r--spec/ruby/core/string/shared/codepoints.rb62
-rw-r--r--spec/ruby/core/string/shared/concat.rb159
-rw-r--r--spec/ruby/core/string/shared/dedup.rb51
-rw-r--r--spec/ruby/core/string/shared/each_char_without_block.rb26
-rw-r--r--spec/ruby/core/string/shared/each_codepoint_without_block.rb33
-rw-r--r--spec/ruby/core/string/shared/each_line.rb162
-rw-r--r--spec/ruby/core/string/shared/each_line_without_block.rb17
-rw-r--r--spec/ruby/core/string/shared/encode.rb432
-rw-r--r--spec/ruby/core/string/shared/eql.rb38
-rw-r--r--spec/ruby/core/string/shared/equal_value.rb29
-rw-r--r--spec/ruby/core/string/shared/grapheme_clusters.rb16
-rw-r--r--spec/ruby/core/string/shared/length.rb55
-rw-r--r--spec/ruby/core/string/shared/partition.rb33
-rw-r--r--spec/ruby/core/string/shared/replace.rb48
-rw-r--r--spec/ruby/core/string/shared/slice.rb517
-rw-r--r--spec/ruby/core/string/shared/strip.rb14
-rw-r--r--spec/ruby/core/string/shared/succ.rb87
-rw-r--r--spec/ruby/core/string/shared/to_s.rb13
-rw-r--r--spec/ruby/core/string/shared/to_sym.rb72
21 files changed, 1993 insertions, 0 deletions
diff --git a/spec/ruby/core/string/shared/byte_index_common.rb b/spec/ruby/core/string/shared/byte_index_common.rb
new file mode 100644
index 0000000000..3de1453f4f
--- /dev/null
+++ b/spec/ruby/core/string/shared/byte_index_common.rb
@@ -0,0 +1,63 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../../spec_helper'
+
+describe :byte_index_common, shared: true do
+ describe "raises on type errors" do
+ it "raises a TypeError if passed nil" do
+ -> { "abc".send(@method, nil) }.should raise_error(TypeError, "no implicit conversion of nil into String")
+ end
+
+ it "raises a TypeError if passed a boolean" do
+ -> { "abc".send(@method, true) }.should raise_error(TypeError, "no implicit conversion of true into String")
+ end
+
+ it "raises a TypeError if passed a Symbol" do
+ not_supported_on :opal do
+ -> { "abc".send(@method, :a) }.should raise_error(TypeError, "no implicit conversion of Symbol into String")
+ end
+ end
+
+ it "raises a TypeError if passed a Symbol" do
+ obj = mock('x')
+ obj.should_not_receive(:to_int)
+ -> { "hello".send(@method, obj) }.should raise_error(TypeError, "no implicit conversion of MockObject into String")
+ end
+
+ it "raises a TypeError if passed an Integer" do
+ -> { "abc".send(@method, 97) }.should raise_error(TypeError, "no implicit conversion of Integer into String")
+ end
+ end
+
+ describe "with multibyte codepoints" do
+ it "raises an IndexError when byte offset lands in the middle of a multibyte character" do
+ -> { "わ".send(@method, "", 1) }.should raise_error(IndexError, "offset 1 does not land on character boundary")
+ -> { "わ".send(@method, "", 2) }.should raise_error(IndexError, "offset 2 does not land on character boundary")
+ -> { "わ".send(@method, "", -1) }.should raise_error(IndexError, "offset 2 does not land on character boundary")
+ -> { "わ".send(@method, "", -2) }.should raise_error(IndexError, "offset 1 does not land on character boundary")
+ end
+
+ it "raises an Encoding::CompatibilityError if the encodings are incompatible" do
+ re = Regexp.new "れ".encode(Encoding::EUC_JP)
+ -> do
+ "あれ".send(@method, re)
+ end.should raise_error(Encoding::CompatibilityError, "incompatible encoding regexp match (EUC-JP regexp with UTF-8 string)")
+ end
+ end
+
+ describe "with global variables" do
+ it "doesn't set $~ for non regex search" do
+ $~ = nil
+
+ 'hello.'.send(@method, 'll')
+ $~.should == nil
+ end
+
+ it "sets $~ to MatchData of match and nil when there's none" do
+ 'hello.'.send(@method, /.e./)
+ $~[0].should == 'hel'
+
+ 'hello.'.send(@method, /not/)
+ $~.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/chars.rb b/spec/ruby/core/string/shared/chars.rb
new file mode 100644
index 0000000000..c730643cf4
--- /dev/null
+++ b/spec/ruby/core/string/shared/chars.rb
@@ -0,0 +1,66 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_chars, shared: true do
+ it "passes each char in self to the given block" do
+ a = []
+ "hello".send(@method) { |c| a << c }
+ a.should == ['h', 'e', 'l', 'l', 'o']
+ end
+
+ it "returns self" do
+ s = StringSpecs::MyString.new "hello"
+ s.send(@method){}.should equal(s)
+ end
+
+
+ it "is unicode aware" do
+ "\303\207\342\210\202\303\251\306\222g".send(@method).to_a.should ==
+ ["\303\207", "\342\210\202", "\303\251", "\306\222", "g"]
+ end
+
+ it "returns characters in the same encoding as self" do
+ "&%".dup.force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'}
+ "&%".encode('BINARY').send(@method).to_a.all? {|c| c.encoding.should == Encoding::BINARY }
+ end
+
+ it "works with multibyte characters" do
+ s = "\u{8987}".dup.force_encoding("UTF-8")
+ s.bytesize.should == 3
+ s.send(@method).to_a.should == [s]
+ end
+
+ it "works if the String's contents is invalid for its encoding" do
+ xA4 = [0xA4].pack('C')
+ xA4.force_encoding('UTF-8')
+ xA4.valid_encoding?.should be_false
+ xA4.send(@method).to_a.should == [xA4.force_encoding("UTF-8")]
+ end
+
+ it "returns a different character if the String is transcoded" do
+ s = "\u{20AC}".dup.force_encoding('UTF-8')
+ s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')]
+ s.encode('iso-8859-15').send(@method).to_a.should == [[0xA4].pack('C').force_encoding('iso-8859-15')]
+ s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')]
+ end
+
+ it "uses the String's encoding to determine what characters it contains" do
+ s = +"\u{24B62}"
+
+ s.force_encoding('UTF-8').send(@method).to_a.should == [
+ s.force_encoding('UTF-8')
+ ]
+ s.force_encoding('BINARY').send(@method).to_a.should == [
+ [0xF0].pack('C').force_encoding('BINARY'),
+ [0xA4].pack('C').force_encoding('BINARY'),
+ [0xAD].pack('C').force_encoding('BINARY'),
+ [0xA2].pack('C').force_encoding('BINARY')
+ ]
+ s.force_encoding('SJIS').send(@method).to_a.should == [
+ [0xF0,0xA4].pack('CC').force_encoding('SJIS'),
+ [0xAD].pack('C').force_encoding('SJIS'),
+ [0xA2].pack('C').force_encoding('SJIS')
+ ]
+ end
+end
diff --git a/spec/ruby/core/string/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb
new file mode 100644
index 0000000000..1c28ba3d5e
--- /dev/null
+++ b/spec/ruby/core/string/shared/codepoints.rb
@@ -0,0 +1,62 @@
+# encoding: binary
+describe :string_codepoints, shared: true do
+ it "returns self" do
+ s = "foo"
+ result = s.send(@method) {}
+ result.should equal s
+ end
+
+ it "raises an ArgumentError when self has an invalid encoding and a method is called on the returned Enumerator" do
+ s = "\xDF".dup.force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.send(@method).to_a }.should raise_error(ArgumentError)
+ end
+
+ it "yields each codepoint to the block if one is given" do
+ codepoints = []
+ "abcd".send(@method) do |codepoint|
+ codepoints << codepoint
+ end
+ codepoints.should == [97, 98, 99, 100]
+ end
+
+ it "raises an ArgumentError if self's encoding is invalid and a block is given" do
+ s = "\xDF".dup.force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ -> { s.send(@method) { } }.should raise_error(ArgumentError)
+ end
+
+ it "yields codepoints as Integers" do
+ "glark\u{20}".send(@method).to_a.each do |codepoint|
+ codepoint.should be_an_instance_of(Integer)
+ end
+ end
+
+ it "yields one codepoint for each character" do
+ s = "\u{9876}\u{28}\u{1987}"
+ s.send(@method).to_a.size.should == s.chars.to_a.size
+ end
+
+ it "works for multibyte characters" do
+ s = "\u{9819}"
+ s.bytesize.should == 3
+ s.send(@method).to_a.should == [38937]
+ end
+
+ it "yields the codepoints corresponding to the character's position in the String's encoding" do
+ "\u{787}".send(@method).to_a.should == [1927]
+ end
+
+ it "round-trips to the original String using Integer#chr" do
+ s = "\u{13}\u{7711}\u{1010}"
+ s2 = +""
+ s.send(@method) {|n| s2 << n.chr(Encoding::UTF_8)}
+ s.should == s2
+ end
+
+ it "is synonymous with #bytes for Strings which are single-byte optimizable" do
+ s = "(){}".encode('ascii')
+ s.ascii_only?.should be_true
+ s.send(@method).to_a.should == s.bytes.to_a
+ end
+end
diff --git a/spec/ruby/core/string/shared/concat.rb b/spec/ruby/core/string/shared/concat.rb
new file mode 100644
index 0000000000..dded9a69e7
--- /dev/null
+++ b/spec/ruby/core/string/shared/concat.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: false
+describe :string_concat, shared: true do
+ it "concatenates the given argument to self and returns self" do
+ str = 'hello '
+ str.send(@method, 'world').should equal(str)
+ str.should == "hello world"
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "hello"
+ a.freeze
+
+ -> { a.send(@method, "") }.should raise_error(FrozenError)
+ -> { a.send(@method, "test") }.should raise_error(FrozenError)
+ end
+
+ it "returns a String when given a subclass instance" do
+ a = "hello"
+ a.send(@method, StringSpecs::MyString.new(" world"))
+ a.should == "hello world"
+ a.should be_an_instance_of(String)
+ end
+
+ it "returns an instance of same class when called on a subclass" do
+ str = StringSpecs::MyString.new("hello")
+ str.send(@method, " world")
+ str.should == "hello world"
+ str.should be_an_instance_of(StringSpecs::MyString)
+ end
+
+ describe "with Integer" do
+ it "concatenates the argument interpreted as a codepoint" do
+ b = "".send(@method, 33)
+ b.should == "!"
+
+ b.encode!(Encoding::UTF_8)
+ b.send(@method, 0x203D)
+ b.should == "!\u203D"
+ end
+
+ # #5855
+ it "returns a BINARY string if self is US-ASCII and the argument is between 128-255 (inclusive)" do
+ a = ("".encode(Encoding::US_ASCII).send(@method, 128))
+ a.encoding.should == Encoding::BINARY
+ a.should == 128.chr
+
+ a = ("".encode(Encoding::US_ASCII).send(@method, 255))
+ a.encoding.should == Encoding::BINARY
+ a.should == 255.chr
+ end
+
+ it "raises RangeError if the argument is an invalid codepoint for self's encoding" do
+ -> { "".encode(Encoding::US_ASCII).send(@method, 256) }.should raise_error(RangeError)
+ -> { "".encode(Encoding::EUC_JP).send(@method, 0x81) }.should raise_error(RangeError)
+ end
+
+ it "raises RangeError if the argument is negative" do
+ -> { "".send(@method, -200) }.should raise_error(RangeError)
+ -> { "".send(@method, -bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "doesn't call to_int on its argument" do
+ x = mock('x')
+ x.should_not_receive(:to_int)
+
+ -> { "".send(@method, x) }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError when self is frozen" do
+ a = "hello"
+ a.freeze
+
+ -> { a.send(@method, 0) }.should raise_error(FrozenError)
+ -> { a.send(@method, 33) }.should raise_error(FrozenError)
+ end
+ end
+end
+
+describe :string_concat_encoding, shared: true do
+ describe "when self is in an ASCII-incompatible encoding incompatible with the argument's encoding" do
+ it "uses self's encoding if both are empty" do
+ "".encode("UTF-16LE").send(@method, "").encoding.should == Encoding::UTF_16LE
+ end
+
+ it "uses self's encoding if the argument is empty" do
+ "x".encode("UTF-16LE").send(@method, "").encoding.should == Encoding::UTF_16LE
+ end
+
+ it "uses the argument's encoding if self is empty" do
+ "".encode("UTF-16LE").send(@method, "x".encode("UTF-8")).encoding.should == Encoding::UTF_8
+ end
+
+ it "raises Encoding::CompatibilityError if neither are empty" do
+ -> { "x".encode("UTF-16LE").send(@method, "y".encode("UTF-8")) }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ describe "when the argument is in an ASCII-incompatible encoding incompatible with self's encoding" do
+ it "uses self's encoding if both are empty" do
+ "".encode("UTF-8").send(@method, "".encode("UTF-16LE")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses self's encoding if the argument is empty" do
+ "x".encode("UTF-8").send(@method, "".encode("UTF-16LE")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses the argument's encoding if self is empty" do
+ "".encode("UTF-8").send(@method, "x".encode("UTF-16LE")).encoding.should == Encoding::UTF_16LE
+ end
+
+ it "raises Encoding::CompatibilityError if neither are empty" do
+ -> { "x".encode("UTF-8").send(@method, "y".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ describe "when self and the argument are in different ASCII-compatible encodings" do
+ it "uses self's encoding if both are ASCII-only" do
+ "abc".encode("UTF-8").send(@method, "123".encode("SHIFT_JIS")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses self's encoding if the argument is ASCII-only" do
+ "\u00E9".encode("UTF-8").send(@method, "123".encode("ISO-8859-1")).encoding.should == Encoding::UTF_8
+ end
+
+ it "uses the argument's encoding if self is ASCII-only" do
+ "abc".encode("UTF-8").send(@method, "\u00E9".encode("ISO-8859-1")).encoding.should == Encoding::ISO_8859_1
+ end
+
+ it "raises Encoding::CompatibilityError if neither are ASCII-only" do
+ -> { "\u00E9".encode("UTF-8").send(@method, "\u00E9".encode("ISO-8859-1")) }.should raise_error(Encoding::CompatibilityError)
+ end
+ end
+
+ describe "when self is BINARY and argument is US-ASCII" do
+ it "uses BINARY encoding" do
+ "abc".encode("BINARY").send(@method, "123".encode("US-ASCII")).encoding.should == Encoding::BINARY
+ end
+ end
+end
+
+describe :string_concat_type_coercion, shared: true do
+ it "converts the given argument to a String using to_str" do
+ obj = mock('world!')
+ obj.should_receive(:to_str).and_return("world!")
+ a = 'hello '.send(@method, obj)
+ a.should == 'hello world!'
+ end
+
+ it "raises a TypeError if the given argument can't be converted to a String" do
+ -> { 'hello '.send(@method, []) }.should raise_error(TypeError)
+ -> { 'hello '.send(@method, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do
+ obj = mock('world!')
+ obj.should_receive(:to_str).and_raise(NoMethodError)
+ -> { 'hello '.send(@method, obj) }.should raise_error(NoMethodError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/dedup.rb b/spec/ruby/core/string/shared/dedup.rb
new file mode 100644
index 0000000000..1ffd6aa0fd
--- /dev/null
+++ b/spec/ruby/core/string/shared/dedup.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: false
+describe :string_dedup, shared: true do
+ it 'returns self if the String is frozen' do
+ input = 'foo'.freeze
+ output = input.send(@method)
+
+ output.should equal(input)
+ output.should.frozen?
+ end
+
+ it 'returns a frozen copy if the String is not frozen' do
+ input = 'foo'
+ output = input.send(@method)
+
+ output.should.frozen?
+ output.should_not equal(input)
+ output.should == 'foo'
+ end
+
+ it "returns the same object for equal unfrozen strings" do
+ origin = "this is a string"
+ dynamic = %w(this is a string).join(' ')
+
+ origin.should_not equal(dynamic)
+ origin.send(@method).should equal(dynamic.send(@method))
+ end
+
+ it "returns the same object when it's called on the same String literal" do
+ "unfrozen string".send(@method).should equal("unfrozen string".send(@method))
+ "unfrozen string".send(@method).should_not equal("another unfrozen string".send(@method))
+ end
+
+ it "deduplicates frozen strings" do
+ dynamic = %w(this string is frozen).join(' ').freeze
+
+ dynamic.should_not equal("this string is frozen".freeze)
+
+ dynamic.send(@method).should equal("this string is frozen".freeze)
+ dynamic.send(@method).should equal("this string is frozen".send(@method).freeze)
+ end
+
+ it "does not deduplicate a frozen string when it has instance variables" do
+ dynamic = %w(this string is frozen).join(' ')
+ dynamic.instance_variable_set(:@a, 1)
+ dynamic.freeze
+
+ dynamic.send(@method).should_not equal("this string is frozen".freeze)
+ dynamic.send(@method).should_not equal("this string is frozen".send(@method).freeze)
+ dynamic.send(@method).should equal(dynamic)
+ end
+end
diff --git a/spec/ruby/core/string/shared/each_char_without_block.rb b/spec/ruby/core/string/shared/each_char_without_block.rb
new file mode 100644
index 0000000000..397100ce0e
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_char_without_block.rb
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_each_char_without_block, shared: true do
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ enum = "hello".send(@method)
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ['h', 'e', 'l', 'l', 'o']
+ end
+
+ describe "returned enumerator" do
+ describe "size" do
+ it "should return the size of the string" do
+ str = "hello"
+ str.send(@method).size.should == str.size
+ str = "ola"
+ str.send(@method).size.should == str.size
+ str = "\303\207\342\210\202\303\251\306\222g"
+ str.send(@method).size.should == str.size
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/each_codepoint_without_block.rb b/spec/ruby/core/string/shared/each_codepoint_without_block.rb
new file mode 100644
index 0000000000..c88e5c54c7
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_codepoint_without_block.rb
@@ -0,0 +1,33 @@
+# encoding: binary
+describe :string_each_codepoint_without_block, shared: true do
+ describe "when no block is given" do
+ it "returns an Enumerator" do
+ "".send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ it "returns an Enumerator even when self has an invalid encoding" do
+ s = "\xDF".dup.force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ s.send(@method).should be_an_instance_of(Enumerator)
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return the size of the string" do
+ str = "hello"
+ str.send(@method).size.should == str.size
+ str = "ola"
+ str.send(@method).size.should == str.size
+ str = "\303\207\342\210\202\303\251\306\222g"
+ str.send(@method).size.should == str.size
+ end
+
+ it "should return the size of the string even when the string has an invalid encoding" do
+ s = "\xDF".dup.force_encoding(Encoding::UTF_8)
+ s.valid_encoding?.should be_false
+ s.send(@method).size.should == 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/each_line.rb b/spec/ruby/core/string/shared/each_line.rb
new file mode 100644
index 0000000000..231a6d9d4f
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_line.rb
@@ -0,0 +1,162 @@
+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
+
+ it "yields paragraphs (broken by 2 or more successive newlines) when passed '' and 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
+
+ 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_error(TypeError)
+ -> { "hello world".send(@method, mock('x')) {} }.should raise_error(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_error(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
+end
diff --git a/spec/ruby/core/string/shared/each_line_without_block.rb b/spec/ruby/core/string/shared/each_line_without_block.rb
new file mode 100644
index 0000000000..8e08b0390c
--- /dev/null
+++ b/spec/ruby/core/string/shared/each_line_without_block.rb
@@ -0,0 +1,17 @@
+describe :string_each_line_without_block, shared: true do
+ describe "when no block is given" do
+ it "returns an enumerator" do
+ enum = "hello world".send(@method, ' ')
+ enum.should be_an_instance_of(Enumerator)
+ enum.to_a.should == ["hello ", "world"]
+ end
+
+ describe "returned Enumerator" do
+ describe "size" do
+ it "should return nil" do
+ "hello world".send(@method, ' ').size.should == nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/string/shared/encode.rb b/spec/ruby/core/string/shared/encode.rb
new file mode 100644
index 0000000000..9466308886
--- /dev/null
+++ b/spec/ruby/core/string/shared/encode.rb
@@ -0,0 +1,432 @@
+# -*- encoding: utf-8 -*-
+# frozen_string_literal: false
+describe :string_encode, shared: true do
+ describe "when passed no options" do
+ it "transcodes to Encoding.default_internal when set" do
+ Encoding.default_internal = Encoding::UTF_8
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method).should == "あ"
+ end
+
+ it "transcodes a 7-bit String despite no generic converting being available" do
+ -> do
+ Encoding::Converter.new Encoding::Emacs_Mule, Encoding::BINARY
+ end.should raise_error(Encoding::ConverterNotFoundError)
+
+ Encoding.default_internal = Encoding::Emacs_Mule
+ str = "\x79".force_encoding Encoding::BINARY
+
+ str.send(@method).should == "y".force_encoding(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::ConverterNotFoundError when no conversion is possible" do
+ Encoding.default_internal = Encoding::Emacs_Mule
+ str = [0x80].pack('C').force_encoding Encoding::BINARY
+ -> { str.send(@method) }.should raise_error(Encoding::ConverterNotFoundError)
+ end
+ end
+
+ describe "when passed to encoding" do
+ it "accepts a String argument" do
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, "utf-8").should == "あ"
+ end
+
+ it "calls #to_str to convert the object to an Encoding" do
+ enc = mock("string encode encoding")
+ enc.should_receive(:to_str).and_return("utf-8")
+
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, enc).should == "あ"
+ end
+
+ it "transcodes to the passed encoding" do
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, Encoding::UTF_8).should == "あ"
+ end
+
+ it "transcodes Japanese multibyte characters" do
+ str = "あいうえお"
+ str.send(@method, Encoding::ISO_2022_JP).should ==
+ "\e\x24\x42\x24\x22\x24\x24\x24\x26\x24\x28\x24\x2A\e\x28\x42".force_encoding(Encoding::ISO_2022_JP)
+ end
+
+ it "transcodes a 7-bit String despite no generic converting being available" do
+ -> do
+ Encoding::Converter.new Encoding::Emacs_Mule, Encoding::BINARY
+ end.should raise_error(Encoding::ConverterNotFoundError)
+
+ str = "\x79".force_encoding Encoding::BINARY
+ str.send(@method, Encoding::Emacs_Mule).should == "y".force_encoding(Encoding::BINARY)
+ end
+
+ it "raises an Encoding::ConverterNotFoundError when no conversion is possible" do
+ str = [0x80].pack('C').force_encoding Encoding::BINARY
+ -> do
+ str.send(@method, Encoding::Emacs_Mule)
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+
+ it "raises an Encoding::ConverterNotFoundError for an invalid encoding" do
+ -> do
+ "abc".send(@method, "xyz")
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+ end
+
+ describe "when passed options" do
+ it "does not process transcoding options if not transcoding" do
+ result = "あ\ufffdあ".send(@method, undef: :replace)
+ result.should == "あ\ufffdあ"
+ end
+
+ it "calls #to_hash to convert the object" do
+ options = mock("string encode options")
+ options.should_receive(:to_hash).and_return({ undef: :replace })
+
+ result = "あ\ufffdあ".send(@method, **options)
+ result.should == "あ\ufffdあ"
+ end
+
+ it "transcodes to Encoding.default_internal when set" do
+ Encoding.default_internal = Encoding::UTF_8
+ str = [0xA4, 0xA2].pack('CC').force_encoding Encoding::EUC_JP
+ str.send(@method, invalid: :replace).should == "あ"
+ end
+
+ it "raises an Encoding::ConverterNotFoundError when no conversion is possible despite 'invalid: :replace, undef: :replace'" do
+ Encoding.default_internal = Encoding::Emacs_Mule
+ str = [0x80].pack('C').force_encoding Encoding::BINARY
+ -> do
+ str.send(@method, invalid: :replace, undef: :replace)
+ end.should raise_error(Encoding::ConverterNotFoundError)
+ end
+
+ it "replaces invalid characters when replacing Emacs-Mule encoded strings" do
+ got = [0x80].pack('C').force_encoding('Emacs-Mule').send(@method, invalid: :replace)
+
+ got.should == "?".encode('Emacs-Mule')
+ end
+ end
+
+ describe "when passed to, from" do
+ it "transcodes between the encodings ignoring the String encoding" do
+ str = "あ"
+ result = [0xA6, 0xD0, 0x8F, 0xAB, 0xE4, 0x8F, 0xAB, 0xB1].pack('C8')
+ result.force_encoding Encoding::EUC_JP
+ str.send(@method, "euc-jp", "ibm437").should == result
+ end
+
+ it "calls #to_str to convert the from object to an Encoding" do
+ enc = mock("string encode encoding")
+ enc.should_receive(:to_str).and_return("ibm437")
+
+ str = "あ"
+ result = [0xA6, 0xD0, 0x8F, 0xAB, 0xE4, 0x8F, 0xAB, 0xB1].pack('C8')
+ result.force_encoding Encoding::EUC_JP
+
+ str.send(@method, "euc-jp", enc).should == result
+ end
+ end
+
+ describe "when passed to, options" do
+ it "replaces undefined characters in the destination encoding" do
+ result = "あ?あ".send(@method, Encoding::EUC_JP, undef: :replace)
+ # testing for: "\xA4\xA2?\xA4\xA2"
+ xA4xA2 = [0xA4, 0xA2].pack('CC')
+ result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp")
+ end
+
+ it "replaces invalid characters in the destination encoding" do
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ "ab#{xFF}c".send(@method, Encoding::ISO_8859_1, invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_hash to convert the options object" do
+ options = mock("string encode options")
+ options.should_receive(:to_hash).and_return({ undef: :replace })
+
+ result = "あ?あ".send(@method, Encoding::EUC_JP, **options)
+ xA4xA2 = [0xA4, 0xA2].pack('CC').force_encoding('utf-8')
+ result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp")
+ end
+ end
+
+ describe "when passed to, from, options" do
+ it "replaces undefined characters in the destination encoding" do
+ str = "あ?あ".force_encoding Encoding::BINARY
+ result = str.send(@method, "euc-jp", "utf-8", undef: :replace)
+ xA4xA2 = [0xA4, 0xA2].pack('CC').force_encoding('utf-8')
+ result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp")
+ end
+
+ it "replaces invalid characters in the destination encoding" do
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, "iso-8859-1", "utf-8", invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_str to convert the to object to an encoding" do
+ to = mock("string encode to encoding")
+ to.should_receive(:to_str).and_return("iso-8859-1")
+
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, to, "utf-8", invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_str to convert the from object to an encoding" do
+ from = mock("string encode to encoding")
+ from.should_receive(:to_str).and_return("utf-8")
+
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, "iso-8859-1", from, invalid: :replace).should == "ab?c"
+ end
+
+ it "calls #to_hash to convert the options object" do
+ options = mock("string encode options")
+ options.should_receive(:to_hash).and_return({ invalid: :replace })
+
+ xFF = [0xFF].pack('C').force_encoding('utf-8')
+ str = "ab#{xFF}c".force_encoding Encoding::BINARY
+ str.send(@method, "iso-8859-1", "utf-8", **options).should == "ab?c"
+ end
+ end
+
+ describe "given the fallback option" do
+ context "given a hash" do
+ it "looks up the replacement value from the hash" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => "bar" })
+ encoded.should == "Bbar"
+ end
+
+ it "calls to_str on the returned value" do
+ obj = Object.new
+ obj.should_receive(:to_str).and_return("bar")
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => obj })
+ encoded.should == "Bbar"
+ end
+
+ it "does not call to_s on the returned value" do
+ obj = Object.new
+ obj.should_not_receive(:to_s)
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => obj })
+ }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises an error if the key is not present in the hash" do
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: { "foo" => "bar" })
+ }.should raise_error(Encoding::UndefinedConversionError, "U+FFFD from UTF-8 to US-ASCII")
+ end
+
+ it "raises an error if the value is itself invalid" do
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => "\uffee" })
+ }.should raise_error(ArgumentError, "too big fallback string")
+ end
+
+ it "uses the hash's default value if set" do
+ hash = {}
+ hash.default = "bar"
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: hash)
+ encoded.should == "Bbar"
+ end
+
+ it "uses the result of calling default_proc if set" do
+ hash = {}
+ hash.default_proc = -> _, _ { "bar" }
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: hash)
+ encoded.should == "Bbar"
+ end
+ end
+
+ context "given an object inheriting from Hash" do
+ before do
+ klass = Class.new(Hash)
+ @hash_like = klass.new
+ @hash_like["\ufffd"] = "bar"
+ end
+
+ it "looks up the replacement value from the object" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: @hash_like)
+ encoded.should == "Bbar"
+ end
+ end
+
+ context "given an object responding to []" do
+ before do
+ klass = Class.new do
+ def [](c) = c.bytes.inspect
+ end
+ @hash_like = klass.new
+ end
+
+ it "calls [] on the object, passing the invalid character" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: @hash_like)
+ encoded.should == "B[239, 191, 189]"
+ end
+ end
+
+ context "given an object not responding to []" do
+ before do
+ @non_hash_like = Object.new
+ end
+
+ it "raises an error" do
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: @non_hash_like)
+ }.should raise_error(Encoding::UndefinedConversionError, "U+FFFD from UTF-8 to US-ASCII")
+ end
+ end
+
+ context "given a proc" do
+ it "calls the proc to get the replacement value, passing in the invalid character" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| c.bytes.inspect })
+ encoded.should == "B[239, 191, 189]"
+ end
+
+ it "calls to_str on the returned value" do
+ obj = Object.new
+ obj.should_receive(:to_str).and_return("bar")
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| obj })
+ encoded.should == "Bbar"
+ end
+
+ it "does not call to_s on the returned value" do
+ obj = Object.new
+ obj.should_not_receive(:to_s)
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| obj })
+ }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises an error if the returned value is itself invalid" do
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { "\uffee" })
+ }.should raise_error(ArgumentError, "too big fallback string")
+ end
+ end
+
+ context "given a lambda" do
+ it "calls the lambda to get the replacement value, passing in the invalid character" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { c.bytes.inspect })
+ encoded.should == "B[239, 191, 189]"
+ end
+
+ it "calls to_str on the returned value" do
+ obj = Object.new
+ obj.should_receive(:to_str).and_return("bar")
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { obj })
+ encoded.should == "Bbar"
+ end
+
+ it "does not call to_s on the returned value" do
+ obj = Object.new
+ obj.should_not_receive(:to_s)
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { obj })
+ }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises an error if the returned value is itself invalid" do
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { "\uffee" })
+ }.should raise_error(ArgumentError, "too big fallback string")
+ end
+ end
+
+ context "given a method" do
+ def replace(c) = c.bytes.inspect
+ def replace_bad(c) = "\uffee"
+
+ def replace_to_str(c)
+ obj = Object.new
+ obj.should_receive(:to_str).and_return("bar")
+ obj
+ end
+
+ def replace_to_s(c)
+ obj = Object.new
+ obj.should_not_receive(:to_s)
+ obj
+ end
+
+ it "calls the method to get the replacement value, passing in the invalid character" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace))
+ encoded.should == "B[239, 191, 189]"
+ end
+
+ it "calls to_str on the returned value" do
+ encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_to_str))
+ encoded.should == "Bbar"
+ end
+
+ it "does not call to_s on the returned value" do
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_to_s))
+ }.should raise_error(TypeError, "no implicit conversion of Object into String")
+ end
+
+ it "raises an error if the returned value is itself invalid" do
+ -> {
+ "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_bad))
+ }.should raise_error(ArgumentError, "too big fallback string")
+ end
+ end
+ end
+
+ describe "given the xml: :text option" do
+ it "replaces all instances of '&' with '&amp;'" do
+ '& and &'.send(@method, "UTF-8", xml: :text).should == '&amp; and &amp;'
+ end
+
+ it "replaces all instances of '<' with '&lt;'" do
+ '< and <'.send(@method, "UTF-8", xml: :text).should == '&lt; and &lt;'
+ end
+
+ it "replaces all instances of '>' with '&gt;'" do
+ '> and >'.send(@method, "UTF-8", xml: :text).should == '&gt; and &gt;'
+ end
+
+ it "does not replace '\"'" do
+ '" and "'.send(@method, "UTF-8", xml: :text).should == '" and "'
+ end
+
+ it "replaces undefined characters with their upper-case hexadecimal numeric character references" do
+ 'ürst'.send(@method, Encoding::US_ASCII, xml: :text).should == '&#xFC;rst'
+ end
+ end
+
+ describe "given the xml: :attr option" do
+ it "surrounds the encoded text with double-quotes" do
+ 'abc'.send(@method, "UTF-8", xml: :attr).should == '"abc"'
+ end
+
+ it "replaces all instances of '&' with '&amp;'" do
+ '& and &'.send(@method, "UTF-8", xml: :attr).should == '"&amp; and &amp;"'
+ end
+
+ it "replaces all instances of '<' with '&lt;'" do
+ '< and <'.send(@method, "UTF-8", xml: :attr).should == '"&lt; and &lt;"'
+ end
+
+ it "replaces all instances of '>' with '&gt;'" do
+ '> and >'.send(@method, "UTF-8", xml: :attr).should == '"&gt; and &gt;"'
+ end
+
+ it "replaces all instances of '\"' with '&quot;'" do
+ '" and "'.send(@method, "UTF-8", xml: :attr).should == '"&quot; and &quot;"'
+ end
+
+ it "replaces undefined characters with their upper-case hexadecimal numeric character references" do
+ 'ürst'.send(@method, Encoding::US_ASCII, xml: :attr).should == '"&#xFC;rst"'
+ end
+ end
+
+ it "raises ArgumentError if the value of the :xml option is not :text or :attr" do
+ -> { ''.send(@method, "UTF-8", xml: :other) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/eql.rb b/spec/ruby/core/string/shared/eql.rb
new file mode 100644
index 0000000000..d5af337d53
--- /dev/null
+++ b/spec/ruby/core/string/shared/eql.rb
@@ -0,0 +1,38 @@
+# encoding: binary
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_eql_value, shared: true do
+ it "returns true if self <=> string returns 0" do
+ 'hello'.send(@method, 'hello').should be_true
+ end
+
+ it "returns false if self <=> string does not return 0" do
+ "more".send(@method, "MORE").should be_false
+ "less".send(@method, "greater").should be_false
+ end
+
+ it "ignores encoding difference of compatible string" do
+ "hello".dup.force_encoding("utf-8").send(@method, "hello".dup.force_encoding("iso-8859-1")).should be_true
+ end
+
+ it "considers encoding difference of incompatible string" do
+ "\xff".dup.force_encoding("utf-8").send(@method, "\xff".dup.force_encoding("iso-8859-1")).should be_false
+ end
+
+ it "considers encoding compatibility" do
+ "abcd".dup.force_encoding("utf-8").send(@method, "abcd".dup.force_encoding("utf-32le")).should be_false
+ end
+
+ it "ignores subclass differences" do
+ a = "hello"
+ b = StringSpecs::MyString.new("hello")
+
+ a.send(@method, b).should be_true
+ b.send(@method, a).should be_true
+ end
+
+ it "returns true when comparing 2 empty strings but one is not ASCII-compatible" do
+ "".send(@method, "".dup.force_encoding('iso-2022-jp')).should == true
+ end
+end
diff --git a/spec/ruby/core/string/shared/equal_value.rb b/spec/ruby/core/string/shared/equal_value.rb
new file mode 100644
index 0000000000..fccafb5821
--- /dev/null
+++ b/spec/ruby/core/string/shared/equal_value.rb
@@ -0,0 +1,29 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_equal_value, shared: true do
+ it "returns false if obj does not respond to to_str" do
+ 'hello'.send(@method, 5).should be_false
+ not_supported_on :opal do
+ 'hello'.send(@method, :hello).should be_false
+ end
+ 'hello'.send(@method, mock('x')).should be_false
+ end
+
+ it "returns obj == self if obj responds to to_str" do
+ obj = Object.new
+
+ # String#== merely checks if #to_str is defined. It does
+ # not call it.
+ obj.stub!(:to_str)
+
+ # Don't use @method for :== in `obj.should_receive(:==)`
+ obj.should_receive(:==).and_return(true)
+
+ 'hello'.send(@method, obj).should be_true
+ end
+
+ it "is not fooled by NUL characters" do
+ "abc\0def".send(@method, "abc\0xyz").should be_false
+ end
+end
diff --git a/spec/ruby/core/string/shared/grapheme_clusters.rb b/spec/ruby/core/string/shared/grapheme_clusters.rb
new file mode 100644
index 0000000000..8b666868b1
--- /dev/null
+++ b/spec/ruby/core/string/shared/grapheme_clusters.rb
@@ -0,0 +1,16 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_grapheme_clusters, shared: true do
+ it "passes each grapheme cluster in self to the given block" do
+ a = []
+ # test string: abc[rainbow flag emoji][paw prints]
+ "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}".send(@method) { |c| a << c }
+ a.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"]
+ end
+
+ it "returns self" do
+ s = StringSpecs::MyString.new "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}"
+ s.send(@method) {}.should equal(s)
+ end
+end
diff --git a/spec/ruby/core/string/shared/length.rb b/spec/ruby/core/string/shared/length.rb
new file mode 100644
index 0000000000..ae572ba755
--- /dev/null
+++ b/spec/ruby/core/string/shared/length.rb
@@ -0,0 +1,55 @@
+# encoding: utf-8
+
+describe :string_length, shared: true do
+ it "returns the length of self" do
+ "".send(@method).should == 0
+ "\x00".send(@method).should == 1
+ "one".send(@method).should == 3
+ "two".send(@method).should == 3
+ "three".send(@method).should == 5
+ "four".send(@method).should == 4
+ end
+
+ it "returns the length of a string in different encodings" do
+ utf8_str = 'こにちわ' * 100
+ utf8_str.send(@method).should == 400
+ utf8_str.encode(Encoding::UTF_32BE).send(@method).should == 400
+ utf8_str.encode(Encoding::SHIFT_JIS).send(@method).should == 400
+ end
+
+ it "returns the length of the new self after encoding is changed" do
+ str = +'こにちわ'
+ str.send(@method)
+
+ str.force_encoding('BINARY').send(@method).should == 12
+ end
+
+ it "returns the correct length after force_encoding(BINARY)" do
+ utf8 = "あ"
+ ascii = "a"
+ concat = utf8 + ascii
+
+ concat.encoding.should == Encoding::UTF_8
+ concat.bytesize.should == 4
+
+ concat.send(@method).should == 2
+ concat.force_encoding(Encoding::ASCII_8BIT)
+ concat.send(@method).should == 4
+ end
+
+ it "adds 1 for every invalid byte in UTF-8" do
+ "\xF4\x90\x80\x80".send(@method).should == 4
+ "a\xF4\x90\x80\x80b".send(@method).should == 6
+ "é\xF4\x90\x80\x80è".send(@method).should == 6
+ end
+
+ it "adds 1 (and not 2) for a incomplete surrogate in UTF-16" do
+ "\x00\xd8".dup.force_encoding("UTF-16LE").send(@method).should == 1
+ "\xd8\x00".dup.force_encoding("UTF-16BE").send(@method).should == 1
+ end
+
+ it "adds 1 for a broken sequence in UTF-32" do
+ "\x04\x03\x02\x01".dup.force_encoding("UTF-32LE").send(@method).should == 1
+ "\x01\x02\x03\x04".dup.force_encoding("UTF-32BE").send(@method).should == 1
+ end
+end
diff --git a/spec/ruby/core/string/shared/partition.rb b/spec/ruby/core/string/shared/partition.rb
new file mode 100644
index 0000000000..4cac149ce5
--- /dev/null
+++ b/spec/ruby/core/string/shared/partition.rb
@@ -0,0 +1,33 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_partition, shared: true do
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("hello").send(@method, "l").each do |item|
+ item.should be_an_instance_of(String)
+ end
+
+ StringSpecs::MyString.new("hello").send(@method, "x").each do |item|
+ item.should be_an_instance_of(String)
+ end
+
+ StringSpecs::MyString.new("hello").send(@method, /l./).each do |item|
+ item.should be_an_instance_of(String)
+ end
+ end
+
+ it "returns before- and after- parts in the same encoding as self" do
+ strings = "hello".encode("US-ASCII").send(@method, "ello")
+ strings[0].encoding.should == Encoding::US_ASCII
+ strings[2].encoding.should == Encoding::US_ASCII
+
+ strings = "hello".encode("US-ASCII").send(@method, /ello/)
+ strings[0].encoding.should == Encoding::US_ASCII
+ strings[2].encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns the matching part in the separator's encoding" do
+ strings = "hello".encode("US-ASCII").send(@method, "ello")
+ strings[1].encoding.should == Encoding::UTF_8
+ end
+end
diff --git a/spec/ruby/core/string/shared/replace.rb b/spec/ruby/core/string/shared/replace.rb
new file mode 100644
index 0000000000..24dac0eb27
--- /dev/null
+++ b/spec/ruby/core/string/shared/replace.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: false
+describe :string_replace, shared: true do
+ it "returns self" do
+ a = "a"
+ a.send(@method, "b").should equal(a)
+ end
+
+ it "replaces the content of self with other" do
+ a = "some string"
+ a.send(@method, "another string")
+ a.should == "another string"
+ end
+
+ it "replaces the encoding of self with that of other" do
+ a = "".encode("UTF-16LE")
+ b = "".encode("UTF-8")
+ a.send(@method, b)
+ a.encoding.should == Encoding::UTF_8
+ end
+
+ it "carries over the encoding invalidity" do
+ a = "\u{8765}".force_encoding('ascii')
+ "".send(@method, a).valid_encoding?.should be_false
+ end
+
+ it "tries to convert other to string using to_str" do
+ other = mock('x')
+ other.should_receive(:to_str).and_return("converted to a string")
+ "hello".send(@method, other).should == "converted to a string"
+ end
+
+ it "raises a TypeError if other can't be converted to string" do
+ -> { "hello".send(@method, 123) }.should raise_error(TypeError)
+ -> { "hello".send(@method, []) }.should raise_error(TypeError)
+ -> { "hello".send(@method, mock('x')) }.should raise_error(TypeError)
+ end
+
+ it "raises a FrozenError on a frozen instance that is modified" do
+ a = "hello".freeze
+ -> { a.send(@method, "world") }.should raise_error(FrozenError)
+ end
+
+ # see [ruby-core:23666]
+ it "raises a FrozenError on a frozen instance when self-replacing" do
+ a = "hello".freeze
+ -> { a.send(@method, a) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb
new file mode 100644
index 0000000000..7b9b9f6a14
--- /dev/null
+++ b/spec/ruby/core/string/shared/slice.rb
@@ -0,0 +1,517 @@
+describe :string_slice, shared: true do
+ it "returns the character code of the character at the given index" do
+ "hello".send(@method, 0).should == ?h
+ "hello".send(@method, -1).should == ?o
+ end
+
+ it "returns nil if index is outside of self" do
+ "hello".send(@method, 20).should == nil
+ "hello".send(@method, -20).should == nil
+
+ "".send(@method, 0).should == nil
+ "".send(@method, -1).should == nil
+ end
+
+ it "calls to_int on the given index" do
+ "hello".send(@method, 0.5).should == ?h
+
+ obj = mock('1')
+ obj.should_receive(:to_int).and_return(1)
+ "hello".send(@method, obj).should == ?e
+ end
+
+ it "raises a TypeError if the given index is nil" do
+ -> { "hello".send(@method, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError if the given index can't be converted to an Integer" do
+ -> { "hello".send(@method, mock('x')) }.should raise_error(TypeError)
+ -> { "hello".send(@method, {}) }.should raise_error(TypeError)
+ -> { "hello".send(@method, []) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if the index is too big" do
+ -> { "hello".send(@method, bignum_value) }.should raise_error(RangeError)
+ end
+end
+
+describe :string_slice_index_length, shared: true do
+ it "returns the substring starting at the given index with the given length" do
+ "hello there".send(@method, 0,0).should == ""
+ "hello there".send(@method, 0,1).should == "h"
+ "hello there".send(@method, 0,3).should == "hel"
+ "hello there".send(@method, 0,6).should == "hello "
+ "hello there".send(@method, 0,9).should == "hello the"
+ "hello there".send(@method, 0,12).should == "hello there"
+
+ "hello there".send(@method, 1,0).should == ""
+ "hello there".send(@method, 1,1).should == "e"
+ "hello there".send(@method, 1,3).should == "ell"
+ "hello there".send(@method, 1,6).should == "ello t"
+ "hello there".send(@method, 1,9).should == "ello ther"
+ "hello there".send(@method, 1,12).should == "ello there"
+
+ "hello there".send(@method, 3,0).should == ""
+ "hello there".send(@method, 3,1).should == "l"
+ "hello there".send(@method, 3,3).should == "lo "
+ "hello there".send(@method, 3,6).should == "lo the"
+ "hello there".send(@method, 3,9).should == "lo there"
+
+ "hello there".send(@method, 4,0).should == ""
+ "hello there".send(@method, 4,3).should == "o t"
+ "hello there".send(@method, 4,6).should == "o ther"
+ "hello there".send(@method, 4,9).should == "o there"
+
+ "foo".send(@method, 2,1).should == "o"
+ "foo".send(@method, 3,0).should == ""
+ "foo".send(@method, 3,1).should == ""
+
+ "".send(@method, 0,0).should == ""
+ "".send(@method, 0,1).should == ""
+
+ "x".send(@method, 0,0).should == ""
+ "x".send(@method, 0,1).should == "x"
+ "x".send(@method, 1,0).should == ""
+ "x".send(@method, 1,1).should == ""
+
+ "x".send(@method, -1,0).should == ""
+ "x".send(@method, -1,1).should == "x"
+
+ "hello there".send(@method, -3,2).should == "er"
+ end
+
+ it "returns a string with the same encoding as self" do
+ s = "hello there"
+ s.send(@method, 1, 9).encoding.should == s.encoding
+
+ a = "hello".dup.force_encoding("binary")
+ b = " there".dup.force_encoding("ISO-8859-1")
+ c = (a + b).force_encoding(Encoding::US_ASCII)
+
+ c.send(@method, 0, 5).encoding.should == Encoding::US_ASCII
+ c.send(@method, 5, 6).encoding.should == Encoding::US_ASCII
+ c.send(@method, 1, 3).encoding.should == Encoding::US_ASCII
+ c.send(@method, 8, 2).encoding.should == Encoding::US_ASCII
+ c.send(@method, 1, 10).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns nil if the offset falls outside of self" do
+ "hello there".send(@method, 20,3).should == nil
+ "hello there".send(@method, -20,3).should == nil
+
+ "".send(@method, 1,0).should == nil
+ "".send(@method, 1,1).should == nil
+
+ "".send(@method, -1,0).should == nil
+ "".send(@method, -1,1).should == nil
+
+ "x".send(@method, 2,0).should == nil
+ "x".send(@method, 2,1).should == nil
+
+ "x".send(@method, -2,0).should == nil
+ "x".send(@method, -2,1).should == nil
+
+ "x".send(@method, fixnum_max, 1).should == nil
+ end
+
+ it "returns nil if the length is negative" do
+ "hello there".send(@method, 4,-3).should == nil
+ "hello there".send(@method, -4,-3).should == nil
+ end
+
+ platform_is pointer_size: 64 do
+ it "returns nil if the length is negative big value" do
+ "hello there".send(@method, 4, -(1 << 31)).should == nil
+
+ # by some reason length < -(1 << 31) on CI on Windows leads to
+ # 'RangeError: bignum too big to convert into `long'' error
+ platform_is_not :windows do
+ "hello there".send(@method, 4, -(1 << 63)).should == nil
+ end
+ end
+ end
+
+ it "calls to_int on the given index and the given length" do
+ "hello".send(@method, 0.5, 1).should == "h"
+ "hello".send(@method, 0.5, 2.5).should == "he"
+ "hello".send(@method, 1, 2.5).should == "el"
+
+ obj = mock('2')
+ obj.should_receive(:to_int).exactly(4).times.and_return(2)
+
+ "hello".send(@method, obj, 1).should == "l"
+ "hello".send(@method, obj, obj).should == "ll"
+ "hello".send(@method, 0, obj).should == "he"
+ end
+
+ it "raises a TypeError when idx or length can't be converted to an integer" do
+ -> { "hello".send(@method, mock('x'), 0) }.should raise_error(TypeError)
+ -> { "hello".send(@method, 0, mock('x')) }.should raise_error(TypeError)
+
+ # I'm deliberately including this here.
+ # It means that str.send(@method, other, idx) isn't supported.
+ -> { "hello".send(@method, "", 0) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the given index or the given length is nil" do
+ -> { "hello".send(@method, 1, nil) }.should raise_error(TypeError)
+ -> { "hello".send(@method, nil, 1) }.should raise_error(TypeError)
+ -> { "hello".send(@method, nil, nil) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if the index or length is too big" do
+ -> { "hello".send(@method, bignum_value, 1) }.should raise_error(RangeError)
+ -> { "hello".send(@method, 0, bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "raises a RangeError if the index or length is too small" do
+ -> { "hello".send(@method, -bignum_value, 1) }.should raise_error(RangeError)
+ -> { "hello".send(@method, 0, -bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, 0,0).should be_an_instance_of(String)
+ s.send(@method, 0,4).should be_an_instance_of(String)
+ s.send(@method, 1,4).should be_an_instance_of(String)
+ end
+
+ it "handles repeated application" do
+ "hello world".send(@method, 6, 5).send(@method, 0, 1).should == 'w'
+ "hello world".send(@method, 6, 5).send(@method, 0, 5).should == 'world'
+
+ "hello world".send(@method, 6, 5).send(@method, 1, 1).should == 'o'
+ "hello world".send(@method, 6, 5).send(@method, 1, 4).should == 'orld'
+
+ "hello world".send(@method, 6, 5).send(@method, 4, 1).should == 'd'
+ "hello world".send(@method, 6, 5).send(@method, 5, 0).should == ''
+
+ "hello world".send(@method, 6, 0).send(@method, -1, 0).should == nil
+ "hello world".send(@method, 6, 0).send(@method, 1, 1).should == nil
+ end
+end
+
+describe :string_slice_range, shared: true do
+ it "returns the substring given by the offsets of the range" do
+ "hello there".send(@method, 1..1).should == "e"
+ "hello there".send(@method, 1..3).should == "ell"
+ "hello there".send(@method, 1...3).should == "el"
+ "hello there".send(@method, -4..-2).should == "her"
+ "hello there".send(@method, -4...-2).should == "he"
+ "hello there".send(@method, 5..-1).should == " there"
+ "hello there".send(@method, 5...-1).should == " ther"
+
+ "".send(@method, 0..0).should == ""
+
+ "x".send(@method, 0..0).should == "x"
+ "x".send(@method, 0..1).should == "x"
+ "x".send(@method, 0...1).should == "x"
+ "x".send(@method, 0..-1).should == "x"
+
+ "x".send(@method, 1..1).should == ""
+ "x".send(@method, 1..-1).should == ""
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello there".encode("US-ASCII").send(@method, 1..1).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns nil if the beginning of the range falls outside of self" do
+ "hello there".send(@method, 12..-1).should == nil
+ "hello there".send(@method, 20..25).should == nil
+ "hello there".send(@method, 20..1).should == nil
+ "hello there".send(@method, -20..1).should == nil
+ "hello there".send(@method, -20..-1).should == nil
+
+ "".send(@method, -1..-1).should == nil
+ "".send(@method, -1...-1).should == nil
+ "".send(@method, -1..0).should == nil
+ "".send(@method, -1...0).should == nil
+ end
+
+ it "returns an empty string if range.begin is inside self and > real end" do
+ "hello there".send(@method, 1...1).should == ""
+ "hello there".send(@method, 4..2).should == ""
+ "hello".send(@method, 4..-4).should == ""
+ "hello there".send(@method, -5..-6).should == ""
+ "hello there".send(@method, -2..-4).should == ""
+ "hello there".send(@method, -5..-6).should == ""
+ "hello there".send(@method, -5..2).should == ""
+
+ "".send(@method, 0...0).should == ""
+ "".send(@method, 0..-1).should == ""
+ "".send(@method, 0...-1).should == ""
+
+ "x".send(@method, 0...0).should == ""
+ "x".send(@method, 0...-1).should == ""
+ "x".send(@method, 1...1).should == ""
+ "x".send(@method, 1...-1).should == ""
+ end
+
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, 0...0).should be_an_instance_of(String)
+ s.send(@method, 0..4).should be_an_instance_of(String)
+ s.send(@method, 1..4).should be_an_instance_of(String)
+ end
+
+ it "calls to_int on range arguments" do
+ from = mock('from')
+ to = mock('to')
+
+ # So we can construct a range out of them...
+ from.should_receive(:<=>).twice.and_return(0)
+
+ from.should_receive(:to_int).twice.and_return(1)
+ to.should_receive(:to_int).twice.and_return(-2)
+
+ "hello there".send(@method, from..to).should == "ello ther"
+ "hello there".send(@method, from...to).should == "ello the"
+ end
+
+ it "works with Range subclasses" do
+ a = "GOOD"
+ range_incl = StringSpecs::MyRange.new(1, 2)
+ range_excl = StringSpecs::MyRange.new(-3, -1, true)
+
+ a.send(@method, range_incl).should == "OO"
+ a.send(@method, range_excl).should == "OO"
+ end
+
+ it "handles repeated application" do
+ "hello world".send(@method, 6..11).send(@method, 0..0).should == 'w'
+ "hello world".send(@method, 6..11).send(@method, 0..4).should == 'world'
+
+ "hello world".send(@method, 6..11).send(@method, 1..1).should == 'o'
+ "hello world".send(@method, 6..11).send(@method, 1..4).should == 'orld'
+
+ "hello world".send(@method, 6..11).send(@method, 4..4).should == 'd'
+ "hello world".send(@method, 6..11).send(@method, 5..4).should == ''
+
+ "hello world".send(@method, 6..5).send(@method, -1..-1).should == nil
+ "hello world".send(@method, 6..5).send(@method, 1..1).should == nil
+ end
+
+ it "raises a type error if a range is passed with a length" do
+ ->{ "hello".send(@method, 1..2, 1) }.should raise_error(TypeError)
+ end
+
+ it "raises a RangeError if one of the bound is too big" do
+ -> { "hello".send(@method, bignum_value..(bignum_value + 1)) }.should raise_error(RangeError)
+ -> { "hello".send(@method, 0..bignum_value) }.should raise_error(RangeError)
+ end
+
+ it "works with endless ranges" do
+ "hello there".send(@method, eval("(2..)")).should == "llo there"
+ "hello there".send(@method, eval("(2...)")).should == "llo there"
+ "hello there".send(@method, eval("(-4..)")).should == "here"
+ "hello there".send(@method, eval("(-4...)")).should == "here"
+ end
+
+ it "works with beginless ranges" do
+ "hello there".send(@method, (..5)).should == "hello "
+ "hello there".send(@method, (...5)).should == "hello"
+ "hello there".send(@method, (..-4)).should == "hello th"
+ "hello there".send(@method, (...-4)).should == "hello t"
+ "hello there".send(@method, (...nil)).should == "hello there"
+ end
+end
+
+describe :string_slice_regexp, shared: true do
+ it "returns the matching portion of self" do
+ "hello there".send(@method, /[aeiou](.)\1/).should == "ell"
+ "".send(@method, //).should == ""
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, /xyz/).should == nil
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello there".encode("US-ASCII").send(@method, /[aeiou](.)\1/).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, //).should be_an_instance_of(String)
+ s.send(@method, /../).should be_an_instance_of(String)
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'.send(@method, /./)
+ $~[0].should == 'h'
+
+ 'hello'.send(@method, /not/)
+ $~.should == nil
+ end
+end
+
+describe :string_slice_regexp_index, shared: true do
+ it "returns the capture for the given index" do
+ "hello there".send(@method, /[aeiou](.)\1/, 0).should == "ell"
+ "hello there".send(@method, /[aeiou](.)\1/, 1).should == "l"
+ "hello there".send(@method, /[aeiou](.)\1/, -1).should == "l"
+
+ "har".send(@method, /(.)(.)(.)/, 0).should == "har"
+ "har".send(@method, /(.)(.)(.)/, 1).should == "h"
+ "har".send(@method, /(.)(.)(.)/, 2).should == "a"
+ "har".send(@method, /(.)(.)(.)/, 3).should == "r"
+ "har".send(@method, /(.)(.)(.)/, -1).should == "r"
+ "har".send(@method, /(.)(.)(.)/, -2).should == "a"
+ "har".send(@method, /(.)(.)(.)/, -3).should == "h"
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, /(what?)/, 1).should == nil
+ end
+
+ it "returns nil if the index is larger than the number of captures" do
+ "hello there".send(@method, /hello (.)/, 2).should == nil
+ # You can't refer to 0 using negative indices
+ "hello there".send(@method, /hello (.)/, -2).should == nil
+ end
+
+ it "returns nil if there is no capture for the given index" do
+ "hello there".send(@method, /[aeiou](.)\1/, 2).should == nil
+ end
+
+ it "returns nil if the given capture group was not matched but still sets $~" do
+ "test".send(@method, /te(z)?/, 1).should == nil
+ $~[0].should == "te"
+ $~[1].should == nil
+ end
+
+ it "returns a String in the same encoding as self" do
+ "hello there".encode("US-ASCII").send(@method, /[aeiou](.)\1/, 0).encoding.should == Encoding::US_ASCII
+ end
+
+ it "calls to_int on the given index" do
+ obj = mock('2')
+ obj.should_receive(:to_int).and_return(2)
+
+ "har".send(@method, /(.)(.)(.)/, 1.5).should == "h"
+ "har".send(@method, /(.)(.)(.)/, obj).should == "a"
+ end
+
+ it "raises a TypeError when the given index can't be converted to Integer" do
+ -> { "hello".send(@method, /(.)(.)(.)/, mock('x')) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(.)(.)(.)/, {}) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(.)(.)(.)/, []) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the given index is nil" do
+ -> { "hello".send(@method, /(.)(.)(.)/, nil) }.should raise_error(TypeError)
+ end
+
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, /(.)(.)/, 0).should be_an_instance_of(String)
+ s.send(@method, /(.)(.)/, 1).should be_an_instance_of(String)
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'.send(@method, /.(.)/, 0)
+ $~[0].should == 'he'
+
+ 'hello'.send(@method, /.(.)/, 1)
+ $~[1].should == 'e'
+
+ 'hello'.send(@method, /not/, 0)
+ $~.should == nil
+ end
+end
+
+describe :string_slice_string, shared: true do
+ it "returns other_str if it occurs in self" do
+ s = "lo"
+ "hello there".send(@method, s).should == s
+ end
+
+ it "doesn't set $~" do
+ $~ = nil
+
+ 'hello'.send(@method, 'll')
+ $~.should == nil
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, "bye").should == nil
+ end
+
+ it "doesn't call to_str on its argument" do
+ o = mock('x')
+ o.should_not_receive(:to_str)
+
+ -> { "hello".send(@method, o) }.should raise_error(TypeError)
+ end
+
+ it "returns a String instance when given a subclass instance" do
+ s = StringSpecs::MyString.new("el")
+ r = "hello".send(@method, s)
+ r.should == "el"
+ r.should be_an_instance_of(String)
+ end
+end
+
+describe :string_slice_regexp_group, shared: true do
+ not_supported_on :opal do
+ it "returns the capture for the given name" do
+ "hello there".send(@method, /(?<g>[aeiou](.))/, 'g').should == "el"
+ "hello there".send(@method, /[aeiou](?<g>.)/, 'g').should == "l"
+
+ "har".send(@method, /(?<g>(.)(.)(.))/, 'g').should == "har"
+ "har".send(@method, /(?<h>.)(.)(.)/, 'h').should == "h"
+ "har".send(@method, /(.)(?<a>.)(.)/, 'a').should == "a"
+ "har".send(@method, /(.)(.)(?<r>.)/, 'r').should == "r"
+ "har".send(@method, /(?<h>.)(?<a>.)(?<r>.)/, 'r').should == "r"
+ end
+
+ it "returns the last capture for duplicate names" do
+ "hello there".send(@method, /(?<g>h)(?<g>.)/, 'g').should == "e"
+ "hello there".send(@method, /(?<g>h)(?<g>.)(?<f>.)/, 'g').should == "e"
+ end
+
+ it "returns the innermost capture for nested duplicate names" do
+ "hello there".send(@method, /(?<g>h(?<g>.))/, 'g').should == "e"
+ end
+
+ it "returns nil if there is no match" do
+ "hello there".send(@method, /(?<whut>what?)/, 'whut').should be_nil
+ end
+
+ it "raises an IndexError if there is no capture for the given name" do
+ -> do
+ "hello there".send(@method, /[aeiou](.)\1/, 'non')
+ end.should raise_error(IndexError)
+ end
+
+ it "raises a TypeError when the given name is not a String" do
+ -> { "hello".send(@method, /(?<q>.)/, mock('x')) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(?<q>.)/, {}) }.should raise_error(TypeError)
+ -> { "hello".send(@method, /(?<q>.)/, []) }.should raise_error(TypeError)
+ end
+
+ it "raises an IndexError when given the empty String as a group name" do
+ -> { "hello".send(@method, /(?<q>)/, '') }.should raise_error(IndexError)
+ end
+
+ it "returns String instances" do
+ s = StringSpecs::MyString.new("hello")
+ s.send(@method, /(?<q>.)/, 'q').should be_an_instance_of(String)
+ end
+
+ it "sets $~ to MatchData when there is a match and nil when there's none" do
+ 'hello'.send(@method, /(?<hi>.(.))/, 'hi')
+ $~[0].should == 'he'
+
+ 'hello'.send(@method, /(?<non>not)/, 'non')
+ $~.should be_nil
+ end
+ end
+end
+
+describe :string_slice_symbol, shared: true do
+ it "raises TypeError" do
+ -> { 'hello'.send(@method, :hello) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/strip.rb b/spec/ruby/core/string/shared/strip.rb
new file mode 100644
index 0000000000..3af77b50fe
--- /dev/null
+++ b/spec/ruby/core/string/shared/strip.rb
@@ -0,0 +1,14 @@
+require_relative '../../../spec_helper'
+require_relative '../fixtures/classes'
+
+describe :string_strip, shared: true do
+ it "returns a String in the same encoding as self" do
+ " hello ".encode("US-ASCII").send(@method).encoding.should == Encoding::US_ASCII
+ end
+
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new(" hello ").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new(" ").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new("").send(@method).should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/core/string/shared/succ.rb b/spec/ruby/core/string/shared/succ.rb
new file mode 100644
index 0000000000..7c68345f10
--- /dev/null
+++ b/spec/ruby/core/string/shared/succ.rb
@@ -0,0 +1,87 @@
+# encoding: binary
+describe :string_succ, shared: true do
+ it "returns an empty string for empty strings" do
+ "".send(@method).should == ""
+ end
+
+ it "returns the successor by increasing the rightmost alphanumeric (digit => digit, letter => letter with same case)" do
+ "abcd".send(@method).should == "abce"
+ "THX1138".send(@method).should == "THX1139"
+
+ "<<koala>>".send(@method).should == "<<koalb>>"
+ "==A??".send(@method).should == "==B??"
+ end
+
+ it "increases non-alphanumerics (via ascii rules) if there are no alphanumerics" do
+ "***".send(@method).should == "**+"
+ "**`".send(@method).should == "**a"
+ end
+
+ it "increases the next best alphanumeric (jumping over non-alphanumerics) if there is a carry" do
+ "dz".send(@method).should == "ea"
+ "HZ".send(@method).should == "IA"
+ "49".send(@method).should == "50"
+
+ "izz".send(@method).should == "jaa"
+ "IZZ".send(@method).should == "JAA"
+ "699".send(@method).should == "700"
+
+ "6Z99z99Z".send(@method).should == "7A00a00A"
+
+ "1999zzz".send(@method).should == "2000aaa"
+ "NZ/[]ZZZ9999".send(@method).should == "OA/[]AAA0000"
+ end
+
+ it "increases the next best character if there is a carry for non-alphanumerics" do
+ "(\xFF".send(@method).should == ")\x00"
+ "`\xFF".send(@method).should == "a\x00"
+ "<\xFF\xFF".send(@method).should == "=\x00\x00"
+ end
+
+ it "adds an additional character (just left to the last increased one) if there is a carry and no character left to increase" do
+ "z".send(@method).should == "aa"
+ "Z".send(@method).should == "AA"
+ "9".send(@method).should == "10"
+
+ "zz".send(@method).should == "aaa"
+ "ZZ".send(@method).should == "AAA"
+ "99".send(@method).should == "100"
+
+ "9Z99z99Z".send(@method).should == "10A00a00A"
+
+ "ZZZ9999".send(@method).should == "AAAA0000"
+ "/[]9999".send(@method).should == "/[]10000"
+ "/[]ZZZ9999".send(@method).should == "/[]AAAA0000"
+ "Z/[]ZZZ9999".send(@method).should == "AA/[]AAA0000"
+
+ # non-alphanumeric cases
+ "\xFF".send(@method).should == "\x01\x00"
+ "\xFF\xFF".send(@method).should == "\x01\x00\x00"
+ end
+
+ it "returns String instances when called on a subclass" do
+ StringSpecs::MyString.new("").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new("a").send(@method).should be_an_instance_of(String)
+ StringSpecs::MyString.new("z").send(@method).should be_an_instance_of(String)
+ end
+
+ it "returns a String in the same encoding as self" do
+ "z".encode("US-ASCII").send(@method).encoding.should == Encoding::US_ASCII
+ end
+end
+
+describe :string_succ_bang, shared: true do
+ it "is equivalent to succ, but modifies self in place (still returns self)" do
+ ["", "abcd", "THX1138"].each do |s|
+ s = +s
+ r = s.dup.send(@method)
+ s.send(@method).should equal(s)
+ s.should == r
+ end
+ end
+
+ it "raises a FrozenError if self is frozen" do
+ -> { "".freeze.send(@method) }.should raise_error(FrozenError)
+ -> { "abcd".freeze.send(@method) }.should raise_error(FrozenError)
+ end
+end
diff --git a/spec/ruby/core/string/shared/to_s.rb b/spec/ruby/core/string/shared/to_s.rb
new file mode 100644
index 0000000000..4b87a6cbe1
--- /dev/null
+++ b/spec/ruby/core/string/shared/to_s.rb
@@ -0,0 +1,13 @@
+describe :string_to_s, shared: true do
+ it "returns self when self.class == String" do
+ a = "a string"
+ a.should equal(a.send(@method))
+ end
+
+ it "returns a new instance of String when called on a subclass" do
+ a = StringSpecs::MyString.new("a string")
+ s = a.send(@method)
+ s.should == "a string"
+ s.should be_an_instance_of(String)
+ end
+end
diff --git a/spec/ruby/core/string/shared/to_sym.rb b/spec/ruby/core/string/shared/to_sym.rb
new file mode 100644
index 0000000000..833eae100e
--- /dev/null
+++ b/spec/ruby/core/string/shared/to_sym.rb
@@ -0,0 +1,72 @@
+describe :string_to_sym, shared: true do
+ it "returns the symbol corresponding to self" do
+ "Koala".send(@method).should equal :Koala
+ 'cat'.send(@method).should equal :cat
+ '@cat'.send(@method).should equal :@cat
+ 'cat and dog'.send(@method).should equal :"cat and dog"
+ "abc=".send(@method).should equal :abc=
+ end
+
+ it "does not special case +(binary) and -(binary)" do
+ "+(binary)".send(@method).should equal :"+(binary)"
+ "-(binary)".send(@method).should equal :"-(binary)"
+ end
+
+ it "does not special case certain operators" do
+ "!@".send(@method).should equal :"!@"
+ "~@".send(@method).should equal :"~@"
+ "!(unary)".send(@method).should equal :"!(unary)"
+ "~(unary)".send(@method).should equal :"~(unary)"
+ "+(unary)".send(@method).should equal :"+(unary)"
+ "-(unary)".send(@method).should equal :"-(unary)"
+ end
+
+ it "returns a US-ASCII Symbol for a UTF-8 String containing only US-ASCII characters" do
+ sym = "foobar".send(@method)
+ sym.encoding.should == Encoding::US_ASCII
+ sym.should equal :"foobar"
+ end
+
+ it "returns a US-ASCII Symbol for a binary String containing only US-ASCII characters" do
+ sym = "foobar".b.send(@method)
+ sym.encoding.should == Encoding::US_ASCII
+ sym.should equal :"foobar"
+ end
+
+ it "returns a UTF-8 Symbol for a UTF-8 String containing non US-ASCII characters" do
+ sym = "il était une fois".send(@method)
+ sym.encoding.should == Encoding::UTF_8
+ sym.should equal :"il était une #{'fois'}"
+ end
+
+ it "returns a UTF-16LE Symbol for a UTF-16LE String containing non US-ASCII characters" do
+ utf16_str = "UtéF16".encode(Encoding::UTF_16LE)
+ sym = utf16_str.send(@method)
+ sym.encoding.should == Encoding::UTF_16LE
+ sym.to_s.should == utf16_str
+ end
+
+ it "returns a binary Symbol for a binary String containing non US-ASCII characters" do
+ binary_string = "binarí".b
+ sym = binary_string.send(@method)
+ sym.encoding.should == Encoding::BINARY
+ sym.to_s.should == binary_string
+ end
+
+ it "ignores existing symbols with different encoding" do
+ source = "fée"
+
+ iso_symbol = source.dup.force_encoding(Encoding::ISO_8859_1).send(@method)
+ iso_symbol.encoding.should == Encoding::ISO_8859_1
+ binary_symbol = source.dup.force_encoding(Encoding::BINARY).send(@method)
+ binary_symbol.encoding.should == Encoding::BINARY
+ end
+
+ it "raises an EncodingError for UTF-8 String containing invalid bytes" do
+ invalid_utf8 = "\xC3"
+ invalid_utf8.should_not.valid_encoding?
+ -> {
+ invalid_utf8.send(@method)
+ }.should raise_error(EncodingError, 'invalid symbol in encoding UTF-8 :"\xC3"')
+ end
+end