From 8b970f734efc3e8e171f762780cd18a524b5ba52 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Mon, 20 Sep 2021 12:18:03 +0200 Subject: [PATCH 1/9] Tests - Fix style issues --- Tests/InflectorTest.php | 53 +++--- Tests/NormaliseTest.php | 20 +-- Tests/StringHelperTest.php | 337 +++++++++++++++++++++++-------------- phpunit.xml.dist | 8 - 4 files changed, 249 insertions(+), 169 deletions(-) diff --git a/Tests/InflectorTest.php b/Tests/InflectorTest.php index 9948baf2..debdb3d8 100644 --- a/Tests/InflectorTest.php +++ b/Tests/InflectorTest.php @@ -2,6 +2,9 @@ /** * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE + * + * @noinspection PhpDeprecationInspection + * @noinspection SpellCheckingInspection */ namespace Joomla\String\Tests; @@ -104,8 +107,9 @@ protected function tearDown(): void /** * @testdox A rule cannot be added to the inflector if it is of an unsupported type + * @throws \ReflectionException */ - public function testAddRuleException() + public function testAddRuleException(): void { $this->expectException(\InvalidArgumentException::class); @@ -114,8 +118,9 @@ public function testAddRuleException() /** * @testdox A countable rule can be added to the inflector + * @throws \ReflectionException */ - public function testAddCountableRule() + public function testAddCountableRule(): void { // Add string. $this->inflector->addCountableRule('foo'); @@ -142,8 +147,9 @@ public function testAddCountableRule() /** * @testdox A word can be added to the inflector without a plural form + * @throws \ReflectionException */ - public function testAddWordWithoutPlural() + public function testAddWordWithoutPlural(): void { $this->assertSame( $this->inflector, @@ -152,21 +158,24 @@ public function testAddWordWithoutPlural() $plural = TestHelper::getValue(DoctrineInflector::class, 'plural'); - $this->assertTrue( - in_array('foo', $plural['uninflected']) + $this->assertContains( + 'foo', + $plural['uninflected'] ); $singular = TestHelper::getValue(DoctrineInflector::class, 'singular'); - $this->assertTrue( - in_array('foo', $singular['uninflected']) + $this->assertContains( + 'foo', + $singular['uninflected'] ); } /** * @testdox A word can be added to the inflector with a plural form + * @throws \ReflectionException */ - public function testAddWordWithPlural() + public function testAddWordWithPlural(): void { $this->assertEquals( $this->inflector, @@ -190,8 +199,9 @@ public function testAddWordWithPlural() /** * @testdox A pluralisation rule can be added to the inflector + * @throws \ReflectionException */ - public function testAddPluraliseRule() + public function testAddPluraliseRule(): void { $this->assertSame( $this->inflector->addPluraliseRule(['/^(custom)$/i' => '\1izables']), @@ -210,8 +220,9 @@ public function testAddPluraliseRule() /** * @testdox A singularisation rule can be added to the inflector + * @throws \ReflectionException */ - public function testAddSingulariseRule() + public function testAddSingulariseRule(): void { $this->assertSame( $this->inflector->addSingulariseRule(['/^(inflec|contribu)tors$/i' => '\1ta']), @@ -231,7 +242,7 @@ public function testAddSingulariseRule() /** * @testdox The singleton instance of the inflector can be retrieved */ - public function testGetInstance() + public function testGetInstance(): void { $this->assertInstanceOf( Inflector::class, @@ -247,14 +258,14 @@ public function testGetInstance() } /** - * @testdox A string is checked to determine if it a countable word + * @testdox A string is checked to determine if it is a countable word * * @param string $input A string. * @param boolean $expected The expected result of the function call. * * @dataProvider seedIsCountable */ - public function testIsCountable(string $input, bool $expected) + public function testIsCountable(string $input, bool $expected): void { $this->assertEquals( $expected, @@ -270,7 +281,7 @@ public function testIsCountable(string $input, bool $expected) * * @dataProvider seedSinglePlural */ - public function testIsPlural(string $singular, string $plural) + public function testIsPlural(string $singular, string $plural): void { $this->assertTrue( $this->inflector->isPlural($plural), @@ -294,7 +305,7 @@ public function testIsPlural(string $singular, string $plural) * * @dataProvider seedSinglePlural */ - public function testIsSingular(string $singular, string $plural) + public function testIsSingular(string $singular, string $plural): void { $this->assertTrue( $this->inflector->isSingular($singular), @@ -318,7 +329,7 @@ public function testIsSingular(string $singular, string $plural) * * @dataProvider seedSinglePlural */ - public function testToPlural(string $singular, string $plural) + public function testToPlural(string $singular, string $plural): void { $this->assertSame( $plural, @@ -328,9 +339,9 @@ public function testToPlural(string $singular, string $plural) } /** - * @testdox A string that is already plural is returned in the same form + * @testdox A string that is already plural is returned unchanged */ - public function testToPluralAlreadyPlural() + public function testToPluralAlreadyPlural(): void { $this->assertSame( 'buses', @@ -347,7 +358,7 @@ public function testToPluralAlreadyPlural() * * @dataProvider seedSinglePlural */ - public function testToSingular(string $singular, string $plural) + public function testToSingular(string $singular, string $plural): void { $this->assertSame( $singular, @@ -357,9 +368,9 @@ public function testToSingular(string $singular, string $plural) } /** - * @testdox A string that is already singular is returned in the same form + * @testdox A string that is already singular is returned unchanged */ - public function testToSingularAlreadySingular() + public function testToSingularAlreadySingular(): void { $this->assertSame( 'bus', diff --git a/Tests/NormaliseTest.php b/Tests/NormaliseTest.php index d4229565..48f11b06 100644 --- a/Tests/NormaliseTest.php +++ b/Tests/NormaliseTest.php @@ -42,7 +42,7 @@ public function seedTestFromCamelCase(): \Generator * * @since 1.0 */ - public function seedTestFromCamelCase_nongrouped(): \Generator + public function seedTestFromCamelCaseNonGrouped(): \Generator { yield ['Foo Bar', 'FooBar']; yield ['foo Bar', 'fooBar']; @@ -168,9 +168,9 @@ public function seedTestToKey(): \Generator * @param string $expected The expected value from the method. * @param string $input The input value for the method. * - * @dataProvider seedTestFromCamelCase_nongrouped + * @dataProvider seedTestFromCamelCaseNonGrouped */ - public function testFromCamelCase_nongrouped(string $expected, string $input) + public function testFromCamelCaseNonGrouped(string $expected, string $input): void { $this->assertEquals($expected, Normalise::fromCamelcase($input)); } @@ -183,7 +183,7 @@ public function testFromCamelCase_nongrouped(string $expected, string $input) * * @dataProvider seedTestFromCamelCase */ - public function testFromCamelCase_grouped(string $input, $expected) + public function testFromCamelCase_grouped(string $input, $expected): void { $this->assertEquals($expected, Normalise::fromCamelcase($input, true)); } @@ -196,7 +196,7 @@ public function testFromCamelCase_grouped(string $input, $expected) * * @dataProvider seedTestToCamelCase */ - public function testToCamelCase(string $expected, string $input) + public function testToCamelCase(string $expected, string $input): void { $this->assertEquals($expected, Normalise::toCamelcase($input)); } @@ -209,7 +209,7 @@ public function testToCamelCase(string $expected, string $input) * * @dataProvider seedTestToDashSeparated */ - public function testToDashSeparated(string $expected, string $input) + public function testToDashSeparated(string $expected, string $input): void { $this->assertEquals($expected, Normalise::toDashSeparated($input)); } @@ -222,7 +222,7 @@ public function testToDashSeparated(string $expected, string $input) * * @dataProvider seedTestToSpaceSeparated */ - public function testToSpaceSeparated(string $expected, string $input) + public function testToSpaceSeparated(string $expected, string $input): void { $this->assertEquals($expected, Normalise::toSpaceSeparated($input)); } @@ -235,7 +235,7 @@ public function testToSpaceSeparated(string $expected, string $input) * * @dataProvider seedTestToUnderscoreSeparated */ - public function testToUnderscoreSeparated(string $expected, string $input) + public function testToUnderscoreSeparated(string $expected, string $input): void { $this->assertEquals($expected, Normalise::toUnderscoreSeparated($input)); } @@ -248,7 +248,7 @@ public function testToUnderscoreSeparated(string $expected, string $input) * * @dataProvider seedTestToVariable */ - public function testToVariable(string $expected, string $input) + public function testToVariable(string $expected, string $input): void { $this->assertEquals($expected, Normalise::toVariable($input)); } @@ -261,7 +261,7 @@ public function testToVariable(string $expected, string $input) * * @dataProvider seedTestToKey */ - public function testToKey(string $expected, string $input) + public function testToKey(string $expected, string $input): void { $this->assertEquals($expected, Normalise::toKey($input)); } diff --git a/Tests/StringHelperTest.php b/Tests/StringHelperTest.php index 44c07c12..2d95ff26 100644 --- a/Tests/StringHelperTest.php +++ b/Tests/StringHelperTest.php @@ -1,4 +1,5 @@ - string ', '<>', false, false, 8]; yield ['Би шил {123} идэй {456} чадна', '}{', null, false, 7]; @@ -217,7 +294,7 @@ public function seedTestStrcspn(): \Generator * * @since 1.0 */ - public function seedTestStristr(): \Generator + public function seedTestStrIStr(): \Generator { yield ['haystack', 'needle', false]; yield ['before match, after match', 'match', 'match, after match']; @@ -231,7 +308,7 @@ public function seedTestStristr(): \Generator * * @since 1.0 */ - public function seedTestStrrev(): \Generator + public function seedTestStrRev(): \Generator { yield ['abc def', 'fed cba']; yield ['Би шил', 'лиш иБ']; @@ -244,7 +321,7 @@ public function seedTestStrrev(): \Generator * * @since 1.0 */ - public function seedTestStrspn(): \Generator + public function seedTestStrSpn(): \Generator { yield ['A321 Main Street', '0123456789', 1, 2, 2]; yield ['321 Main Street', '0123456789', null, 2, 2]; @@ -267,7 +344,7 @@ public function seedTestStrspn(): \Generator * * @since 1.0 */ - public function seedTestSubstr_replace(): \Generator + public function seedTestSubstrReplace(): \Generator { yield ['321 Broadway Avenue', '321 Main Street', 'Broadway Avenue', 4, false]; yield ['321 Broadway Street', '321 Main Street', 'Broadway', 4, 4]; @@ -282,7 +359,7 @@ public function seedTestSubstr_replace(): \Generator * * @since 1.0 */ - public function seedTestLtrim(): \Generator + public function seedTestLTrim(): \Generator { yield [' abc def', false, 'abc def']; yield [' abc def', '', ' abc def']; @@ -300,7 +377,7 @@ public function seedTestLtrim(): \Generator * * @since 1.0 */ - public function seedTestRtrim(): \Generator + public function seedTestRTrim(): \Generator { yield ['abc def ', false, 'abc def']; yield ['abc def ', '', 'abc def ']; @@ -420,16 +497,16 @@ public function seedTestUnicodeToUtf16(): \Generator } /** - * @testdox A string is correctly incremented + * @testdox A string is correctly incremented * * @param string $string The source string. - * @param string|null $style The the style (default|dash). + * @param string|null $style The style (default|dash). * @param integer $number If supplied, this number is used for the copy, otherwise it is the 'next' number. * @param string $expected Expected result. * * @dataProvider seedTestIncrement */ - public function testIncrement(string $string, ?string $style, int $number, string $expected) + public function testIncrement(string $string, ?string $style, int $number, string $expected): void { $this->assertEquals( $expected, @@ -438,14 +515,14 @@ public function testIncrement(string $string, ?string $style, int $number, strin } /** - * @testdox A string is checked to determine if it is ASCII + * @testdox A string is checked to determine if it is ASCII * * @param string $string The string to test. * @param boolean $expected Expected result. * - * @dataProvider seedTestIs_ascii + * @dataProvider seedTestIsAscii */ - public function testIs_ascii(string $string, bool $expected) + public function testIsAscii(string $string, bool $expected): void { $this->assertEquals( $expected, @@ -454,16 +531,16 @@ public function testIs_ascii(string $string, bool $expected) } /** - * @testdox UTF-8 aware strpos() is performed on a string + * @testdox UTF-8 aware strpos() is performed on a string * * @param string|boolean $expected Expected result * @param string $haystack String being examined * @param string $needle String being searched for * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed * - * @dataProvider seedTestStrpos + * @dataProvider seedTestStrPos */ - public function testStrpos($expected, string $haystack, string $needle, $offset = 0) + public function testStrPos($expected, string $haystack, string $needle, $offset = 0): void { $this->assertEquals( $expected, @@ -472,16 +549,16 @@ public function testStrpos($expected, string $haystack, string $needle, $offset } /** - * @testdox UTF-8 aware strrpos() is performed on a string + * @testdox UTF-8 aware strrpos() is performed on a string * * @param string|boolean $expected Expected result * @param string $haystack String being examined * @param string $needle String being searched for * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed * - * @dataProvider seedTestStrrpos + * @dataProvider seedTestStrRPos */ - public function testStrrpos($expected, string $haystack, string $needle, int $offset = 0) + public function testStrRPos($expected, string $haystack, string $needle, int $offset = 0): void { $this->assertEquals( $expected, @@ -490,16 +567,16 @@ public function testStrrpos($expected, string $haystack, string $needle, int $of } /** - * @testdox UTF-8 aware substr() is performed on a string + * @testdox UTF-8 aware substr() is performed on a string * * @param string|boolean $expected Expected result * @param string $string String being processed - * @param integer $offset Number of UTF-8 characters offset (from left) - * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed + * @param integer $start Number of UTF-8 characters offset (from left) + * @param integer|null|boolean $length Optional, specifies the length * * @dataProvider seedTestSubstr */ - public function testSubstr($expected, string $string, int $start, $length = false) + public function testSubstr($expected, string $string, int $start, $length = false): void { $this->assertEquals( $expected, @@ -508,14 +585,14 @@ public function testSubstr($expected, string $string, int $start, $length = fals } /** - * @testdox UTF-8 aware strtolower() is performed on a string + * @testdox UTF-8 aware strtolower() is performed on a string * * @param string $string String being processed * @param string|boolean $expected Expected result * - * @dataProvider seedTestStrtolower + * @dataProvider seedTestStrToLower */ - public function testStrtolower(string $string, $expected) + public function testStrToLower(string $string, $expected): void { $this->assertEquals( $expected, @@ -524,14 +601,14 @@ public function testStrtolower(string $string, $expected) } /** - * @testdox UTF-8 aware strtoupper() is performed on a string + * @testdox UTF-8 aware strtoupper() is performed on a string * * @param string $string String being processed * @param string|boolean $expected Expected result * - * @dataProvider seedTestStrtoupper + * @dataProvider seedTestStrToUpper */ - public function testStrtoupper($string, $expected) + public function testStrToUpper(string $string, $expected): void { $this->assertEquals( $expected, @@ -540,14 +617,14 @@ public function testStrtoupper($string, $expected) } /** - * @testdox UTF-8 aware strlen() is performed on a string + * @testdox UTF-8 aware strlen() is performed on a string * * @param string $string String being processed * @param string|boolean $expected Expected result * - * @dataProvider seedTestStrlen + * @dataProvider seedTestStrLen */ - public function testStrlen(string $string, $expected) + public function testStrLen(string $string, $expected): void { $this->assertEquals( $expected, @@ -556,19 +633,19 @@ public function testStrlen(string $string, $expected) } /** - * @testdox UTF-8 aware str_ireplace() is performed on a string + * @testdox UTF-8 aware str_ireplace() is performed on a string * - * @param string $search String to search - * @param string $replace Existing string to replace + * @param string[]|string $search String to search + * @param string[]|string $replace Existing string to replace * @param string $subject New string to replace with * @param integer|null|boolean $count Optional count value to be passed by reference * @param string $expected Expected result * - * @return array + * @return void * - * @dataProvider seedTestStr_ireplace + * @dataProvider seedTestStrIReplace */ - public function testStr_ireplace($search, $replace, $subject, $count, $expected) + public function testStrIReplace($search, $replace, string $subject, $count, string $expected): void { $this->assertEquals( $expected, @@ -577,15 +654,15 @@ public function testStr_ireplace($search, $replace, $subject, $count, $expected) } /** - * @testdox UTF-8 aware str_split() is performed on a string + * @testdox UTF-8 aware str_split() is performed on a string * * @param string $string UTF-8 encoded string to process * @param integer $splitLen Number to characters to split string by * @param array|string|boolean $expected Expected result * - * @dataProvider seedTestStr_split + * @dataProvider seedTestStrSplit */ - public function testStr_split($string, $splitLen, $expected) + public function testStrSplit(string $string, int $splitLen, $expected): void { $this->assertEquals( $expected, @@ -594,16 +671,16 @@ public function testStr_split($string, $splitLen, $expected) } /** - * @testdox UTF-8 aware strcasecmp() is performed on a string + * @testdox UTF-8 aware strcasecmp() is performed on a string * * @param string $string1 String 1 to compare * @param string $string2 String 2 to compare * @param array|string|boolean $locale The locale used by strcoll or false to use classical comparison * @param integer $expected Expected result * - * @dataProvider seedTestStrcasecmp + * @dataProvider seedTestStrCaseCmp */ - public function testStrcasecmp(string $string1, string $string2, $locale, int $expected) + public function testStrCaseCmp(string $string1, string $string2, $locale, int $expected): void { // Convert the $locale param to a string if it is an array if (\is_array($locale)) @@ -611,19 +688,19 @@ public function testStrcasecmp(string $string1, string $string2, $locale, int $e $locale = "'" . implode("', '", $locale) . "'"; } - if (substr(php_uname(), 0, 6) == 'Darwin' && $locale != false) + if ($locale !== false && strpos(php_uname(), 'Darwin') === 0) { $this->markTestSkipped('Darwin bug prevents foreign conversion from working properly'); } - if ($locale != false && !setlocale(LC_COLLATE, $locale)) + if ($locale !== false && !setlocale(LC_COLLATE, $locale)) { - $this->markTestSkipped("Locale {$locale} is not available."); + $this->markTestSkipped("Locale $locale is not available."); } $actual = StringHelper::strcasecmp($string1, $string2, $locale); - if ($actual != 0) + if ($actual !== 0) { $actual /= abs($actual); } @@ -632,16 +709,16 @@ public function testStrcasecmp(string $string1, string $string2, $locale, int $e } /** - * @testdox UTF-8 aware strcmp() is performed on a string + * @testdox UTF-8 aware strcmp() is performed on a string * * @param string $string1 String 1 to compare * @param string $string2 String 2 to compare * @param mixed $locale The locale used by strcoll or false to use classical comparison * @param integer $expected Expected result * - * @dataProvider seedTestStrcmp + * @dataProvider seedTestStrCmp */ - public function testStrcmp(string $string1, string $string2, $locale, int $expected) + public function testStrCmp(string $string1, string $string2, $locale, int $expected): void { // Convert the $locale param to a string if it is an array if (\is_array($locale)) @@ -649,29 +726,29 @@ public function testStrcmp(string $string1, string $string2, $locale, int $expec $locale = "'" . implode("', '", $locale) . "'"; } - if (substr(php_uname(), 0, 6) == 'Darwin' && $locale != false) + if ($locale !== false && strpos(php_uname(), 'Darwin') === 0) { $this->markTestSkipped('Darwin bug prevents foreign conversion from working properly'); } - if ($locale != false && !setlocale(LC_COLLATE, $locale)) + if ($locale !== false && !setlocale(LC_COLLATE, $locale)) { // If the locale is not available, we can't have to transcode the string and can't reliably compare it. - $this->markTestSkipped("Locale {$locale} is not available."); + $this->markTestSkipped("Locale $locale is not available."); } $actual = StringHelper::strcmp($string1, $string2, $locale); - if ($actual != 0) + if ($actual !== 0) { - $actual = $actual / abs($actual); + $actual /= abs($actual); } $this->assertEquals($expected, $actual); } /** - * @testdox UTF-8 aware strcspn() is performed on a string + * @testdox UTF-8 aware strcspn() is performed on a string * * @param string $haystack The string to process * @param string $needles The mask @@ -679,9 +756,9 @@ public function testStrcmp(string $string1, string $string2, $locale, int $expec * @param integer|boolean $len Optional length * @param integer $expected Expected result * - * @dataProvider seedTestStrcspn + * @dataProvider seedTestStrCSpn */ - public function testStrcspn(string $haystack, string $needles, $start, $len, int $expected) + public function testStrCSpn(string $haystack, string $needles, $start, $len, int $expected): void { $this->assertEquals( $expected, @@ -690,15 +767,15 @@ public function testStrcspn(string $haystack, string $needles, $start, $len, int } /** - * @testdox UTF-8 aware stristr() is performed on a string + * @testdox UTF-8 aware stristr() is performed on a string * * @param string $haystack The haystack * @param string $needle The needle - * @param string|boolean $expect Expected result + * @param string|boolean $expected Expected result * - * @dataProvider seedTestStristr + * @dataProvider seedTestStrIStr */ - public function testStristr(string $haystack, string $needle, $expected) + public function testStrIStr(string $haystack, string $needle, $expected): void { $this->assertEquals( $expected, @@ -707,14 +784,14 @@ public function testStristr(string $haystack, string $needle, $expected) } /** - * @testdox UTF-8 aware strrev() is performed on a string + * @testdox UTF-8 aware strrev() is performed on a string * * @param string $string String to be reversed * @param string $expected Expected result * - * @dataProvider seedTestStrrev + * @dataProvider seedTestStrRev */ - public function testStrrev(string $string, string $expected) + public function testStrRev(string $string, string $expected): void { $this->assertEquals( $expected, @@ -723,17 +800,17 @@ public function testStrrev(string $string, string $expected) } /** - * @testdox UTF-8 aware strspn() is performed on a string + * @testdox UTF-8 aware strspn() is performed on a string * - * @param string $subject The haystack - * @param string $mask The mask - * @param integer|null $start Start optional - * @param integer|null $length Length optional - * @param integer $expect Expected result + * @param string $subject The haystack + * @param string $mask The mask + * @param integer|null $start Start optional + * @param integer|null $length Length optional + * @param integer $expected Expected result * - * @dataProvider seedTestStrspn + * @dataProvider seedTestStrSpn */ - public function testStrspn(string $subject, string $mask, $start, $length, int $expected) + public function testStrSpn(string $subject, string $mask, ?int $start, ?int $length, int $expected): void { $this->assertEquals( $expected, @@ -742,7 +819,7 @@ public function testStrspn(string $subject, string $mask, $start, $length, int $ } /** - * @testdox UTF-8 aware substr_replace() is performed on a string + * @testdox UTF-8 aware substr_replace() is performed on a string * * @param string $expected Expected result * @param string $string The haystack @@ -750,9 +827,9 @@ public function testStrspn(string $subject, string $mask, $start, $length, int $ * @param integer $start Start * @param integer|boolean|null $length Length (optional) * - * @dataProvider seedTestSubstr_replace + * @dataProvider seedTestSubstrReplace */ - public function testSubstr_replace(string $expected, string $string, string $replacement, int $start, $length) + public function testSubstrReplace(string $expected, string $string, string $replacement, int $start, $length): void { $this->assertEquals( $expected, @@ -761,15 +838,15 @@ public function testSubstr_replace(string $expected, string $string, string $rep } /** - * @testdox UTF-8 aware ltrim() is performed on a string + * @testdox UTF-8 aware ltrim() is performed on a string * * @param string $string The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim * @param string $expected Expected result * - * @dataProvider seedTestLtrim + * @dataProvider seedTestLTrim */ - public function testLtrim(string $string, $charlist, string $expected) + public function testLTrim(string $string, $charlist, string $expected): void { $this->assertEquals( $expected, @@ -778,15 +855,15 @@ public function testLtrim(string $string, $charlist, string $expected) } /** - * @testdox UTF-8 aware rtrim() is performed on a string + * @testdox UTF-8 aware rtrim() is performed on a string * * @param string $string The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim * @param string $expected Expected result * - * @dataProvider seedTestRtrim + * @dataProvider seedTestRTrim */ - public function testRtrim(string $string, $charlist, string $expected) + public function testRTrim(string $string, $charlist, string $expected): void { $this->assertEquals( $expected, @@ -795,7 +872,7 @@ public function testRtrim(string $string, $charlist, string $expected) } /** - * @testdox UTF-8 aware trim() is performed on a string + * @testdox UTF-8 aware trim() is performed on a string * * @param string $string The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim @@ -803,7 +880,7 @@ public function testRtrim(string $string, $charlist, string $expected) * * @dataProvider seedTestTrim */ - public function testTrim(string $string, $charlist, string $expected) + public function testTrim(string $string, $charlist, string $expected): void { $this->assertEquals( $expected, @@ -812,16 +889,16 @@ public function testTrim(string $string, $charlist, string $expected) } /** - * @testdox UTF-8 aware ucfirst() is performed on a string + * @testdox UTF-8 aware ucfirst() is performed on a string * * @param string $string String to be processed - * @param string|null $delimiter The words delimiter (null means do not split the string) - * @param string|null $newDelimiter The new words delimiter (null means equal to $delimiter) + * @param string|null $delimiter The delimiter (null means do not split the string) + * @param string|null $newDelimiter The new delimiter (null means equal to $delimiter) * @param string $expected Expected result * * @dataProvider seedTestUcfirst */ - public function testUcfirst(string $string, ?string $delimiter, ?string $newDelimiter, string $expected) + public function testUcfirst(string $string, ?string $delimiter, ?string $newDelimiter, string $expected): void { $this->assertEquals( $expected, @@ -830,14 +907,14 @@ public function testUcfirst(string $string, ?string $delimiter, ?string $newDeli } /** - * @testdox UTF-8 aware ucwords() is performed on a string + * @testdox UTF-8 aware ucwords() is performed on a string * * @param string $string String to be processed * @param string $expected Expected result * * @dataProvider seedTestUcwords */ - public function testUcwords(string $string, string $expected) + public function testUcwords(string $string, string $expected): void { $this->assertEquals( $expected, @@ -846,16 +923,16 @@ public function testUcwords(string $string, string $expected) } /** - * @testdox A string is transcoded + * @testdox A string is transcoded * * @param string $source The string to transcode. * @param string $fromEncoding The source encoding. * @param string $toEncoding The target encoding. - * @param string|null $expect Expected result. + * @param string|null $expected Expected result. * * @dataProvider seedTestTranscode */ - public function testTranscode(string $source, string $fromEncoding, string $toEncoding, ?string $expected) + public function testTranscode(string $source, string $fromEncoding, string $toEncoding, ?string $expected): void { $this->assertEquals( $expected, @@ -864,14 +941,14 @@ public function testTranscode(string $source, string $fromEncoding, string $toEn } /** - * @testdox A string is tested as valid UTF-8 + * @testdox A string is tested as valid UTF-8 * * @param string $string UTF-8 encoded string. * @param boolean $expected Expected result. * * @dataProvider seedCompliantStrings */ - public function testValid(string $string, bool $expected) + public function testValid(string $string, bool $expected): void { $this->assertEquals( $expected, @@ -880,14 +957,14 @@ public function testValid(string $string, bool $expected) } /** - * @testdox A string is converted from unicode to UTF-8 + * @testdox A string is converted from unicode to UTF-8 * - * @param string $string Unicode string to convert + * @param string $string The Unicode string to be converted * @param string $expected Expected result * * @dataProvider seedTestUnicodeToUtf8 */ - public function testUnicodeToUtf8(string $string, string $expected) + public function testUnicodeToUtf8(string $string, string $expected): void { $this->assertEquals( $expected, @@ -896,14 +973,14 @@ public function testUnicodeToUtf8(string $string, string $expected) } /** - * @testdox A string is converted from unicode to UTF-16 + * @testdox A string is converted from unicode to UTF-16 * - * @param string $string Unicode string to convert + * @param string $string The Unicode string to be converted * @param string $expected Expected result * * @dataProvider seedTestUnicodeToUtf16 */ - public function testUnicodeToUtf16(string $string, string $expected) + public function testUnicodeToUtf16(string $string, string $expected): void { $this->assertEquals( $expected, @@ -912,14 +989,14 @@ public function testUnicodeToUtf16(string $string, string $expected) } /** - * @testdox A string is checked for UTF-8 compliance + * @testdox A string is checked for UTF-8 compliance * * @param string $string UTF-8 string to check * @param boolean $expected Expected result * * @dataProvider seedCompliantStrings */ - public function testCompliant(string $string, bool $expected) + public function testCompliant(string $string, bool $expected): void { $this->assertEquals( $expected, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 844a69c7..2278bfba 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,5 @@ - - - src - - src/phputf8 - - - Tests From c092df67fcac9581e9e56b310037c3f022544c88 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Mon, 20 Sep 2021 13:41:18 +0200 Subject: [PATCH 2/9] Refactor - Don't use deprecated code internally --- Tests/InflectorTest.php | 94 ++++++++++++++++++++--------------------- Tests/NormaliseTest.php | 16 +++---- src/Inflector.php | 56 ++++++------------------ 3 files changed, 67 insertions(+), 99 deletions(-) diff --git a/Tests/InflectorTest.php b/Tests/InflectorTest.php index debdb3d8..71ed8a07 100644 --- a/Tests/InflectorTest.php +++ b/Tests/InflectorTest.php @@ -28,7 +28,7 @@ class InflectorTest extends TestCase protected $inflector; /** - * Method to seed data to testIsCountable. + * Seed data to testIsCountable. * * @return \Generator */ @@ -39,7 +39,7 @@ public function seedIsCountable(): \Generator } /** - * Method to seed data to testToPlural. + * Seed data to testToPlural. * * @return \Generator * @@ -106,42 +106,48 @@ protected function tearDown(): void } /** - * @testdox A rule cannot be added to the inflector if it is of an unsupported type - * @throws \ReflectionException + * @testdox A single word can be added to the inflector countable rules */ - public function testAddRuleException(): void + public function testAddCountableWord(): void { - $this->expectException(\InvalidArgumentException::class); + $this->assertFalse( + $this->inflector->isCountable('foo'), + '"foo" should not be known to the inflector by default.' + ); - TestHelper::invoke($this->inflector, 'addRule', new \stdClass, 'singular'); + $this->inflector->addCountableRule('foo'); + + $this->assertTrue( + $this->inflector->isCountable('foo'), + '"foo" should be known to the inflector after being set explicitely.' + ); } /** - * @testdox A countable rule can be added to the inflector - * @throws \ReflectionException + * @testdox An array of words can be added to the inflector countable rules */ - public function testAddCountableRule(): void + public function testAddCountableArray(): void { - // Add string. - $this->inflector->addCountableRule('foo'); - - $countable = TestHelper::getValue($this->inflector, 'countable'); + $this->assertFalse( + $this->inflector->isCountable('goo'), + '"goo" should not be known to the inflector by default.' + ); - $this->assertContains( - 'foo', - $countable['rules'], - 'Checks a countable rule was added.' + $this->assertFalse( + $this->inflector->isCountable('car'), + '"car" should not be known to the inflector by default.' ); - // Add array. - $this->inflector->addCountableRule(array('goo', 'car')); + $this->inflector->addCountableRule(['goo', 'car']); - $countable = TestHelper::getValue($this->inflector, 'countable'); + $this->assertTrue( + $this->inflector->isCountable('goo'), + '"goo" should be known to the inflector after being set explicitely.' + ); - $this->assertContains( - 'car', - $countable['rules'], - 'Checks a countable rule was added by array.' + $this->assertTrue( + $this->inflector->isCountable('car'), + '"car" should be known to the inflector after being set explicitely.' ); } @@ -199,43 +205,35 @@ public function testAddWordWithPlural(): void /** * @testdox A pluralisation rule can be added to the inflector - * @throws \ReflectionException */ public function testAddPluraliseRule(): void { - $this->assertSame( - $this->inflector->addPluraliseRule(['/^(custom)$/i' => '\1izables']), - $this->inflector, - 'Checks chaining.' - ); - - $plural = TestHelper::getValue(DoctrineInflector::class, 'plural'); + $this->inflector::rules('plural', ['/^(custom)$/i' => '\1izables']); - $this->assertArrayHasKey( - '/^(custom)$/i', - $plural['rules'], - 'Checks a pluralisation rule was added.' + $this->assertEquals( + 'customizables', + $this->inflector::pluralize('custom'), + '"custom" should become "customizables" after being set explicitely.' ); } /** * @testdox A singularisation rule can be added to the inflector - * @throws \ReflectionException */ public function testAddSingulariseRule(): void { - $this->assertSame( - $this->inflector->addSingulariseRule(['/^(inflec|contribu)tors$/i' => '\1ta']), - $this->inflector, - 'Checks chaining.' - ); + $this->inflector::rules('singular', ['/^(inflec|contribu)tors$/i' => '\1ta']); - $singular = TestHelper::getValue(DoctrineInflector::class, 'singular'); + $this->assertEquals( + 'inflecta', + $this->inflector::singularize('inflectors'), + '"inflectors" should become "inflecta" after being set explicitely.' + ); - $this->assertArrayHasKey( - '/^(inflec|contribu)tors$/i', - $singular['rules'], - 'Checks a singularisation rule was added.' + $this->assertEquals( + 'contributa', + $this->inflector::singularize('contributors'), + '"contributors" should become "contributa" after being set explicitely.' ); } diff --git a/Tests/NormaliseTest.php b/Tests/NormaliseTest.php index 48f11b06..9490bfe2 100644 --- a/Tests/NormaliseTest.php +++ b/Tests/NormaliseTest.php @@ -17,7 +17,7 @@ class NormaliseTest extends TestCase { /** - * Method to seed data to testFromCamelCase. + * Seed data to testFromCamelCase. * * @return \Generator * @@ -36,7 +36,7 @@ public function seedTestFromCamelCase(): \Generator } /** - * Method to seed data to testFromCamelCase. + * Seed data to testFromCamelCase. * * @return \Generator * @@ -51,7 +51,7 @@ public function seedTestFromCamelCaseNonGrouped(): \Generator } /** - * Method to seed data to testToCamelCase. + * Seed data to testToCamelCase. * * @return \Generator * @@ -68,7 +68,7 @@ public function seedTestToCamelCase(): \Generator } /** - * Method to seed data to testToDashSeparated. + * Seed data to testToDashSeparated. * * @return \Generator * @@ -88,7 +88,7 @@ public function seedTestToDashSeparated(): \Generator } /** - * Method to seed data to testToSpaceSeparated. + * Seed data to testToSpaceSeparated. * * @return \Generator * @@ -108,7 +108,7 @@ public function seedTestToSpaceSeparated(): \Generator } /** - * Method to seed data to testToUnderscoreSeparated. + * Seed data to testToUnderscoreSeparated. * * @return \Generator * @@ -128,7 +128,7 @@ public function seedTestToUnderscoreSeparated(): \Generator } /** - * Method to seed data to testToVariable. + * Seed data to testToVariable. * * @return \Generator * @@ -146,7 +146,7 @@ public function seedTestToVariable(): \Generator } /** - * Method to seed data to testToKey. + * Seed data to testToKey. * * @return \Generator * diff --git a/src/Inflector.php b/src/Inflector.php index ddaaf673..74882fa1 100644 --- a/src/Inflector.php +++ b/src/Inflector.php @@ -43,57 +43,25 @@ class Inflector extends DoctrineInflector ]; /** - * Adds inflection regex rules to the inflector. + * Adds a countable word. * - * @param mixed $data A string or an array of strings or regex rules to add. - * @param string $ruleType The rule type: singular | plural | countable + * @param mixed $data A string or an array of strings to add. * - * @return void + * @return $this * * @since 1.0 - * @throws \InvalidArgumentException */ - private function addRule($data, string $ruleType) + public function addCountableRule($data) { if (\is_string($data)) { $data = [$data]; } - elseif (!\is_array($data)) - { - throw new \InvalidArgumentException('Invalid inflector rule data.'); - } - elseif (!\in_array($ruleType, ['singular', 'plural', 'countable'])) - { - throw new \InvalidArgumentException('Unsupported rule type.'); - } - if ($ruleType === 'countable') - { - foreach ($data as $rule) - { - // Ensure a string is pushed. - array_push(self::$countable['rules'], (string) $rule); - } - } - else + foreach ($data as $rule) { - static::rules($ruleType, $data); + self::$countable['rules'][] = (string) $rule; } - } - - /** - * Adds a countable word. - * - * @param mixed $data A string or an array of strings to add. - * - * @return $this - * - * @since 1.0 - */ - public function addCountableRule($data) - { - $this->addRule($data, 'countable'); return $this; } @@ -164,6 +132,7 @@ public function addWord($singular, $plural = '') * * @since 1.0 * @deprecated 3.0 Use Doctrine\Common\Inflector\Inflector::rules() instead. + * @codeCoverageIgnore */ public function addPluraliseRule($data) { @@ -175,7 +144,7 @@ public function addPluraliseRule($data) DoctrineInflector::class ); - $this->addRule($data, 'plural'); + static::rules('plural', $data); return $this; } @@ -189,6 +158,7 @@ public function addPluraliseRule($data) * * @since 1.0 * @deprecated 3.0 Use Doctrine\Common\Inflector\Inflector::rules() instead. + * @codeCoverageIgnore */ public function addSingulariseRule($data) { @@ -200,7 +170,7 @@ public function addSingulariseRule($data) DoctrineInflector::class ); - $this->addRule($data, 'singular'); + static::rules('singular', $data); return $this; } @@ -248,7 +218,7 @@ public static function getInstance($new = false) */ public function isCountable($word) { - return \in_array($word, self::$countable['rules']); + return \in_array($word, self::$countable['rules'], true); } /** @@ -262,7 +232,7 @@ public function isCountable($word) */ public function isPlural($word) { - return $this->toPlural($this->toSingular($word)) === $word; + return static::pluralize(static::singularize($word)) === $word; } /** @@ -276,7 +246,7 @@ public function isPlural($word) */ public function isSingular($word) { - return $this->toSingular($word) === $word; + return static::singularize($word) === $word; } /** From e5552d9c3abfcea09bc45afd83b197bbd93d46d8 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Wed, 22 Sep 2021 00:11:48 +0200 Subject: [PATCH 3/9] Tests - Add and consolidate tests for maximum coverage (as preparation for replacement of the outdated phputf8 library) --- Tests/StringHelperTest.php | 1108 +++++++++++++++++++----------------- src/StringHelper.php | 410 ++++++------- 2 files changed, 792 insertions(+), 726 deletions(-) diff --git a/Tests/StringHelperTest.php b/Tests/StringHelperTest.php index 2d95ff26..7f18ccf2 100644 --- a/Tests/StringHelperTest.php +++ b/Tests/StringHelperTest.php @@ -15,6 +15,17 @@ */ class StringHelperTest extends TestCase { + const FRENCH_LOCALE = [ + 'fr_FR.utf8', + 'fr_FR.UTF-8', + 'fr_FR.UTF-8@euro', + 'French_Standard', + 'french', + 'fr_FR', + 'fre_FR' + ]; + const RUSSIAN_WIN_LOCALE = ['ru_RU.CP1251']; + /** * Data provider for testIncrement * @@ -22,656 +33,444 @@ class StringHelperTest extends TestCase */ public function seedTestIncrement(): \Generator { - // Note: string, style, number, expected - yield 'First default increment' => ['title', null, 0, 'title (2)']; - yield 'Second default increment' => ['title(2)', null, 0, 'title(3)']; - yield 'First dash increment' => ['title', 'dash', 0, 'title-2']; - yield 'Second dash increment' => ['title-2', 'dash', 0, 'title-3']; - yield 'Set default increment' => ['title', null, 4, 'title (4)']; - yield 'Unknown style fallback to default' => ['title', 'foo', 0, 'title (2)']; - } - - /** - * Data provider for testIs_ascii - * - * @return \Generator - */ - public function seedTestIsAscii(): \Generator - { - yield ['ascii', true]; - yield ['1024', true]; - yield ['#$#@$%', true]; - yield ['áÑ', false]; - yield ['ÿ©', false]; - yield ['¡¾', false]; - yield ['÷™', false]; - } - - /** - * Data provider for testStrpos - * - * @return \Generator - */ - public function seedTestStrPos(): \Generator - { - yield [3, 'missing', 'sing', 0]; - yield [false, 'missing', 'sting', 0]; - yield [4, 'missing', 'ing', 0]; - yield [10, ' объектов на карте с', 'на карте', 0]; - yield [0, 'на карте с', 'на карте', 0, 0]; - yield [false, 'на карте с', 'на каррте', 0]; - yield [false, 'на карте с', 'на карте', 2]; - yield [3, 'missing', 'sing', false]; - } - - /** - * Data provider for testStrrpos - * - * @return \Generator - * - * @since 1.0 - */ - public function seedTestStrRPos(): \Generator - { - yield [3, 'missing', 'sing', 0]; - yield [false, 'missing', 'sting', 0]; - yield [4, 'missing', 'ing', 0]; - yield [10, ' объектов на карте с', 'на карте', 0]; - yield [0, 'на карте с', 'на карте', 0]; - yield [false, 'на карте с', 'на каррте', 0]; - yield [3, 'на карте с', 'карт', 2]; - } - - /** - * Data provider for testSubstr - * - * @return \Generator - */ - public function seedTestSubstr(): \Generator - { - yield ['issauga', 'Mississauga', 4, false]; - yield ['на карте с', ' объектов на карте с', 10, false]; - yield ['на ка', ' объектов на карте с', 10, 5]; - yield ['те с', ' объектов на карте с', -4, false]; - yield [false, ' объектов на карте с', 99, false]; - } - - /** - * Data provider for testStrtolower - * - * @return \Generator - */ - public function seedTestStrToLower(): \Generator - { - yield ['Joomla! Rocks', 'joomla! rocks']; - } - - /** - * Data provider for testStrtoupper - * - * @return \Generator - */ - public function seedTestStrToUpper(): \Generator - { - yield ['Joomla! Rocks', 'JOOMLA! ROCKS']; - } - - /** - * Data provider for testStrlen - * - * @return \Generator - */ - public function seedTestStrLen(): \Generator - { - yield ['Joomla! Rocks', 13]; - } - - /** - * Data provider for testStr_ireplace - * - * @return \Generator - */ - public function seedTestStrIReplace(): \Generator - { - yield ['Pig', 'cow', 'the pig jumped', false, 'the cow jumped']; - yield ['Pig', 'cow', 'the pig jumped', true, 'the cow jumped']; - yield ['Pig', 'cow', 'the pig jumped over the cow', true, 'the cow jumped over the cow']; - yield [ - ['PIG', 'JUMPED'], - ['cow', 'hopped'], - 'the pig jumped over the pig', - true, - 'the cow hopped over the cow' - ]; - yield ['шил', 'биш', 'Би шил идэй чадна', true, 'Би биш идэй чадна']; - yield ['/', ':', '/test/slashes/', true, ':test:slashes:']; - } - - /** - * Data provider for testStr_split - * - * @return \Generator - */ - public function seedTestStrSplit(): \Generator - { - yield ['string', 1, ['s', 't', 'r', 'i', 'n', 'g']]; - yield ['string', 2, ['st', 'ri', 'ng']]; - yield ['волн', 3, ['вол', 'н']]; - yield ['волн', 1, ['в', 'о', 'л', 'н']]; + // Note: input string, incrementation style, next number, expected result + yield 'appends " (2)" to an unnumbered string (default style)' => ['title', null, 0, 'title (2)']; + yield 'increments a trailing number by 1 (default style)' => ['title(2)', null, 0, 'title(3)']; + yield 'appends "-2" to an unnumbered string (dash style)' => ['title', 'dash', 0, 'title-2']; + yield 'increments a trailing number by 1 (dash style)' => ['title-2', 'dash', 0, 'title-3']; + yield 'sets the number to the value provided' => ['title', null, 4, 'title (4)']; + yield 'uses default style, if an unknown style is provided' => ['title', 'foo', 0, 'title (2)']; } /** - * Data provider for testStrcasecmp + * @testdox StringHelper::increment() $_dataName * - * @return \Generator - */ - public function seedTestStrCaseCmp(): \Generator - { - yield ['THIS IS STRING1', 'this is string1', false, 0]; - yield ['this is string1', 'this is string2', false, -1]; - yield ['this is string2', 'this is string1', false, 1]; - yield ['бгдпт', 'бгдпт', false, 0]; - yield [ - 'àbc', - 'abc', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - 1 - ]; - yield [ - 'àbc', - 'bcd', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; - yield [ - 'é', - 'è', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; - yield [ - 'É', - 'é', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - 0 - ]; - yield [ - 'œ', - 'p', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; - yield [ - 'œ', - 'n', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - 1 - ]; - } - - /** - * Data provider for testStrcmp + * @param string $string The source string. + * @param string|null $style The style (default|dash). + * @param integer|null $number If supplied and > 0, this number is used for the copy, otherwise it is the 'next' number. + * @param string $expected Expected result. * - * @return \Generator - * - * @since 1.0 + * @dataProvider seedTestIncrement */ - public function seedTestStrCmp(): \Generator + public function testIncrement(string $string, ?string $style, ?int $number, string $expected): void { - yield ['THIS IS STRING1', 'this is string1', false, -1]; - yield ['this is string1', 'this is string2', false, -1]; - yield ['this is string2', 'this is string1', false, 1]; - yield ['a', 'B', false, 1]; - yield ['A', 'b', false, -1]; - yield [ - 'Àbc', - 'abc', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - 1 - ]; - yield [ - 'Àbc', - 'bcd', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; - yield [ - 'É', - 'è', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; - yield [ - 'é', - 'È', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; - yield [ - 'Œ', - 'p', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; - yield [ - 'Œ', - 'n', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - 1 - ]; - yield [ - 'œ', - 'N', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - 1 - ]; - yield [ - 'œ', - 'P', - ['fr_FR.utf8', 'fr_FR.UTF-8', 'fr_FR.UTF-8@euro', 'French_Standard', 'french', 'fr_FR', 'fre_FR'], - -1 - ]; + $this->assertEquals( + $expected, + StringHelper::increment($string, $style, $number) + ); } /** - * Data provider for testStrcspn + * Data provider for testIsAscii * * @return \Generator - * - * @since 1.0 */ - public function seedTestStrCSpn(): \Generator + public function seedTestIsAscii(): \Generator { - yield ['subject string ', '<>', false, false, 8]; - yield ['Би шил {123} идэй {456} чадна', '}{', null, false, 7]; - yield ['Би шил {123} идэй {456} чадна', '}{', 13, 10, 5]; + // Note: input string, expected result + yield '7bit ASCII letters' => ['ascii', true]; + yield 'ASCII numbers' => ['1024', true]; + yield 'ASCII special characters' => ['#$#@$%', true]; + yield 'characters above code 128' => ['áÑÿ©¡¾÷™', false]; + yield 'cyrillic letters' => ['на карте с', false]; + yield 'greek letters' => ['ψυχοφθόρα', false]; + yield 'chinese letters' => ['我能吞', false]; } /** - * Data provider for testStristr + * @testdox StringHelper::is_ascii() correctly recognises $_dataName * - * @return \Generator + * @param string $string The string to test. + * @param boolean $expected Expected result. * - * @since 1.0 + * @dataProvider seedTestIsAscii */ - public function seedTestStrIStr(): \Generator + public function testIsAscii(string $string, bool $expected): void { - yield ['haystack', 'needle', false]; - yield ['before match, after match', 'match', 'match, after match']; - yield ['Би шил идэй чадна', 'шил', 'шил идэй чадна']; + $this->assertEquals( + $expected, + StringHelper::is_ascii($string) + ); } /** - * Data provider for testStrrev + * Data provider for testOrd * * @return \Generator - * - * @since 1.0 */ - public function seedTestStrRev(): \Generator + public function seedTestOrd(): \Generator { - yield ['abc def', 'fed cba']; - yield ['Би шил', 'лиш иБ']; + yield 'lowercase ASCII characters' => ['abc', 97]; + yield 'uppercase ASCII characters' => ['A', 65]; + yield 'cyrillic characters' => ['на', 1085]; + yield 'greek characters' => ['ψ', 968]; + yield 'chinese characters' => ['我能吞', 25105]; } /** - * Data provider for testStrspn + * @testdox SringHelper::ord() returns the ordinal number for $_dataName * - * @return \Generator + * @param string $character + * @param integer $ordinalNumber * - * @since 1.0 + * @dataProvider seedTestOrd */ - public function seedTestStrSpn(): \Generator + public function testOrd(string $character, int $ordinalNumber): void { - yield ['A321 Main Street', '0123456789', 1, 2, 2]; - yield ['321 Main Street', '0123456789', null, 2, 2]; - yield ['A321 Main Street', '0123456789', null, 10, 0]; - yield ['321 Main Street', '0123456789', null, null, 3]; - yield ['Main Street 321', '0123456789', null, -3, 0]; - yield ['321 Main Street', '0123456789', null, -13, 2]; - yield ['321 Main Street', '0123456789', null, -12, 3]; - yield ['A321 Main Street', '0123456789', 0, null, 0]; - yield ['A321 Main Street', '0123456789', 1, 10, 3]; - yield ['A321 Main Street', '0123456789', 1, null, 3]; - yield ['Би шил идэй чадна', 'Би', null, null, 2]; - yield ['чадна Би шил идэй чадна', 'Би', null, null, 0]; - } - - /** - * Data provider for testSubstr_replace - * - * @return \Generator - * - * @since 1.0 - */ - public function seedTestSubstrReplace(): \Generator - { - yield ['321 Broadway Avenue', '321 Main Street', 'Broadway Avenue', 4, false]; - yield ['321 Broadway Street', '321 Main Street', 'Broadway', 4, 4]; - yield ['чадна 我能吞', 'чадна Би шил идэй чадна', '我能吞', 6, false]; - yield ['чадна 我能吞 шил идэй чадна', 'чадна Би шил идэй чадна', '我能吞', 6, 2]; + $this->assertEquals( + $ordinalNumber, + StringHelper::ord($character) + ); } /** - * Data provider for testLtrim + * Data provider for testStrPos * * @return \Generator - * - * @since 1.0 */ - public function seedTestLTrim(): \Generator + public function seedTestStrPos(): \Generator { - yield [' abc def', false, 'abc def']; - yield [' abc def', '', ' abc def']; - yield [' Би шил', false, 'Би шил']; - yield ["\t\n\r\x0BБи шил", false, 'Би шил']; - yield ["\x0B\t\n\rБи шил", "\t\n\x0B", "\rБи шил"]; - yield ["\x09Би шил\x0A", "\x09\x0A", "Би шил\x0A"]; - yield ['1234abc', '0123456789', 'abc']; + // Note: haystack, needle, offset, expected result + yield 'returns the position of the first occurance of the substring' => ['pinging', 'ing', 0, 1]; + yield 'locates substring in ASCII string' => ['missing', 'sing', 0, 3]; + yield 'locates substring in string with accents' => ['Fábio', 'b', 0, 2]; + yield 'locates substring in cyrillic string' => [' объектов на карте с', 'на карте', 0, 10]; + yield 'locates substring beginning in first position' => ['на карте с', 'на карте', 0, 0, 0]; + yield 'returns false for non-existing substrings' => ['missing', 'sting', 0, false]; + yield 'starts search at the given offset' => ['на карте с', 'на карте', 2, false]; + yield 'starts search at position 0 if no offset is provided' => ['missing', 'mis', null, 0]; } /** - * Data provider for testRtrim + * @testdox StringHelper::strpos() $_dataName * - * @return \Generator + * @param string $haystack String being examined + * @param string $needle String being searched for + * @param integer|null|boolean $offset The position from which the search should be performed + * @param string|boolean $expected Expected result * - * @since 1.0 + * @dataProvider seedTestStrPos */ - public function seedTestRTrim(): \Generator + public function testStrPos(string $haystack, string $needle, $offset, $expected): void { - yield ['abc def ', false, 'abc def']; - yield ['abc def ', '', 'abc def ']; - yield ['Би шил ', false, 'Би шил']; - yield ["Би шил\t\n\r\x0B", false, 'Би шил']; - yield ["Би шил\r\x0B\t\n", "\t\n\x0B", "Би шил\r"]; - yield ["\x09Би шил\x0A", "\x09\x0A", "\x09Би шил"]; - yield ['1234abc', 'abc', '1234']; + $this->assertEquals( + $expected, + StringHelper::strpos($haystack, $needle, $offset) + ); } /** - * Data provider for testTrim + * Data provider for testStrRPos * * @return \Generator * * @since 1.0 */ - public function seedTestTrim(): \Generator + public function seedTestStrRPos(): \Generator { - yield [' abc def ', false, 'abc def']; - yield [' abc def ', '', ' abc def ']; - yield [' Би шил ', false, 'Би шил']; - yield ["\t\n\r\x0BБи шил\t\n\r\x0B", false, 'Би шил']; - yield ["\x0B\t\n\rБи шил\r\x0B\t\n", "\t\n\x0B", "\rБи шил\r"]; - yield ["\x09Би шил\x0A", "\x09\x0A", "Би шил"]; - yield ['1234abc56789', '0123456789', 'abc']; + // Note: haystack, needle, offset, expected result + yield 'returns the position of the last occurance of the substring' => ['pinging', 'ing', 0, 4]; + yield 'locates substring in ASCII string' => ['missing', 'sing', 0, 3]; + yield 'locates substring in cyrillic string' => [' объектов на карте с', 'на карте', 0, 10]; + yield 'returns false for non-existing substrings' => ['missing', 'sting', 0, false]; + yield 'locates substring beginning in first position' => ['на карте с', 'на карте', 0, 0]; + yield 'starts search at the given offset' => ['на карте с', 'карт', 2, 3]; } /** - * Data provider for testUcfirst + * @testdox StringHelper::strrpos() $_dataName * - * @return \Generator + * @param string $haystack String being examined + * @param string $needle String being searched for + * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed + * @param string|boolean $expected Expected result * - * @since 1.0 + * @dataProvider seedTestStrRPos */ - public function seedTestUcfirst(): \Generator + public function testStrRPos(string $haystack, string $needle, int $offset, $expected): void { - yield ['george', null, null, 'George']; - yield ['мога', null, null, 'Мога']; - yield ['ψυχοφθόρα', null, null, 'Ψυχοφθόρα']; - yield ['dr jekill and mister hyde', ' ', null, 'Dr Jekill And Mister Hyde']; - yield ['dr jekill and mister hyde', ' ', '_', 'Dr_Jekill_And_Mister_Hyde']; - yield ['dr jekill and mister hyde', ' ', '', 'DrJekillAndMisterHyde']; + $this->assertEquals( + $expected, + StringHelper::strrpos($haystack, $needle, $offset) + ); } /** - * Data provider for testUcwords + * Data provider for testSubstr * * @return \Generator - * - * @since 1.0 */ - public function seedTestUcwords(): \Generator + public function seedTestSubstr(): \Generator { - yield ['george washington', 'George Washington']; - yield ["george\r\nwashington", "George\r\nWashington"]; - yield ['мога', 'Мога']; - yield ['αβγ δεζ', 'Αβγ Δεζ']; - yield ['åbc öde', 'Åbc Öde']; + // Note: string, offset, length, expected result + yield 'extracts substring from offset to end, if no length is provided' => ['Mississauga', 4, null, 'issauga']; + yield 'extracts substring from cyrillic string' => [' объектов на карте с', 10, null, 'на карте с']; + yield 'extracts substring of given length' => [' объектов на карте с', 10, 5, 'на ка']; + yield 'extracts substring from the end, if offset is negative' => [' объектов на карте с', -4, null, 'те с']; + yield 'returns false, if offset is out of bounds' => [' объектов на карте с', 99, null, false]; } /** - * Data provider for testTranscode + * @testdox StringHelper::substr() $_dataName * - * @return \Generator + * @param string $string String being processed + * @param integer $start Number of UTF-8 characters offset (from left) + * @param integer|null $length Optional, specifies the length + * @param string|boolean $expected Expected result * - * @since 1.0 + * @dataProvider seedTestSubstr */ - public function seedTestTranscode(): \Generator + public function testSubstr(string $string, int $start, $length, $expected): void { - yield ['Åbc Öde €100', 'UTF-8', 'ISO-8859-1', "\xc5bc \xd6de EUR100"]; + $this->assertEquals( + $expected, + StringHelper::substr($string, $start, $length) + ); } /** - * Data provider for testing compliant strings + * Data provider for testStrToLower * * @return \Generator - * - * @since 1.0 */ - public function seedCompliantStrings(): \Generator + public function seedTestStrToLower(): \Generator { - yield ["\xCF\xB0", true]; - yield ["\xFBa", false]; - yield ["\xFDa", false]; - yield ["foo\xF7bar", false]; - yield ['george Мога Ž Ψυχοφθόρα ฉันกินกระจกได้ 我能吞下玻璃而不伤身体 ', true]; - yield ["\xFF ABC", false]; - yield ["0xfffd ABC", true]; - yield ['', true]; + yield 'converts ASCII string' => ['Joomla! Rocks', 'joomla! rocks']; + yield 'converts cyrillic string' => ['На Карте С', 'на карте с']; + yield 'converts greek string' => ['Ψυχοφθόρα', 'ψυχοφθόρα']; + yield 'leaves chinese string alone' => ['我能吞', '我能吞']; } /** - * Data provider for testUnicodeToUtf8 + * @testdox StringHelper::strtolower() $_dataName * - * @return \Generator + * @param string $string String being processed + * @param string|boolean $expected Expected result * - * @since 1.2.0 + * @dataProvider seedTestStrToLower */ - public function seedTestUnicodeToUtf8(): \Generator + public function testStrToLower(string $string, $expected): void { - yield ["\u0422\u0435\u0441\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b", "Тест системы"]; - yield ["\u00dcberpr\u00fcfung der Systemumstellung", "Überprüfung der Systemumstellung"]; + $this->assertEquals( + $expected, + StringHelper::strtolower($string) + ); } /** - * Data provider for testUnicodeToUtf16 + * Data provider for testStrToUpper * * @return \Generator - * - * @since 1.2.0 */ - public function seedTestUnicodeToUtf16(): \Generator + public function seedTestStrToUpper(): \Generator { - yield ["\u0422\u0435\u0441\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b", "Тест системы"]; - yield ["\u00dcberpr\u00fcfung der Systemumstellung", "Überprüfung der Systemumstellung"]; + yield 'converts ASCII string' => ['Joomla! Rocks', 'JOOMLA! ROCKS']; + yield 'converts cyrillic string' => ['На Карте С', 'НА КАРТЕ С']; + yield 'converts greek string' => ['Ψυχοφθόρα', 'ΨΥΧΟΦΘΌΡΑ']; + yield 'leaves chinese string alone' => ['我能吞', '我能吞']; } /** - * @testdox A string is correctly incremented + * @testdox StringHelper::strtoupper() $_dataName * - * @param string $string The source string. - * @param string|null $style The style (default|dash). - * @param integer $number If supplied, this number is used for the copy, otherwise it is the 'next' number. - * @param string $expected Expected result. + * @param string $string String being processed + * @param string|boolean $expected Expected result * - * @dataProvider seedTestIncrement + * @dataProvider seedTestStrToUpper */ - public function testIncrement(string $string, ?string $style, int $number, string $expected): void + public function testStrToUpper(string $string, $expected): void { $this->assertEquals( $expected, - StringHelper::increment($string, $style, $number) + StringHelper::strtoupper($string) ); } /** - * @testdox A string is checked to determine if it is ASCII + * Data provider for testStrLen * - * @param string $string The string to test. - * @param boolean $expected Expected result. - * - * @dataProvider seedTestIsAscii + * @return \Generator */ - public function testIsAscii(string $string, bool $expected): void + public function seedTestStrLen(): \Generator { - $this->assertEquals( - $expected, - StringHelper::is_ascii($string) - ); + yield 'an ASCII string' => ['Joomla! Rocks', 13]; + yield 'a cyrillic string' => ['На Карте С', 10]; + yield 'a greek string' => ['Ψυχοφθόρα', 9]; + yield 'a chinese string' => ['我能吞', 3]; } /** - * @testdox UTF-8 aware strpos() is performed on a string + * @testdox StringHelper::strlen() determines the length of $_dataName * - * @param string|boolean $expected Expected result - * @param string $haystack String being examined - * @param string $needle String being searched for - * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed + * @param string $string String being processed + * @param string|boolean $expected Expected result * - * @dataProvider seedTestStrPos + * @dataProvider seedTestStrLen */ - public function testStrPos($expected, string $haystack, string $needle, $offset = 0): void + public function testStrLen(string $string, $expected): void { $this->assertEquals( $expected, - StringHelper::strpos($haystack, $needle, $offset) + StringHelper::strlen($string) ); } /** - * @testdox UTF-8 aware strrpos() is performed on a string - * - * @param string|boolean $expected Expected result - * @param string $haystack String being examined - * @param string $needle String being searched for - * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed + * Data provider for testStrIReplace * - * @dataProvider seedTestStrRPos + * @return \Generator */ - public function testStrRPos($expected, string $haystack, string $needle, int $offset = 0): void + public function seedTestStrIReplace(): \Generator { - $this->assertEquals( - $expected, - StringHelper::strrpos($haystack, $needle, $offset) - ); + // Note: search, replace, subject, count, expected result + yield 'does not require "count" variable' => ['Pig', 'cow', 'the pig jumped', null, 'the cow jumped']; + yield 'counts the number of replacements' => ['Pig', 'cow', 'the pig jumped', 1, 'the cow jumped']; + yield 'supports arrays for search and replace values' => [ + ['PIG', 'JUMPED'], + ['cow', 'hopped'], + 'the pig jumped over the pig', + 3, + 'the cow hopped over the cow' + ]; + yield 'operates on cyrillic string' => ['шил', 'биш', 'Би шил идэй чадна', 1, 'Би биш идэй чадна']; + yield 'replaces special characters' => ['/', ':', '/test/slashes/', 3, ':test:slashes:']; + yield 'performes replacement on the result of previous replacement' => [ + ['Pig', 'cow'], + ['cow', 'dog'], + 'the pig jumped over the cow', + 3, + 'the dog jumped over the dog' + ]; } /** - * @testdox UTF-8 aware substr() is performed on a string + * @testdox StringHelper::str_ireplace() $_dataName * - * @param string|boolean $expected Expected result - * @param string $string String being processed - * @param integer $start Number of UTF-8 characters offset (from left) - * @param integer|null|boolean $length Optional, specifies the length + * @param string[]|string $search String to search + * @param string[]|string $replace Existing string to replace + * @param string $subject New string to replace with + * @param integer|null $expectedCount Optional count value to be passed by reference + * @param string $expectedResult Expected result * - * @dataProvider seedTestSubstr + * @return void + * + * @dataProvider seedTestStrIReplace */ - public function testSubstr($expected, string $string, int $start, $length = false): void - { - $this->assertEquals( - $expected, - StringHelper::substr($string, $start, $length) - ); + public function testStrIReplace( + $search, + $replace, + string $subject, + ?int $expectedCount, + string $expectedResult + ): void { + $actualCount = null; + + if ($expectedCount !== null) + { + $actualResult = StringHelper::str_ireplace($search, $replace, $subject, $actualCount); + } + else + { + $actualResult = StringHelper::str_ireplace($search, $replace, $subject); + } + + $this->assertEquals($expectedResult, $actualResult); + $this->assertEquals($expectedCount, $actualCount); } /** - * @testdox UTF-8 aware strtolower() is performed on a string - * - * @param string $string String being processed - * @param string|boolean $expected Expected result + * Data provider for testStrPad * - * @dataProvider seedTestStrToLower + * @return \Generator */ - public function testStrToLower(string $string, $expected): void + public function seedTestStrPad(): \Generator { - $this->assertEquals( - $expected, - StringHelper::strtolower($string) - ); + // Note: input, length, padStr, type, expected result + yield 'can pad to the right' => ['foo', 5, ' ', STR_PAD_RIGHT, 'foo ']; + yield 'can pad to the left' => ['foo', 5, ' ', STR_PAD_LEFT, ' foo']; + yield 'can pad to both sides' => ['foo', 5, ' ', STR_PAD_BOTH, ' foo ']; + yield 'truncates the pad string to fit' => ['foo', 7, 'bar', STR_PAD_BOTH, 'bafooba']; + yield 'can pad a cyrillic string' => ['На Карте С', 12, 'т', STR_PAD_RIGHT, 'На Карте Стт']; + yield 'can pad a greek string' => ['Ψυχοφθόρα', 11, 'φ', STR_PAD_RIGHT, 'Ψυχοφθόραφφ']; + yield 'can pad a chinese string' => ['我能吞', 5, '我', STR_PAD_RIGHT, '我能吞我我']; } /** - * @testdox UTF-8 aware strtoupper() is performed on a string + * @testdox StringHelper::strpad() $_dataName * - * @param string $string String being processed - * @param string|boolean $expected Expected result + * @dataProvider seedTestStrPad * - * @dataProvider seedTestStrToUpper + * @param string $string The input string. + * @param int $length Desired string length + * @param string $padString The string to add; may be truncated + * @param int $padType One of STR_PAD_RIGHT, STR_PAD_LEFT or STR_PAD_BOTH + * @param string $expected The expected result */ - public function testStrToUpper(string $string, $expected): void + public function testStrPad(string $string, int $length, string $padString, int $padType, string $expected): void { $this->assertEquals( $expected, - StringHelper::strtoupper($string) + StringHelper::str_pad($string, $length, $padString, $padType) ); } /** - * @testdox UTF-8 aware strlen() is performed on a string + * Data provider for testStrSplit * - * @param string $string String being processed - * @param string|boolean $expected Expected result - * - * @dataProvider seedTestStrLen + * @return \Generator */ - public function testStrLen(string $string, $expected): void + public function seedTestStrSplit(): \Generator { - $this->assertEquals( - $expected, - StringHelper::strlen($string) - ); + yield 'splits into single characters by default' => ['string', null, ['s', 't', 'r', 'i', 'n', 'g']]; + yield 'splits into chunks of given size' => ['strings', 2, ['st', 'ri', 'ng', 's']]; + yield 'splits cyrillic strings' => ['волн', 1, ['в', 'о', 'л', 'н']]; } /** - * @testdox UTF-8 aware str_ireplace() is performed on a string - * - * @param string[]|string $search String to search - * @param string[]|string $replace Existing string to replace - * @param string $subject New string to replace with - * @param integer|null|boolean $count Optional count value to be passed by reference - * @param string $expected Expected result + * @testdox StringHelper::str_split() $_dataName * - * @return void + * @param string $string UTF-8 encoded string to process + * @param integer|null $splitLen Number to characters to split string by + * @param array $expected Expected result * - * @dataProvider seedTestStrIReplace + * @dataProvider seedTestStrSplit */ - public function testStrIReplace($search, $replace, string $subject, $count, string $expected): void + public function testStrSplit(string $string, ?int $splitLen, array $expected): void { - $this->assertEquals( - $expected, - StringHelper::str_ireplace($search, $replace, $subject, $count) - ); + if ($splitLen !== null) + { + $actual = StringHelper::str_split($string, $splitLen); + } + else + { + $actual = StringHelper::str_split($string); + } + + $this->assertEquals($expected, $actual); } /** - * @testdox UTF-8 aware str_split() is performed on a string + * Data provider for testStrCaseCmp * - * @param string $string UTF-8 encoded string to process - * @param integer $splitLen Number to characters to split string by - * @param array|string|boolean $expected Expected result - * - * @dataProvider seedTestStrSplit + * @return \Generator */ - public function testStrSplit(string $string, int $splitLen, $expected): void + public function seedTestStrCaseCmp(): \Generator { - $this->assertEquals( - $expected, - StringHelper::str_split($string, $splitLen) - ); + yield 'A = a with default locale' => ['THIS IS STRING1', 'this is string1', false, 0]; + yield 'a < b with default locale' => ['this is string1', 'this is string2', false, -1]; + yield 'b > a with default locale' => ['this is string2', 'this is string1', false, 1]; + yield 'cyrillic д = д with default locale' => ['бгдпт', 'бгдпт', false, 0]; + yield 'à > a with french locale' => ['àbc', 'abc', self::FRENCH_LOCALE, 1]; + yield 'à < b with french locale' => ['àbc', 'bcd', self::FRENCH_LOCALE, -1]; + yield 'é < è with french locale' => ['é', 'è', self::FRENCH_LOCALE, -1]; + yield 'É = é with french locale' => ['É', 'é', self::FRENCH_LOCALE, 0]; + yield 'œ < p with french locale' => ['œ', 'p', self::FRENCH_LOCALE, -1]; + yield 'œ > n with french locale' => ['œ', 'n', self::FRENCH_LOCALE, 1]; + yield 'cyrillic р < т with russian locale' => ['р', 'т', self::RUSSIAN_WIN_LOCALE, -1]; } /** - * @testdox UTF-8 aware strcasecmp() is performed on a string + * @testdox StringHelper::strcasecmp() compares $_dataName * * @param string $string1 String 1 to compare * @param string $string2 String 2 to compare @@ -682,20 +481,19 @@ public function testStrSplit(string $string, int $splitLen, $expected): void */ public function testStrCaseCmp(string $string1, string $string2, $locale, int $expected): void { - // Convert the $locale param to a string if it is an array - if (\is_array($locale)) - { - $locale = "'" . implode("', '", $locale) . "'"; - } - if ($locale !== false && strpos(php_uname(), 'Darwin') === 0) { $this->markTestSkipped('Darwin bug prevents foreign conversion from working properly'); } - if ($locale !== false && !setlocale(LC_COLLATE, $locale)) + if ($locale !== false && setlocale(LC_COLLATE, $locale) === false) { - $this->markTestSkipped("Locale $locale is not available."); + $this->markTestSkipped( + sprintf( + "Locale %s is not available.", + implode(', ', (array)$locale) + ) + ); } $actual = StringHelper::strcasecmp($string1, $string2, $locale); @@ -709,32 +507,55 @@ public function testStrCaseCmp(string $string1, string $string2, $locale, int $e } /** - * @testdox UTF-8 aware strcmp() is performed on a string + * Data provider for testStrCmp + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestStrCmp(): \Generator + { + yield 'A < a with default locale' => ['THIS IS STRING1', 'this is string1', false, -1]; + yield 'a < b with default locale' => ['this is string1', 'this is string2', false, -1]; + yield 'b > a with default locale' => ['this is string2', 'this is string1', false, 1]; + yield 'a > B with default locale' => ['a', 'B', false, 1]; + yield 'A < b with default locale' => ['A', 'b', false, -1]; + yield 'À > a with french locale' => ['Àbc', 'abc', self::FRENCH_LOCALE, 1]; + yield 'À < b with french locale' => ['Àbc', 'bcd', self::FRENCH_LOCALE, -1]; + yield 'É < è with french locale' => ['É', 'è', self::FRENCH_LOCALE, -1]; + yield 'é < È with french locale' => ['é', 'È', self::FRENCH_LOCALE, -1]; + yield 'Œ < p with french locale' => ['Œ', 'p', self::FRENCH_LOCALE, -1]; + yield 'Œ > n with french locale' => ['Œ', 'n', self::FRENCH_LOCALE, 1]; + yield 'œ > N with french locale' => ['œ', 'N', self::FRENCH_LOCALE, 1]; + yield 'œ < P with french locale' => ['œ', 'P', self::FRENCH_LOCALE, -1]; + yield 'cyrillic р < т with russian locale' => ['р', 'т', self::RUSSIAN_WIN_LOCALE, -1]; + } + + /** + * @testdox StringHelper::strcmp() compares $_dataName * - * @param string $string1 String 1 to compare - * @param string $string2 String 2 to compare - * @param mixed $locale The locale used by strcoll or false to use classical comparison - * @param integer $expected Expected result + * @param string $string1 String 1 to compare + * @param string $string2 String 2 to compare + * @param string[]|string $locale The locale used by strcoll or false to use classical comparison + * @param integer $expected Expected result * * @dataProvider seedTestStrCmp */ public function testStrCmp(string $string1, string $string2, $locale, int $expected): void { - // Convert the $locale param to a string if it is an array - if (\is_array($locale)) - { - $locale = "'" . implode("', '", $locale) . "'"; - } - if ($locale !== false && strpos(php_uname(), 'Darwin') === 0) { $this->markTestSkipped('Darwin bug prevents foreign conversion from working properly'); } - if ($locale !== false && !setlocale(LC_COLLATE, $locale)) + if ($locale !== false && setlocale(LC_COLLATE, $locale) === false) { - // If the locale is not available, we can't have to transcode the string and can't reliably compare it. - $this->markTestSkipped("Locale $locale is not available."); + $this->markTestSkipped( + sprintf( + "Locale %s is not available.", + implode(', ', (array)$locale) + ) + ); } $actual = StringHelper::strcmp($string1, $string2, $locale); @@ -748,7 +569,23 @@ public function testStrCmp(string $string1, string $string2, $locale, int $expec } /** - * @testdox UTF-8 aware strcspn() is performed on a string + * Data provider for testStrCSpn + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestStrCSpn(): \Generator + { + // Note: haystack, needles, start, len, expected result + yield 'an ASCII string' => ['subject string ', '<>', null, null, 8]; + yield 'an ASCII string given a start position' => ['subject string ', '<>', 1, null, 7]; + yield 'a cyrillic string' => ['Би шил {123} идэй {456} чадна', '}{', null, null, 7]; + yield 'a limited substring' => ['Би шил {123} идэй {456} чадна', '}{', 13, 10, 5]; + } + + /** + * @testdox StringHelper::strcspn() finds length of non-matching segment in $_dataName * * @param string $haystack The string to process * @param string $needles The mask @@ -767,7 +604,22 @@ public function testStrCSpn(string $haystack, string $needles, $start, $len, int } /** - * @testdox UTF-8 aware stristr() is performed on a string + * Data provider for testStrIStr + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestStrIStr(): \Generator + { + yield 'non-matching needle' => ['haystack', 'needle', false]; + yield 'match case needle' => ['before match, after match', 'match', 'match, after match']; + yield 'non-match case needle' => ['before match, after match', 'MATCH', 'match, after match']; + yield 'cyrillic strings' => ['Би шил идэй чадна', 'шил', 'шил идэй чадна']; + } + + /** + * @testdox StringHelper::stristr() finds the first occurrence of a string * * @param string $haystack The haystack * @param string $needle The needle @@ -784,7 +636,20 @@ public function testStrIStr(string $haystack, string $needle, $expected): void } /** - * @testdox UTF-8 aware strrev() is performed on a string + * Data provider for testStrRev + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestStrRev(): \Generator + { + yield 'an ASCII string' => ['abc def', 'fed cba']; + yield 'a cyrillic string' => ['Би шил', 'лиш иБ']; + } + + /** + * @testdox StringHelper::strrev() reverses $_dataName * * @param string $string String to be reversed * @param string $expected Expected result @@ -800,7 +665,31 @@ public function testStrRev(string $string, string $expected): void } /** - * @testdox UTF-8 aware strspn() is performed on a string + * Data provider for testStrSpn + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestStrSpn(): \Generator + { + // Note: subject, mask, start, length, expected result + yield ['A321 Main Street', '0123456789', 1, 2, 2]; + yield ['321 Main Street', '0123456789', null, 2, 2]; + yield ['A321 Main Street', '0123456789', null, 10, 0]; + yield ['321 Main Street', '0123456789', null, null, 3]; + yield ['Main Street 321', '0123456789', null, -3, 0]; + yield ['321 Main Street', '0123456789', null, -13, 2]; + yield ['321 Main Street', '0123456789', null, -12, 3]; + yield ['A321 Main Street', '0123456789', 0, null, 0]; + yield ['A321 Main Street', '0123456789', 1, 10, 3]; + yield ['A321 Main Street', '0123456789', 1, null, 3]; + yield ['Би шил идэй чадна', 'Би', null, null, 2]; + yield ['чадна Би шил идэй чадна', 'Би', null, null, 0]; + } + + /** + * @testdox StringHelper::strspn() finds length of matching segment * * @param string $subject The haystack * @param string $mask The mask @@ -819,17 +708,50 @@ public function testStrSpn(string $subject, string $mask, ?int $start, ?int $len } /** - * @testdox UTF-8 aware substr_replace() is performed on a string + * Data provider for testSubstrReplace + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestSubstrReplace(): \Generator + { + yield 'the remainder of the string, if no length is given' => [ + '321 Main Street', + 'Broadway Avenue', + 4, + false, + '321 Broadway Avenue' + ]; + yield 'only the given number of characters' => ['321 Main Street', 'Broadway', 4, 4, '321 Broadway Street']; + yield 'the given number of characters in a cyrillic string' => [ + 'чадна Би шил идэй чадна', + '我能吞', + 6, + 2, + 'чадна 我能吞 шил идэй чадна' + ]; + yield 'the remainder of a cyrillic string, if no length is given' => [ + 'чадна Би шил идэй чадна', + '我能吞', + 6, + false, + 'чадна 我能吞' + ]; + } + + /** + * @testdox StringHelper::substr_replace() replaces $_dataName * - * @param string $expected Expected result * @param string $string The haystack * @param string $replacement The replacement string * @param integer $start Start * @param integer|boolean|null $length Length (optional) + * @param string $expected Expected result * * @dataProvider seedTestSubstrReplace */ - public function testSubstrReplace(string $expected, string $string, string $replacement, int $start, $length): void + public function testSubstrReplace(string $string, string $replacement, int $start, $length, string $expected): void { $this->assertEquals( $expected, @@ -838,7 +760,25 @@ public function testSubstrReplace(string $expected, string $string, string $repl } /** - * @testdox UTF-8 aware ltrim() is performed on a string + * Data provider for testLTrim + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestLTrim(): \Generator + { + yield 'spaces with default char list' => [' abc def', false, 'abc def']; + yield 'nothing with empty char list' => [' abc def', '', ' abc def']; + yield 'spaces with default char list on cyrillic strings' => [' Би шил', false, 'Би шил']; + yield 'HTAB, VTAB, NL, CR with default char list' => ["\t\n\r\x0BБи шил", false, 'Би шил']; + yield 'special characters from provided char list' => ["\x0B\t\n\rБи шил", "\t\n\x0B", "\rБи шил"]; + yield 'characters only on the left side' => ["\x09Би шил\x0A", "\x09\x0A", "Би шил\x0A"]; + yield 'normal characters from provided char list' => ['1234abc', '0123456789', 'abc']; + } + + /** + * @testdox StringHelper::ltrim() removes $_dataName * * @param string $string The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim @@ -855,7 +795,25 @@ public function testLTrim(string $string, $charlist, string $expected): void } /** - * @testdox UTF-8 aware rtrim() is performed on a string + * Data provider for testRTrim + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestRTrim(): \Generator + { + yield 'spaces with default char list' => ['abc def ', false, 'abc def']; + yield 'nothing with empty char list' => ['abc def ', '', 'abc def ']; + yield 'spaces with default char list on cyrillic strings' => ['Би шил ', false, 'Би шил']; + yield 'HTAB, VTAB, NL, CR with default char list' => ["Би шил\t\n\r\x0B", false, 'Би шил']; + yield 'special characters from provided char list' => ["Би шил\r\x0B\t\n", "\t\n\x0B", "Би шил\r"]; + yield 'characters only on the right side' => ["\x09Би шил\x0A", "\x09\x0A", "\x09Би шил"]; + yield 'normal characters from provided char list' => ['1234abc', 'abcdefgh', '1234']; + } + + /** + * @testdox StringHelper::rtrim() removes $_dataName * * @param string $string The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim @@ -872,7 +830,25 @@ public function testRTrim(string $string, $charlist, string $expected): void } /** - * @testdox UTF-8 aware trim() is performed on a string + * Data provider for testTrim + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestTrim(): \Generator + { + yield 'spaces with default char list' => [' abc def ', false, 'abc def']; + yield 'nothing with empty char list' => [' abc def ', '', ' abc def ']; + yield 'spaces with default char list on cyrillic strings' => [' Би шил ', false, 'Би шил']; + yield 'HTAB, VTAB, NL, CR with default char list' => ["\t\n\r\x0BБи шил\t\n\r\x0B", false, 'Би шил']; + yield 'special characters from provided char list' => ["\x0B\t\n\rБи шил\r\x0B\t\n", "\t\n\x0B", "\rБи шил\r"]; + yield 'characters on both sides' => ["\x09Би шил\x0A", "\x09\x0A", "Би шил"]; + yield 'normal characters from provided char list' => ['1234abc56789', '0123456789', 'abc']; + } + + /** + * @testdox StringHelper::trim() removes $_dataName * * @param string $string The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim @@ -889,7 +865,24 @@ public function testTrim(string $string, $charlist, string $expected): void } /** - * @testdox UTF-8 aware ucfirst() is performed on a string + * Data provider for testUcfirst + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestUcfirst(): \Generator + { + // Note: string, delimiter, newDelimiter expected result + yield 'only the first character by default' => ['george michael', null, null, 'George michael']; + yield 'the first character of a cyrillic string' => ['мога', null, null, 'Мога']; + yield 'the first character of a greek string' => ['ψυχοφθόρα', null, null, 'Ψυχοφθόρα']; + yield 'the first character of every chunk, if a delimiter is provided' => ['dr jekill and mister hyde', ' ', null, 'Dr Jekill And Mister Hyde']; + yield 'the chunks and optionally replaces the delimiter' => ['dr jekill and mister hyde', ' ', '_', 'Dr_Jekill_And_Mister_Hyde']; + } + + /** + * @testdox StringHelper::ucfirst() uppercases $_dataName * * @param string $string String to be processed * @param string|null $delimiter The delimiter (null means do not split the string) @@ -907,7 +900,23 @@ public function testUcfirst(string $string, ?string $delimiter, ?string $newDeli } /** - * @testdox UTF-8 aware ucwords() is performed on a string + * Data provider for testUcwords + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestUcwords(): \Generator + { + yield 'each word' => ['george washington', 'George Washington']; + yield 'words at the beginning of a new line' => ["george\r\nwashington", "George\r\nWashington"]; + yield 'cyrillic words' => ['мога', 'Мога']; + yield 'greek words' => ['αβγ δεζ', 'Αβγ Δεζ']; + yield 'words with Umlauts' => ['åbc öde', 'Åbc Öde']; + } + + /** + * @testdox StringHelper::ucwords() uppercases $_dataName * * @param string $string String to be processed * @param string $expected Expected result @@ -923,7 +932,19 @@ public function testUcwords(string $string, string $expected): void } /** - * @testdox A string is transcoded + * Data provider for testTranscode + * + * @return \Generator + * + * @since 1.0 + */ + public function seedTestTranscode(): \Generator + { + yield 'UTF-8 to ISO-8859-1' => ['Åbc Öde €100', 'UTF-8', 'ISO-8859-1', "\xc5bc \xd6de EUR100"]; + } + + /** + * @testdox StringHelper::transcode transcodes $_dataName * * @param string $source The string to transcode. * @param string $fromEncoding The source encoding. @@ -941,23 +962,20 @@ public function testTranscode(string $source, string $fromEncoding, string $toEn } /** - * @testdox A string is tested as valid UTF-8 + * Data provider for testUnicodeToUtf8 * - * @param string $string UTF-8 encoded string. - * @param boolean $expected Expected result. + * @return \Generator * - * @dataProvider seedCompliantStrings + * @since 1.2.0 */ - public function testValid(string $string, bool $expected): void + public function seedTestUnicodeToUtf8(): \Generator { - $this->assertEquals( - $expected, - StringHelper::valid($string) - ); + yield 'cyrillic' => ["\u0422\u0435\u0441\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b", "Тест системы"]; + yield 'umlaut' => ["\u00dcberpr\u00fcfung der Systemumstellung", "Überprüfung der Systemumstellung"]; } /** - * @testdox A string is converted from unicode to UTF-8 + * @testdox StringHelper::unicode_to_utf8() converts a $_dataName string from unicode to UTF-8 * * @param string $string The Unicode string to be converted * @param string $expected Expected result @@ -973,7 +991,20 @@ public function testUnicodeToUtf8(string $string, string $expected): void } /** - * @testdox A string is converted from unicode to UTF-16 + * Data provider for testUnicodeToUtf16 + * + * @return \Generator + * + * @since 1.2.0 + */ + public function seedTestUnicodeToUtf16(): \Generator + { + yield 'cyrillic' => ["\u0422\u0435\u0441\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b", "Тест системы"]; + yield 'umlaut' => ["\u00dcberpr\u00fcfung der Systemumstellung", "Überprüfung der Systemumstellung"]; + } + + /** + * @testdox StringHelper::unicode_to_utf16() converts a $_dataName string from unicode to UTF-16 * * @param string $string The Unicode string to be converted * @param string $expected Expected result @@ -989,7 +1020,26 @@ public function testUnicodeToUtf16(string $string, string $expected): void } /** - * @testdox A string is checked for UTF-8 compliance + * Data provider for testing compliant strings + * + * @return \Generator + * + * @since 1.0 + */ + public function seedCompliantStrings(): \Generator + { + yield ["\xCF\xB0", true]; + yield ["\xFBa", false]; + yield ["\xFDa", false]; + yield ["foo\xF7bar", false]; + yield ['george Мога Ž Ψυχοφθόρα ฉันกินกระจกได้ 我能吞下玻璃而不伤身体 ', true]; + yield ["\xFF ABC", false]; + yield ["0xfffd ABC", true]; + yield ['', true]; + } + + /** + * @testdox StringHelper::compliant() detects UTF-8 compliant strings * * @param string $string UTF-8 string to check * @param boolean $expected Expected result @@ -1003,4 +1053,20 @@ public function testCompliant(string $string, bool $expected): void StringHelper::compliant($string) ); } + + /** + * @testdox StringHelper::valid() validates UTF-8 strings + * + * @param string $string UTF-8 encoded string. + * @param boolean $expected Expected result. + * + * @dataProvider seedCompliantStrings + */ + public function testValid(string $string, bool $expected): void + { + $this->assertEquals( + $expected, + StringHelper::valid($string) + ); + } } diff --git a/src/StringHelper.php b/src/StringHelper.php index d8b3a4cc..e107fb00 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -8,7 +8,6 @@ namespace Joomla\String; -// PHP mbstring and iconv local configuration @ini_set('default_charset', 'UTF-8'); /** @@ -26,74 +25,54 @@ abstract class StringHelper */ protected static $incrementStyles = [ 'dash' => [ - '#-(\d+)$#', - '-%d', + 'regexp' => '#-(\d+)$#', + 'printf' => '-%d', ], 'default' => [ - ['#\((\d+)\)$#', '#\(\d+\)$#'], - [' (%d)', '(%d)'], + 'regexp' => ['#\((\d+)\)$#', '#\(\d+\)$#'], + 'printf' => [' (%d)', '(%d)'], ], ]; /** - * Increments a trailing number in a string. + * Increment a trailing number in a string. * * Used to easily create distinct labels when copying objects. The method has the following styles: * * default: "Label" becomes "Label (2)" * dash: "Label" becomes "Label-2" * - * @param string $string The source string. - * @param string|null $style The the style (default|dash). - * @param integer $n If supplied, this number is used for the copy, otherwise it is the 'next' number. + * @param string $string The source string. + * @param string|null $style The style (default|dash). + * @param integer|null $n If a positive number is supplied, this number is used for the copy, otherwise it is the 'next' number. * * @return string The incremented string. * * @since 1.3.0 */ - public static function increment($string, $style = 'default', $n = 0) + public static function increment($string, $style = 'default', $n = null) { $styleSpec = static::$incrementStyles[$style] ?? static::$incrementStyles['default']; // Regular expression search and replace patterns. - if (\is_array($styleSpec[0])) - { - $rxSearch = $styleSpec[0][0]; - $rxReplace = $styleSpec[0][1]; - } - else - { - $rxSearch = $rxReplace = $styleSpec[0]; - } + [$rxSearch, $rxReplace] = self::splitSearchReplace($styleSpec['regexp']); // New and old (existing) sprintf formats. - if (\is_array($styleSpec[1])) - { - $newFormat = $styleSpec[1][0]; - $oldFormat = $styleSpec[1][1]; - } - else - { - $newFormat = $oldFormat = $styleSpec[1]; - } + [$newFormat, $oldFormat] = self::splitSearchReplace($styleSpec['printf']); // Check if we are incrementing an existing pattern, or appending a new one. if (preg_match($rxSearch, $string, $matches)) { - $n = empty($n) ? ($matches[1] + 1) : $n; - $string = preg_replace($rxReplace, sprintf($oldFormat, $n), $string); - } - else - { - $n = empty($n) ? 2 : $n; - $string .= sprintf($newFormat, $n); + return preg_replace($rxReplace, sprintf($oldFormat, $n ?: ($matches[1] + 1)), $string); } + $string .= sprintf($newFormat, $n ?: 2); + return $string; } /** - * Tests whether a string contains only 7bit ASCII bytes. + * Test whether a string contains only 7bit ASCII bytes. * * You might use this to conditionally check whether a string needs handling as UTF-8 or not, potentially offering performance * benefits by using the native PHP equivalent if it's just ASCII e.g.; @@ -122,9 +101,9 @@ public static function is_ascii($str) } /** - * UTF-8 aware alternative to ord() + * Convert the first byte of a string to its ordinal number * - * Returns the unicode ordinal for a character. + * UTF-8 aware alternative to ord() * * @param string $chr UTF-8 encoded character * @@ -139,65 +118,74 @@ public static function ord($chr) } /** - * UTF-8 aware alternative to strpos() + * Find the position of the first occurrence of a substring in a string * - * Find position of first occurrence of a string. + * UTF-8 aware alternative to strpos() * - * @param string $str String being examined - * @param string $search String being searched for - * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed + * @param string $haystack The string to search in + * @param string $needle String being searched for + * @param integer|null $offset If specified, search will start this number of characters counted from the + * beginning of the string. Unlike {@see strrpos()}, the offset cannot be negative. * - * @return integer|boolean Number of characters before the first match or FALSE on failure + * @return integer|boolean Returns the position where the needle exists relative to the beginnning of the haystack + * string (independent of search direction or offset). Also note that string positions + * start at 0, and not 1. + * Returns false if the needle was not found. * * @link https://www.php.net/strpos * @since 1.3.0 */ - public static function strpos($str, $search, $offset = false) + public static function strpos($haystack, $needle, $offset = null) { - if ($offset === false) + if ($offset === null) { - return utf8_strpos($str, $search); + return utf8_strpos($haystack, $needle); } - return utf8_strpos($str, $search, $offset); + return utf8_strpos($haystack, $needle, $offset); } /** - * UTF-8 aware alternative to strrpos() + * Find the position of the last occurrence of a substring in a string * - * Finds position of last occurrence of a string. + * UTF-8 aware alternative to strrpos() * - * @param string $str String being examined. - * @param string $search String being searched for. - * @param integer $offset Offset from the left of the string. + * @param string $haystack The string to search in. + * @param string $needle String being searched for. + * @param integer $offset If specified, search will start this number of characters counted from the beginning + * of the string. If the value is negative, search will instead start from that many + * characters from the end of the string, searching backwards. * - * @return integer|boolean Number of characters before the last match or false on failure + * @return integer|boolean Returns the position where the needle exists relative to the beginnning of the haystack + * string (independent of search direction or offset). Also note that string positions + * start at 0, and not 1. + * Returns false if the needle was not found. * * @link https://www.php.net/strrpos * @since 1.3.0 */ - public static function strrpos($str, $search, $offset = 0) + public static function strrpos($haystack, $needle, $offset = 0) { - return utf8_strrpos($str, $search, $offset); + return utf8_strrpos($haystack, $needle, $offset); } /** - * UTF-8 aware alternative to substr() + * Get part of a string given character offset (and optionally length). * - * Return part of a string given character offset (and optionally length). + * UTF-8 aware alternative to substr() * - * @param string $str String being processed - * @param integer $offset Number of UTF-8 characters offset (from left) - * @param integer|null|boolean $length Optional length in UTF-8 characters from offset + * @param string $str String being processed + * @param integer $offset Number of UTF-8 characters offset (from left) + * @param integer|null $length Optional length in UTF-8 characters from offset * * @return string|boolean * * @link https://www.php.net/substr * @since 1.3.0 */ - public static function substr($str, $offset, $length = false) + public static function substr($str, $offset, $length = null) { - if ($length === false) + if ($length === null) { return utf8_substr($str, $offset); } @@ -206,10 +194,10 @@ public static function substr($str, $offset, $length = false) } /** - * UTF-8 aware alternative to strtolower() - * * Make a string lowercase * + * UTF-8 aware alternative to strtolower() + * * Note: The concept of a characters "case" only exists is some alphabets such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does * not exist in the Chinese alphabet, for example. See Unicode Standard Annex #21: Case Mappings * @@ -226,10 +214,10 @@ public static function strtolower($str) } /** - * UTF-8 aware alternative to strtoupper() - * * Make a string uppercase * + * UTF-8 aware alternative to strtoupper() + * * Note: The concept of a characters "case" only exists is some alphabets such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does * not exist in the Chinese alphabet, for example. See Unicode Standard Annex #21: Case Mappings * @@ -263,34 +251,33 @@ public static function strlen($str) } /** + * Replace (parts of) a string in a case-insensitive manner + * * UTF-8 aware alternative to str_ireplace() * - * Case-insensitive version of str_replace() * - * @param string $search String to search - * @param string $replace Existing string to replace - * @param string $str New string to replace with - * @param integer|null|boolean $count Optional count value to be passed by referene + * @param string[]|string $search String(s) to search + * Every replacement with search array is + * performed on the result of previous replacement. + * @param string[]|string $replace New string(s) to replace with + * @param string $subject Existing string to replace + * @param integer|null $count Optional count value to be passed by reference * * @return string UTF-8 String * * @link https://www.php.net/str_ireplace * @since 1.3.0 */ - public static function str_ireplace($search, $replace, $str, $count = null) + public static function str_ireplace($search, $replace, $subject, &$count = null) { - if ($count === false) - { - return utf8_ireplace($search, $replace, $str); - } - - return utf8_ireplace($search, $replace, $str, $count); + return utf8_ireplace($search, $replace, $subject, $count); } /** + * Pad a string to a certain length with another string. + * * UTF-8 aware alternative to str_pad() * - * Pad a string to a certain length with another string. * $padStr may contain multi-byte characters. * * @param string $input The input string. @@ -309,9 +296,9 @@ public static function str_pad($input, $length, $padStr = ' ', $type = STR_PAD_R } /** - * UTF-8 aware alternative to str_split() + * Split a string into an array. * - * Convert a string to an array. + * UTF-8 aware alternative to str_split() * * @param string $str UTF-8 encoded string to process * @param integer $splitLen Number to characters to split string by @@ -327,13 +314,13 @@ public static function str_split($str, $splitLen = 1) } /** - * UTF-8/LOCALE aware alternative to strcasecmp() + * Compare strings in a case-insensitive manner. * - * A case insensitive string comparison. + * UTF-8/LOCALE aware alternative to strcasecmp() * - * @param string $str1 string 1 to compare - * @param string $str2 string 2 to compare - * @param string|boolean $locale The locale used by strcoll or false to use classical comparison + * @param string $str1 string 1 to compare + * @param string $str2 string 2 to compare + * @param array|string|boolean $locale The locale used by strcoll or false to use classical comparison * * @return integer < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal. * @@ -349,30 +336,10 @@ public static function strcasecmp($str1, $str2, $locale = false) return utf8_strcasecmp($str1, $str2); } - // Get current locale - $locale0 = setlocale(LC_COLLATE, 0); - - if (!$locale = setlocale(LC_COLLATE, $locale)) - { - $locale = $locale0; - } - - // See if we have successfully set locale to UTF-8 - if (!stristr($locale, 'UTF-8') && stristr($locale, '_') && preg_match('~\.(\d+)$~', $locale, $m)) - { - $encoding = 'CP' . $m[1]; - } - elseif (stristr($locale, 'UTF-8') || stristr($locale, 'utf8')) - { - $encoding = 'UTF-8'; - } - else - { - $encoding = 'nonrecodable'; - } + $encoding = self::setLocale($locale); // If we successfully set encoding it to utf-8 or encoding is sth weird don't recode - if ($encoding == 'UTF-8' || $encoding == 'nonrecodable') + if ($encoding === 'UTF-8' || $encoding === 'nonrecodable') { return strcoll(utf8_strtolower($str1), utf8_strtolower($str2)); } @@ -384,13 +351,13 @@ public static function strcasecmp($str1, $str2, $locale = false) } /** - * UTF-8/LOCALE aware alternative to strcmp() + * Compare strings in a case-sensitive manner. * - * A case sensitive string comparison. + * UTF-8/LOCALE aware alternative to strcmp() * - * @param string $str1 string 1 to compare - * @param string $str2 string 2 to compare - * @param mixed $locale The locale used by strcoll or false to use classical comparison + * @param string $str1 string 1 to compare + * @param string $str2 string 2 to compare + * @param array|string|boolean $locale The locale used by strcoll or false to use classical comparison * * @return integer < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal. * @@ -401,47 +368,27 @@ public static function strcasecmp($str1, $str2, $locale = false) */ public static function strcmp($str1, $str2, $locale = false) { - if ($locale) + if ($locale === false) { - // Get current locale - $locale0 = setlocale(LC_COLLATE, 0); - - if (!$locale = setlocale(LC_COLLATE, $locale)) - { - $locale = $locale0; - } - - // See if we have successfully set locale to UTF-8 - if (!stristr($locale, 'UTF-8') && stristr($locale, '_') && preg_match('~\.(\d+)$~', $locale, $m)) - { - $encoding = 'CP' . $m[1]; - } - elseif (stristr($locale, 'UTF-8') || stristr($locale, 'utf8')) - { - $encoding = 'UTF-8'; - } - else - { - $encoding = 'nonrecodable'; - } + return strcmp($str1, $str2); + } - // If we successfully set encoding it to utf-8 or encoding is sth weird don't recode - if ($encoding == 'UTF-8' || $encoding == 'nonrecodable') - { - return strcoll($str1, $str2); - } + $encoding = self::setLocale($locale); - return strcoll(static::transcode($str1, 'UTF-8', $encoding), static::transcode($str2, 'UTF-8', $encoding)); + // If we successfully set encoding it to utf-8 or encoding is sth weird don't recode + if ($encoding === 'UTF-8' || $encoding === 'nonrecodable') + { + return strcoll($str1, $str2); } - return strcmp($str1, $str2); + return strcoll(static::transcode($str1, 'UTF-8', $encoding), static::transcode($str2, 'UTF-8', $encoding)); } /** - * UTF-8 aware alternative to strcspn() - * * Find length of initial segment not matching mask. * + * UTF-8 aware alternative to strcspn() + * * @param string $str The string to process * @param string $mask The mask * @param integer|boolean $start Optional starting character position (in characters) @@ -454,13 +401,13 @@ public static function strcmp($str1, $str2, $locale = false) */ public static function strcspn($str, $mask, $start = null, $length = null) { - if ($start === false && $length === false) + if ($length === null) { - return utf8_strcspn($str, $mask); - } + if ($start === null) + { + return utf8_strcspn($str, $mask); + } - if ($length === false) - { return utf8_strcspn($str, $mask, $start); } @@ -468,10 +415,12 @@ public static function strcspn($str, $mask, $start = null, $length = null) } /** + * Get everything from haystack from the first occurrence of needle to the end. + * * UTF-8 aware alternative to stristr() * - * Returns all of haystack from the first occurrence of needle to the end. Needle and haystack are examined in a case-insensitive manner to - * find the first occurrence of a string using case insensitive comparison. + * Needle and haystack are examined in a case-insensitive manner to find the first occurrence of a string using + * case-insensitive comparison. * * @param string $str The haystack * @param string $search The needle @@ -487,10 +436,10 @@ public static function stristr($str, $search) } /** - * UTF-8 aware alternative to strrev() - * * Reverse a string. * + * UTF-8 aware alternative to strrev() + * * @param string $str String to be reversed * * @return string The string in reverse character order @@ -504,10 +453,10 @@ public static function strrev($str) } /** - * UTF-8 aware alternative to strspn() - * * Find length of initial segment matching mask. * + * UTF-8 aware alternative to strspn() + * * @param string $str The haystack * @param string $mask The mask * @param integer|null $start Start optional @@ -520,13 +469,13 @@ public static function strrev($str) */ public static function strspn($str, $mask, $start = null, $length = null) { - if ($start === null && $length === null) - { - return utf8_strspn($str, $mask); - } - if ($length === null) { + if ($start === null) + { + return utf8_strspn($str, $mask); + } + return utf8_strspn($str, $mask, $start); } @@ -534,10 +483,10 @@ public static function strspn($str, $mask, $start = null, $length = null) } /** - * UTF-8 aware alternative to substr_replace() - * * Replace text within a portion of a string. * + * UTF-8 aware alternative to substr_replace() + * * @param string $str The haystack * @param string $repl The replacement string * @param integer $start Start @@ -550,7 +499,6 @@ public static function strspn($str, $mask, $start = null, $length = null) */ public static function substr_replace($str, $repl, $start, $length = null) { - // Loaded by library loader if ($length === false) { return utf8_substr_replace($str, $repl, $start); @@ -560,10 +508,12 @@ public static function substr_replace($str, $repl, $start, $length = null) } /** + * Strip whitespace (or other characters) from the beginning of a string. + * * UTF-8 aware replacement for ltrim() * - * Strip whitespace (or other characters) from the beginning of a string. You only need to use this if you are supplying the charlist - * optional arg and it contains UTF-8 characters. Otherwise ltrim will work normally on a UTF-8 string. + * You only need to use this if you are supplying the char list optional arg, and it contains UTF-8 characters. + * Otherwise, ltrim will work normally on a UTF-8 string. * * @param string $str The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim @@ -575,24 +525,26 @@ public static function substr_replace($str, $repl, $start, $length = null) */ public static function ltrim($str, $charlist = false) { - if (empty($charlist) && $charlist !== false) + if ($charlist === false) { - return $str; + return utf8_ltrim($str); } - if ($charlist === false) + if (empty($charlist)) { - return utf8_ltrim($str); + return $str; } return utf8_ltrim($str, $charlist); } /** + * Strip whitespace (or other characters) from the end of a string. + * * UTF-8 aware replacement for rtrim() * - * Strip whitespace (or other characters) from the end of a string. You only need to use this if you are supplying the charlist - * optional arg and it contains UTF-8 characters. Otherwise rtrim will work normally on a UTF-8 string. + * You only need to use this if you are supplying the char list optional arg, and it contains UTF-8 characters. + * Otherwise, rtrim will work normally on a UTF-8 string. * * @param string $str The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim @@ -604,24 +556,26 @@ public static function ltrim($str, $charlist = false) */ public static function rtrim($str, $charlist = false) { - if (empty($charlist) && $charlist !== false) + if ($charlist === false) { - return $str; + return utf8_rtrim($str); } - if ($charlist === false) + if (empty($charlist)) { - return utf8_rtrim($str); + return $str; } return utf8_rtrim($str, $charlist); } /** + * Strip whitespace (or other characters) from the beginning and end of a string. + * * UTF-8 aware replacement for trim() * - * Strip whitespace (or other characters) from the beginning and end of a string. You only need to use this if you are supplying the charlist - * optional arg and it contains UTF-8 characters. Otherwise trim will work normally on a UTF-8 string + * You only need to use this if you are supplying the charlist optional arg and it contains UTF-8 characters. + * Otherwise, trim will work normally on a UTF-8 string * * @param string $str The string to be trimmed * @param string|boolean $charlist The optional charlist of additional characters to trim @@ -633,24 +587,24 @@ public static function rtrim($str, $charlist = false) */ public static function trim($str, $charlist = false) { - if (empty($charlist) && $charlist !== false) + if ($charlist === false) { - return $str; + return utf8_trim($str); } - if ($charlist === false) + if (empty($charlist)) { - return utf8_trim($str); + return $str; } return utf8_trim($str, $charlist); } /** - * UTF-8 aware alternative to ucfirst() - * * Make a string's first character uppercase or all words' first character uppercase. * + * UTF-8 aware alternative to ucfirst() + * * @param string $str String to be processed * @param string|null $delimiter The words delimiter (null means do not split the string) * @param string|null $newDelimiter The new words delimiter (null means equal to $delimiter) @@ -678,10 +632,10 @@ public static function ucfirst($str, $delimiter = null, $newDelimiter = null) } /** - * UTF-8 aware alternative to ucwords() - * * Uppercase the first character of each word in a string. * + * UTF-8 aware alternative to ucwords() + * * @param string $str String to be processed * * @return string String with first char of each word uppercase @@ -709,19 +663,13 @@ public static function ucwords($str) */ public static function transcode($source, $fromEncoding, $toEncoding) { - switch (ICONV_IMPL) - { - case 'glibc': - return @iconv($fromEncoding, $toEncoding . '//TRANSLIT,IGNORE', $source); + $modifier = ICONV_IMPL === 'glibc' ? '//TRANSLIT,IGNORE' : '//IGNORE//TRANSLIT'; - case 'libiconv': - default: - return iconv($fromEncoding, $toEncoding . '//IGNORE//TRANSLIT', $source); - } + return @iconv($fromEncoding, $toEncoding . $modifier, $source); } /** - * Tests a string as to whether it's valid UTF-8 and supported by the Unicode standard. + * Tests a string whether it's valid UTF-8 and supported by the Unicode standard. * * Note: this function has been modified to simple return true or false. * @@ -742,10 +690,11 @@ public static function valid($str) /** * Tests whether a string complies as UTF-8. * - * This will be much faster than StringHelper::valid() but will pass five and six octet UTF-8 sequences, which are not supported by Unicode and - * so cannot be displayed correctly in a browser. In other words it is not as strict as StringHelper::valid() but it's faster. If you use it to - * validate user input, you place yourself at the risk that attackers will be able to inject 5 and 6 byte sequences (which may or may not be a - * significant risk, depending on what you are are doing). + * This will be much faster than StringHelper::valid() but will pass five and six octet UTF-8 sequences, which are + * not supported by Unicode and so cannot be displayed correctly in a browser. In other words it is not as strict + * as StringHelper::valid() but it's faster. If you use it to validate user input, you place yourself at the risk + * that attackers will be able to inject 5 and 6 byte sequences (which may or may not be a significant risk, + * depending on what you are doing). * * @param string $str UTF-8 string to check * @@ -775,15 +724,14 @@ public static function unicode_to_utf8($str) { return preg_replace_callback( '/\\\\u([0-9a-fA-F]{4})/', - static function ($match) - { + static function ($match) { return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE'); }, $str ); } - return $str; + return $str; // @codeCoverageIgnore } /** @@ -801,14 +749,66 @@ public static function unicode_to_utf16($str) { return preg_replace_callback( '/\\\\u([0-9a-fA-F]{4})/', - static function ($match) - { + static function ($match) { return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UTF-16BE'); }, $str ); } - return $str; + return $str; // @codeCoverageIgnore + } + + /** + * @param $value + * + * @return array + */ + private static function splitSearchReplace($value): array + { + if (\is_array($value)) + { + [$one, $two] = $value; + } + else + { + $one = $two = $value; + } + + return array($one, $two); + } + + /** + * @param string[]|string $locale + * + * @return string + */ + private static function setLocale($locale): string + { + $locale = setlocale(LC_COLLATE, $locale); + + if ($locale === false) + { + $locale = setlocale(LC_COLLATE, 0); + } + + // See if we have successfully set locale to UTF-8 + if (stripos($locale, 'UTF-8') === false + && strpos($locale, '_') !== false + && preg_match('~(?:\.|cp)(\d+)$~i', $locale, $m) + ) + { + $encoding = 'CP' . $m[1]; + } + elseif (stripos($locale, 'UTF-8') !== false || stripos($locale, 'utf8') !== false) + { + $encoding = 'UTF-8'; + } + else + { + $encoding = 'nonrecodable'; + } + + return $encoding; } } From e42aaa7f850e6427e1ac95e6f20c4349aa639bdf Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Wed, 22 Sep 2021 00:12:36 +0200 Subject: [PATCH 4/9] Fix - Populate optional count variable --- src/phputf8/str_ireplace.php | 138 +++++++++++++++++------------------ 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/src/phputf8/str_ireplace.php b/src/phputf8/str_ireplace.php index c4b4df8d..84abff3c 100644 --- a/src/phputf8/str_ireplace.php +++ b/src/phputf8/str_ireplace.php @@ -1,77 +1,77 @@ Date: Wed, 22 Sep 2021 00:29:28 +0200 Subject: [PATCH 5/9] Style - Fix CS complaints --- src/StringHelper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/StringHelper.php b/src/StringHelper.php index e107fb00..f05cd654 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -731,7 +731,7 @@ static function ($match) { ); } - return $str; // @codeCoverageIgnore + return $str; } /** @@ -756,11 +756,11 @@ static function ($match) { ); } - return $str; // @codeCoverageIgnore + return $str; } /** - * @param $value + * @param string[]|string $value The value * * @return array */ @@ -779,7 +779,7 @@ private static function splitSearchReplace($value): array } /** - * @param string[]|string $locale + * @param string[]|string $locale The locale * * @return string */ From e902bc3ab683798c5275485b6bc45357f4717c76 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Wed, 22 Sep 2021 14:35:23 +0200 Subject: [PATCH 6/9] Tests - Cover case length = null --- Tests/StringHelperTest.php | 28 +++++++++++++++++++--------- src/StringHelper.php | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Tests/StringHelperTest.php b/Tests/StringHelperTest.php index 7f18ccf2..f65a16d3 100644 --- a/Tests/StringHelperTest.php +++ b/Tests/StringHelperTest.php @@ -169,7 +169,7 @@ public function testStrPos(string $haystack, string $needle, $offset, $expected) public function seedTestStrRPos(): \Generator { // Note: haystack, needle, offset, expected result - yield 'returns the position of the last occurance of the substring' => ['pinging', 'ing', 0, 4]; + yield 'returns the position of the last occurance of the substring' => ['pinging', 'ing', null, 4]; yield 'locates substring in ASCII string' => ['missing', 'sing', 0, 3]; yield 'locates substring in cyrillic string' => [' объектов на карте с', 'на карте', 0, 10]; yield 'returns false for non-existing substrings' => ['missing', 'sting', 0, false]; @@ -180,18 +180,18 @@ public function seedTestStrRPos(): \Generator /** * @testdox StringHelper::strrpos() $_dataName * - * @param string $haystack String being examined - * @param string $needle String being searched for - * @param integer|null|boolean $offset Optional, specifies the position from which the search should be performed - * @param string|boolean $expected Expected result + * @param string $haystack String being examined + * @param string $needle String being searched for + * @param integer|null $offset Optional, specifies the position from which the search should be performed + * @param string|boolean $expected Expected result * * @dataProvider seedTestStrRPos */ - public function testStrRPos(string $haystack, string $needle, int $offset, $expected): void + public function testStrRPos(string $haystack, string $needle, ?int $offset, $expected): void { $this->assertEquals( $expected, - StringHelper::strrpos($haystack, $needle, $offset) + StringHelper::strrpos($haystack, $needle, $offset ?? 0) ); } @@ -877,8 +877,18 @@ public function seedTestUcfirst(): \Generator yield 'only the first character by default' => ['george michael', null, null, 'George michael']; yield 'the first character of a cyrillic string' => ['мога', null, null, 'Мога']; yield 'the first character of a greek string' => ['ψυχοφθόρα', null, null, 'Ψυχοφθόρα']; - yield 'the first character of every chunk, if a delimiter is provided' => ['dr jekill and mister hyde', ' ', null, 'Dr Jekill And Mister Hyde']; - yield 'the chunks and optionally replaces the delimiter' => ['dr jekill and mister hyde', ' ', '_', 'Dr_Jekill_And_Mister_Hyde']; + yield 'the first character of every chunk, if a delimiter is provided' => [ + 'dr jekill and mister hyde', + ' ', + null, + 'Dr Jekill And Mister Hyde' + ]; + yield 'the chunks and optionally replaces the delimiter' => [ + 'dr jekill and mister hyde', + ' ', + '_', + 'Dr_Jekill_And_Mister_Hyde' + ]; } /** diff --git a/src/StringHelper.php b/src/StringHelper.php index f05cd654..e6c3a344 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -166,7 +166,7 @@ public static function strpos($haystack, $needle, $offset = null) */ public static function strrpos($haystack, $needle, $offset = 0) { - return utf8_strrpos($haystack, $needle, $offset); + return utf8_strrpos($haystack, $needle, $offset ?? 0); } /** From 4f755779b6febee064696386d75d74ffd30d6009 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Thu, 23 Sep 2021 18:57:14 +0200 Subject: [PATCH 7/9] Fix - Merge issues --- Tests/InflectorTest.php | 5 +++++ composer.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/InflectorTest.php b/Tests/InflectorTest.php index b1791c32..a694e05c 100644 --- a/Tests/InflectorTest.php +++ b/Tests/InflectorTest.php @@ -291,6 +291,11 @@ public function testIsCountable(string $input, bool $expected): void */ public function testIsPlural(string $singular, string $plural): void { + if ($singular === 'bus' && !$this->checkInflectorImplementation($this->inflector)) + { + $this->markTestSkipped('"bus/buses" is not known to the new implementation'); + } + $this->assertTrue( $this->inflector->isPlural($plural), "'$plural' should be reported as plural" diff --git a/composer.json b/composer.json index fa625bfc..d5882c85 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "Joomla\\String\\Tests\\": "Tests/" } }, - "minimum-stability": "dev", + "minimum-stability": "stable", "extra": { "branch-alias": { "dev-2.0-dev": "2.0-dev" From 929ec381c8b12d29026533db029c0c2bb29fe33e Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Fri, 24 Sep 2021 13:30:56 +0200 Subject: [PATCH 8/9] Refactor - Restore signatures from 2.0.0 for detection of possible b/c breaks --- src/StringHelper.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/StringHelper.php b/src/StringHelper.php index e6c3a344..072c9605 100644 --- a/src/StringHelper.php +++ b/src/StringHelper.php @@ -44,13 +44,13 @@ abstract class StringHelper * * @param string $string The source string. * @param string|null $style The style (default|dash). - * @param integer|null $n If a positive number is supplied, this number is used for the copy, otherwise it is the 'next' number. + * @param integer $n If a positive number is supplied, this number is used for the copy, otherwise it is the 'next' number. * * @return string The incremented string. * * @since 1.3.0 */ - public static function increment($string, $style = 'default', $n = null) + public static function increment($string, $style = 'default', $n = 0) { $styleSpec = static::$incrementStyles[$style] ?? static::$incrementStyles['default']; @@ -135,7 +135,7 @@ public static function ord($chr) * @link https://www.php.net/strpos * @since 1.3.0 */ - public static function strpos($haystack, $needle, $offset = null) + public static function strpos($haystack, $needle, $offset = false) { if ($offset === null) { @@ -176,16 +176,16 @@ public static function strrpos($haystack, $needle, $offset = 0) * * @param string $str String being processed * @param integer $offset Number of UTF-8 characters offset (from left) - * @param integer|null $length Optional length in UTF-8 characters from offset + * @param integer|null|boolean $length Optional length in UTF-8 characters from offset * * @return string|boolean * * @link https://www.php.net/substr * @since 1.3.0 */ - public static function substr($str, $offset, $length = null) + public static function substr($str, $offset, $length = false) { - if ($length === null) + if ($length === false) { return utf8_substr($str, $offset); } From f0a26f6276eaa24fc7f83e02c86e3cb0054ee736 Mon Sep 17 00:00:00 2001 From: Niels Braczek Date: Fri, 24 Sep 2021 14:11:26 +0200 Subject: [PATCH 9/9] Tests - Add tests to ensure that signature changes are not breaking b/c --- Tests/StringHelperTest.php | 201 +++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/Tests/StringHelperTest.php b/Tests/StringHelperTest.php index f65a16d3..56f61290 100644 --- a/Tests/StringHelperTest.php +++ b/Tests/StringHelperTest.php @@ -1079,4 +1079,205 @@ public function testValid(string $string, bool $expected): void StringHelper::valid($string) ); } + + /** + * @return \Generator + */ + public function seedTestIncrementSignature(): \Generator + { + // Note: string, style + yield 'unnumbered default' => ['test', 'default']; + yield 'unnumbered dash' => ['test', 'dash']; + yield 'numbered default' => ['test (3)', 'default']; + yield 'numbered dash' => ['test-3', 'dash']; + } + + /** + * @testdox Change of default value in StringHelper::increment() is backward compatible + * + * @dataProvider seedTestIncrementSignature + */ + public function testIncrementSignature(string $string, string $style): void + { + $curDefault = StringHelper::increment($string, $style); + $oldDefault = StringHelper::increment($string, $style, 0); + $newDefault = StringHelper::increment($string, $style, null); + + $this->assertSame( + $curDefault, + $oldDefault, + 'Omitting argument should give the same result as providing the old default value' + ); + $this->assertSame( + $curDefault, + $newDefault, + 'Omitting argument should give the same result as providing the new default value' + ); + } + + /** + * @return \Generator + */ + public function seedTestStrPosSignature(): \Generator + { + // Note: haystack, needle + yield 'existing substring at the beginning' => ['teststring', 'test']; + yield 'existing substring within the string' => ['teststring', 'string']; + yield 'non-existing substring' => ['teststring', 'foo']; + } + + /** + * @testdox Change of default value in StringHelper::strpos() is backward compatible + * + * @dataProvider seedTestStrPosSignature + */ + public function testStrPosSignature(string $haystack, string $needle): void + { + $curDefault = StringHelper::strpos($haystack, $needle); + $actDefault = StringHelper::strpos($haystack, $needle, 0); + $oldDefault = StringHelper::strpos($haystack, $needle, false); + $newDefault = StringHelper::strpos($haystack, $needle, null); + + $this->assertSame( + $curDefault, + $actDefault, + 'Omitting argument should give the same result as providing the actual default value' + ); + $this->assertSame( + $curDefault, + $oldDefault, + 'Omitting argument should give the same result as providing the old default value' + ); + $this->assertSame( + $curDefault, + $newDefault, + 'Omitting argument should give the same result as providing the new default value' + ); + } + + /** + * @testdox Change of default value in StringHelper::strrpos() is backward compatible + * + * @dataProvider seedTestStrPosSignature + */ + public function testStrRPosSignature(string $haystack, string $needle): void + { + $curDefault = StringHelper::strrpos($haystack, $needle); + $oldDefault = StringHelper::strrpos($haystack, $needle, 0); + $newDefault = StringHelper::strrpos($haystack, $needle, null); + + $this->assertSame( + $curDefault, + $oldDefault, + 'Omitting argument should give the same result as providing the old default value' + ); + $this->assertSame( + $curDefault, + $newDefault, + 'Omitting argument should give the same result as providing the new default value' + ); + } + + /** + * @return \Generator + */ + public function seedTestSubStrSignature(): \Generator + { + // Note: haystack, needle + yield 'offset at the beginning' => ['teststring', 0]; + yield 'offset within the string' => ['teststring', 4]; + yield 'offset out of bounds' => ['teststring', 12]; + } + + /** + * @testdox Change of default value in StringHelper::substr() is backward compatible + * + * @dataProvider seedTestSubStrSignature + */ + public function testSubStrSignature(string $str, int $offset): void + { + $curDefault = StringHelper::substr($str, $offset); + $oldDefault = StringHelper::substr($str, $offset, false); + $newDefault = StringHelper::substr($str, $offset, null); + + $this->assertSame( + $curDefault, + $oldDefault, + 'Omitting argument should give the same result as providing the old default value' + ); + $this->assertSame( + $curDefault, + $newDefault, + 'Omitting argument should give the same result as providing the new default value' + ); + } + + /** + * @return \Generator + */ + public function seedTestStrCmpSignature(): \Generator + { + // Note: haystack, needle + yield 'a < b' => ['a', 'b']; + yield 'a = a' => ['a', 'a']; + yield 'b > a' => ['b', 'a']; + } + + /** + * @testdox Change of default value in StringHelper::strcasecmp() is backward compatible + * + * @dataProvider seedTestStrCmpSignature + */ + public function testStrCaseCmpSignature(string $str1, string $str2): void + { + $curDefault = StringHelper::strcasecmp($str1, $str2); + $oldDefault = StringHelper::strcasecmp($str1, $str2, false); + $newDefault = StringHelper::strcasecmp($str1, $str2, null); + + $this->assertSame( + $this->sign($curDefault), + $this->sign($oldDefault), + 'Omitting argument should give the same result as providing the old default value' + ); + $this->assertSame( + $this->sign($curDefault), + $this->sign($newDefault), + 'Omitting argument should give the same result as providing the new default value' + ); + } + + /** + * @testdox Change of default value in StringHelper::strcmp() is backward compatible + * + * @dataProvider seedTestStrCmpSignature + */ + public function testStrCmpSignature(string $str1, string $str2): void + { + $curDefault = StringHelper::strcmp($str1, $str2); + $oldDefault = StringHelper::strcmp($str1, $str2, false); + $newDefault = StringHelper::strcmp($str1, $str2, null); + + $this->assertSame( + $this->sign($curDefault), + $this->sign($oldDefault), + 'Omitting argument should give the same result as providing the old default value' + ); + $this->assertSame( + $this->sign($curDefault), + $this->sign($newDefault), + 'Omitting argument should give the same result as providing the new default value' + ); + } + + /** + * Determine the sign of an integer + * + * @param int $value + * + * @return int + */ + private function sign(int $value): int + { + return $value <=> 0; + } }