parser = new DomTemplateParser(); $this->cssInliner = new CssInliner($this->parser); $this->renderer = new EmailTemplateRenderer($this->parser, $this->cssInliner); }); describe('CssInliner', function () { it('inlines CSS from style tags', function () { $html = '
Hello
Click me '; $result = $this->cssInliner->inline($html); expect($result)->toContain('style="color: blue"'); expect($result)->toContain('style="background-color: red; padding: 10px"'); }); it('handles multiple selectors', function () { $html = 'Text
'; $result = $this->cssInliner->inline($html); // Existing inline style should win expect($result)->toContain('color: red'); // But new property should be added expect($result)->toContain('font-size: 14px'); }); it('removes !important from inline styles', function () { $html = 'Alert
'; $result = $this->cssInliner->inline($html); expect($result)->toContain('style="color: red"'); expect($result)->not->toContain('!important'); }); }); describe('EmailContext', function () { it('implements TemplateContext interface', function () { $context = new EmailContext( data: ['name' => 'John', 'email' => 'john@example.com'] ); expect($context->getData())->toBe(['name' => 'John', 'email' => 'john@example.com']); expect($context->get('name'))->toBe('John'); expect($context->get('missing', 'default'))->toBe('default'); expect($context->has('email'))->toBeTrue(); expect($context->has('missing'))->toBeFalse(); }); it('creates context with tracking parameters', function () { $context = EmailContext::withTracking( data: ['name' => 'John'], source: 'newsletter', campaign: 'welcome' ); expect($context->utmParams)->toHaveKey('utm_source', 'newsletter'); expect($context->utmParams)->toHaveKey('utm_medium', 'email'); expect($context->utmParams)->toHaveKey('utm_campaign', 'welcome'); expect($context->trackingId)->toStartWith('email_'); }); }); describe('EmailContent', function () { it('calculates size using Byte value object', function () { $content = new EmailContent( html: 'Hello World
', // 18 bytes text: 'Hello World' // 11 bytes ); $size = $content->getSize(); expect($size)->toBeInstanceOf(Byte::class); expect($size->toBytes())->toBe(29); }); it('detects multipart content', function () { $withBoth = new EmailContent( html: 'Hello
', text: 'Hello' ); $htmlOnly = new EmailContent( html: 'Hello
', text: '' ); expect($withBoth->hasMultipart())->toBeTrue(); expect($htmlOnly->hasMultipart())->toBeFalse(); }); }); describe('EmailSubject', function () { it('validates subject length', function () { $subject = new EmailSubject('Valid subject'); expect($subject->value)->toBe('Valid subject'); expect($subject->isWithinRecommendedLength())->toBeTrue(); }); it('removes line breaks from subject', function () { $subject = new EmailSubject("Subject with\nnewline"); expect($subject->value)->toBe('Subject with newline'); }); it('throws exception for empty subject', function () { expect(fn () => new EmailSubject(' '))->toThrow(\InvalidArgumentException::class); }); it('creates preview with ellipsis', function () { $longSubject = new EmailSubject('This is a very long subject that needs to be truncated'); expect($longSubject->getPreview(20))->toBe('This is a very lo...'); }); }); describe('EmailTemplateRenderer', function () { it('renders template with variable replacement', function () { // Create a test template $templatePath = __DIR__ . '/test-email.html'; file_put_contents($templatePath, 'Dear {{name}},
Your email is {{email}}
'); $context = new EmailContext( data: [ 'name' => 'John Doe', 'email' => 'john@example.com', ] ); $content = $this->renderer->render($templatePath, $context); expect($content->html)->toContain('Dear John Doe,'); expect($content->html)->toContain('Your email is john@example.com'); expect($content->subject)->toBeInstanceOf(EmailSubject::class); expect($content->subject->value)->toBe('Hello John Doe!'); expect($content->text)->toContain('Dear John Doe,'); // Clean up unlink($templatePath); }); it('adds UTM tracking parameters to links', function () { $templatePath = __DIR__ . '/test-tracking.html'; file_put_contents($templatePath, ' Link 1 Link 2 Anchor Email '); $context = EmailContext::withTracking( data: [], source: 'email', campaign: 'test' ); $content = $this->renderer->render($templatePath, $context); expect($content->html)->toContain('https://example.com?utm_source=email'); expect($content->html)->toContain('https://example.com?foo=bar&utm_source=email'); expect($content->html)->toContain('href="#anchor"'); // Unchanged expect($content->html)->toContain('href="mailto:test@example.com"'); // Unchanged unlink($templatePath); }); it('generates plain text from HTML', function () { $templatePath = __DIR__ . '/test-plaintext.html'; file_put_contents($templatePath, 'Paragraph 1
Paragraph 2
Content
'); $context = new EmailContext( data: [], preheader: 'This is a preview text' ); $content = $this->renderer->render($templatePath, $context); expect($content->html)->toContain(''); expect($content->html)->toContain('display:none'); expect($content->html)->toContain('This is a preview text'); unlink($templatePath); }); }); describe('Integration: Welcome Email Template', function () { it('renders the welcome email template correctly', function () { $context = new EmailContext( data: [ 'name' => 'Max Mustermann', 'email' => 'max@example.com', 'company_name' => 'Test Company', 'login_url' => 'https://app.example.com/login', 'current_year' => '2024', 'facebook_url' => 'https://facebook.com/testcompany', 'twitter_url' => 'https://twitter.com/testcompany', 'linkedin_url' => 'https://linkedin.com/company/testcompany', 'unsubscribe_url' => 'https://app.example.com/unsubscribe', 'webview_url' => 'https://app.example.com/email/view/123', ], preheader: 'Willkommen bei Test Company!' ); $content = $this->renderer->render('welcome', $context); // Check HTML content expect($content->html)->toContain('Hallo Max Mustermann,'); expect($content->html)->toContain('max@example.com'); expect($content->html)->toContain('Test Company'); expect($content->html)->toContain('https://app.example.com/login'); // Check subject extraction expect($content->subject)->toBeInstanceOf(EmailSubject::class); expect($content->subject->value)->toBe('Willkommen bei Test Company!'); // Check plain text generation expect($content->text)->toContain('Hallo Max Mustermann,'); expect($content->text)->not->toContain('