- 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>
251 lines
8 KiB
PHP
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');
|
|
});
|