ldc-arcadia-genesis-php-client/tests/Unit/Utils/StringEncoderTest.php
2026-04-16 09:48:49 +02:00

350 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
use Uwil\ArcadiaGenesisPhpClient\Utils\StringEncoder;
describe('toUtf8()', function (): void {
it('returns an empty string when given an empty string', function (): void {
// Given
$input = '';
// When
$result = StringEncoder::toUtf8($input, 'UTF-8');
// Then
expect($result)->toBe('');
});
it('passes through a valid UTF-8 string with multibyte characters', function (): void {
// Given
$input = 'Héllo café résumé';
// When
$result = StringEncoder::toUtf8($input, 'UTF-8');
// Then
expect($result)->toBe('Héllo café résumé');
});
it('throws ValueError for an invalid UTF-8 string', function (): void {
// Given
$input = "Invalid \xC3\x28 sequence";
// When / Then
StringEncoder::toUtf8($input, 'UTF-8');
})->throws(\ValueError::class, 'not valid UTF-8');
it('strips the UTF-8 BOM by default', function (): void {
// Given
$input = "\xEF\xBB\xBFBonjour";
// When
$result = StringEncoder::toUtf8($input, 'UTF-8');
// Then
expect($result)->toBe('Bonjour');
});
it('preserves the UTF-8 BOM when stripBom is false', function (): void {
// Given
$input = "\xEF\xBB\xBFBonjour";
// When
$result = StringEncoder::toUtf8($input, 'UTF-8', stripBom: false);
// Then
expect($result)->toBe("\xEF\xBB\xBFBonjour");
});
it('converts Windows-1252 special characters to UTF-8', function (int $byte, string $expectedUtf8): void {
// Given
$input = chr($byte);
// When
$result = StringEncoder::toUtf8($input, 'Windows-1252');
// Then
expect($result)->toBe($expectedUtf8);
})->with([
'euro sign (0x80)' => [0x80, '€'],
'single low-9 quote (0x82)' => [0x82, '‚'],
'latin small f (0x83)' => [0x83, 'ƒ'],
'double low-9 quote (0x84)' => [0x84, '„'],
'ellipsis (0x85)' => [0x85, '…'],
'dagger (0x86)' => [0x86, '†'],
'double dagger (0x87)' => [0x87, '‡'],
'circumflex (0x88)' => [0x88, 'ˆ'],
'per mille (0x89)' => [0x89, '‰'],
'S caron (0x8A)' => [0x8A, 'Š'],
'left single guillemet (0x8B)' => [0x8B, '‹'],
'OE ligature (0x8C)' => [0x8C, 'Œ'],
'Z caron (0x8E)' => [0x8E, 'Ž'],
'left single quote (0x91)' => [0x91, "\u{2018}"],
'right single quote (0x92)' => [0x92, "\u{2019}"],
'left double quote (0x93)' => [0x93, "\u{201C}"],
'right double quote (0x94)' => [0x94, "\u{201D}"],
'bullet (0x95)' => [0x95, '•'],
'en dash (0x96)' => [0x96, '–'],
'em dash (0x97)' => [0x97, '—'],
'tilde (0x98)' => [0x98, '˜'],
'trademark (0x99)' => [0x99, '™'],
's caron (0x9A)' => [0x9A, 'š'],
'right single guillemet (0x9B)' => [0x9B, '›'],
'oe ligature (0x9C)' => [0x9C, 'œ'],
'z caron (0x9E)' => [0x9E, 'ž'],
'Y diaeresis (0x9F)' => [0x9F, 'Ÿ'],
]);
it('converts Windows-1252 high bytes (0xA0-0xFF) as ISO-8859-1 identity', function (): void {
// Given
$input = chr(0xE9);
// When
$result = StringEncoder::toUtf8($input, 'Windows-1252');
// Then the result is the UTF-8 encoded é
expect($result)->toBe('é');
});
it('converts a full Windows-1252 sentence with mixed ranges', function (): void {
// Given
$input = "B\x9Cuf \xE0 5\x80";
// When
$result = StringEncoder::toUtf8($input, 'Windows-1252');
// Then
expect($result)->toBe('Bœuf à 5€');
});
it('handles undefined Windows-1252 bytes (0x81, 0x8D, 0x8F, 0x90, 0x9D) via Latin-1 fallback', function (int $byte): void {
// Given
$input = chr($byte);
// When
$result = StringEncoder::toUtf8($input, 'Windows-1252');
// Then
expect(mb_check_encoding($result, 'UTF-8'))->toBeTrue()
->and(mb_strlen($result, 'UTF-8'))->toBe(1);
})->with([0x81, 0x8D, 0x8F, 0x90, 0x9D]);
it('converts ISO-8859-15 euro sign to UTF-8', function (): void {
// Given
$input = chr(0xA4);
// When
$result = StringEncoder::toUtf8($input, 'ISO-8859-15');
// Then
expect($result)->toBe('€');
});
it('accepts encoding aliases', function (string $alias, string $expected): void {
// Given
$input = "caf\xE9"; // é in Latin-1/Win-1252
// When
$result = StringEncoder::toUtf8($input, $alias);
// Then
expect($result)->toBe($expected);
})->with([
'latin1' => ['latin1', 'café'],
'latin-1' => ['latin-1', 'café'],
'win1252' => ['win1252', 'café'],
'windows1252' => ['windows1252', 'café'],
'cp1252' => ['cp1252', 'café'],
]);
it('throws ValueError for an unsupported encoding', function (): void {
// Given / When / Then
StringEncoder::toUtf8('hello', 'EBCDIC-42');
})->throws(\ValueError::class, 'Unsupported encoding');
});
describe('detectAndConvertToUtf8()', function (): void {
it('returns an empty string when given an empty string', function (): void {
// Given
$input = '';
// When
$result = StringEncoder::detectAndConvertToUtf8($input);
// Then
expect($result)->toBe('');
});
it('detects and passes through valid UTF-8 with a hint', function (): void {
// Given
$input = 'Héllo world 🌍';
// When
$result = StringEncoder::detectAndConvertToUtf8($input, 'Windows-1252');
// Then
expect($result)->toBe('Héllo world 🌍');
});
it('strips the UTF-8 BOM from auto-detected UTF-8 strings', function (): void {
// Given
$input = "\xEF\xBB\xBFBonjour";
// When
$result = StringEncoder::detectAndConvertToUtf8($input);
// Then
expect($result)->toBe('Bonjour');
});
it('uses encoding hints to guide detection', function (): void {
// Given
$input = "5\x80";
// When
$result = StringEncoder::detectAndConvertToUtf8($input, 'Windows-1252');
// Then
expect($result)->toBe('5€');
});
it('produces valid UTF-8 output regardless of detection result', function (): void {
// Given
$input = "\xFE\x01\xFE\x01";
// When / Then
try {
$result = StringEncoder::detectAndConvertToUtf8($input);
expect(mb_check_encoding($result, 'UTF-8'))->toBeTrue();
} catch (\RuntimeException $e) {
expect($e->getMessage())->toContain('Unable to detect encoding');
}
});
});
describe('arrayToUtf8()', function (): void {
it('handles an empty array', function (): void {
// Given
$input = [];
// When
$result = StringEncoder::arrayToUtf8($input, 'UTF-8');
// Then
expect($result)->toBe([]);
});
it('converts an array of Windows-1252 strings with special characters', function (): void {
// Given
$input = ["5\x80", "B\x9Cuf"];
// When
$result = StringEncoder::arrayToUtf8($input, 'Windows-1252');
// Then
expect($result)->toBe(['5€', 'Bœuf']);
});
});
describe('supportedEncodings()', function (): void {
it('returns a non-empty list of strings', function (): void {
// Given / When
$result = StringEncoder::supportedEncodings();
// Then
expect($result)
->toBeArray()
->not->toBeEmpty()
->each->toBeString();
});
it('includes all canonical encodings', function (): void {
// Given
$expected = ['UTF-8', 'ISO-8859-15', 'Windows-1252'];
// When
$result = StringEncoder::supportedEncodings();
// Then
foreach ($expected as $encoding) {
expect($result)->toContain($encoding);
}
});
it('includes common aliases', function (): void {
// Given
$aliases = ['latin1', 'latin-1', 'latin9', 'cp1252', 'utf8', 'utf-8'];
// When
$result = StringEncoder::supportedEncodings();
// Then
foreach ($aliases as $alias) {
expect($result)->toContain($alias);
}
});
it('contains no duplicates', function (): void {
// Given
$result = StringEncoder::supportedEncodings();
// When / Then
expect($result)->toBe(array_values(array_unique($result)));
});
});
describe('encoding resolution (via toUtf8)', function (): void {
it('resolves case-insensitively', function (string $variant): void {
// Given
$input = 'hello';
// When
$result = StringEncoder::toUtf8($input, $variant);
// Then
expect($result)->toBe('hello');
})->with(['utf-8', 'UTF-8', 'Utf-8', 'UTF8', 'utf8']);
it('trims whitespace around encoding names', function (): void {
// Given
$input = 'hello';
// When
$result = StringEncoder::toUtf8($input, ' utf-8 ');
// Then
expect($result)->toBe('hello');
});
});
describe('regressions', function (): void {
it('correctly converts "Bœuf" from Windows-1252 (œ = 0x9C)', function (): void {
// Given
$input = "B\x9Cuf";
// When
$result = StringEncoder::toUtf8($input, 'Windows-1252');
// Then
expect($result)->toBe('Bœuf')
->and(mb_check_encoding($result, 'UTF-8'))->toBeTrue();
});
it('round-trips UTF-8 → identity without corruption', function (): void {
// Given
$input = 'Héllo 世界 🎵 Ÿ œ € ™';
// When
$result = StringEncoder::toUtf8($input, 'UTF-8');
// Then
expect($result)->toBe($input);
});
});