330 lines
12 KiB
PHP
330 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit;
|
|
|
|
use Tests\TestCase;
|
|
use App\Utils\Encode;
|
|
|
|
class EncodeEmailSubjectTest extends TestCase
|
|
{
|
|
/**
|
|
* Test the exact example provided by the user
|
|
*/
|
|
public function testUserSpecificExample()
|
|
{
|
|
$originalSubject = "Rappel facture impayée (\$invoice) 🚀";
|
|
$convertedSubject = Encode::convert($originalSubject);
|
|
|
|
// Should return unchanged - already valid UTF-8
|
|
$this->assertEquals($originalSubject, $convertedSubject);
|
|
$this->assertTrue(mb_check_encoding($convertedSubject, 'UTF-8'));
|
|
|
|
// Verify emoji is preserved
|
|
$this->assertStringContainsString('🚀', $convertedSubject);
|
|
$this->assertStringContainsString('é', $convertedSubject);
|
|
// Verify accented characters are preserved
|
|
$this->assertStringContainsString('impayée', $convertedSubject);
|
|
|
|
// Verify the string length is correct (emojis are multi-byte)
|
|
$this->assertEquals(mb_strlen($originalSubject, 'UTF-8'), mb_strlen($convertedSubject, 'UTF-8'));
|
|
}
|
|
|
|
/**
|
|
* Test various email subject scenarios with emojis
|
|
*/
|
|
public function testEmojiEmailSubjects()
|
|
{
|
|
$testCases = [
|
|
// Single emoji
|
|
"Invoice Ready 📧" => "Invoice Ready 📧",
|
|
|
|
// Multiple emojis
|
|
"Payment Received ✅ 🎉" => "Payment Received ✅ 🎉",
|
|
|
|
// Emoji at start
|
|
"🚨 Urgent: Payment Overdue" => "🚨 Urgent: Payment Overdue",
|
|
|
|
// Emoji at end
|
|
"Welcome to our service! 🎯" => "Welcome to our service! 🎯",
|
|
|
|
// Complex emojis (family, skin tones, etc.)
|
|
"Team meeting 👨💻👩💻" => "Team meeting 👨💻👩💻",
|
|
|
|
// Mixed flags and symbols
|
|
"Conference in Paris 🇫🇷 ✈️" => "Conference in Paris 🇫🇷 ✈️",
|
|
"Nouvelle facture de Réact" => "Nouvelle facture de Réact"
|
|
];
|
|
|
|
foreach ($testCases as $input => $expected) {
|
|
$result = Encode::convert($input);
|
|
|
|
$this->assertEquals($expected, $result, "Failed for emoji test: {$input}");
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'), "Not valid UTF-8: {$input}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test accented characters common in email subjects
|
|
*/
|
|
public function testAccentedCharacters()
|
|
{
|
|
$testCases = [
|
|
// French
|
|
"Café résumé naïve façade" => "Café résumé naïve façade",
|
|
|
|
// Spanish
|
|
"Niño piñata mañana" => "Niño piñata mañana",
|
|
|
|
// German
|
|
"Größe Weiß Mädchen" => "Größe Weiß Mädchen",
|
|
|
|
// Portuguese
|
|
"Coração São Paulo" => "Coração São Paulo",
|
|
|
|
// Mixed languages
|
|
"Café & Niño résumé" => "Café & Niño résumé",
|
|
"Nouvelle facture de Réact" => "Nouvelle facture de Réact"
|
|
];
|
|
|
|
foreach ($testCases as $input => $expected) {
|
|
$result = Encode::convert($input);
|
|
|
|
$this->assertEquals($expected, $result, "Failed for accent test: {$input}");
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'), "Not valid UTF-8: {$input}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test special symbols commonly used in email subjects
|
|
*/
|
|
public function testSpecialSymbols()
|
|
{
|
|
$testCases = [
|
|
// Currency symbols
|
|
"Invoice €50.00 £25.99 ¥1000" => "Invoice €50.00 £25.99 ¥1000",
|
|
|
|
// Smart quotes and dashes
|
|
"Company's \"quoted\" text—dash…ellipsis" => "Company's \"quoted\" text—dash…ellipsis",
|
|
|
|
// Copyright and trademark
|
|
"Product™ Service© Brand®" => "Product™ Service© Brand®",
|
|
|
|
// Mathematical symbols
|
|
"Discount ≥ 20% ± 5%" => "Discount ≥ 20% ± 5%",
|
|
|
|
// Arrows and symbols
|
|
"Process → Complete ✓" => "Process → Complete ✓",
|
|
"Nouvelle facture de Réact" => "Nouvelle facture de Réact"
|
|
];
|
|
|
|
foreach ($testCases as $input => $expected) {
|
|
$result = Encode::convert($input);
|
|
|
|
$this->assertEquals($expected, $result, "Failed for symbol test: {$input}");
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'), "Not valid UTF-8: {$input}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test email subjects with mixed content (the most realistic scenario)
|
|
*/
|
|
public function testMixedContentEmailSubjects()
|
|
{
|
|
$testCases = [
|
|
// User's exact example
|
|
"Rappel facture impayée (\$invoice) 🚀" => "Rappel facture impayée (\$invoice) 🚀",
|
|
|
|
// Invoice with currency and emoji
|
|
"Facture #123 - €150.00 💰" => "Facture #123 - €150.00 💰",
|
|
|
|
// Reminder with accents and emoji
|
|
"Relance: paiement en retard 📅 ⚠️" => "Relance: paiement en retard 📅 ⚠️",
|
|
|
|
// Welcome message
|
|
"Bienvenue chez Café ☕ 🥐" => "Bienvenue chez Café ☕ 🥐",
|
|
|
|
// Complex business scenario
|
|
"Réunion équipe → 15h30 📊 🎯" => "Réunion équipe → 15h30 📊 🎯",
|
|
"Nouvelle facture de Réact" => "Nouvelle facture de Réact"
|
|
];
|
|
|
|
foreach ($testCases as $input => $expected) {
|
|
$result = Encode::convert($input);
|
|
|
|
$this->assertEquals($expected, $result, "Failed for mixed content test: {$input}");
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'), "Not valid UTF-8: {$input}");
|
|
|
|
// Verify character count is preserved (important for emojis)
|
|
$this->assertEquals(
|
|
mb_strlen($expected, 'UTF-8'),
|
|
mb_strlen($result, 'UTF-8'),
|
|
"Character count mismatch for: {$input}"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test corrupted Windows-1252 content that needs conversion
|
|
*/
|
|
public function testCorruptedEncodingConversion()
|
|
{
|
|
// Simulate content that was incorrectly encoded as Windows-1252
|
|
$windows1252Input = mb_convert_encoding("Café résumé", 'WINDOWS-1252', 'UTF-8');
|
|
$result = Encode::convert($windows1252Input);
|
|
|
|
$this->assertEquals("Café résumé", $result);
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'));
|
|
}
|
|
|
|
/**
|
|
* Test Gmail-specific email subject requirements
|
|
*/
|
|
public function testGmailCompatibility()
|
|
{
|
|
$testCases = [
|
|
// Long subject with emojis (Gmail truncates at ~70 chars in preview)
|
|
"This is a long email subject with emojis 🚀 that might get truncated by Gmail 📧",
|
|
|
|
// Subject with only emojis
|
|
"🚀📧🎉✅⚠️💰",
|
|
|
|
// Subject with special characters Gmail handles
|
|
"Re: Fw: [URGENT] Company's \"Project\" Status—Update ✓",
|
|
|
|
// International content
|
|
"国际业务 🌍 Négociation €500K 💼",
|
|
"Nouvelle facture de Réact" => "Nouvelle facture de Réact"
|
|
];
|
|
|
|
foreach ($testCases as $input) {
|
|
$result = Encode::convert($input);
|
|
|
|
// Should be valid UTF-8 (Gmail requirement)
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'), "Gmail compatibility failed for: {$input}");
|
|
|
|
// Should not contain replacement characters
|
|
$this->assertStringNotContainsString("\xEF\xBF\xBD", $result, "Contains replacement characters: {$input}");
|
|
$this->assertStringNotContainsString('�', $result, "Contains double-encoded replacement: {$input}");
|
|
|
|
// Should preserve original content for valid UTF-8
|
|
$this->assertEquals($input, $result, "Content changed unnecessarily: {$input}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test edge cases that might break email clients
|
|
*/
|
|
public function testEmailClientEdgeCases()
|
|
{
|
|
$testCases = [
|
|
// Empty string
|
|
"" => "",
|
|
|
|
// Only spaces
|
|
" " => " ",
|
|
|
|
// Only special characters
|
|
"€£¥" => "€£¥",
|
|
|
|
// Only emojis
|
|
"🚀🎉📧" => "🚀🎉📧",
|
|
|
|
// Mixed spaces and emojis
|
|
" 🚀 📧 🎉 " => " 🚀 📧 🎉 ",
|
|
|
|
// Newlines and tabs (should be preserved)
|
|
"Line 1\nLine 2\tTabbed" => "Line 1\nLine 2\tTabbed",
|
|
"Nouvelle facture de Réact" => "Nouvelle facture de Réact"
|
|
];
|
|
|
|
foreach ($testCases as $input => $expected) {
|
|
$result = Encode::convert($input);
|
|
|
|
$this->assertEquals($expected, $result, "Edge case failed: " . var_export($input, true));
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'), "Not valid UTF-8: " . var_export($input, true));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Test performance with typical email subject lengths
|
|
*/
|
|
public function testPerformanceWithTypicalSubjects2()
|
|
{
|
|
$baseSubject = "Nouvelle facture de Réact";
|
|
|
|
// Test with different subject lengths
|
|
$subjects = [
|
|
$baseSubject, // ~40 chars
|
|
str_repeat($baseSubject . " ", 2), // ~80 chars
|
|
str_repeat($baseSubject . " ", 5), // ~200 chars
|
|
];
|
|
|
|
foreach ($subjects as $subject) {
|
|
$startTime = microtime(true);
|
|
$result = Encode::convert($subject);
|
|
$endTime = microtime(true);
|
|
|
|
$executionTime = ($endTime - $startTime) * 1000; // Convert to milliseconds
|
|
|
|
// Should complete quickly (under 10ms for email subjects)
|
|
$this->assertLessThan(10, $executionTime, "Too slow for subject: " . strlen($subject) . " chars");
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'));
|
|
}
|
|
}
|
|
/**
|
|
* Test performance with typical email subject lengths
|
|
*/
|
|
public function testPerformanceWithTypicalSubjects()
|
|
{
|
|
$baseSubject = "Rappel facture impayée (\$invoice) 🚀";
|
|
|
|
// Test with different subject lengths
|
|
$subjects = [
|
|
$baseSubject, // ~40 chars
|
|
str_repeat($baseSubject . " ", 2), // ~80 chars
|
|
str_repeat($baseSubject . " ", 5), // ~200 chars
|
|
];
|
|
|
|
foreach ($subjects as $subject) {
|
|
$startTime = microtime(true);
|
|
$result = Encode::convert($subject);
|
|
$endTime = microtime(true);
|
|
|
|
$executionTime = ($endTime - $startTime) * 1000; // Convert to milliseconds
|
|
|
|
// Should complete quickly (under 10ms for email subjects)
|
|
$this->assertLessThan(10, $executionTime, "Too slow for subject: " . strlen($subject) . " chars");
|
|
$this->assertTrue(mb_check_encoding($result, 'UTF-8'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test that the method is safe to call multiple times
|
|
*/
|
|
public function testIdempotency()
|
|
{
|
|
$original = "Rappel facture impayée (\$invoice) 🚀";
|
|
|
|
$first = Encode::convert($original);
|
|
$second = Encode::convert($first);
|
|
$third = Encode::convert($second);
|
|
|
|
// Should be identical after multiple conversions
|
|
$this->assertEquals($original, $first);
|
|
$this->assertEquals($first, $second);
|
|
$this->assertEquals($second, $third);
|
|
|
|
|
|
$original = "Nouvelle facture de Réact";
|
|
|
|
$first = Encode::convert($original);
|
|
$second = Encode::convert($first);
|
|
$third = Encode::convert($second);
|
|
|
|
// Should be identical after multiple conversions
|
|
$this->assertEquals($original, $first);
|
|
$this->assertEquals($first, $second);
|
|
$this->assertEquals($second, $third);
|
|
}
|
|
}
|