b2in/tests/Feature/AppointmentWidgetTest.php
Kevin Adametz 4b07bbdcae Startseite ruhiger, Otterpohl mit Montage, Marken-Bilder und Farbwelt-Entwurf
- Hinweisleiste per B2IN_ANNOUNCEMENT_BAR ausgeblendet (Standard aus)
- Hero: „Räume aus einer Hand.“ als neue Überschrift, zweiter Button entfernt
- Projekttext mit zentralem deutschen Vertragspartner (Punkt statt Gedankenstrich)
- Otterpohl als vierte Kernmarke: Text v2, Abschnitt „Montage“, Titelbild getauscht
- Markenabschnitte: Absätze mit „- “ als Liste mit fetter Einleitung
- Titelbilder und Galerien für CABINET, Rotpunkt, Febrü und Otterpohl (WebP)
- Feedback 17.09. in den Markentexten, Demobilder entfernt, WhatsApp-Nummer
- Palette „Modern“ fest ohne Schalter, Entwurf /entwurf mit Hero-Slideshow
- Seeder-Liste fürs Deployment im Entwicklungsplan

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:34:21 +00:00

251 lines
8 KiB
PHP

<?php
declare(strict_types=1);
use Acme\ContactForm\ContactFormService;
use App\Livewire\Web\Components\Ui\AppointmentWidget;
use App\Models\CmsBrand;
use FluxCms\Core\Models\CmsContent;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Livewire\Livewire;
uses(RefreshDatabase::class);
function storeWhatsappNumber(): void
{
CmsContent::create([
'group' => 'global',
'key' => 'contact',
'type' => 'json',
'value' => ['de' => ['whatsapp' => '+49 151 51002992']],
'order' => 0,
]);
}
beforeEach(function () {
$this->seed(\Database\Seeders\CmsBrandSeeder::class);
config()->set('contact-form.recipient', 'termine@b2in.test');
});
// ========================================
// Einbindung
// ========================================
it('sits on every page, not only on the homepage', function (string $url) {
$this->get($url)
->assertSuccessful()
->assertSeeLivewire(AppointmentWidget::class);
})->with(['/', '/marken', '/marken/cabinet', '/magazin', '/contact']);
it('offers the maintained brands plus the fixed topics', function () {
$topics = Livewire::test(AppointmentWidget::class)->get('topics');
expect($topics)->toHaveKey('cabinet')
->and($topics)->toHaveKey('rotpunkt')
->and($topics)->toHaveKey('februe')
->and($topics['projektausstattung'])->toBe('Projektausstattung')
->and($topics['sonstiges'])->toBe('Sonstiges');
});
it('picks up a brand that was added in the CMS', function () {
CmsBrand::factory()->create(['slug' => 'neue-marke', 'name' => ['de' => 'Neue Marke', 'en' => 'New Brand']]);
expect(Livewire::test(AppointmentWidget::class)->get('topics'))->toHaveKey('neue-marke');
});
// ========================================
// Validierung
// ========================================
it('requires name, email and the privacy consent', function () {
Livewire::test(AppointmentWidget::class)
->call('submit')
->assertHasErrors(['name' => 'required', 'email' => 'required', 'privacy' => 'accepted']);
});
it('rejects a malformed email', function () {
Livewire::test(AppointmentWidget::class)
->set('name', 'Max Muster')
->set('email', 'keine-adresse')
->set('privacy', true)
->call('submit')
->assertHasErrors(['email' => 'email']);
});
it('rejects a preferred date in the past', function () {
Livewire::test(AppointmentWidget::class)
->set('name', 'Max Muster')
->set('email', 'max@example.com')
->set('preferredDate', now()->subDay()->toDateString())
->set('privacy', true)
->call('submit')
->assertHasErrors(['preferredDate']);
});
it('accepts a request without phone, date and message', function () {
Mail::fake();
$service = $this->mock(ContactFormService::class);
$service->shouldReceive('handle')->once();
Livewire::test(AppointmentWidget::class)
->set('name', 'Max Muster')
->set('email', 'max@example.com')
->set('privacy', true)
->call('submit')
->assertHasNoErrors()
->assertSet('success', true);
});
// ========================================
// Versand
// ========================================
it('names the chosen brand in the mail subject and hands over the request', function () {
Mail::fake();
$service = $this->mock(ContactFormService::class);
$service->shouldReceive('handle')
->once()
->withArgs(function (array $payload, string $subject, string $logContext) {
return $subject === 'Terminanfrage B2in Interior: CABINET'
&& $logContext === 'appointment-widget'
&& $payload['topic'] === 'CABINET'
&& $payload['email'] === 'max@example.com';
});
Livewire::test(AppointmentWidget::class)
->set('name', 'Max Muster')
->set('email', 'max@example.com')
->set('topic', 'cabinet')
->set('privacy', true)
->call('submit')
->assertHasNoErrors();
});
it('clears the form after a successful request', function () {
Mail::fake();
$this->mock(ContactFormService::class)->shouldReceive('handle')->once();
Livewire::test(AppointmentWidget::class)
->set('name', 'Max Muster')
->set('email', 'max@example.com')
->set('message', 'Bitte um Rückruf.')
->set('privacy', true)
->call('submit')
->assertSet('name', '')
->assertSet('email', '')
->assertSet('message', '')
->assertSet('privacy', false);
});
it('lets the visitor start a second request', function () {
Mail::fake();
$this->mock(ContactFormService::class)->shouldReceive('handle')->once();
Livewire::test(AppointmentWidget::class)
->set('name', 'Max Muster')
->set('email', 'max@example.com')
->set('privacy', true)
->call('submit')
->assertSet('success', true)
->call('startOver')
->assertSet('success', false);
});
it('swallows a filled honeypot instead of mailing it', function () {
Mail::fake();
$this->mock(ContactFormService::class)->shouldReceive('handle')->once();
Livewire::test(AppointmentWidget::class)
->set('name', 'Bot')
->set('email', 'bot@example.com')
->set('website', '')
->set('privacy', true)
->call('submit')
->assertHasNoErrors();
// Honeypot leer → regulärer Versand. Gegenprobe: gefülltes Feld fällt in der Validierung durch.
Livewire::test(AppointmentWidget::class)
->set('name', 'Bot')
->set('email', 'bot@example.com')
->set('website', 'http://spam.example')
->set('privacy', true)
->call('submit')
->assertHasErrors(['website']);
});
// ========================================
// WhatsApp
// ========================================
it('hides the whatsapp button while no number is stored', function () {
$this->get('/')
->assertSuccessful()
->assertDontSee('wa.me');
});
it('shows the whatsapp button once a number is stored', function () {
storeWhatsappNumber();
$html = $this->get('/')->assertSuccessful()->getContent();
// Die Basis-URL steht in den Alpine-Daten, dort escaped @js die Schrägstriche.
expect($html)->toContain('wa.me')
->and($html)->toContain('4915151002992');
});
it('prepares the whatsapp message from what is already filled in', function () {
storeWhatsappNumber();
$html = $this->get('/')->assertSuccessful()->getContent();
// Der Text wird im Browser aus dem Formularstand zusammengesetzt.
expect($html)->toContain('whatsappHref')
->and($html)->toContain('Hallo B2in Interior')
->and($html)->toContain('Wunschtermin: ');
});
it('says plainly that the visitor sends the whatsapp message themselves', function () {
storeWhatsappNumber();
$this->get('/')
->assertSuccessful()
->assertSee('Abgeschickt wird die Nachricht erst von Ihnen', false);
});
it('offers the brand labels to the whatsapp text, not the slugs', function () {
storeWhatsappNumber();
$html = $this->get('/')->assertSuccessful()->getContent();
expect($html)->toContain('topicLabels');
});
it('puts the whatsapp button above the form', function () {
storeWhatsappNumber();
$html = $this->get('/')->assertSuccessful()->getContent();
$whatsapp = strpos($html, 'data-appointment-whatsapp');
$form = strpos($html, 'wire:submit="submit"');
expect($whatsapp)->not->toBeFalse()
->and($form)->not->toBeFalse()
->and($whatsapp < $form)->toBeTrue('WhatsApp-Block muss vor dem Formular stehen');
expect($html)->toContain('oder per Formular')
->toContain('Am schnellsten per WhatsApp');
});
it('shows no separator line while there is no whatsapp number', function () {
$this->get('/')
->assertSuccessful()
->assertDontSee('oder per Formular');
});
it('uses the light blue of the theme for the floating button', function () {
$this->get('/')
->assertSuccessful()
->assertSee('btn-secondary-accent flex items-center gap-2 rounded-full');
});