'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('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'); });