require "./spec_helper" require "../support/string" require "spec/helpers/iterate" require "spec/helpers/string" describe "String" do describe "[]" do it "gets with positive index" do c = "hello!"[1] c.should be_a(Char) c.should eq('e') end it "gets with negative index" do "hello!"[-1].should eq('!') end it "gets with inclusive range" do "hello!"[1..4].should eq("ello") end it "gets with inclusive range with negative indices" do "hello!"[-5..-2].should eq("ello") end it "gets with exclusive range" do "hello!"[1...4].should eq("ell") end it "gets with start and count" do "hello"[1, 3].should eq("ell") end it "gets with exclusive range with unicode" do "há日本語"[1..3].should eq("á日本") end it "gets with range without end" do "há日本語"[1..nil].should eq("á日本語") end it "gets with range without beginning" do "há日本語"[nil..2].should eq("há日") end it "gets when index is last and count is zero" do "foo"[3, 0].should eq("") end it "gets when index is last and count is positive" do "foo"[3, 10].should eq("") end it "gets when index is last and count is negative at last" do expect_raises(ArgumentError) do "foo"[3, -1] end end it { "foo"[3..-10].should eq("") } it "gets when index is last and count is negative at last with utf-8" do expect_raises(ArgumentError) do "há日本語"[5, -1] end end it "gets when index is last and count is zero in utf-8" do "há日本語"[5, 0].should eq("") end it "gets when index is last and count is positive in utf-8" do "há日本語"[5, 10].should eq("") end it "raises IndexError if pointing after last char which is non-ASCII" do expect_raises(IndexError) do "ß"[1] end end it "raises index out of bound on index out of range with range" do expect_raises(IndexError) do "foo"[4..1] end end it "raises index out of bound on index out of range with range and utf-8" do expect_raises(IndexError) do "há日本語"[6..1] end end it "gets with exclusive with start and count" do "há日本語"[1, 3].should eq("á日本") end it "gets with exclusive with start and count to end" do "há日本語"[1, 4].should eq("á日本語") end it "gets with start and count with negative start" do "こんにちは"[-3, 2].should eq("にち") end it "raises if index out of bounds" do expect_raises(IndexError) do "foo"[4, 1] end end it "raises if index out of bounds with utf-8" do expect_raises(IndexError) do "こんにちは"[6, 1] end end it "raises if count is negative" do expect_raises(ArgumentError) do "foo"[1, -1] end end it "raises if count is negative with utf-8" do expect_raises(ArgumentError) do "こんにちは"[3, -1] end end it "gets with single char" do ";"[0..-2].should eq("") end it "raises on too negative left bound" do expect_raises IndexError do "foo"[-4..0] end end describe "with a regex" do it { "FooBar"[/o+/].should eq "oo" } it { "FooBar"[/([A-Z])/, 1].should eq "F" } it { "FooBar"[/x/]?.should be_nil } it { "FooBar"[/x/, 1]?.should be_nil } it { "FooBar"[/(x)/, 1]?.should be_nil } it { "FooBar"[/o(o)/, 2]?.should be_nil } it { "FooBar"[/o(?o)/, "this"].should eq "o" } it { "FooBar"[/(?x)/, "that"]?.should be_nil } end it "gets with a string" do "FooBar"["Bar"].should eq "Bar" expect_raises(Exception, "Nil assertion failed") { "FooBar"["Baz"] } end it "gets with a char" do expect_raises(Exception, "Nil assertion failed") { "foo/bar"['-'] } end end describe "[]?" do it "gets with a string" do "FooBar"["Bar"]?.should eq "Bar" "FooBar"["Baz"]?.should be_nil end it "gets with a char" do "foo/bar"['/'].should eq '/' expect_raises(Exception, "Nil assertion failed") { "foo/bar"['-'] } "foo/bar"['/']?.should eq '/' "foo/bar"['-']?.should be_nil end it "gets with index" do "hello"[1]?.should eq('e') "hello"[5]?.should be_nil "hello"[-1]?.should eq('o') "hello"[-6]?.should be_nil end it "returns nil if pointing after last char which is non-ASCII" do "ß"[1]?.should be_nil end it "gets with range" do "hello"[1..2]?.should eq "el" "hello"[6..-1]?.should be_nil "hello"[-6..-1]?.should be_nil "hello"[-6..]?.should be_nil end it "gets with start and count" do "hello"[1, 3]?.should eq("ell") "hello"[6, 3]?.should be_nil end it "gets with range without end" do "hello"[1..nil]?.should eq("ello") "hello"[6..nil]?.should be_nil end it "gets with range without beginning" do "hello"[nil..2]?.should eq("hel") end end describe "#byte_slice" do it "gets byte_slice" do "hello".byte_slice(1, 3).should eq("ell") "hello".byte_slice(1..3).should eq("ell") end it "gets byte_slice with negative count" do expect_raises(ArgumentError) do "hello".byte_slice(1, -10) end end it "gets byte_slice with negative count at last" do expect_raises(ArgumentError) do "hello".byte_slice(5, -1) end end it "gets byte_slice with start out of bounds" do expect_raises(IndexError) do "hello".byte_slice(10, 3) end expect_raises(IndexError) do "hello".byte_slice(10..13) end end it "gets byte_slice with large count" do "hello".byte_slice(1, 10).should eq("ello") "hello".byte_slice(1..10).should eq("ello") end it "gets byte_slice with negative index" do "hello".byte_slice(-2, 3).should eq("lo") "hello".byte_slice(-2..-1).should eq("lo") end it "gets byte_slice(Int) with start out of bounds" do expect_raises(IndexError) do "hello".byte_slice(10) end expect_raises(IndexError) do "hello".byte_slice(-10) end end end describe "#byte_slice?" do it "gets byte_slice" do "hello".byte_slice?(1, 3).should eq("ell") "hello".byte_slice?(1..3).should eq("ell") end it "gets byte_slice with negative count" do expect_raises(ArgumentError) do "hello".byte_slice?(1, -10) end end it "gets byte_slice with negative count at last" do expect_raises(ArgumentError) do "hello".byte_slice?(5, -1) end end it "gets byte_slice with start out of bounds" do "hello".byte_slice?(10, 3).should be_nil "hello".byte_slice?(10..13).should be_nil end it "gets byte_slice with large count" do "hello".byte_slice?(1, 10).should eq("ello") "hello".byte_slice?(1..11).should eq("ello") end it "gets byte_slice with negative index" do "hello".byte_slice?(-2, 3).should eq("lo") "hello".byte_slice?(-2..-1).should eq("lo") end it "gets byte_slice(Int) with start out of bounds" do "hello".byte_slice?(10).should be_nil "hello".byte_slice?(-10).should be_nil end end describe "to_i" do it { "1234".to_i.should eq(1234) } it { "-128".to_i8.should eq(-128) } it { " +1234 ".to_i.should eq(1234) } it { " -1234 ".to_i.should eq(-1234) } it { " +1234 ".to_i.should eq(1234) } it { " -00001234".to_i.should eq(-1234) } it { "\u00A01234\u00A0".to_i.should eq(1234) } it { "1_234".to_i(underscore: true).should eq(1234) } it { "1101".to_i(base: 2).should eq(13) } it { "12ab".to_i(16).should eq(4779) } it { "0x123abc".to_i(prefix: true).should eq(1194684) } it { "0X123abc".to_i(prefix: true).should eq(1194684) } it { "0b1101".to_i(prefix: true).should eq(13) } it { "0B1101".to_i(prefix: true).should eq(13) } it { "0b001101".to_i(prefix: true).should eq(13) } it { "0123".to_i(prefix: true).should eq(123) } it { "0o123".to_i(prefix: true).should eq(83) } it { "0O123".to_i(prefix: true).should eq(83) } it { "0123".to_i(leading_zero_is_octal: true).should eq(83) } it { "123".to_i(leading_zero_is_octal: true).should eq(123) } it { "0o755".to_i(prefix: true, leading_zero_is_octal: true).should eq(493) } it { "5".to_i(prefix: true).should eq(5) } it { "0".to_i(prefix: true).should eq(0) } it { "00".to_i(prefix: true).should eq(0) } it { "00".to_i(leading_zero_is_octal: true).should eq(0) } it { "00".to_i(prefix: true, leading_zero_is_octal: true).should eq(0) } it { "123hello".to_i(strict: false).should eq(123) } it { "99 red balloons".to_i(strict: false).should eq(99) } it { " 99 red balloons".to_i(strict: false).should eq(99) } it { expect_raises(ArgumentError) { "hello".to_i } } it { expect_raises(ArgumentError) { "1__234".to_i } } it { expect_raises(ArgumentError) { "1_234".to_i } } it { expect_raises(ArgumentError) { " 1234 ".to_i(whitespace: false) } } it { expect_raises(ArgumentError) { "".to_i(whitespace: false) } } it { expect_raises(ArgumentError) { "0x123".to_i } } it { expect_raises(ArgumentError) { "0b123".to_i } } it { expect_raises(ArgumentError) { "000b123".to_i(prefix: true) } } it { expect_raises(ArgumentError) { "000x123".to_i(prefix: true) } } it { expect_raises(ArgumentError) { "000o89a".to_i(prefix: true) } } it { expect_raises(ArgumentError) { "123hello".to_i } } it { expect_raises(ArgumentError) { "0".to_i(leading_zero_is_octal: true) } } it { expect_raises(ArgumentError) { "0o755".to_i(leading_zero_is_octal: true) } } it { "z".to_i(36).should eq(35) } it { "Z".to_i(36).should eq(35) } it { "0".to_i(62).should eq(0) } it { "1".to_i(62).should eq(1) } it { "a".to_i(62).should eq(10) } it { "z".to_i(62).should eq(35) } it { "A".to_i(62).should eq(36) } it { "Z".to_i(62).should eq(61) } it { "10".to_i(62).should eq(62) } it { "1z".to_i(62).should eq(97) } it { "ZZ".to_i(62).should eq(3843) } # Test for #11671 it { "0_1".to_i(underscore: true).should eq(1) } describe "to_i8" do it { "127".to_i8.should eq(127) } it { "-128".to_i8.should eq(-128) } it { expect_raises(ArgumentError) { "128".to_i8 } } it { expect_raises(ArgumentError) { "-129".to_i8 } } it { "127".to_i8?.should eq(127) } it { "128".to_i8?.should be_nil } it { "128".to_i8 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_i8 } } end describe "to_u8" do it { "255".to_u8.should eq(255) } it { "0".to_u8.should eq(0) } it { expect_raises(ArgumentError) { "256".to_u8 } } it { expect_raises(ArgumentError) { "-1".to_u8 } } it { "255".to_u8?.should eq(255) } it { "256".to_u8?.should be_nil } it { "256".to_u8 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_u8 } } end describe "to_i16" do it { "32767".to_i16.should eq(32767) } it { "-32768".to_i16.should eq(-32768) } it { expect_raises(ArgumentError) { "32768".to_i16 } } it { expect_raises(ArgumentError) { "-32769".to_i16 } } it { "32767".to_i16?.should eq(32767) } it { "32768".to_i16?.should be_nil } it { "32768".to_i16 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_i16 } } end describe "to_u16" do it { "65535".to_u16.should eq(65535) } it { "0".to_u16.should eq(0) } it { expect_raises(ArgumentError) { "65536".to_u16 } } it { expect_raises(ArgumentError) { "-1".to_u16 } } it { "65535".to_u16?.should eq(65535) } it { "65536".to_u16?.should be_nil } it { "65536".to_u16 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_u16 } } end describe "to_i32" do it { "2147483647".to_i32.should eq(2147483647) } it { "-2147483648".to_i32.should eq(-2147483648) } it { expect_raises(ArgumentError) { "2147483648".to_i32 } } it { expect_raises(ArgumentError) { "-2147483649".to_i32 } } it { "2147483647".to_i32?.should eq(2147483647) } it { "2147483648".to_i32?.should be_nil } it { "2147483648".to_i32 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_i32 } } end describe "to_u32" do it { "4294967295".to_u32.should eq(4294967295) } it { "0".to_u32.should eq(0) } it { expect_raises(ArgumentError) { "4294967296".to_u32 } } it { expect_raises(ArgumentError) { "-1".to_u32 } } it { "4294967295".to_u32?.should eq(4294967295) } it { "4294967296".to_u32?.should be_nil } it { "4294967296".to_u32 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_u32 } } end describe "to_i64" do it { "9223372036854775807".to_i64.should eq(9223372036854775807) } it { "-9223372036854775808".to_i64.should eq(-9223372036854775808) } it { expect_raises(ArgumentError) { "9223372036854775808".to_i64 } } it { expect_raises(ArgumentError) { "-9223372036854775809".to_i64 } } it { "9223372036854775807".to_i64?.should eq(9223372036854775807) } it { "9223372036854775808".to_i64?.should be_nil } it { "9223372036854775808".to_i64 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_i64 } } end describe "to_u64" do it { "18446744073709551615".to_u64.should eq(18446744073709551615u64) } it { "0".to_u64.should eq(0) } it { expect_raises(ArgumentError) { "18446744073709551616".to_u64 } } it { expect_raises(ArgumentError) { "-1".to_u64 } } it { "18446744073709551615".to_u64?.should eq(18446744073709551615u64) } it { "18446744073709551616".to_u64?.should be_nil } it { "18446744073709551616".to_u64 { 0 }.should eq(0) } end describe "to_i128" do it { "170141183460469231731687303715884105727".to_i128.should eq(Int128::MAX) } it { "-170141183460469231731687303715884105728".to_i128.should eq(Int128::MIN) } it { expect_raises(ArgumentError) { "170141183460469231731687303715884105728".to_i128 } } it { expect_raises(ArgumentError) { "-170141183460469231731687303715884105729".to_i128 } } it { "170141183460469231731687303715884105727".to_i128?.should eq(Int128::MAX) } it { "170141183460469231731687303715884105728".to_i128?.should be_nil } it { "170141183460469231731687303715884105728".to_i128 { 0 }.should eq(0) } it { expect_raises(ArgumentError) { "340282366920938463463374607431768211456".to_i128 } } end describe "to_u128" do it { "340282366920938463463374607431768211455".to_u128.should eq(UInt128::MAX) } it { "0".to_u128.should eq(0) } it { expect_raises(ArgumentError) { "340282366920938463463374607431768211456".to_u128 } } it { expect_raises(ArgumentError) { "-1".to_u128 } } it { "340282366920938463463374607431768211455".to_u128?.should eq(UInt128::MAX) } it { "340282366920938463463374607431768211456".to_u128?.should be_nil } it { "340282366920938463463374607431768211456".to_u128 { 0 }.should eq(0) } end it { "1234".to_i32.should eq(1234) } it { "1234123412341234".to_i64.should eq(1234123412341234_i64) } it { "9223372036854775808".to_u64.should eq(9223372036854775808_u64) } it { expect_raises(ArgumentError, "Invalid base 1") { "12ab".to_i(1) } } it { expect_raises(ArgumentError, "Invalid base 37") { "12ab".to_i(37) } } it { expect_raises(ArgumentError, "Invalid Int32") { "1Y2P0IJ32E8E7".to_i(36) } } it { "1Y2P0IJ32E8E7".to_i64(36).should eq(9223372036854775807) } end # more specs are available in `spec/manual/string_to_f_supplemental_spec.cr` it "does to_f" do expect_raises(ArgumentError) { "".to_f } "".to_f?.should be_nil expect_raises(ArgumentError) { " ".to_f } " ".to_f?.should be_nil "0".to_f.should eq(0_f64) "0.0".to_f.should eq(0_f64) "+0.0".to_f.should eq(0_f64) "-0.0".to_f.should eq(0_f64) "1234.56".to_f.should eq(1234.56_f64) "1234.56".to_f?.should eq(1234.56_f64) "+1234.56".to_f?.should eq(1234.56_f64) "-1234.56".to_f?.should eq(-1234.56_f64) expect_raises(ArgumentError) { "foo".to_f } "foo".to_f?.should be_nil " 1234.56 ".to_f.should eq(1234.56_f64) " 1234.56 ".to_f?.should eq(1234.56_f64) expect_raises(ArgumentError) { " 1234.56 ".to_f(whitespace: false) } " 1234.56 ".to_f?(whitespace: false).should be_nil expect_raises(ArgumentError) { " 1234.56foo".to_f } " 1234.56foo".to_f?.should be_nil "\u{A0}\u{2028}\u{2029}1234.56\u{A0}\u{2028}\u{2029}".to_f.should eq(1234.56_f64) "123.45 x".to_f64(strict: false).should eq(123.45_f64) expect_raises(ArgumentError) { "x1.2".to_f64 } "x1.2".to_f64?.should be_nil expect_raises(ArgumentError) { "x1.2".to_f64(strict: false) } "x1.2".to_f64?(strict: false).should be_nil "1#{Float64::MAX}".to_f?.should be_nil "-1#{Float64::MAX}".to_f?.should be_nil " NaN".to_f?.try(&.nan?).should be_true "NaN".to_f?.try(&.nan?).should be_true "-NaN".to_f?.try(&.nan?).should be_true "nan".to_f?(whitespace: false).try(&.nan?).should be_true " nan".to_f?(whitespace: false).should be_nil "nan ".to_f?(whitespace: false).should be_nil expect_raises(ArgumentError) { "".to_f(whitespace: false) } "nani".to_f?(strict: true).should be_nil " INF".to_f?.should eq Float64::INFINITY "INF".to_f?.should eq Float64::INFINITY "-INF".to_f?.should eq -Float64::INFINITY " +INF".to_f?.should eq Float64::INFINITY "inf".to_f?(whitespace: false).should eq Float64::INFINITY "info".to_f?(strict: true).should be_nil end it "does to_f32" do expect_raises(ArgumentError) { "".to_f32 } "".to_f32?.should be_nil expect_raises(ArgumentError) { " ".to_f32 } " ".to_f32?.should be_nil "0".to_f32.should eq(0_f32) "0.0".to_f32.should eq(0_f32) "+0.0".to_f32.should eq(0_f32) "-0.0".to_f32.should eq(0_f32) "1234.56".to_f32.should eq(1234.56_f32) "1234.56".to_f32?.should eq(1234.56_f32) "+1234.56".to_f32?.should eq(1234.56_f32) "-1234.56".to_f32?.should eq(-1234.56_f32) expect_raises(ArgumentError) { "foo".to_f32 } "foo".to_f32?.should be_nil " 1234.56 ".to_f32.should eq(1234.56_f32) " 1234.56 ".to_f32?.should eq(1234.56_f32) expect_raises(ArgumentError) { " 1234.56 ".to_f32(whitespace: false) } " 1234.56 ".to_f32?(whitespace: false).should be_nil expect_raises(ArgumentError) { " 1234.56foo".to_f32 } " 1234.56foo".to_f32?.should be_nil "\u{A0}\u{2028}\u{2029}1234.56\u{A0}\u{2028}\u{2029}".to_f32.should eq(1234.56_f32) "123.45 x".to_f32(strict: false).should eq(123.45_f32) expect_raises(ArgumentError) { "x1.2".to_f32 } "x1.2".to_f32?.should be_nil expect_raises(ArgumentError) { "x1.2".to_f32(strict: false) } "x1.2".to_f32?(strict: false).should be_nil "1#{Float32::MAX}".to_f32?.should be_nil "-1#{Float32::MAX}".to_f32?.should be_nil " NaN".to_f32?.try(&.nan?).should be_true "NaN".to_f32?.try(&.nan?).should be_true "-NaN".to_f32?.try(&.nan?).should be_true "nan".to_f32?(whitespace: false).try(&.nan?).should be_true " nan".to_f32?(whitespace: false).should be_nil "nan ".to_f32?(whitespace: false).should be_nil "nani".to_f32?(strict: true).should be_nil " INF".to_f32?.should eq Float32::INFINITY "INF".to_f32?.should eq Float32::INFINITY "-INF".to_f32?.should eq -Float32::INFINITY " +INF".to_f32?.should eq Float32::INFINITY "inf".to_f32?(whitespace: false).should eq Float32::INFINITY "info".to_f32?(strict: true).should be_nil end it "does to_f64" do expect_raises(ArgumentError) { "".to_f64 } "".to_f64?.should be_nil expect_raises(ArgumentError) { " ".to_f64 } " ".to_f64?.should be_nil "0".to_f64.should eq(0_f64) "0.0".to_f64.should eq(0_f64) "+0.0".to_f64.should eq(0_f64) "-0.0".to_f64.should eq(0_f64) "1234.56".to_f64.should eq(1234.56_f64) "1234.56".to_f64?.should eq(1234.56_f64) "+1234.56".to_f?.should eq(1234.56_f64) "-1234.56".to_f?.should eq(-1234.56_f64) expect_raises(ArgumentError) { "foo".to_f64 } "foo".to_f64?.should be_nil " 1234.56 ".to_f64.should eq(1234.56_f64) " 1234.56 ".to_f64?.should eq(1234.56_f64) expect_raises(ArgumentError) { " 1234.56 ".to_f64(whitespace: false) } " 1234.56 ".to_f64?(whitespace: false).should be_nil expect_raises(ArgumentError) { " 1234.56foo".to_f64 } " 1234.56foo".to_f64?.should be_nil "\u{A0}\u{2028}\u{2029}1234.56\u{A0}\u{2028}\u{2029}".to_f64.should eq(1234.56_f64) "123.45 x".to_f64(strict: false).should eq(123.45_f64) expect_raises(ArgumentError) { "x1.2".to_f64 } "x1.2".to_f64?.should be_nil expect_raises(ArgumentError) { "x1.2".to_f64(strict: false) } "x1.2".to_f64?(strict: false).should be_nil "1#{Float64::MAX}".to_f64?.should be_nil "-1#{Float64::MAX}".to_f64?.should be_nil " NaN".to_f64?.try(&.nan?).should be_true "NaN".to_f64?.try(&.nan?).should be_true "-NaN".to_f64?.try(&.nan?).should be_true "nan".to_f64?(whitespace: false).try(&.nan?).should be_true " nan".to_f64?(whitespace: false).should be_nil "nan ".to_f64?(whitespace: false).should be_nil "nani".to_f64?(strict: true).should be_nil " INF".to_f64?.should eq Float64::INFINITY "INF".to_f64?.should eq Float64::INFINITY "-INF".to_f64?.should eq -Float64::INFINITY " +INF".to_f64?.should eq Float64::INFINITY "inf".to_f64?(whitespace: false).should eq Float64::INFINITY "info".to_f64?(strict: true).should be_nil end it "compares strings: different size" do "foo".should_not eq("fo") end it "compares strings: same object" do f = "foo" f.should eq(f) end it "compares strings: same size, same string" do "foo".should eq("fo" + "o") end it "compares strings: same size, different string" do "foo".should_not eq("bar") end it "interpolates string" do foo = "" bar = 123 "foo #{bar}".should eq("foo 123") "foo #{bar}".should eq("foo 123") "#{foo} bar".should eq(" bar") end it "multiplies" do str = "foo" (str * 0).should eq("") (str * 3).should eq("foofoofoo") end it "multiplies with size one" do str = "f" (str * 0).should eq("") (str * 10).should eq("ffffffffff") end it "multiplies with negative size" do expect_raises(ArgumentError, "Negative argument") do "f" * -1 end end describe "#downcase" do it { assert_prints "HELLO!".downcase, "hello!" } it { assert_prints "HELLO MAN!".downcase, "hello man!" } it { assert_prints "ÁÉÍÓÚĀ".downcase, "áéíóúā" } it { assert_prints "AEIİOU".downcase(Unicode::CaseOptions::Turkic), "aeıiou" } it { assert_prints "ÁEÍOÚ".downcase(Unicode::CaseOptions::ASCII), "ÁeÍoÚ" } it { assert_prints "İ".downcase, "i̇" } it { assert_prints "Baffle".downcase(Unicode::CaseOptions::Fold), "baffle" } it { assert_prints "ff".downcase(Unicode::CaseOptions::Fold), "ff" } it { assert_prints "tschüß".downcase(Unicode::CaseOptions::Fold), "tschüss" } it { assert_prints "ΣίσυφοςfiÆ".downcase(Unicode::CaseOptions::Fold), "σίσυφοσfiæ" } it { assert_prints "ꭰ".downcase(Unicode::CaseOptions::Fold), "Ꭰ" } it { assert_prints "Ꭰ".downcase(Unicode::CaseOptions::Fold), "Ꭰ" } it "does not touch invalid code units in an otherwise ascii string" do "\xB5!\xE0\xC1\xB5?".downcase.should eq("\xB5!\xE0\xC1\xB5?") end end describe "#upcase" do it { "hello!".upcase.should eq("HELLO!") } it { "hello man!".upcase.should eq("HELLO MAN!") } it { "áéíóúā".upcase.should eq("ÁÉÍÓÚĀ") } it { "aeıiou".upcase(Unicode::CaseOptions::Turkic).should eq("AEIİOU") } it { "áeíoú".upcase(Unicode::CaseOptions::ASCII).should eq("áEíOú") } it { "aeiou".upcase(Unicode::CaseOptions::Turkic).should eq("AEİOU") } it { "baffle".upcase.should eq("BAFFLE") } it { "ff".upcase.should eq("FF") } it { "ňž".upcase.should eq("ŇŽ") } # #7922 it "does not touch invalid code units in an otherwise ascii string" do "\xB5!\xE0\xC1\xB5?".upcase.should eq("\xB5!\xE0\xC1\xB5?") end describe "with IO" do it { String.build { |io| "hello!".upcase io }.should eq "HELLO!" } it { String.build { |io| "hello man!".upcase io }.should eq "HELLO MAN!" } it { String.build { |io| "áéíóúā".upcase io }.should eq "ÁÉÍÓÚĀ" } it { String.build { |io| "aeıiou".upcase io, Unicode::CaseOptions::Turkic }.should eq "AEIİOU" } it { String.build { |io| "áeíoú".upcase io, Unicode::CaseOptions::ASCII }.should eq "áEíOú" } it { String.build { |io| "aeiou".upcase io, Unicode::CaseOptions::Turkic }.should eq "AEİOU" } it { String.build { |io| "baffle".upcase io }.should eq "BAFFLE" } it { String.build { |io| "ff".upcase io }.should eq "FF" } it { String.build { |io| "ňž".upcase io }.should eq "ŇŽ" } end end describe "#capitalize" do it { assert_prints "HELLO!".capitalize, "Hello!" } it { assert_prints "HELLO MAN!".capitalize, "Hello man!" } it { assert_prints "".capitalize, "" } it { assert_prints "iO".capitalize(Unicode::CaseOptions::Turkic), "İo" } it "handles multi-character mappings correctly (#13533)" do assert_prints "fflİ".capitalize, "Ffli̇" end it "does not touch invalid code units in an otherwise ascii string" do "\xB5!\xE0\xC1\xB5?".capitalize.should eq("\xB5!\xE0\xC1\xB5?") String.build { |io| "\xB5!\xE0\xC1\xB5?".capitalize(io) }.should eq("\xB5!\xE0\xC1\xB5?".scrub) end end describe "#titleize" do it { assert_prints "hEllO tAb\tworld".titleize, "Hello Tab\tWorld" } it { assert_prints " spaces before".titleize, " Spaces Before" } it { assert_prints "testa-se muito".titleize, "Testa-se Muito" } it { assert_prints "hÉllÕ tAb\tworld".titleize, "Héllõ Tab\tWorld" } it { assert_prints " spáçes before".titleize, " Spáçes Before" } it { assert_prints "testá-se múitô".titleize, "Testá-se Múitô" } it { assert_prints "iO iO".titleize(Unicode::CaseOptions::Turkic), "İo İo" } it { assert_prints "foo_Bar".titleize, "Foo_bar" } it { assert_prints "foo_bar".titleize, "Foo_bar" } it { assert_prints "testá_se múitô".titleize(underscore_to_space: true), "Testá Se Múitô" } it { assert_prints "foo_bar".titleize(underscore_to_space: true), "Foo Bar" } it "handles multi-character mappings correctly (#13533)" do assert_prints "fflİ İffl dz DZ".titleize, "Ffli̇ İffl Dz Dz" end it "does not touch invalid code units in an otherwise ascii string" do "\xB5!\xE0\xC1\xB5?".titleize.should eq("\xB5!\xE0\xC1\xB5?") "a\xA0b".titleize.should eq("A\xA0b") String.build { |io| "\xB5!\xE0\xC1\xB5?".titleize(io) }.should eq("\xB5!\xE0\xC1\xB5?".scrub) String.build { |io| "a\xA0b".titleize(io) }.should eq("A\xA0b".scrub) end describe "with IO" do it { String.build { |io| "foo_Bar".titleize io }.should eq "Foo_bar" } it { String.build { |io| "foo_bar".titleize io }.should eq "Foo_bar" } it { String.build { |io| "foo_bar".titleize(io, underscore_to_space: true) }.should eq "Foo Bar" } end end describe "chomp" do it { "hello\n".chomp.should eq("hello") } it { "hello\r".chomp.should eq("hello") } it { "hello\r\n".chomp.should eq("hello") } it { "hello".chomp.should eq("hello") } it { "hello".chomp.should eq("hello") } it { "かたな\n".chomp.should eq("かたな") } it { "かたな\r".chomp.should eq("かたな") } it { "かたな\r\n".chomp.should eq("かたな") } it { "hello\n\n".chomp.should eq("hello\n") } it { "hello\r\n\n".chomp.should eq("hello\r\n") } it { "hello\r\n".chomp('\n').should eq("hello") } it { "hello".chomp('a').should eq("hello") } it { "hello".chomp('o').should eq("hell") } it { "かたな".chomp('な').should eq("かた") } it { "hello".chomp("good").should eq("hello") } it { "hello".chomp("llo").should eq("he") } it { "かたな".chomp("たな").should eq("か") } it { "hello\n\n\n\n".chomp("").should eq("hello\n\n\n\n") } it { "hello\r\n".chomp("\n").should eq("hello") } it "pre-computes string size if possible" do {"!hello!", "\u{1f602}hello\u{1f602}", "\xFEhello\xFF"}.each do |str| {"", "\n", "\r", "\r\n"}.each do |newline| x = str + newline x.size_known?.should be_true y = x.chomp y.@length.should eq(7) end end end it "does not pre-compute string size if not possible" do x = String.build &.<< "abc\n" x.size_known?.should be_false y = x.chomp y.size_known?.should be_false end end describe "lchop" do it { "".lchop.should eq("") } it { "h".lchop.should eq("") } it { "hello".lchop.should eq("ello") } it { "かたな".lchop.should eq("たな") } it { "hello".lchop('g').should eq("hello") } it { "hello".lchop('h').should eq("ello") } it { "かたな".lchop('か').should eq("たな") } it { "".lchop("").should eq("") } it { "hello".lchop("good").should eq("hello") } it { "hello".lchop("hel").should eq("lo") } it { "かたな".lchop("かた").should eq("な") } it { "\n\n\n\nhello".lchop("").should eq("\n\n\n\nhello") } end describe "lchop?" do it { "".lchop?.should be_nil } it { "h".lchop?.should eq("") } it { "hello".lchop?.should eq("ello") } it { "かたな".lchop?.should eq("たな") } it { "hello".lchop?('g').should be_nil } it { "hello".lchop?('h').should eq("ello") } it { "かたな".lchop?('か').should eq("たな") } it { "".lchop?("").should eq("") } it { "hello".lchop?("good").should be_nil } it { "hello".lchop?("hel").should eq("lo") } it { "かたな".lchop?("かた").should eq("な") } it { "\n\n\n\nhello".lchop("").should eq("\n\n\n\nhello") } end describe "rchop" do it { "".rchop.should eq("") } it { "foo".rchop.should eq("fo") } it { "foo\n".rchop.should eq("foo") } it { "foo\r".rchop.should eq("foo") } it { "foo\r\n".rchop.should eq("foo\r") } it { "\r\n".rchop.should eq("\r") } it { "かたな".rchop.should eq("かた") } it { "かたな\n".rchop.should eq("かたな") } it { "かたな\r\n".rchop.should eq("かたな\r") } it { "foo".rchop('o').should eq("fo") } it { "foo".rchop('x').should eq("foo") } it { "".rchop("").should eq("") } it { "foobar".rchop("bar").should eq("foo") } it { "foobar".rchop("baz").should eq("foobar") } end describe "rchop?" do it { "".rchop?.should be_nil } it { "\n".rchop?.should eq("") } it { "foo".rchop?.should eq("fo") } it { "foo\n".rchop?.should eq("foo") } it { "foo\r".rchop?.should eq("foo") } it { "foo\r\n".rchop?.should eq("foo\r") } it { "\r\n".rchop?.should eq("\r") } it { "かたな".rchop?.should eq("かた") } it { "かたな\n".rchop?.should eq("かたな") } it { "かたな\r\n".rchop?.should eq("かたな\r") } it { "foo".rchop?('o').should eq("fo") } it { "foo".rchop?('x').should be_nil } it { "".rchop?("").should eq("") } it { "foobar".rchop?("bar").should eq("foo") } it { "foobar".rchop?("baz").should be_nil } end describe "strip" do it { " hello \n\t\f\v\r".strip.should eq("hello") } it { "hello".strip.should eq("hello") } it { "かたな \n\f\v".strip.should eq("かたな") } it { " \n\t かたな \n\f\v".strip.should eq("かたな") } it { " \n\t かたな".strip.should eq("かたな") } it { "かたな".strip.should eq("かたな") } it { "".strip.should eq("") } it { "\n".strip.should eq("") } it { "\n\t ".strip.should eq("") } it { "\u00A0".strip.should eq("") } it(tags: %w[slow]) { (" " * 167772160).strip.should eq("") } it { "".strip("xyz").should eq("") } it { "foobar".strip("").should eq("foobar") } it { "rrfoobarr".strip("r").should eq("fooba") } it { "rfoobar".strip("x").should eq("rfoobar") } it { "rrrfooba".strip("r").should eq("fooba") } it { "foobarrr".strip("r").should eq("fooba") } it { "rabfooabr".strip("bar").should eq("foo") } it { "rabfooabr".strip("xyz").should eq("rabfooabr") } it { "fooabr".strip("bar").should eq("foo") } it { "rabfoo".strip("bar").should eq("foo") } it { "rababr".strip("bar").should eq("") } it { "aaabcdaaa".strip('a').should eq("bcd") } it { "bcdaaa".strip('a').should eq("bcd") } it { "aaabcd".strip('a').should eq("bcd") } it { "ababcdaba".strip(&.in?('a', 'b')).should eq("cd") } end describe "rstrip" do it { "".rstrip.should eq("") } it { " hello ".rstrip.should eq(" hello") } it { "hello".rstrip.should eq("hello") } it { " かたな \n\f\v".rstrip.should eq(" かたな") } it { "かたな".rstrip.should eq("かたな") } it { "".rstrip("xyz").should eq("") } it { "foobar".rstrip("").should eq("foobar") } it { "foobarrrr".rstrip("r").should eq("fooba") } it { "foobars".rstrip("r").should eq("foobars") } it { "foobar".rstrip("rab").should eq("foo") } it { "foobar".rstrip("foo").should eq("foobar") } it { "bararbr".rstrip("bar").should eq("") } it { "foobarrrr".rstrip('r').should eq("fooba") } it { "foobar".rstrip('x').should eq("foobar") } it { "foobar".rstrip(&.in?('a', 'r')).should eq("foob") } it "does not touch invalid code units in an otherwise ascii string" do " \xA0 ".rstrip.should eq(" \xA0") end end describe "lstrip" do it { " hello ".lstrip.should eq("hello ") } it { "hello".lstrip.should eq("hello") } it { " \n\v かたな ".lstrip.should eq("かたな ") } it { " かたな".lstrip.should eq("かたな") } it { "".lstrip("xyz").should eq("") } it { "barfoo".lstrip("").should eq("barfoo") } it { "bbbarfoo".lstrip("b").should eq("arfoo") } it { "sbarfoo".lstrip("r").should eq("sbarfoo") } it { "barfoo".lstrip("rab").should eq("foo") } it { "barfoo".lstrip("foo").should eq("barfoo") } it { "b".lstrip("bar").should eq("") } it { "bbbbarfoo".lstrip('b').should eq("arfoo") } it { "barfoo".lstrip('x').should eq("barfoo") } it { "barfoo".lstrip(&.in?('a', 'b')).should eq("rfoo") } it "does not touch invalid code units in an otherwise ascii string" do " \xA0 ".lstrip.should eq("\xA0 ") end end describe "empty?" do it { "a".empty?.should be_false } it { "".empty?.should be_true } end describe "blank?" do it { " \t\n".blank?.should be_true } it { "\u{1680}\u{2029}".blank?.should be_true } it { "hello".blank?.should be_false } end describe "presence" do it { " \t\n".presence.should be_nil } it { "\u{1680}\u{2029}".presence.should be_nil } it { "hello".presence.should eq("hello") } end describe "#index" do describe "by char" do it { "foo".index('o').should eq(1) } it { "foo".index('g').should be_nil } it { "bar".index('r').should eq(2) } it { "日本語".index('本').should eq(1) } it { "bar".index('あ').should be_nil } it { "あいう_えお".index('_').should eq(3) } it { "xyz\xFFxyz".index('\u{FFFD}').should eq(3) } it { "日\xFF語".index('\u{FFFD}').should eq(1) } describe "with offset" do it { "foobarbaz".index('a', 5).should eq(7) } it { "foobarbaz".index('a', -4).should eq(7) } it { "foo".index('g', 1).should be_nil } it { "foo".index('g', -20).should be_nil } it { "日本語日本語".index('本', 2).should eq(4) } it { "xyz\xFFxyz".index('\u{FFFD}', 2).should eq(3) } it { "xyz\xFFxyz".index('\u{FFFD}', 4).should be_nil } it { "日本\xFF語".index('\u{FFFD}', 2).should eq(2) } it { "日本\xFF語".index('\u{FFFD}', 3).should be_nil } # Check offset type it { "foobarbaz".index('a', 5_i64).should eq(7) } it { "foobarbaz".index('a', 5_i64).should be_a(Int32) } it { "日本語日本語".index('本', 2_i64).should eq(4) } it { "日本語日本語".index('本', 2_i64).should be_a(Int32) } end end describe "by string" do it { "foo bar".index("o b").should eq(2) } it { "foo".index("fg").should be_nil } it { "foo".index("").should eq(0) } it { "foo".index("foo").should eq(0) } it { "日本語日本語".index("本語").should eq(1) } it { "\xFF\xFFcrystal".index("crystal").should eq(2) } it { "\xFD\x9A\xAD\x50NG".index("PNG").should eq(3) } it { "🧲$".index("✅").should be_nil } # #11745 describe "with offset" do it { "foobarbaz".index("ba", 4).should eq(6) } it { "foobarbaz".index("ba", -5).should eq(6) } it { "foo".index("ba", 1).should be_nil } it { "foo".index("ba", -20).should be_nil } it { "foo".index("", 3).should eq(3) } it { "foo".index("", 4).should be_nil } it { "日本語日本語".index("本語", 2).should eq(4) } it { "\xFD\x9A\xAD\x50NG".index("PNG", 2).should eq(3) } it { "\xFD\x9A\xAD\x50NG".index("PNG", 4).should be_nil } # Check offset type it { "foobarbaz".index("a", 5_i64).should eq(7) } it { "foobarbaz".index("a", 5_i64).should be_a(Int32) } it { "日本語日本語".index("本", 2_i64).should eq(4) } it { "日本語日本語".index("本", 2_i64).should be_a(Int32) } it { "日本語日本語".index("", 2_i64).should eq 2 } it { "日本語日本語".index("", 2_i64).should be_a(Int64) } end end describe "by regex" do it { "string 12345".index(/\d+/).should eq(7) } it { "12345".index(/\d/).should eq(0) } it { "Hello\xFF".index(/l/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it { "Hello, world!".index(/\d/).should be_nil } it { "abcdef".index(/[def]/).should eq(3) } it { "日本語日本語".index(/本語/).should eq(1) } describe "with offset" do it { "abcDef".index(/[A-Z]/).should eq(3) } it { "foobarbaz".index(/ba/, -5).should eq(6) } it { "Hello\xFF".index(/l/, 3, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it { "Foo".index(/[A-Z]/, 1).should be_nil } it { "foo".index(/o/, 2).should eq(2) } it { "foo".index(//, 3).should eq(3) } it { "foo".index(//, 4).should be_nil } it { "日本語日本語".index(/本語/, 2).should eq(4) } end end end describe "#index!" do describe "by char" do it { "foo".index!('o').should eq(1) } it do expect_raises(Enumerable::NotFoundError) do "foo".index!('g') end end describe "with offset" do it { "foobarbaz".index!('a', 5).should eq(7) } it { "foobarbaz".index!('a', -4).should eq(7) } it do expect_raises(Enumerable::NotFoundError) do "foo".index!('f', 1) end end it do expect_raises(Enumerable::NotFoundError) do "foo".index!('g', -20) end end end end describe "by string" do it { "foo bar".index!("o b").should eq(2) } it { "foo".index!("").should eq(0) } it { "foo".index!("foo").should eq(0) } it do expect_raises(Enumerable::NotFoundError) do "foo".index!("fg") end end describe "with offset" do it { "foobarbaz".index!("ba", 4).should eq(6) } it { "foobarbaz".index!("ba", -5).should eq(6) } it do expect_raises(Enumerable::NotFoundError) do "foo".index!("ba", 1) end end it do expect_raises(Enumerable::NotFoundError) do "foo".index!("ba", -20) end end end end describe "by regex" do it { "string 12345".index!(/\d+/).should eq(7) } it { "12345".index!(/\d/).should eq(0) } it { "Hello\xFF".index!(/l/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it do expect_raises(Enumerable::NotFoundError) do "Hello, world!".index!(/\d/) end end describe "with offset" do it { "abcDef".index!(/[A-Z]/).should eq(3) } it { "foobarbaz".index!(/ba/, -5).should eq(6) } it { "Hello\xFF".index!(/l/, 3, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it do expect_raises(Enumerable::NotFoundError) do "Foo".index!(/[A-Z]/, 1) end end end end end describe "#rindex" do describe "by char" do it { "bbbb".rindex('b').should eq(3) } it { "foobar".rindex('a').should eq(4) } it { "foobar".rindex('g').should be_nil } it { "日本語日本語".rindex('本').should eq(4) } it { "あいう_えお".rindex('_').should eq(3) } it { "xyz\xFFxyz".rindex('\u{FFFD}').should eq(3) } it { "日\xFF語".rindex('\u{FFFD}').should eq(1) } describe "with offset" do it { "bbbb".rindex('b', 2).should eq(2) } it { "abbbb".rindex('b', 0).should be_nil } it { "abbbb".rindex('b', 1).should eq(1) } it { "abbbb".rindex('a', 0).should eq(0) } it { "bbbb".rindex('b', -2).should eq(2) } it { "bbbb".rindex('b', -5).should be_nil } it { "bbbb".rindex('b', -4).should eq(0) } it { "faobar".rindex('a', 3).should eq(1) } it { "faobarbaz".rindex('a', -3).should eq(4) } it { "日本語日本語".rindex('本', 3).should eq(1) } it { "xyz\xFFxyz".rindex('\u{FFFD}', 4).should eq(3) } it { "xyz\xFFxyz".rindex('\u{FFFD}', 2).should be_nil } it { "日本\xFF語".rindex('\u{FFFD}', 2).should eq(2) } it { "日本\xFF語".rindex('\u{FFFD}', 1).should be_nil } # Check offset type it { "bbbb".rindex('b', 2_i64).should eq(2) } it { "bbbb".rindex('b', 2_i64).should be_a(Int64) } it { "日本語日本語".rindex('本', 3_i64).should eq(1) } it { "日本語日本語".rindex('本', 3_i64).should be_a(Int64) } end end describe "by string" do it { "bbbb".rindex("b").should eq(3) } it { "foo baro baz".rindex("o b").should eq(7) } it { "foo baro baz".rindex("fg").should be_nil } it { "日本語日本語".rindex("日本").should eq(3) } describe "with offset" do it { "bbbb".rindex("b", 2).should eq(2) } it { "abbbb".rindex("b", 0).should be_nil } it { "abbbb".rindex("b", 1).should eq(1) } it { "abbbb".rindex("a", 0).should eq(0) } it { "bbbb".rindex("b", -2).should eq(2) } it { "bbbb".rindex("b", -5).should be_nil } it { "bbbb".rindex("b", -4).should eq(0) } it { "foo baro baz".rindex("o b", 6).should eq(2) } it { "foo".rindex("", 3).should eq(3) } it { "foo".rindex("", 4).should eq(3) } it { "日本語日本語".rindex("日本", 2).should eq(0) } # Check offset type it { "bbbb".rindex("b", 2_i64).should eq(2) } it { "bbbb".rindex("b", 2_i64).should be_a(Int32) } it { "日本語日本語".rindex("本", 3_i64).should eq(1) } it { "日本語日本語".rindex("本", 3_i64).should be_a(Int32) } it { "日本語日本語".rindex("", 3_i64).should eq(3) } it { "日本語日本語".rindex("", 3_i64).should be_a(Int32) } end end describe "by regex" do it { "bbbb".rindex(/b/).should eq(3) } it { "\xFFbbb".rindex(/b/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it { "a43b53".rindex(/\d+/).should eq(4) } it { "bbbb".rindex(/\d/).should be_nil } describe "which matches empty string" do it { "foo".rindex(/o*/).should eq(3) } it { "foo".rindex(//).should eq(3) } it { "foo".rindex(/\b/).should eq(3) } end describe "with offset" do it { "bbbb".rindex(/b/, 2).should eq(2) } it { "\xFFbbb".rindex(/b/, 2, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it { "abbbb".rindex(/b/, 0).should be_nil } it { "abbbb".rindex(/a/, 0).should eq(0) } it { "bbbb".rindex(/b/, -2).should eq(2) } it { "bbbb".rindex(/b/, -5).should be_nil } it { "bbbb".rindex(/b/, -4).should eq(0) } it { "日本語日本語".rindex(/日本/, 2).should eq(0) } end end end describe "#rindex!" do describe "by char" do it { "bbbb".rindex!('b').should eq(3) } it { "foobar".rindex!('a').should eq(4) } it do expect_raises(Enumerable::NotFoundError) do "foobar".rindex!('g') end end describe "with offset" do it { "bbbb".rindex!('b', 2).should eq(2) } it do expect_raises(Enumerable::NotFoundError) do "abbbb".rindex!('b', 0) end end end end describe "by string" do it { "bbbb".rindex!("b").should eq(3) } it { "foo baro baz".rindex!("o b").should eq(7) } it do expect_raises(Enumerable::NotFoundError) do "foo baro baz".rindex!("fg") end end describe "with offset" do it { "bbbb".rindex!("b", 2).should eq(2) } it do expect_raises(Enumerable::NotFoundError) do "abbbb".rindex!("b", 0) end end it do expect_raises(Enumerable::NotFoundError) do "bbbb".rindex!("b", -5) end end end end describe "by regex" do it { "bbbb".rindex!(/b/).should eq(3) } it { "\xFFbbb".rindex!(/b/, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(3) } it { "a43b53".rindex!(/\d+/).should eq(4) } it do expect_raises(Enumerable::NotFoundError) do "bbbb".rindex!(/\d/) end end describe "with offset" do it { "bbbb".rindex!(/b/, 2).should eq(2) } it { "\xFFbbb".rindex!(/b/, 2, options: Regex::MatchOptions::NO_UTF_CHECK).should eq(2) } it do expect_raises(Enumerable::NotFoundError) do "abbbb".rindex!(/b/, 0) end end end end end describe "partition" do describe "by char" do it { "hello".partition('h').should eq({"", "h", "ello"}) } it { "hello".partition('o').should eq({"hell", "o", ""}) } it { "hello".partition('l').should eq({"he", "l", "lo"}) } it { "hello".partition('x').should eq({"hello", "", ""}) } end describe "by string" do it { "hello".partition("h").should eq({"", "h", "ello"}) } it { "hello".partition("o").should eq({"hell", "o", ""}) } it { "hello".partition("l").should eq({"he", "l", "lo"}) } it { "hello".partition("ll").should eq({"he", "ll", "o"}) } it { "hello".partition("x").should eq({"hello", "", ""}) } end describe "by regex" do it { "hello".partition(/h/).should eq({"", "h", "ello"}) } it { "hello".partition(/o/).should eq({"hell", "o", ""}) } it { "hello".partition(/l/).should eq({"he", "l", "lo"}) } it { "hello".partition(/ll/).should eq({"he", "ll", "o"}) } it { "hello".partition(/.l/).should eq({"h", "el", "lo"}) } it { "hello".partition(/.h/).should eq({"hello", "", ""}) } it { "hello".partition(/h./).should eq({"", "he", "llo"}) } it { "hello".partition(/o./).should eq({"hello", "", ""}) } it { "hello".partition(/.o/).should eq({"hel", "lo", ""}) } it { "hello".partition(/x/).should eq({"hello", "", ""}) } end end describe "rpartition" do describe "by char" do it { "hello".rpartition('l').should eq({"hel", "l", "o"}) } it { "hello".rpartition('o').should eq({"hell", "o", ""}) } it { "hello".rpartition('h').should eq({"", "h", "ello"}) } end describe "by string" do it { "hello".rpartition("l").should eq({"hel", "l", "o"}) } it { "hello".rpartition("x").should eq({"", "", "hello"}) } it { "hello".rpartition("o").should eq({"hell", "o", ""}) } it { "hello".rpartition("h").should eq({"", "h", "ello"}) } it { "hello".rpartition("ll").should eq({"he", "ll", "o"}) } it { "hello".rpartition("lo").should eq({"hel", "lo", ""}) } it { "hello".rpartition("he").should eq({"", "he", "llo"}) } end describe "by regex" do it { "hello".rpartition(/.l/).should eq({"he", "ll", "o"}) } it { "hello".rpartition(/ll/).should eq({"he", "ll", "o"}) } it { "hello".rpartition(/.o/).should eq({"hel", "lo", ""}) } it { "hello".rpartition(/.e/).should eq({"", "he", "llo"}) } it { "hello".rpartition(/l./).should eq({"hel", "lo", ""}) } end end describe "byte_index" do it { "foo".byte_index('o'.ord).should eq(1) } it { "foo bar booz".byte_index('o'.ord, 3).should eq(9) } it { "foo".byte_index('a'.ord).should be_nil } it { "foo".byte_index('a'.ord).should be_nil } it { "foo".byte_index('o'.ord, 3).should be_nil } it { "Dizzy Miss Lizzy".byte_index('z'.ord).should eq(2) "Dizzy Miss Lizzy".byte_index('z'.ord, 3).should eq(3) "Dizzy Miss Lizzy".byte_index('z'.ord, -4).should eq(13) "Dizzy Miss Lizzy".byte_index('z'.ord, -17).should be_nil } it { "foo".byte_index('o').should eq(1) } it { "foo bar booz".byte_index('o', 3).should eq(9) } it { "foo".byte_index('a').should be_nil } it { "foo".byte_index('a').should be_nil } it { "foo".byte_index('o', 3).should be_nil } it { "Hi, 💣".byte_index('💣').should eq(4) } it { "Dizzy Miss Lizzy".byte_index('z').should eq(2) "Dizzy Miss Lizzy".byte_index('z', 3).should eq(3) "Dizzy Miss Lizzy".byte_index('z', -4).should eq(13) "Dizzy Miss Lizzy".byte_index('z', -17).should be_nil } it "gets byte index of string" do "hello world".byte_index("he").should eq(0) "hello world".byte_index("lo").should eq(3) "hello world".byte_index("world", 7).should be_nil "foo foo".byte_index("oo").should eq(1) "foo foo".byte_index("oo", 2).should eq(5) "こんにちは世界".byte_index("ちは").should eq(9) end it "gets byte index of regex" do str = "0123x" pattern = /x/ str.byte_index(pattern).should eq(4) str.byte_index(pattern, offset: 4).should eq(4) str.byte_index(pattern, offset: 5).should be_nil str.byte_index(pattern, offset: -1).should eq(4) str.byte_index(/y/).should be_nil str = "012abc678" pattern = /[abc]/ str.byte_index(pattern).should eq(3) str.byte_index(pattern, offset: 2).should eq(3) str.byte_index(pattern, offset: 5).should eq(5) str.byte_index(pattern, offset: -4).should eq(5) str.byte_index(pattern, offset: -1).should be_nil str.byte_index(/y/).should be_nil end end describe "includes?" do describe "by char" do it { "foo".includes?('o').should be_true } it { "foo".includes?('g').should be_false } end describe "by string" do it { "foo bar".includes?("o b").should be_true } it { "foo".includes?("fg").should be_false } it { "foo".includes?("").should be_true } end end describe "split" do describe "by whitespace" do it { " foo bar\n\t baz ".split.should eq(["foo", "bar", "baz"]) } it { " foo bar\n\t baz ".split(1).should eq([" foo bar\n\t baz "]) } it { " foo bar\n\t baz ".split(2).should eq(["foo", "bar\n\t baz "]) } it { "日本語 \n\t 日本 \n\n 語".split.should eq(["日本語", "日本", "語"]) } it { " foo\u00A0bar baz".split.should eq(["foo", "bar", "baz"]) } end describe "by char" do it { "".split(',').should eq([""]) } it { "".split(',', remove_empty: true).should eq([] of String) } it { "foo,bar,,baz,".split(',').should eq(["foo", "bar", "", "baz", ""]) } it { "foo,bar,,baz,".split(',', remove_empty: true).should eq(["foo", "bar", "baz"]) } it { "foo,bar,,baz".split(',').should eq(["foo", "bar", "", "baz"]) } it { "foo,bar,,baz".split(',', remove_empty: true).should eq(["foo", "bar", "baz"]) } it { "foo".split(',').should eq(["foo"]) } it { "foo".split(' ').should eq(["foo"]) } it { " foo".split(' ').should eq(["", "", "", "foo"]) } it { "foo ".split(' ').should eq(["foo", "", "", ""]) } it { " foo bar".split(' ').should eq(["", "", "", "foo", "", "bar"]) } it { " foo bar\n\t baz ".split(' ').should eq(["", "", "", "foo", "", "", "bar\n\t", "", "baz", "", "", ""]) } it { " foo bar\n\t baz ".split(' ').should eq(["", "", "", "foo", "", "", "bar\n\t", "", "baz", "", "", ""]) } it { "foo,bar,baz,qux".split(',', 1).should eq(["foo,bar,baz,qux"]) } it { "foo,bar,baz,qux".split(',', 3).should eq(["foo", "bar", "baz,qux"]) } it { "foo,bar,baz,qux".split(',', 30).should eq(["foo", "bar", "baz", "qux"]) } it { "foo bar baz qux".split(' ', 1).should eq(["foo bar baz qux"]) } it { "foo bar baz qux".split(' ', 3).should eq(["foo", "bar", "baz qux"]) } it { "foo bar baz qux".split(' ', 30).should eq(["foo", "bar", "baz", "qux"]) } it { "a,b,".split(',', 3).should eq(["a", "b", ""]) } it { "日本ん語日本ん語".split('ん').should eq(["日本", "語日本", "語"]) } it { "=".split('=').should eq(["", ""]) } it { "a=".split('=').should eq(["a", ""]) } it { "=b".split('=').should eq(["", "b"]) } it { "=".split('=', 2).should eq(["", ""]) } it { "=".split('=', 2, remove_empty: true).should eq([] of String) } end describe "by string" do it { "".split(",").should eq([""]) } it { "".split(":-").should eq([""]) } it { "".split(":-", remove_empty: true).should eq([] of String) } it { "foo:-bar:-:-baz:-".split(":-").should eq(["foo", "bar", "", "baz", ""]) } it { "foo:-bar:-:-baz:-".split(":-", remove_empty: true).should eq(["foo", "bar", "baz"]) } it { "foo:-bar:-:-baz".split(":-").should eq(["foo", "bar", "", "baz"]) } it { "foo".split(":-").should eq(["foo"]) } it { "foo".split("").should eq(["f", "o", "o"]) } it { "日本さん語日本さん語".split("さん").should eq(["日本", "語日本", "語"]) } it { "foo,bar,baz,qux".split(",", 1).should eq(["foo,bar,baz,qux"]) } it { "foo,bar,baz,qux".split(",", 3).should eq(["foo", "bar", "baz,qux"]) } it { "foo,bar,baz,qux".split(",", 30).should eq(["foo", "bar", "baz", "qux"]) } it { "a b c".split(" ", 2).should eq(["a", "b c"]) } it { "=".split("=").should eq(["", ""]) } it { "a=".split("=").should eq(["a", ""]) } it { "=b".split("=").should eq(["", "b"]) } it { "=".split("=", 2).should eq(["", ""]) } it { "=".split("=", 2, remove_empty: true).should eq([] of String) } end describe "by regex" do it { "".split(/\n\t/).should eq([""]) } it { "".split(/\n\t/, remove_empty: true).should eq([] of String) } it { "foo\n\tbar\n\t\n\tbaz".split(/\n\t/).should eq(["foo", "bar", "", "baz"]) } it { "foo\n\tbar\n\t\n\tbaz".split(/\n\t/, remove_empty: true).should eq(["foo", "bar", "baz"]) } it { "foo\n\tbar\n\t\n\tbaz".split(/(?:\n\t)+/).should eq(["foo", "bar", "baz"]) } it { "foo,bar".split(/,/, 1).should eq(["foo,bar"]) } it { "foo,bar,".split(/,/).should eq(["foo", "bar", ""]) } it { "foo,bar,baz,qux".split(/,/, 1).should eq(["foo,bar,baz,qux"]) } it { "foo,bar,baz,qux".split(/,/, 3).should eq(["foo", "bar", "baz,qux"]) } it { "foo,bar,baz,qux".split(/,/, 30).should eq(["foo", "bar", "baz", "qux"]) } it { "a b c".split(Regex.new(" "), 2).should eq(["a", "b c"]) } it { "日本ん語日本ん語".split(/ん/).should eq(["日本", "語日本", "語"]) } it { "九十九十九".split(/(?=十)/).should eq(["九", "十九", "十九"]) } it { "hello world".split(/\b/).should eq(["hello", " ", "world", ""]) } it { "hello world".split(/\w+|(?= )/).should eq(["", " ", ""]) } it { "abc".split(//).should eq(["a", "b", "c"]) } it { "hello".split(/\w+/).should eq(["", ""]) } it { "foo".split(/o/).should eq(["f", "", ""]) } it { "=".split(/\=/).should eq(["", ""]) } it { "a=".split(/\=/).should eq(["a", ""]) } it { "=b".split(/\=/).should eq(["", "b"]) } it { "=".split(/\=/, 2).should eq(["", ""]) } it { "=".split(/\=/, 2, remove_empty: true).should eq([] of String) } it { ",".split(/(?:(x)|(,))/).should eq(["", ",", ""]) } it { "ba".split(/a/, options: :anchored).should eq ["ba"] } it "keeps groups" do s = "split on the word on okay?" s.split(/(on)/).should eq(["split ", "on", " the word ", "on", " okay?"]) s.split(/o(?:(n)|(r))/).should eq(["split ", "n", " the w", "r", "d ", "n", " okay?"]) s.split(/()/, limit: 4, remove_empty: true).should eq(["s", "", "p", "", "l", "", "it on the word on okay?"]) end end end describe "starts_with?" do it { "foobar".starts_with?("foo").should be_true } it { "foobar".starts_with?("").should be_true } it { "foobar".starts_with?("foobarbaz").should be_false } it { "foobar".starts_with?("foox").should be_false } it { "foobar".starts_with?(/foo/).should be_true } it { "foobar".starts_with?(/bar/).should be_false } it { "foobar".starts_with?('f').should be_true } it { "foobar".starts_with?('g').should be_false } it { "よし".starts_with?('よ').should be_true } it { "よし!".starts_with?("よし").should be_true } it "treats first char as replacement char if invalid in an otherwise ascii string" do "\xEEfoo".starts_with?('\u{EE}').should be_false "\xEEfoo".starts_with?(Char::REPLACEMENT).should be_true end end describe "ends_with?" do it { "foobar".ends_with?("bar").should be_true } it { "foobar".ends_with?("").should be_true } it { "foobar".ends_with?("foobarbaz").should be_false } it { "foobar".ends_with?("xbar").should be_false } it { "foobar".ends_with?(/bar/).should be_true } it { "foobar".ends_with?(/foo|baz/).should be_false } it { "foobar".ends_with?('r').should be_true } it { "foobar".ends_with?('x').should be_false } it { "よし".ends_with?('し').should be_true } it { "よし".ends_with?('な').should be_false } it { "あいう_".ends_with?('_').should be_true } it "treats last char as replacement char if invalid in an otherwise ascii string" do "foo\xEE".ends_with?('\u{EE}').should be_false "foo\xEE".ends_with?(Char::REPLACEMENT).should be_true end end describe "=~" do it "matches with group" do "foobar" =~ /(o+)ba(r?)/ $1.should eq("oo") $2.should eq("r") end it "returns nil with string" do ("foo" =~ "foo").should be_nil end it "returns nil with regex and regex" do (/foo/ =~ /foo/).should be_nil end end describe "delete" do it { "foobar".delete { |char| char == 'o' }.should eq("fbar") } it { "hello world".delete("lo").should eq("he wrd") } it { "hello world".delete("lo", "o").should eq("hell wrld") } it { "hello world".delete("hello", "^l").should eq("ll wrld") } it { "hello world".delete("ej-m").should eq("ho word") } it { "hello^world".delete("\\^aeiou").should eq("hllwrld") } it { "hello-world".delete("a\\-eo").should eq("hllwrld") } it { "hello world\\r\\n".delete("\\").should eq("hello worldrn") } it { "hello world\\r\\n".delete("\\A").should eq("hello world\\r\\n") } it { "hello world\\r\\n".delete("X-\\w").should eq("hello orldrn") } it "deletes one char" do "foobar".delete('o').should eq("fbar") "foobar".delete('x').should eq("foobar") end end describe "#reverse" do it "reverses string" do "foobar".reverse.should eq("raboof") end it "reverses utf-8 string" do "こんにちは".reverse.should eq("はちにんこ") end it "reverses taking grapheme clusters into account" do "noël".reverse.should eq("lëon") end pending "converts invalid code units to replacement char" do "!\xB0\xC2?".reverse.chars.should eq("?\uFFFD!".chars) "\xC2\xB0\xB0\xC2?".reverse.chars.should eq("?\uFFFD\xC2\xB0".chars) end end describe "sub" do it "subs char with char" do "foobar".sub('o', 'e').should eq("feobar") end it "subs char with string" do "foobar".sub('o', "ex").should eq("fexobar") end it "subs char with string" do "foobar".sub do |char| char.should eq 'f' "some" end.should eq("someoobar") empty = "" empty.sub { 'f' }.should be(empty) end it "subs with regex and block" do actual = "foo booor booooz".sub(/o+/) do |str| "#{str}#{str.size}" end actual.should eq("foo2 booor booooz") end it "subs with regex and block with group" do actual = "foo booor booooz".sub(/(o+).*?(o+)/) do |str, match| "#{match[1].size}#{match[2].size}" end actual.should eq("f23r booooz") end it "subs with regex and string" do "foo boor booooz".sub(/o+/, "a").should eq("fa boor booooz") end it "subs with regex and string, returns self if no match" do str = "hello" str.sub(/a/, "b").should be(str) end it "subs with regex and string (utf-8)" do "fここ bここr bここここz".sub(/こ+/, "そこ").should eq("fそこ bここr bここここz") end it "subs with empty string" do "foo".sub("", "x").should eq("xfoo") end it "subs with empty regex" do "foo".sub(//, "x").should eq("xfoo") end it "subs null character" do null = "\u{0}" "f\u{0}\u{0}".sub(/#{null}/, "o").should eq("fo\u{0}") end it "subs with string and string" do "foo boor booooz".sub("oo", "a").should eq("fa boor booooz") end it "subs with string and string return self if no match" do str = "hello" str.sub("a", "b").should be(str) end it "subs with string and string (utf-8)" do "fここ bここr bここここz".sub("ここ", "そこ").should eq("fそこ bここr bここここz") end it "subs with string and string (#3258)" do "私は日本人です".sub("日本", "スペイン").should eq("私はスペイン人です") end it "subs with string and block" do result = "foo boo".sub("oo") do |value| value.should eq("oo") "a" end result.should eq("fa boo") end it "subs with char hash" do str = "hello" str.sub({'e' => 'a', 'l' => 'd'}).should eq("hallo") empty = "" empty.sub({'a' => 'b'}).should be(empty) end it "subs with regex and hash" do str = "hello" str.sub(/(he|l|o)/, {"he" => "ha", "l" => "la"}).should eq("hallo") str.sub(/(he|l|o)/, {"l" => "la"}).should be(str) end it "subs with regex and named tuple" do str = "hello" str.sub(/(he|l|o)/, {he: "ha", l: "la"}).should eq("hallo") str.sub(/(he|l|o)/, {l: "la"}).should be(str) end it "subs using $~" do "foo".sub(/(o)/) { "x#{$1}x" }.should eq("fxoxo") end it "subs using with \\" do "foo".sub(/(o)/, "\\").should eq("f\\o") end it "subs using with z\\w" do "foo".sub(/(o)/, "z\\w").should eq("fz\\wo") end it "replaces with numeric back-reference" do "foo".sub(/o/, "x\\0x").should eq("fxoxo") "foo".sub(/(o)/, "x\\1x").should eq("fxoxo") "foo".sub(/(o)/, "\\\\1").should eq("f\\1o") "hello".sub(/[aeiou]/, "(\\0)").should eq("h(e)llo") end it "replaces with incomplete named back-reference (1)" do "foo".sub(/(oo)/, "|\\k|").should eq("f|\\k|") end it "replaces with incomplete named back-reference (2)" do "foo".sub(/(oo)/, "|\\k\\1|").should eq("f|\\koo|") end it "replaces with named back-reference" do "foo".sub(/(?oo)/, "|\\k|").should eq("f|oo|") end it "replaces with multiple named back-reference" do "fooxx".sub(/(?oo)(?x+)/, "|\\k|\\k|").should eq("f|oo|xx|") end it "replaces with \\a" do "foo".sub(/(oo)/, "|\\a|").should eq("f|\\a|") end it "replaces with \\\\\\1" do "foo".sub(/(oo)/, "|\\\\\\1|").should eq("f|\\oo|") end it "ignores if backreferences: false" do "foo".sub(/o/, "x\\0x", backreferences: false).should eq("fx\\0xo") end it "subs at index with char" do "hello".sub(1, 'a').should eq("hallo") end it "subs at index with char, non-ascii" do "あいうえお".sub(2, 'の').should eq("あいのえお") end it "subs at negative index with char" do string = "abc".sub(-1, 'd') string.should eq("abd") string = string.sub(-2, 'n') string.should eq("and") end it "subs at index with string" do "hello".sub(1, "eee").should eq("heeello") end it "subs at negative index with string" do "hello".sub(-1, "ooo").should eq("hellooo") end it "subs at index with string, non-ascii" do "あいうえお".sub(2, "けくこ").should eq("あいけくこえお") end it "raises if index is out of bounds" do expect_raises(IndexError) { "hello".sub(5, 'x') } expect_raises(IndexError) { "hello".sub(6, "") } expect_raises(IndexError) { "hello".sub(-6, 'x') } expect_raises(IndexError) { "hello".sub(-7, "") } expect_raises(IndexError) { "あいうえお".sub(5, 'x') } expect_raises(IndexError) { "あいうえお".sub(6, "") } expect_raises(IndexError) { "あいうえお".sub(-6, 'x') } expect_raises(IndexError) { "あいうえお".sub(-7, "") } end it "subs range with char" do "hello".sub(1..2, 'a').should eq("halo") end it "subs range with char, non-ascii" do "あいうえお".sub(1..2, 'け').should eq("あけえお") end it "subs range with string" do "hello".sub(1..2, "eee").should eq("heeelo") end it "subs range with string, non-ascii" do "あいうえお".sub(1..2, "けくこ").should eq("あけくこえお") end it "subs endless range with char" do "hello".sub(2..nil, 'a').should eq("hea") end it "subs endless range with string" do "hello".sub(2..nil, "ya").should eq("heya") end it "subs beginless range with char" do "hello".sub(nil..2, 'a').should eq("alo") end it "subs beginless range with string" do "hello".sub(nil..2, "ye").should eq("yelo") end it "subs the last char" do str = "hello" str.sub('o', 'a').should eq("hella") str.sub('o', "ad").should eq("hellad") str.sub(4, 'a').should eq("hella") str.sub(4, "ad").should eq("hellad") str.sub(4..4, 'a').should eq("hella") str.sub(4..4, "ad").should eq("hellad") str.sub({'a' => 'b', 'o' => 'a'}).should eq("hella") str.sub({'a' => 'b', 'o' => "ad"}).should eq("hellad") str.sub(/o/, 'a').should eq("hella") str.sub(/o/, "ad").should eq("hellad") str.sub(/o/) { 'a' }.should eq("hella") str.sub(/o/) { "ad" }.should eq("hellad") str.sub(/(o)/, {"o" => 'a'}).should eq("hella") str.sub(/(o)/, {"o" => "ad"}).should eq("hellad") str.sub(/(o)/) { 'a' }.should eq("hella") str.sub(/(o)/) { "ad" }.should eq("hellad") end end describe "#gsub" do it "gsubs char with char" do "foobar".gsub('o', 'e').should eq("feebar") end it "gsubs char with string" do "foobar".gsub('o', "ex").should eq("fexexbar") end it "gsubs char with string (nop)" do s = "foobar" s.gsub('x', "yz").should be(s) end it "gsubs char with char in non-ascii string" do "/ä".gsub('/', '-').should eq("-ä") end it "gsubs char with string depending on the char" do replaced = "foobar".gsub do |char| case char when 'f' "some" when 'o' "thing" when 'a' "ex" else char end end replaced.should eq("somethingthingbexr") end it "gsubs with regex and block" do actual = "foo booor booooz".gsub(/o+/) do |str| "#{str}#{str.size}" end actual.should eq("foo2 booo3r boooo4z") end it "gsubs with regex and block with group" do actual = "foo booor booooz".gsub(/(o+).*?(o+)/) do |str, match| "#{match[1].size}#{match[2].size}" end actual.should eq("f23r b31z") end it "gsubs with regex and string" do "foo boor booooz".gsub(/o+/, "a").should eq("fa bar baz") end it "gsubs with regex and string, returns self if no match" do str = "hello" str.gsub(/a/, "b").should be(str) end it "gsubs with regex and string (utf-8)" do "fここ bここr bここここz".gsub(/こ+/, "そこ").should eq("fそこ bそこr bそこz") end it "gsubs with empty string" do "foo".gsub("", "x").should eq("xfxoxox") end it "gsubs with empty regex" do "foo".gsub(//, "x").should eq("xfxoxox") end it "gsubs null character" do null = "\u{0}" "f\u{0}\u{0}".gsub(/#{null}/, "o").should eq("foo") end it "gsubs with string and string" do "foo boor booooz".gsub("oo", "a").should eq("fa bar baaz") end it "gsubs with string and string return self if no match" do str = "hello" str.gsub("a", "b").should be(str) end it "gsubs with string and string (utf-8)" do "fここ bここr bここここz".gsub("ここ", "そこ").should eq("fそこ bそこr bそこそこz") end it "gsubs with string and block" do i = 0 result = "foo boo".gsub("oo") do |value| value.should eq("oo") i += 1 i == 1 ? "a" : "e" end result.should eq("fa be") end it "gsubs with char hash" do str = "hello" str.gsub({'e' => 'a', 'l' => 'd'}).should eq("haddo") end it "gsubs with char named tuple" do str = "hello" str.gsub({e: 'a', l: 'd'}).should eq("haddo") end it "gsubs with regex and hash" do str = "hello" str.gsub(/(he|l|o)/, {"he" => "ha", "l" => "la"}).should eq("halala") end it "gsubs with regex and named tuple" do str = "hello" str.gsub(/(he|l|o)/, {he: "ha", l: "la"}).should eq("halala") end it "gsubs using $~" do "foo".gsub(/(o)/) { "x#{$1}x" }.should eq("fxoxxox") end it "replaces with numeric back-reference" do "foo".gsub(/o/, "x\\0x").should eq("fxoxxox") "foo".gsub(/(o)/, "x\\1x").should eq("fxoxxox") "foo".gsub(/(ここ)|(oo)/, "x\\1\\2x").should eq("fxoox") end it "replaces with named back-reference" do "foo".gsub(/(?oo)/, "|\\k|").should eq("f|oo|") "foo".gsub(/(?ここ)|(?oo)/, "|\\k|").should eq("f|oo|") end it "replaces with incomplete back-reference (1)" do "foo".gsub(/o/, "\\").should eq("f\\\\") end it "replaces with incomplete back-reference (2)" do "foo".gsub(/o/, "\\\\").should eq("f\\\\") end it "replaces with incomplete back-reference (3)" do "foo".gsub(/o/, "\\k").should eq("f\\k\\k") end it "raises with incomplete back-reference (1)" do expect_raises(ArgumentError) do "foo".gsub(/(?oo)/, "|\\k' for '\\\\k<...") do "foo".gsub(/o/, "\\k<") end end it "replaces with back-reference to missing capture group" do "foo".gsub(/o/, "\\1").should eq("f") expect_raises(IndexError, "Undefined group name reference: \"bar\"") do "foo".gsub(/o/, "\\k").should eq("f") end expect_raises(IndexError, "Undefined group name reference: \"\"") do "foo".gsub(/o/, "\\k<>") end end it "replaces with escaped back-reference" do "foo".gsub(/o/, "\\\\0").should eq("f\\0\\0") "foo".gsub(/oo/, "\\\\k").should eq("f\\k") end it "ignores if backreferences: false" do "foo".gsub(/o/, "x\\0x", backreferences: false).should eq("fx\\0xx\\0x") end it "empty match" do "a b".gsub(/\B/, "-").should eq "a - b" "┬ 7".gsub(/\B/, "-").should eq "-┬- - 7" end it "empty string" do "ab".gsub("", "-").should eq "-a-b-" "┬7".gsub("", "-").should eq "-┬-7-" end end it "scans using $~" do str = String.build do |str| "fooxooo".scan(/(o+)/) { str << $1 } end str.should eq("ooooo") end it "#dump" do assert_prints "a".dump, %("a") assert_prints "\\".dump, %("\\\\") assert_prints "\"".dump, %("\\"") assert_prints "\0".dump, %("\\u0000") assert_prints "\x01".dump, %("\\u0001") assert_prints "\xFF".dump, %("\\xFF") assert_prints "\a".dump, %("\\a") assert_prints "\b".dump, %("\\b") assert_prints "\e".dump, %("\\e") assert_prints "\f".dump, %("\\f") assert_prints "\n".dump, %("\\n") assert_prints "\r".dump, %("\\r") assert_prints "\t".dump, %("\\t") assert_prints "\v".dump, %("\\v") assert_prints "\#{".dump, %("\\\#{") assert_prints "á".dump, %("\\u00E1") assert_prints "\u{81}".dump, %("\\u0081") assert_prints "\u{1F48E}".dump, %("\\u{1F48E}") assert_prints "\uF8FF".dump, %("\\uF8FF") # private use character (Co) assert_prints "\u202A".dump, %("\\u202A") # bidi control character (Cf) assert_prints "\u{110BD}".dump, %("\\u{110BD}") # Format character > U+FFFF (Cf) assert_prints "\u00A0".dump, %("\\u00A0") # white space (Zs) assert_prints "\u200D".dump, %("\\u200D") # format character (Cf) assert_prints " ".dump, %(" ") end it "#dump_unquoted" do assert_prints "a".dump_unquoted, %(a) assert_prints "\\".dump_unquoted, %(\\\\) assert_prints "á".dump_unquoted, %(\\u00E1) assert_prints "\u{81}".dump_unquoted, %(\\u0081) assert_prints "\u{1F48E}".dump_unquoted, %(\\u{1F48E}) end it "#inspect" do assert_prints "a".inspect, %("a") assert_prints "\\".inspect, %("\\\\") assert_prints "\"".inspect, %("\\"") assert_prints "\0".inspect, %("\\u0000") assert_prints "\x01".inspect, %("\\u0001") assert_prints "\xFF".inspect, %("\\xFF") assert_prints "\a".inspect, %("\\a") assert_prints "\b".inspect, %("\\b") assert_prints "\e".inspect, %("\\e") assert_prints "\f".inspect, %("\\f") assert_prints "\n".inspect, %("\\n") assert_prints "\r".inspect, %("\\r") assert_prints "\t".inspect, %("\\t") assert_prints "\v".inspect, %("\\v") assert_prints "\#{".inspect, %("\\\#{") assert_prints "á".inspect, %("á") assert_prints "\u{81}".inspect, %("\\u0081") assert_prints "\u{1F48E}".inspect, %("\u{1F48E}") assert_prints "\uF8FF".inspect, %("\\uF8FF") # private use character (Co) assert_prints "\u202A".inspect, %("\\u202A") # bidi control character (Cf) assert_prints "\u{110BD}".inspect, %("\\u{110BD}") # Format character > U+FFFF (Cf) assert_prints "\u00A0".inspect, %("\\u00A0") # white space (Zs) assert_prints "\u200D".inspect, %("\\u200D") # format character (Cf) assert_prints " ".inspect, %(" ") end it "#inspect_unquoted" do assert_prints "a".inspect_unquoted, %(a) assert_prints "\\".inspect_unquoted, %(\\\\) assert_prints "á".inspect_unquoted, %(á) assert_prints "\u{81}".inspect_unquoted, %(\\u0081) assert_prints "\u{1F48E}".inspect_unquoted, %(\u{1F48E}) end it "does pretty_inspect" do "a".pretty_inspect.should eq(%("a")) "hello\nworld".pretty_inspect.should eq(%("hello\\n" + "world")) "hello\nworld".pretty_inspect(width: 9).should eq(%("hello\\n" +\n"world")) "hello\nworld\n".pretty_inspect(width: 9).should eq(%("hello\\n" +\n"world\\n")) end it "does *" do ("foo" * 10).should eq("foofoofoofoofoofoofoofoofoofoo") end describe "+" do it "does for both ascii" do str = "foo" + "bar" str.@length.should eq(6) # Check that it was pre-computed str.should eq("foobar") end it "does for both unicode" do str = "青い" + "旅路" str.@length.should eq(4) # Check that it was pre-computed str.should eq("青い旅路") end it "does with ascii char" do str = "foo" str2 = str + '/' str2.should eq("foo/") end it "does with unicode char" do str = "fooba" str2 = str + 'る' str2.should eq("foobaる") end it "does when right is empty" do str1 = "foo" str2 = "" (str1 + str2).should be(str1) end it "does when left is empty" do str1 = "" str2 = "foo" (str1 + str2).should be(str2) end end it "escapes chars" do "\a"[0].should eq('\a') "\b"[0].should eq('\b') "\t"[0].should eq('\t') "\n"[0].should eq('\n') "\v"[0].should eq('\v') "\f"[0].should eq('\f') "\r"[0].should eq('\r') "\e"[0].should eq('\e') "\""[0].should eq('"') "\\"[0].should eq('\\') end it "escapes with octal" do "\3"[0].ord.should eq(3) "\23"[0].ord.should eq((2 * 8) + 3) "\123"[0].ord.should eq((1 * 8 * 8) + (2 * 8) + 3) "\033"[0].ord.should eq((3 * 8) + 3) "\033a"[1].should eq('a') end it "escapes with unicode" do "\u{12}".codepoint_at(0).should eq(1 * 16 + 2) "\u{A}".codepoint_at(0).should eq(10) "\u{AB}".codepoint_at(0).should eq(10 * 16 + 11) "\u{AB}1".codepoint_at(1).should eq('1'.ord) end it "does char_at" do "いただきます".char_at(2).should eq('だ') "foo".char_at(0).should eq('f') "foo".char_at(4) { 'x' }.should eq('x') expect_raises(IndexError) do "foo".char_at(4) end end it "does byte_at" do "hello".byte_at(1).should eq('e'.ord) expect_raises(IndexError) { "hello".byte_at(5) } end it "does byte_at?" do "hello".byte_at?(1).should eq('e'.ord) "hello".byte_at?(5).should be_nil end it "does chars" do "ぜんぶ".chars.should eq(['ぜ', 'ん', 'ぶ']) end describe "creating from a pointer" do it "allows creating a string with zeros" do p = Pointer(UInt8).malloc(3) p[0] = 'a'.ord.to_u8 p[1] = '\0'.ord.to_u8 p[2] = 'b'.ord.to_u8 s = String.new(p, 3) s[0].should eq('a') s[1].should eq('\0') s[2].should eq('b') s.bytesize.should eq(3) end it "raises an exception when creating a string with a null pointer and no size" do expect_raises ArgumentError do String.new(Pointer(UInt8).null) end end it "raises when creating from a null pointer with a nonzero size" do expect_raises ArgumentError, "Cannot create a string with a null pointer and a non-zero (3) bytesize" do String.new(Pointer(UInt8).null, 3) end end it "doesn't raise creating from a null pointer with size 0" do String.new(Pointer(UInt8).null, 0).should eq "" end end describe "creating from a slice" do it "allows creating from an empty slice" do String.new(Bytes.empty).should eq("") end it "allows creating from a non-empty slice" do String.new(UInt8.slice(102, 111, 111, 0, 98, 97, 114)).should eq("foo\0bar") end it "allows creating from a null-terminated slice" do String.new(Bytes.empty, truncate_at_null: true).should eq("") String.new(UInt8.slice(102, 111, 111, 98, 97, 114), truncate_at_null: true).should eq("foobar") String.new(UInt8.slice(102, 111, 111, 0, 98, 97, 114), truncate_at_null: true).should eq("foo") end end describe "tr" do it "translates" do "bla".tr("a", "h").should eq("blh") "bla".tr("a", "⊙").should eq("bl⊙") "bl⊙a".tr("⊙", "a").should eq("blaa") "bl⊙a".tr("⊙", "ⓧ").should eq("blⓧa") "bl⊙a⊙asdfd⊙dsfsdf⊙⊙⊙".tr("a⊙", "ⓧt").should eq("bltⓧtⓧsdfdtdsfsdfttt") "hello".tr("aeiou", "*").should eq("h*ll*") "hello".tr("el", "ip").should eq("hippo") "Lisp".tr("Lisp", "Crys").should eq("Crys") "hello".tr("helo", "1212").should eq("12112") "this".tr("this", "ⓧ").should eq("ⓧⓧⓧⓧ") "über".tr("ü", "u").should eq("uber") "aabbcc".tr("a", "xyz").should eq("xxbbcc") "aabbcc".tr("a", "いろは").should eq("いいbbcc") end context "given no replacement characters" do it "acts as #delete" do "foo".tr("o", "").should eq("foo".delete("o")) end end end describe "compare" do it "compares with == when same string" do "foo".should eq("foo") end it "compares with == when different strings same contents" do ("fo" + "o").should eq("fo" + "o") end it "compares with == when different contents" do ("fo" + "o").should_not eq("bo" + "o") end it "sorts strings" do ["foo1", "foo", "bar"].sort.should eq(["bar", "foo", "foo1"]) end end describe "#underscore" do it { assert_prints "Foo".underscore, "foo" } it { assert_prints "FooBar".underscore, "foo_bar" } it { assert_prints "ABCde".underscore, "ab_cde" } it { assert_prints "FOO_bar".underscore, "foo_bar" } it { assert_prints "Char_S".underscore, "char_s" } it { assert_prints "Char_".underscore, "char_" } it { assert_prints "C_".underscore, "c_" } it { assert_prints "HTTP".underscore, "http" } it { assert_prints "HTTP_CLIENT".underscore, "http_client" } it { assert_prints "CSS3".underscore, "css3" } it { assert_prints "HTTP1.1".underscore, "http1.1" } it { assert_prints "3.14IsPi".underscore, "3.14_is_pi" } it { assert_prints "I2C".underscore, "i2_c" } it "handles multi-character mappings correctly" do assert_prints "İxİİ0İİxİ0".underscore, "i̇x_i̇i̇0_i̇_i̇x_i̇0" end end describe "#camelcase" do it { assert_prints "foo".camelcase, "Foo" } it { assert_prints "foo_bar".camelcase, "FooBar" } it { assert_prints "foo".camelcase(lower: true), "foo" } it { assert_prints "foo_bar".camelcase(lower: true), "fooBar" } it { assert_prints "Foo".camelcase, "Foo" } it { assert_prints "Foo_bar".camelcase, "FooBar" } it { assert_prints "Foo".camelcase(lower: true), "foo" } it { assert_prints "Foo_bar".camelcase(lower: true), "fooBar" } it "handles multi-character mappings correctly (#13533)" do assert_prints "ffl_xffl".camelcase, "FflXffl" assert_prints "İ_xffl".camelcase(lower: true), "i̇Xffl" end end describe "ascii_only?" do it "answers ascii_only?" do "a".ascii_only?.should be_true "あ".ascii_only?.should be_false str = String.new(1) do |buffer| buffer.value = 'a'.ord.to_u8 {1, 0} end str.ascii_only?.should be_true str = String.new(4) do |buffer| count = 0 'あ'.each_byte do |byte| buffer[count] = byte count += 1 end {count, 0} end str.ascii_only?.should be_false end it "broken UTF-8 is not ascii_only" do "\xED\xA0\x80\xED\xBF\xBF".ascii_only?.should be_false end end describe "#scan" do it "does without block" do a = "cruel world" a.scan(/\w+/).map(&.[0]).should eq(["cruel", "world"]) a.scan(/.../).map(&.[0]).should eq(["cru", "el ", "wor"]) a.scan(/(...)/).map(&.[1]).should eq(["cru", "el ", "wor"]) a.scan(/(..)(..)/).map { |m| {m[1], m[2]} }.should eq([{"cr", "ue"}, {"l ", "wo"}]) end it "does with block" do a = "foo goo" i = 0 a.scan(/\w(o+)/) do |match| case i when 0 match[0].should eq("foo") match[1].should eq("oo") when 1 match[0].should eq("goo") match[1].should eq("oo") else fail "expected two matches" end i += 1 end end it "does with utf-8" do a = "こん こん" a.scan(/こ/).map(&.[0]).should eq(["こ", "こ"]) end it "works when match is empty" do r = %r([\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)) "hello".scan(r).map(&.[0]).should eq(["hello", ""]) "hello world".scan(/\w+|(?= )/).map(&.[0]).should eq(["hello", "", "world"]) end it "works when match is empty, multibyte char" do "\u{80}\u{800}\u{10000}".scan(/()/).map(&.begin).should eq([0, 1, 2, 3]) " Äa".scan(/(?=(\S))/).map(&.[1]).should eq(["Ä", "a"]) end it "works with strings with block" do res = [] of String "bla bla ablf".scan("bl") { |s| res << s } res.should eq(["bl", "bl", "bl"]) end it "works with strings" do "bla bla ablf".scan("bl").should eq(["bl", "bl", "bl"]) "hello".scan("world").should eq([] of String) "bbb".scan("bb").should eq(["bb"]) "ⓧⓧⓧ".scan("ⓧⓧ").should eq(["ⓧⓧ"]) "ⓧ".scan("ⓧ").should eq(["ⓧ"]) "ⓧ ⓧ ⓧ".scan("ⓧ").should eq(["ⓧ", "ⓧ", "ⓧ"]) "".scan("").should eq([] of String) "a".scan("").should eq([] of String) "".scan("a").should eq([] of String) end it "does with number and string" do "1ab4".scan(/\d+/).map(&.[0]).should eq(["1", "4"]) end it "options parameter" do "ba".scan(/a/, options: :anchored).map(&.[0]).should eq [] of String end end describe "#match" do it "has match" do match = "FooBar".match(/oo/).should_not be_nil match[0].should eq("oo") end it "matches with position" do match = "こんにちは".match(/./, 1).should_not be_nil match[0].should eq("ん") end it "matches empty string" do match = "".match(/.*/).should_not be_nil match.group_size.should eq(0) match[0].should eq("") end it "returns nil" do "foo".match(/bar/).should be_nil end end it "matches, but returns Bool" do "foo".matches?(/foo/).should be_true "foo".matches?(/bar/).should be_false end it "#matches_full?" do pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} "foo".matches_full?(/foo/).should be_true "fooo".matches_full?(/foo/).should be_false "ofoo".matches_full?(/foo/).should be_false "pattern".matches_full?(/(\A)?pattern(\z)?/).should be_true "_pattern_".matches_full?(/(\A)?pattern(\z)?/).should be_false end it "#match_full" do pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} "foo".match_full(/foo/).not_nil![0].should eq "foo" "fooo".match_full(/foo/).should be_nil "ofoo".match_full(/foo/).should be_nil "pattern".match_full(/(\A)?pattern(\z)?/).not_nil![0].should eq "pattern" "_pattern_".match_full(/(\A)?pattern(\z)?/).should be_nil end it "#match_full!" do pending! if {{ Regex::Engine.resolve.name == "Regex::PCRE" }} "foo".match_full!(/foo/).not_nil![0].should eq "foo" expect_raises(Regex::Error) { "fooo".match_full!(/foo/) } expect_raises(Regex::Error) { "ofoo".match_full!(/foo/) } "pattern".match_full!(/(\A)?pattern(\z)?/).not_nil![0].should eq "pattern" expect_raises(Regex::Error) { "_pattern_".match_full!(/(\A)?pattern(\z)?/) } end it "has size (same as size)" do "テスト".size.should eq(3) end describe "count" do it { "hello world".count("lo").should eq(5) } it { "hello world".count("lo", "o").should eq(2) } it { "hello world".count("hello", "^l").should eq(4) } it { "hello world".count("ej-m").should eq(4) } it { "hello^world".count("\\^aeiou").should eq(4) } it { "hello-world".count("a\\-eo").should eq(4) } it { "hello world\\r\\n".count("\\").should eq(2) } it { "hello world\\r\\n".count("\\A").should eq(0) } it { "hello world\\r\\n".count("X-\\w").should eq(3) } it { "aabbcc".count('a').should eq(2) } it { "aabbcc".count(&.in?('a', 'b')).should eq(4) } end describe "squeeze" do it { "aaabbbccc".squeeze(&.in?('a', 'b')).should eq("abccc") } it { "aaabbbccc".squeeze(&.in?('a', 'c')).should eq("abbbc") } it { "a bbb".squeeze.should eq("a b") } it { "a bbb".squeeze(' ').should eq("a bbb") } it { "aaabbbcccddd".squeeze("b-d").should eq("aaabcd") } end describe "ljust" do it { "123".ljust(2).should eq("123") } it { "123".ljust(5).should eq("123 ") } it { "12".ljust(7, '-').should eq("12-----") } it { "12".ljust(7, 'あ').should eq("12あああああ") } describe "to io" do it { String.build { |io| "123".ljust(io, 2) }.should eq("123") } it { String.build { |io| "123".ljust(io, 5) }.should eq("123 ") } it { String.build { |io| "12".ljust(io, 7, '-') }.should eq("12-----") } it { String.build { |io| "12".ljust(io, 7, 'あ') }.should eq("12あああああ") } end end describe "rjust" do it { "123".rjust(2).should eq("123") } it { "123".rjust(5).should eq(" 123") } it { "12".rjust(7, '-').should eq("-----12") } it { "12".rjust(7, 'あ').should eq("あああああ12") } describe "to io" do it { String.build { |io| "123".rjust(io, 2) }.should eq("123") } it { String.build { |io| "123".rjust(io, 5) }.should eq(" 123") } it { String.build { |io| "12".rjust(io, 7, '-') }.should eq("-----12") } it { String.build { |io| "12".rjust(io, 7, 'あ') }.should eq("あああああ12") } end end describe "center" do it { "123".center(2).should eq("123") } it { "123".center(5).should eq(" 123 ") } it { "12".center(7, '-').should eq("--12---") } it { "12".center(7, 'あ').should eq("ああ12あああ") } describe "to io" do it { String.build { |io| "123".center(io, 2) }.should eq("123") } it { String.build { |io| "123".center(io, 5) }.should eq(" 123 ") } it { String.build { |io| "12".center(io, 7, '-') }.should eq("--12---") } it { String.build { |io| "12".center(io, 7, 'あ') }.should eq("ああ12あああ") } end end describe "succ" do it "returns an empty string for empty strings" do "".succ.should eq("") end it "returns the successor by increasing the rightmost alphanumeric (digit => digit, letter => letter with same case)" do "abcd".succ.should eq("abce") "THX1138".succ.should eq("THX1139") "<>".succ.should eq("<>") "==A??".succ.should eq("==B??") end it "increases non-alphanumerics (via ascii rules) if there are no alphanumerics" do "***".succ.should eq("**+") "**`".succ.should eq("**a") end it "increases the next best alphanumeric (jumping over non-alphanumerics) if there is a carry" do "dz".succ.should eq("ea") "HZ".succ.should eq("IA") "49".succ.should eq("50") "izz".succ.should eq("jaa") "IZZ".succ.should eq("JAA") "699".succ.should eq("700") "6Z99z99Z".succ.should eq("7A00a00A") "1999zzz".succ.should eq("2000aaa") "NZ/[]ZZZ9999".succ.should eq("OA/[]AAA0000") 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".succ.should eq("aa") "Z".succ.should eq("AA") "9".succ.should eq("10") "zz".succ.should eq("aaa") "ZZ".succ.should eq("AAA") "99".succ.should eq("100") "9Z99z99Z".succ.should eq("10A00a00A") "ZZZ9999".succ.should eq("AAAA0000") "/[]ZZZ9999".succ.should eq("/[]AAAA0000") "Z/[]ZZZ9999".succ.should eq("AA/[]AAA0000") end end describe "match!" do it "returns matchdata" do md = "Crystal".match! /(?.)(?.)/ md[0].should eq "Cr" md.captures.should eq [] of String md.named_captures.should eq({"bar" => "C", "foo" => "r"}) end it "assigns captures" do md = "foo".match! /foo/ $~.should eq md end it "raises on non-match" do expect_raises(Regex::Error, "Match not found") { "foo".match! /Crystal/ } expect_raises(NilAssertionError) { $~ } end context "with options" do it "Regex::Match options" do expect_raises(Regex::Error, "Match not found") do ".foo".match!(/foo/, options: :anchored) end "foo".match!(/foo/, options: :anchored) end end end it "does %" do ("Hello %d world" % 123).should eq("Hello 123 world") ("Hello %d world" % [123]).should eq("Hello 123 world") end it "does each_char" do s = "abc" i = 0 s.each_char do |c| case i when 0 c.should eq('a') when 1 c.should eq('b') when 2 c.should eq('c') else fail "shouldn't happen" end i += 1 end.should be_nil i.should eq(3) end it "does each_char_with_index" do s = "abc" values = [] of {Char, Int32} s.each_char_with_index do |c, i| values << {c, i} end values.should eq([{'a', 0}, {'b', 1}, {'c', 2}]) end it "does each_char_with_index, with offset" do s = "abc" values = [] of {Char, Int32} s.each_char_with_index(10) do |c, i| values << {c, i} end values.should eq([{'a', 10}, {'b', 11}, {'c', 12}]) end it_iterates "#each_char", ['a', 'b', 'c'], "abc".each_char it_iterates "#each_char with empty string", [] of Char, "".each_char it_iterates "#each_byte", ['a'.ord.to_u8, 'b'.ord.to_u8, 'c'.ord.to_u8], "abc".each_byte it "gets lines" do "".lines.should eq([] of String) "\n".lines.should eq([""] of String) "\r".lines.should eq(["\r"] of String) "\r\n".lines.should eq([""] of String) "foo".lines.should eq(["foo"]) "foo\n".lines.should eq(["foo"]) "foo\r\n".lines.should eq(["foo"]) "foo\nbar\r\nbaz\n".lines.should eq(["foo", "bar", "baz"]) "foo\nbar\r\nbaz\r\n".lines.should eq(["foo", "bar", "baz"]) end it "gets lines with chomp = false" do "foo".lines(chomp: false).should eq(["foo"]) "foo\nbar\r\nbaz\n".lines(chomp: false).should eq(["foo\n", "bar\r\n", "baz\n"]) "foo\nbar\r\nbaz\r\n".lines(chomp: false).should eq(["foo\n", "bar\r\n", "baz\r\n"]) end it "gets each_line" do lines = [] of String "foo\n\nbar\r\nbaz\n".each_line do |line| lines << line end.should be_nil lines.should eq(["foo", "", "bar", "baz"]) end it "gets each_line with chomp = false" do lines = [] of String "foo\n\nbar\r\nbaz\r\n".each_line(chomp: false) do |line| lines << line end.should be_nil lines.should eq(["foo\n", "\n", "bar\r\n", "baz\r\n"]) end it "gets each_line with remove_empty = true" do lines = [] of String "\nfoo\n\nbar\r\nbaz\n\n".each_line(remove_empty: true) do |line| lines << line end.should be_nil lines.should eq(["foo", "bar", "baz"]) end it "gets each_line with remove_empty = true and chomp = false" do lines = [] of String "\nfoo\n\nbar\r\n\r\nbaz".each_line(remove_empty: true, chomp: false) do |line| lines << line end.should be_nil lines.should eq(["foo\n", "bar\r\n", "baz"]) end it_iterates "#each_line", ["foo", "bar", "baz"], "foo\nbar\r\nbaz\r\n".each_line it_iterates "#each_line(chomp: false)", ["foo\n", "bar\r\n", "baz\r\n"], "foo\nbar\r\nbaz\r\n".each_line(chomp: false) it_iterates "#each_line(remove_empty: true)", ["foo", "bar", "baz"], "\nfoo\n\nbar\r\n\r\nbaz".each_line(remove_empty: true) it_iterates "#each_line(remove_empty: true, chomp: false)", ["foo\n", "bar\r\n", "baz"], "\nfoo\n\nbar\r\n\r\nbaz".each_line(remove_empty: true, chomp: false) it_iterates "#each_codepoint", [97, 98, 9731], "ab☃".each_codepoint it "has codepoints" do "ab☃".codepoints.should eq [97, 98, 9731] end it "gets size of \\0 string" do "\0\0".size.should eq(2) end describe "char_index_to_byte_index" do it "with ascii" do "foo".char_index_to_byte_index(0).should eq(0) "foo".char_index_to_byte_index(1).should eq(1) "foo".char_index_to_byte_index(2).should eq(2) "foo".char_index_to_byte_index(3).should eq(3) "foo".char_index_to_byte_index(4).should be_nil end it "with utf-8" do "これ".char_index_to_byte_index(0).should eq(0) "これ".char_index_to_byte_index(1).should eq(3) "これ".char_index_to_byte_index(2).should eq(6) "これ".char_index_to_byte_index(3).should be_nil end end describe "byte_index_to_char_index" do it "with ascii" do "foo".byte_index_to_char_index(0).should eq(0) "foo".byte_index_to_char_index(1).should eq(1) "foo".byte_index_to_char_index(2).should eq(2) "foo".byte_index_to_char_index(3).should eq(3) "foo".byte_index_to_char_index(4).should be_nil end it "with utf-8" do "これ".byte_index_to_char_index(0).should eq(0) "これ".byte_index_to_char_index(3).should eq(1) "これ".byte_index_to_char_index(6).should eq(2) "これ".byte_index_to_char_index(7).should be_nil "これ".byte_index_to_char_index(1).should be_nil end end describe "String.new(&)" do it "creates with matching capacity" do String.new(3) { |buf| buf[0] = 'f'.ord.to_u8 buf[1] = 'o'.ord.to_u8 buf[2] = 'o'.ord.to_u8 {3, 3} }.should eq "foo" end it "creates with excess capacity" do String.new(5) { |buf| buf[0] = 'f'.ord.to_u8 buf[1] = 'o'.ord.to_u8 buf[2] = 'o'.ord.to_u8 {3, 3} }.should eq "foo" end it "raises if string capacity is negative" do expect_raises(ArgumentError, "Negative capacity") do String.new(-1) { |buf| {0, 0} } end end it "raises if capacity too big with UInt32::MAX" do expect_raises(ArgumentError, "Capacity too big") do String.new(UInt32::MAX) { {0, 0} } end end it "raises if capacity too big with UInt32::MAX - String::HEADER_SIZE - 1" do expect_raises(ArgumentError, "Capacity too big") do String.new(UInt32::MAX - String::HEADER_SIZE) { {0, 0} } end end it "raises if capacity too big with UInt64::MAX" do expect_raises(ArgumentError, "Capacity too big") do String.new(UInt64::MAX) { {0, 0} } end end pending_wasm32 "allocates buffer of correct size (#3332)" do String.new(255_u8) do |buffer| LibGC.size(buffer).should be > 255 {255, 0} end end it "raises if returned bytesize is greater than capacity" do expect_raises ArgumentError, "Bytesize out of capacity bounds" do String.new(123) do |buffer| {124, 0} end end end end describe "#compare" do it "compares case-sensitive" do "fo".compare("foo").should eq(-1) "foo".compare("fo").should eq(1) "foo".compare("foo").should eq(0) "foo".compare("fox").should eq(-1) "fox".compare("foo").should eq(1) "foo".compare("Foo").should eq(1) "hällo".compare("Hällo").should eq(1) "".compare("").should eq(0) end it "compares case-insensitive" do "foo".compare("FO", case_insensitive: true).should eq(1) "FOO".compare("fo", case_insensitive: true).should eq(1) "fo".compare("FOO", case_insensitive: true).should eq(-1) "FOX".compare("foo", case_insensitive: true).should eq(1) "foo".compare("FOX", case_insensitive: true).should eq(-1) "foo".compare("FOO", case_insensitive: true).should eq(0) "hELLo".compare("HellO", case_insensitive: true).should eq(0) "fo\u{0}".compare("FO", case_insensitive: true).should eq(1) "fo".compare("FO\u{0}", case_insensitive: true).should eq(-1) "\u{0}".compare("\u{0}", case_insensitive: true).should eq(0) "z".compare("hello", case_insensitive: true).should eq(1) "h".compare("zzz", case_insensitive: true).should eq(-1) "ä".compare("äA", case_insensitive: true).should eq(-1) "äÄ".compare("äÄ", case_insensitive: true).should eq(0) "heIIo".compare("heııo", case_insensitive: true, options: Unicode::CaseOptions::Turkic).should eq(0) "".compare("abc", case_insensitive: true).should eq(-1) "abc".compare("", case_insensitive: true).should eq(1) "abcA".compare("abca", case_insensitive: true).should eq(0) end it "compares case-insensitive, multiple chars after case conversion (#4513)" do "ffl".compare("ffl", case_insensitive: true, options: Unicode::CaseOptions::Fold).should eq(0) "FFL".compare("ffl", case_insensitive: true, options: Unicode::CaseOptions::Fold).should eq(0) "sß".compare("ßs", case_insensitive: true, options: Unicode::CaseOptions::Fold).should eq(0) end it "treats invalid code units as replacement char in an otherwise ascii string" do "\xC0".compare("\xE0", case_insensitive: true).should eq(0) "\xE0".compare("\xC0", case_insensitive: true).should eq(0) "\xC0".compare("a", case_insensitive: true).should eq(1) "a".compare("\xC0", case_insensitive: true).should eq(-1) end end it "builds with write_byte" do string = String.build do |io| 255_u8.times do |byte| io.write_byte(byte) end end 255.times do |i| string.byte_at(i).should eq(i) end end it "raises if String.build negative capacity" do expect_raises(ArgumentError, "Negative capacity") do String.build(-1) { } end end it "raises if String.build capacity too big" do expect_raises(ArgumentError, "Capacity too big") do String.build(UInt32::MAX) { } end end {% unless flag?(:without_iconv) %} describe "encode" do it "encodes" do bytes = "Hello".encode("UCS-2LE") bytes.should eq Bytes[72, 0, 101, 0, 108, 0, 108, 0, 111, 0] end {% unless flag?(:musl) || flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "flushes the shift state (#11992)" do "\u{00CA}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{00CA}\u{0304}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) end {% end %} # FreeBSD iconv encoder expects ISO/IEC 10646 compatibility code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "flushes the shift state (#11992)" do "\u{F329}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x66]) "\u{F325}".encode("BIG5-HKSCS").should eq(Bytes[0x88, 0x62]) end {% end %} it "raises if wrong encoding" do expect_raises ArgumentError, "Invalid encoding: FOO" do "Hello".encode("FOO") end end it "raises if wrong encoding with skip" do expect_raises ArgumentError, "Invalid encoding: FOO" do "Hello".encode("FOO", invalid: :skip) end end it "raises if illegal byte sequence" do expect_raises ArgumentError, "Invalid multibyte sequence" do "\xff".encode("EUC-JP") end end it "doesn't raise on invalid byte sequence" do "好\xff是".encode("EUC-JP", invalid: :skip).should eq(Bytes[185, 165, 192, 167]) end it "raises if incomplete byte sequence" do expect_raises ArgumentError, "Incomplete multibyte sequence" do "好".byte_slice(0, 1).encode("EUC-JP") end end it "doesn't raise if incomplete byte sequence" do ("好".byte_slice(0, 1) + "是").encode("EUC-JP", invalid: :skip).should eq(Bytes[192, 167]) end it "decodes" do bytes = "Hello".encode("UTF-16LE") String.new(bytes, "UTF-16LE").should eq("Hello") end {% unless flag?(:solaris) || flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{00CA}\u{0304}") end {% end %} # FreeBSD iconv decoder returns ISO/IEC 10646-1:2000 code points, # see https://www.ccli.gov.hk/doc/e_hkscs_2008.pdf for details. {% if flag?(:freebsd) || flag?(:dragonfly) || flag?(:netbsd) %} it "decodes with shift state" do String.new(Bytes[0x88, 0x66], "BIG5-HKSCS").should eq("\u{00CA}") String.new(Bytes[0x88, 0x62], "BIG5-HKSCS").should eq("\u{F325}") end {% end %} it "decodes with skip" do bytes = Bytes[186, 195, 255, 202, 199] String.new(bytes, "EUC-JP", invalid: :skip).should eq("挫頁") end end {% end %} it "inserts" do "bar".insert(0, "foo").should eq("foobar") "bar".insert(1, "foo").should eq("bfooar") "bar".insert(2, "foo").should eq("bafoor") "bar".insert(3, "foo").should eq("barfoo") "bar".insert(-1, "foo").should eq("barfoo") "bar".insert(-2, "foo").should eq("bafoor") "ともだち".insert(0, "ねこ").should eq("ねこともだち") "ともだち".insert(1, "ねこ").should eq("とねこもだち") "ともだち".insert(2, "ねこ").should eq("ともねこだち") "ともだち".insert(4, "ねこ").should eq("ともだちねこ") "ともだち".insert(0, 'ね').should eq("ねともだち") "ともだち".insert(1, 'ね').should eq("とねもだち") "ともだち".insert(2, 'ね').should eq("ともねだち") "ともだち".insert(4, 'ね').should eq("ともだちね") "ともだち".insert(-1, 'ね').should eq("ともだちね") "ともだち".insert(-2, 'ね').should eq("ともだねち") expect_raises(IndexError) { "bar".insert(4, "foo") } expect_raises(IndexError) { "bar".insert(-5, "foo") } expect_raises(IndexError) { "bar".insert(4, 'f') } expect_raises(IndexError) { "bar".insert(-5, 'f') } "barbar".insert(0, "foo").size.should eq(9) "ともだち".insert(0, "ねこ").size.should eq(6) "foo".insert(0, 'a').ascii_only?.should be_true "foo".insert(0, 'あ').ascii_only?.should be_false "".insert(0, 'a').ascii_only?.should be_true "".insert(0, 'あ').ascii_only?.should be_false end it "hexbytes" do expect_raises(ArgumentError) { "abc".hexbytes } expect_raises(ArgumentError) { "abc ".hexbytes } "abcd".hexbytes.should eq(Bytes[171, 205]) end it "hexbytes?" do "abc".hexbytes?.should be_nil "abc ".hexbytes?.should be_nil "abcd".hexbytes?.should eq(Bytes[171, 205]) end it "dups" do string = "foo" dup = string.dup string.should be(dup) end it "clones" do string = "foo" clone = string.clone string.should be(clone) end describe "invalid UTF-8 byte sequence" do it "gets size" do string = String.new(Bytes[255, 0, 0, 0, 65]) string.size.should eq(5) end it "gets size (2)" do string = String.new(Bytes[104, 101, 108, 108, 111, 32, 255, 32, 255, 32, 119, 111, 114, 108, 100, 33]) string.size.should eq(16) end it "gets chars" do string = String.new(Bytes[255, 0, 0, 0, 65]) string.chars.should eq([Char::REPLACEMENT, 0.chr, 0.chr, 0.chr, 65.chr]) end it "gets chars (2)" do string = String.new(Bytes[255, 0]) string.chars.should eq([Char::REPLACEMENT, 0.chr]) end it "valid_encoding?" do "hello".valid_encoding?.should be_true "hello\u{80}\u{7FF}\u{800}\u{FFFF}\u{10000}\u{10FFFF}".valid_encoding?.should be_true {% for bytes in VALID_UTF8_BYTE_SEQUENCES %} String.new(Bytes{{ bytes }}).valid_encoding?.should be_true {% end %} {% for bytes in INVALID_UTF8_BYTE_SEQUENCES %} String.new(Bytes{{ bytes }}).valid_encoding?.should be_false {% end %} end it "scrubs" do string = String.new(Bytes[255, 129, 97, 255, 97]) string.scrub.bytes.should eq([239, 191, 189, 239, 191, 189, 97, 239, 191, 189, 97]) string.scrub("?").should eq("??a?a") "hello".scrub.should eq("hello") end end describe "interpolation" do it "of a single string" do string = "hello" interpolated = String.interpolation(string) interpolated.should be(string) end it "of a single non-string" do String.interpolation(123).should eq("123") end it "of string and char" do String.interpolation("hello", '!').should eq("hello!") end it "of char and string" do String.interpolation('!', "hello").should eq("!hello") end it "of multiple strings" do String.interpolation("a", "bcd", "ef").should eq("abcdef") end it "of multiple possibly non-strings" do String.interpolation("a", 123, "b", 456, "cde").should eq("a123b456cde") end end describe "delete_at" do describe "char" do it { "abcde".delete_at(0).should eq("bcde") } it { "abcde".delete_at(1).should eq("acde") } it { "abcde".delete_at(2).should eq("abde") } it { "abcde".delete_at(4).should eq("abcd") } it { "abcde".delete_at(-2).should eq("abce") } it { expect_raises(IndexError) { "abcde".delete_at(5) } } it { expect_raises(IndexError) { "abcde".delete_at(-6) } } it { "二ノ国".delete_at(0).should eq("ノ国") } it { "二ノ国".delete_at(1).should eq("二国") } it { "二ノ国".delete_at(2).should eq("二ノ") } it { "二ノ国".delete_at(-2).should eq("二国") } it { expect_raises(IndexError) { "二ノ国".delete_at(3) } } it { expect_raises(IndexError) { "二ノ国".delete_at(-4) } } end describe "start, count" do it { "abcdefg".delete_at(0, 2).should eq("cdefg") } it { "abcdefg".delete_at(1, 2).should eq("adefg") } it { "abcdefg".delete_at(3, 10).should eq("abc") } it { "abcdefg".delete_at(-3, 2).should eq("abcdg") } it { "abcdefg".delete_at(7, 10).should eq("abcdefg") } it { expect_raises(IndexError) { "abcdefg".delete_at(8, 1) } } it { expect_raises(IndexError) { "abcdefg".delete_at(-8, 1) } } it "raises on negative count" do expect_raises(ArgumentError, "Negative count: -1") { "abcdefg".delete_at(1, -1) } end it { "セキロ:シャドウズ ダイ トゥワイス".delete_at(4, 6).should eq("セキロ:ダイ トゥワイス") } it { "セキロ:シャドウズ ダイ トゥワイス".delete_at(0, 4).should eq("シャドウズ ダイ トゥワイス") } it { "セキロ:シャドウズ ダイ トゥワイス".delete_at(3, 20).should eq("セキロ") } it { "セキロ:シャドウズ ダイ トゥワイス".delete_at(-14, 6).should eq("セキロ:ダイ トゥワイス") } it { "セキロ:シャドウズ ダイ トゥワイス".delete_at(18, 3).should eq("セキロ:シャドウズ ダイ トゥワイス") } it { expect_raises(IndexError) { "セキロ:シャドウズ ダイ トゥワイス".delete_at(19, 1) } } it { expect_raises(IndexError) { "セキロ:シャドウズ ダイ トゥワイス".delete_at(-19, 1) } } it "raises on negative count" do expect_raises(ArgumentError, "Negative count: -1") { "セキロ:シャドウズ ダイ トゥワイス".delete_at(1, -1) } end end describe "range" do it { "abcdefg".delete_at(0..1).should eq("cdefg") } it { "abcdefg".delete_at(0...2).should eq("cdefg") } it { "abcdefg".delete_at(1..3).should eq("aefg") } it { "abcdefg".delete_at(3..10).should eq("abc") } it { "abcdefg".delete_at(-3..-2).should eq("abcdg") } it { "abcdefg".delete_at(3..).should eq("abc") } it { "abcdefg".delete_at(..-3).should eq("fg") } it { expect_raises(IndexError) { "abcdefg".delete_at(8..1) } } it { expect_raises(IndexError) { "abcdefg".delete_at(-8..1) } } it { "セキロ:シャドウズ ダイ トゥワイス".delete_at(4...10).should eq("セキロ:ダイ トゥワイス") } it { expect_raises(IndexError) { "セキロ:シャドウズ ダイ トゥワイス".delete_at(19..1) } } it { expect_raises(IndexError) { "セキロ:シャドウズ ダイ トゥワイス".delete_at(-19..1) } } end end describe "ensure_suffix" do context "with string suffix" do it "adds suffix if not present" do "foo".ensure_suffix("bar").should eq("foobar") "foo".ensure_suffix("FOO").should eq("fooFOO") "foo".ensure_suffix("").should eq("foo") "foobar".ensure_suffix("arr").should eq("foobararr") end it "does not add suffix if already present" do "foobar".ensure_suffix("bar").should eq("foobar") "FOOBAR".ensure_suffix("BAR").should eq("FOOBAR") end end context "with char suffix" do it "adds suffix if not present" do "foo".ensure_suffix('b').should eq("foob") "foo".ensure_suffix('O').should eq("fooO") end it "does not add suffix if already present" do "foob".ensure_suffix('b').should eq("foob") "FOOB".ensure_suffix('B').should eq("FOOB") end end end describe "ensure_prefix" do context "with string prefix" do it "adds prefix if not present" do "foo".ensure_prefix("bar").should eq("barfoo") "foo".ensure_prefix("FOO").should eq("FOOfoo") "foo".ensure_prefix("").should eq("foo") "foo".ensure_prefix("barf").should eq("barffoo") end it "does not add prefix if already present" do "foobar".ensure_prefix("foo").should eq("foobar") "FOOBAR".ensure_prefix("FOO").should eq("FOOBAR") end end context "with char prefix" do it "adds prefix if not present" do "foo".ensure_prefix('b').should eq("bfoo") "foo".ensure_prefix('F').should eq("Ffoo") end it "does not add prefix if already present" do "bfoo".ensure_prefix('b').should eq("bfoo") "BFOO".ensure_prefix('B').should eq("BFOO") end end end it ".additive_identity" do String.additive_identity.should be "" end end class String describe String do it ".char_bytesize_at" do {% for bytes, char in VALID_UTF8_BYTE_SEQUENCES %} String.char_bytesize_at(Bytes[{{ bytes.splat }}, 0].to_unsafe).should eq({{ bytes.size }}) {% end %} {% for bytes in INVALID_UTF8_BYTE_SEQUENCES %} String.char_bytesize_at(Bytes[{{ bytes.splat }}, 0].to_unsafe).should eq 1 {% end %} end end end