$reflection->newInstance()) ->toThrow(\ReflectionException::class); }); describe('toUtf8', function (): void { it('returns empty string for empty input', function (): void { expect(StringEncoder::toUtf8('', 'UTF-8'))->toBe('') ->and(StringEncoder::toUtf8('', 'Windows-1252'))->toBe(''); }); it('passes valid UTF-8 through unchanged', function (): void { $utf8 = 'Émincés & Rôti de poulet fermier'; expect(StringEncoder::toUtf8($utf8, 'UTF-8'))->toBe($utf8); }); it('converts Windows-1252 special characters to UTF-8', function (): void { // 0x80 (€), 0x8C (Œ), 0x9C (œ), 0x92 (’) $win1252 = "\x80 50 \x8Cuf \x9Cuf l\x92arbre"; $expected = '€ 50 Œuf œuf l’arbre'; expect(StringEncoder::toUtf8($win1252, 'Windows-1252'))->toBe($expected); }); it('converts ISO-8859-15 specific characters to UTF-8', function (): void { // 0xA4 (€ in Latin-9), 0xBC (Œ), 0xBD (œ) $latin9 = "\xA4 50 \xBCuf \xBDuf"; $expected = '€ 50 Œuf œuf'; expect(StringEncoder::toUtf8($latin9, 'ISO-8859-15'))->toBe($expected); }); it('supports encoding aliases case-insensitively', function (string $alias): void { $input = "\xE9"; // é in Latin-1/Win-1252 expect(StringEncoder::toUtf8($input, $alias))->toBe('é'); })->with(['windows-1252', 'WIN1252', 'cp1252', 'latin1', 'latin-1', 'iso-8859-1']); it('strips UTF-8 BOM when stripBom is true', function (): void { $withBom = "\xEF\xBB\xBFHello World"; expect(StringEncoder::toUtf8($withBom, 'UTF-8', stripBom: true))->toBe('Hello World') ->and(StringEncoder::toUtf8($withBom, 'UTF-8', stripBom: false))->toBe($withBom); }); it('throws ValueError for unsupported encoding', function (): void { expect(fn () => StringEncoder::toUtf8('test', 'Shift-JIS')) ->toThrow(\ValueError::class); }); it('throws ValueError for invalid UTF-8 in toUtf8 with UTF-8 encoding', function (): void { $invalidUtf8 = "\xC3\x28"; // Invalid UTF-8 sequence expect(fn () => StringEncoder::toUtf8($invalidUtf8, 'UTF-8')) ->toThrow(\ValueError::class); }); }); describe('detectAndConvertToUtf8', function (): void { it('returns empty string for empty input', function (): void { expect(StringEncoder::detectAndConvertToUtf8(''))->toBe(''); }); it('preserves valid UTF-8 without re-encoding', function (): void { $utf8 = 'Volaille rôti bio'; expect(StringEncoder::detectAndConvertToUtf8($utf8))->toBe($utf8); }); it('converts non-UTF8 strings using fallback encoding', function (): void { // Non-UTF-8: "é" in Windows-1252 is 0xE9 $win1252 = "R\xE9gal du Volailler"; expect(StringEncoder::detectAndConvertToUtf8($win1252, 'Windows-1252'))->toBe('Régal du Volailler'); }); it('throws ValueError when fallbackEncoding is UTF-8', function (): void { expect(fn () => StringEncoder::detectAndConvertToUtf8('test', 'UTF-8')) ->toThrow(\ValueError::class); }); }); describe('arrayToUtf8', function (): void { it('converts an array of strings to UTF-8', function (): void { $inputs = ["\xE9", "\xE0", "\xE8"]; $expected = ['é', 'à', 'è']; expect(StringEncoder::arrayToUtf8($inputs, 'Windows-1252'))->toBe($expected); }); }); describe('supportedEncodings', function (): void { it('returns list of all supported canonical and alias encodings', function (): void { $encodings = StringEncoder::supportedEncodings(); expect($encodings)->toContain('UTF-8') ->and($encodings)->toContain('Windows-1252') ->and($encodings)->toContain('ISO-8859-15') ->and($encodings)->toContain('latin1') ->and($encodings)->toContain('cp1252'); }); }); });