This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Str;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Str\CaseConverter;
|
||||
|
||||
final class CaseConverterTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// toSnake
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testCamelToSnake(): void
|
||||
{
|
||||
self::assertSame('user_profile_name', CaseConverter::toSnake('userProfileName'));
|
||||
}
|
||||
|
||||
public function testPascalToSnake(): void
|
||||
{
|
||||
self::assertSame('user_profile_name', CaseConverter::toSnake('UserProfileName'));
|
||||
}
|
||||
|
||||
public function testKebabToSnake(): void
|
||||
{
|
||||
self::assertSame('user_profile_name', CaseConverter::toSnake('user-profile-name'));
|
||||
}
|
||||
|
||||
public function testScreamingSnakeToSnake(): void
|
||||
{
|
||||
self::assertSame('user_profile_name', CaseConverter::toSnake('USER_PROFILE_NAME'));
|
||||
}
|
||||
|
||||
public function testSnakeToSnakeIsIdempotent(): void
|
||||
{
|
||||
self::assertSame('user_profile_name', CaseConverter::toSnake('user_profile_name'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// toCamel
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testSnakeToCamel(): void
|
||||
{
|
||||
self::assertSame('userProfileName', CaseConverter::toCamel('user_profile_name'));
|
||||
}
|
||||
|
||||
public function testKebabToCamel(): void
|
||||
{
|
||||
self::assertSame('userProfileName', CaseConverter::toCamel('user-profile-name'));
|
||||
}
|
||||
|
||||
public function testPascalToCamel(): void
|
||||
{
|
||||
self::assertSame('userProfileName', CaseConverter::toCamel('UserProfileName'));
|
||||
}
|
||||
|
||||
public function testCamelToCamelIsIdempotent(): void
|
||||
{
|
||||
self::assertSame('userProfileName', CaseConverter::toCamel('userProfileName'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// toPascal
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testSnakeToPascal(): void
|
||||
{
|
||||
self::assertSame('UserProfileName', CaseConverter::toPascal('user_profile_name'));
|
||||
}
|
||||
|
||||
public function testKebabToPascal(): void
|
||||
{
|
||||
self::assertSame('UserProfileName', CaseConverter::toPascal('user-profile-name'));
|
||||
}
|
||||
|
||||
public function testCamelToPascal(): void
|
||||
{
|
||||
self::assertSame('UserProfileName', CaseConverter::toPascal('userProfileName'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// toKebab
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testPascalToKebab(): void
|
||||
{
|
||||
self::assertSame('user-profile-name', CaseConverter::toKebab('UserProfileName'));
|
||||
}
|
||||
|
||||
public function testSnakeToKebab(): void
|
||||
{
|
||||
self::assertSame('user-profile-name', CaseConverter::toKebab('user_profile_name'));
|
||||
}
|
||||
|
||||
public function testCamelToKebab(): void
|
||||
{
|
||||
self::assertSame('user-profile-name', CaseConverter::toKebab('userProfileName'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// toScreamingSnake
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testCamelToScreamingSnake(): void
|
||||
{
|
||||
self::assertSame('USER_ID', CaseConverter::toScreamingSnake('userId'));
|
||||
}
|
||||
|
||||
public function testKebabToScreamingSnake(): void
|
||||
{
|
||||
self::assertSame('USER_PROFILE_NAME', CaseConverter::toScreamingSnake('user-profile-name'));
|
||||
}
|
||||
|
||||
public function testSnakeToScreamingSnake(): void
|
||||
{
|
||||
self::assertSame('USER_PROFILE_NAME', CaseConverter::toScreamingSnake('user_profile_name'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Special cases
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testSingleWord(): void
|
||||
{
|
||||
self::assertSame('name', CaseConverter::toSnake('name'));
|
||||
self::assertSame('name', CaseConverter::toCamel('name'));
|
||||
self::assertSame('Name', CaseConverter::toPascal('name'));
|
||||
self::assertSame('NAME', CaseConverter::toScreamingSnake('name'));
|
||||
}
|
||||
|
||||
public function testAcronymInPascal(): void
|
||||
{
|
||||
self::assertSame('html_parser', CaseConverter::toSnake('HTMLParser'));
|
||||
}
|
||||
|
||||
public function testEmptyString(): void
|
||||
{
|
||||
self::assertSame('', CaseConverter::toCamel(''));
|
||||
self::assertSame('', CaseConverter::toSnake(''));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Str;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Str\Interpolate;
|
||||
|
||||
final class InterpolateTest extends TestCase
|
||||
{
|
||||
public function testBasicReplacement(): void
|
||||
{
|
||||
$result = Interpolate::render(
|
||||
'Hello {name}, you have {count} messages.',
|
||||
['name' => 'Anna', 'count' => 5],
|
||||
);
|
||||
|
||||
self::assertSame('Hello Anna, you have 5 messages.', $result);
|
||||
}
|
||||
|
||||
public function testNoPlaceholdersReturnsTemplateUnchanged(): void
|
||||
{
|
||||
self::assertSame('Hello World', Interpolate::render('Hello World', []));
|
||||
}
|
||||
|
||||
public function testCustomDelimiters(): void
|
||||
{
|
||||
$result = Interpolate::render(
|
||||
'Hello :name!',
|
||||
['name' => 'Bob'],
|
||||
prefix: ':',
|
||||
suffix: '',
|
||||
);
|
||||
|
||||
self::assertSame('Hello Bob!', $result);
|
||||
}
|
||||
|
||||
public function testNestedKeyWithDotNotation(): void
|
||||
{
|
||||
$result = Interpolate::render(
|
||||
'Welcome, {user.name}!',
|
||||
['user' => ['name' => 'Clara']],
|
||||
);
|
||||
|
||||
self::assertSame('Welcome, Clara!', $result);
|
||||
}
|
||||
|
||||
public function testDeeplyNestedKey(): void
|
||||
{
|
||||
$result = Interpolate::render(
|
||||
'{a.b.c}',
|
||||
['a' => ['b' => ['c' => 'deep']]],
|
||||
);
|
||||
|
||||
self::assertSame('deep', $result);
|
||||
}
|
||||
|
||||
public function testMissingKeyThrowsByDefault(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Missing key "missing"');
|
||||
|
||||
Interpolate::render('Hello {missing}!', []);
|
||||
}
|
||||
|
||||
public function testMissingKeyUsesFallback(): void
|
||||
{
|
||||
$result = Interpolate::render('Hello {missing}!', [], fallback: 'N/A');
|
||||
|
||||
self::assertSame('Hello N/A!', $result);
|
||||
}
|
||||
|
||||
public function testEmptyStringFallback(): void
|
||||
{
|
||||
$result = Interpolate::render('{greeting} World', [], fallback: '');
|
||||
|
||||
self::assertSame(' World', $result);
|
||||
}
|
||||
|
||||
public function testIntegerValueIsStringified(): void
|
||||
{
|
||||
$result = Interpolate::render('Count: {n}', ['n' => 42]);
|
||||
|
||||
self::assertSame('Count: 42', $result);
|
||||
}
|
||||
|
||||
public function testEmptyTemplateReturnsEmpty(): void
|
||||
{
|
||||
self::assertSame('', Interpolate::render('', []));
|
||||
}
|
||||
|
||||
public function testMissingNestedKeyThrows(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
Interpolate::render('{user.email}', ['user' => ['name' => 'Anna']]);
|
||||
}
|
||||
|
||||
public function testMultiplePlaceholdersReplacedInOrder(): void
|
||||
{
|
||||
$result = Interpolate::render(
|
||||
'{a} {b} {a}',
|
||||
['a' => 'foo', 'b' => 'bar'],
|
||||
);
|
||||
|
||||
self::assertSame('foo bar foo', $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Str;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Str\Mask;
|
||||
|
||||
final class MaskTest extends TestCase
|
||||
{
|
||||
// --- email ---
|
||||
|
||||
public function testEmailMasksLocalPart(): void
|
||||
{
|
||||
self::assertSame('t***@example.com', Mask::email('test@example.com'));
|
||||
}
|
||||
|
||||
public function testEmailKeepsOnlyFirstChar(): void
|
||||
{
|
||||
self::assertSame('a***@domain.org', Mask::email('administrator@domain.org'));
|
||||
}
|
||||
|
||||
public function testEmailEmptyStringReturnsEmpty(): void
|
||||
{
|
||||
self::assertSame('', Mask::email(''));
|
||||
}
|
||||
|
||||
public function testEmailWithoutAtSignReturnsOriginal(): void
|
||||
{
|
||||
self::assertSame('notanemail', Mask::email('notanemail'));
|
||||
}
|
||||
|
||||
public function testEmailAtSignAtStartReturnsOriginal(): void
|
||||
{
|
||||
self::assertSame('@example.com', Mask::email('@example.com'));
|
||||
}
|
||||
|
||||
// --- creditCard ---
|
||||
|
||||
public function testCreditCardShowsLastFourDigits(): void
|
||||
{
|
||||
self::assertSame('**** **** **** 1111', Mask::creditCard('4111111111111111'));
|
||||
}
|
||||
|
||||
public function testCreditCardStripsSpaces(): void
|
||||
{
|
||||
self::assertSame('**** **** **** 1234', Mask::creditCard('4000 0000 0000 1234'));
|
||||
}
|
||||
|
||||
public function testCreditCardStripsHyphens(): void
|
||||
{
|
||||
self::assertSame('**** **** **** 9999', Mask::creditCard('4000-0000-0000-9999'));
|
||||
}
|
||||
|
||||
public function testCreditCardTooShortReturnsOriginal(): void
|
||||
{
|
||||
self::assertSame('123', Mask::creditCard('123'));
|
||||
}
|
||||
|
||||
// --- iban ---
|
||||
|
||||
public function testIbanMasksMiddleSection(): void
|
||||
{
|
||||
self::assertSame('DE89 **** **** **** **** 00', Mask::iban('DE89370400440532013000'));
|
||||
}
|
||||
|
||||
public function testIbanStripsExistingSpaces(): void
|
||||
{
|
||||
self::assertSame('DE89 **** **** **** **** 00', Mask::iban('DE89 3704 0044 0532 0130 00'));
|
||||
}
|
||||
|
||||
public function testIbanTooShortReturnsOriginal(): void
|
||||
{
|
||||
self::assertSame('DE89', Mask::iban('DE89'));
|
||||
}
|
||||
|
||||
// --- custom ---
|
||||
|
||||
public function testCustomMasksMiddle(): void
|
||||
{
|
||||
self::assertSame('My*********et', Mask::custom('MySuperSecret', start: 2, end: 2));
|
||||
}
|
||||
|
||||
public function testCustomStartOnly(): void
|
||||
{
|
||||
self::assertSame('He********', Mask::custom('HelloWorld', start: 2, end: 0));
|
||||
}
|
||||
|
||||
public function testCustomEndOnly(): void
|
||||
{
|
||||
self::assertSame('********ld', Mask::custom('HelloWorld', start: 0, end: 2));
|
||||
}
|
||||
|
||||
public function testCustomDifferentMaskChar(): void
|
||||
{
|
||||
self::assertSame('He######ld', Mask::custom('HelloWorld', start: 2, end: 2, char: '#'));
|
||||
}
|
||||
|
||||
public function testCustomStartPlusEndEqualsLengthReturnsOriginal(): void
|
||||
{
|
||||
self::assertSame('Hello', Mask::custom('Hello', start: 3, end: 2));
|
||||
}
|
||||
|
||||
public function testCustomEmptyStringReturnsEmpty(): void
|
||||
{
|
||||
self::assertSame('', Mask::custom('', start: 1, end: 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Str;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Str\Random;
|
||||
|
||||
final class RandomTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// token()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testTokenReturnsCorrectLength(): void
|
||||
{
|
||||
self::assertSame(32, strlen(Random::token(32)));
|
||||
self::assertSame(64, strlen(Random::token(64)));
|
||||
}
|
||||
|
||||
public function testTokenDefaultLengthIs32(): void
|
||||
{
|
||||
self::assertSame(32, strlen(Random::token()));
|
||||
}
|
||||
|
||||
public function testTokenReturnsOnlyHexChars(): void
|
||||
{
|
||||
self::assertMatchesRegularExpression('/^[0-9a-f]+$/', Random::token(64));
|
||||
}
|
||||
|
||||
public function testTokenOddLengthWorks(): void
|
||||
{
|
||||
self::assertSame(7, strlen(Random::token(7)));
|
||||
}
|
||||
|
||||
public function testTokenThrowsOnZeroLength(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
Random::token(0);
|
||||
}
|
||||
|
||||
public function testTokenThrowsOnNegativeLength(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
Random::token(-1);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// alphanumeric()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAlphanumericReturnsCorrectLength(): void
|
||||
{
|
||||
self::assertSame(16, strlen(Random::alphanumeric(16)));
|
||||
}
|
||||
|
||||
public function testAlphanumericDefaultLengthIs32(): void
|
||||
{
|
||||
self::assertSame(32, strlen(Random::alphanumeric()));
|
||||
}
|
||||
|
||||
public function testAlphanumericReturnsOnlyAlphanumericChars(): void
|
||||
{
|
||||
self::assertMatchesRegularExpression('/^[a-zA-Z0-9]+$/', Random::alphanumeric(128));
|
||||
}
|
||||
|
||||
public function testAlphanumericThrowsOnZeroLength(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
Random::alphanumeric(0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// charset()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testCharsetReturnsCorrectLength(): void
|
||||
{
|
||||
self::assertSame(12, strlen(Random::charset(12, '0123456789')));
|
||||
}
|
||||
|
||||
public function testCharsetReturnsOnlyCharsFromAlphabet(): void
|
||||
{
|
||||
$alphabet = 'ABCDE';
|
||||
$result = Random::charset(100, $alphabet);
|
||||
|
||||
self::assertMatchesRegularExpression('/^[ABCDE]+$/', $result);
|
||||
}
|
||||
|
||||
public function testCharsetWithSingleCharReturnsRepeatedChar(): void
|
||||
{
|
||||
self::assertSame('aaaa', Random::charset(4, 'a'));
|
||||
}
|
||||
|
||||
public function testCharsetThrowsOnZeroLength(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
Random::charset(0, 'abc');
|
||||
}
|
||||
|
||||
public function testCharsetThrowsOnEmptyAlphabet(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
Random::charset(8, '');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Randomness sanity check
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testTwoTokensAreDifferent(): void
|
||||
{
|
||||
self::assertNotSame(Random::token(64), Random::token(64));
|
||||
}
|
||||
|
||||
public function testTwoAlphanumericStringsAreDifferent(): void
|
||||
{
|
||||
self::assertNotSame(Random::alphanumeric(64), Random::alphanumeric(64));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Str;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Str\Slugify;
|
||||
|
||||
final class SlugifyTest extends TestCase
|
||||
{
|
||||
public function testBasicSlug(): void
|
||||
{
|
||||
self::assertSame('hello-world', Slugify::slug('Hello World'));
|
||||
}
|
||||
|
||||
public function testGermanUmlauts(): void
|
||||
{
|
||||
self::assertSame('aerger-mit-umlauten', Slugify::slug('Ärger mit Umlauten!'));
|
||||
}
|
||||
|
||||
public function testGermanSharpS(): void
|
||||
{
|
||||
self::assertSame('strasse', Slugify::slug('Straße'));
|
||||
}
|
||||
|
||||
public function testFrenchAccents(): void
|
||||
{
|
||||
self::assertSame('creme-brulee', Slugify::slug('Crème brûlée'));
|
||||
}
|
||||
|
||||
public function testMultipleSpacesCollapsed(): void
|
||||
{
|
||||
self::assertSame('multiple-spaces', Slugify::slug(' Multiple Spaces '));
|
||||
}
|
||||
|
||||
public function testCustomSeparator(): void
|
||||
{
|
||||
self::assertSame('hello_world', Slugify::slug('Hello World', '_'));
|
||||
}
|
||||
|
||||
public function testSpecialCharactersReplaced(): void
|
||||
{
|
||||
self::assertSame('foo-bar-baz', Slugify::slug('foo/bar::baz'));
|
||||
}
|
||||
|
||||
public function testLeadingAndTrailingSeparatorsRemoved(): void
|
||||
{
|
||||
self::assertSame('hello', Slugify::slug('---hello---'));
|
||||
}
|
||||
|
||||
public function testEmptyStringReturnsEmpty(): void
|
||||
{
|
||||
self::assertSame('', Slugify::slug(''));
|
||||
}
|
||||
|
||||
public function testBlankStringReturnsEmpty(): void
|
||||
{
|
||||
self::assertSame('', Slugify::slug(' '));
|
||||
}
|
||||
|
||||
public function testNordicCharacters(): void
|
||||
{
|
||||
self::assertSame('ae-o', Slugify::slug('Æ Ø'));
|
||||
}
|
||||
|
||||
public function testSpanishN(): void
|
||||
{
|
||||
self::assertSame('canon', Slugify::slug('Cañon'));
|
||||
}
|
||||
|
||||
public function testAlreadyValidSlugUnchanged(): void
|
||||
{
|
||||
self::assertSame('hello-world-123', Slugify::slug('hello-world-123'));
|
||||
}
|
||||
|
||||
public function testNumbersPreserved(): void
|
||||
{
|
||||
self::assertSame('php-8-3', Slugify::slug('PHP 8.3'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Str;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Str\Truncate;
|
||||
|
||||
final class TruncateTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// Short strings — returned unchanged
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testShortStringReturnedUnchanged(): void
|
||||
{
|
||||
self::assertSame('Short', Truncate::chars('Short', 20));
|
||||
}
|
||||
|
||||
public function testStringExactlyAtLimitReturnedUnchanged(): void
|
||||
{
|
||||
self::assertSame('12345', Truncate::chars('12345', 5));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Word boundary (default)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testTruncatesAtWordBoundary(): void
|
||||
{
|
||||
// cut position falls on a space → keep up to the space
|
||||
self::assertSame(
|
||||
'The quick brown fox…',
|
||||
Truncate::chars('The quick brown fox jumps over the lazy dog', 20),
|
||||
);
|
||||
}
|
||||
|
||||
public function testTruncatesBackToLastWordWhenCutIsInsideWord(): void
|
||||
{
|
||||
// limit=15, suffix=1 → cut at 14 chars → 'The quick brow' → trims to 'The quick'
|
||||
self::assertSame('The quick…', Truncate::chars('The quick brown fox', 15));
|
||||
}
|
||||
|
||||
public function testWordBoundaryWithNoSpaceInCutKeepsRawCut(): void
|
||||
{
|
||||
self::assertSame('Hello…', Truncate::chars('Hellothere', 6));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Exact cut (wordBoundary = false)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testExactCutIgnoresWordBoundary(): void
|
||||
{
|
||||
self::assertSame(
|
||||
'The quick brown fox…',
|
||||
Truncate::chars('The quick brown fox jumps', 20, wordBoundary: false),
|
||||
);
|
||||
}
|
||||
|
||||
public function testExactCutSplitsWord(): void
|
||||
{
|
||||
self::assertSame('The quick bro…', Truncate::chars('The quick brown fox', 14, wordBoundary: false));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Custom suffix
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testCustomSuffix(): void
|
||||
{
|
||||
// suffix=' [...]' (6 chars), limit=22 → cut at 16 → 'The quick brown ' → trims to 'The quick brown'
|
||||
self::assertSame(
|
||||
'The quick brown [...]',
|
||||
Truncate::chars('The quick brown fox jumps', 22, suffix: ' [...]'),
|
||||
);
|
||||
}
|
||||
|
||||
public function testEmptySuffix(): void
|
||||
{
|
||||
self::assertSame('The quick', Truncate::chars('The quick brown fox', 9, suffix: ''));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Multibyte
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testMultibyteCharactersCountedCorrectly(): void
|
||||
{
|
||||
// 'Ärger mit Umlauten' — each char counts as 1 even though ä is multibyte
|
||||
self::assertSame('Ärger…', Truncate::chars('Ärger mit Umlauten', 6));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Edge cases / exceptions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testThrowsWhenLimitShorterThanSuffix(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
Truncate::chars('Hello', 2, suffix: '...');
|
||||
}
|
||||
|
||||
public function testLimitEqualToSuffixLengthReturnsEmptySuffix(): void
|
||||
{
|
||||
// limit=3, suffix='...' (3 chars) → cutLength=0 → '' + '...'
|
||||
self::assertSame('...', Truncate::chars('Hello world', 3, suffix: '...'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user