b2in/tests/Feature/MarkenPageTest.php
Kevin Adametz 6d53243ea6 B2in Interior: Relaunch der Website, Kernmarken-CMS und Terminanfrage
Umsetzung von AP0-AP6 aus dev/2026-08-28/entwicklungsplan.md sowie der
vorab gefundenen Befunde T1 und B1-B13.

Befunde
- CmsContentSeeder überschrieb bei jedem Lauf alle redaktionellen Inhalte;
  jetzt Redaktionsschutz plus gezielter Relaunch-Seeder (B1)
- Magazin-CMS war nicht ans Frontend angebunden, Artikel jetzt über Slugs
  mit 301 von den alten numerischen URLs (B2)
- Vier veröffentlichte Display-Playlists zeigten auf b2in.test und wären
  live nicht ladbar gewesen (B13)
- Info-Tablet speicherte geleerte Öffnungszeiten nicht (T1)
- /service und /portfolio lieferten seit Februar 500 (B12)

Website
- Startseite neu strukturiert und umgetextet, eigene Seite /marken mit
  Detailseite je Kernmarke, /netzwerk und Altrouten leiten dorthin
- Galerie je Marke, getrennt nach realisierten Projekten und
  Visualisierungen; ohne gesetzte Kategorie gilt ein Bild als Entwurf
- Seite /referenzen, gespeist aus den Markengalerien
- Showroom-Display im 9:16-Rahmen über eine schreibgeschützte Embed-Route
  ohne Preview-Token
- Terminanfrage-Widget auf allen Seiten, Markenauswahl aus dem CMS,
  WhatsApp per Klick-zum-Chat-Link mit vorbereitetem Text
- FAQ, Über-uns, Kontakt und Magazin inhaltlich auf Interior umgestellt

Sonstiges
- Vorschau-Instanzen liefern noindex und verlinken sich bei unbekanntem
  Host auf sich selbst statt auf eine fremde Domain
- Seeder-Zugangsdaten aus dem Code in die Konfiguration (B8)
- Ablauf für die Vorschau: dev/2026-08-28/preview-deployment.md

833 Tests grün (3084 Assertions), Pint sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:31:19 +00:00

181 lines
5.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
use App\Models\CmsBrand;
use FluxCms\Core\Models\CmsContent;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
/**
* Sichtbares Markup ohne Livewire-Snapshots.
*
* wire:snapshot serialisiert die Komponentendaten als JSON ins Attribut. Umlaute werden dort
* zu \uXXXX escaped, ASCII-Namen wie "CABINET" nicht – Positionsvergleiche auf dem rohen HTML
* messen sonst den Snapshot statt des sichtbaren Textes.
*/
function markenBody(string $html): string
{
$html = preg_replace('/wire:snapshot="[^"]*"/', '', $html) ?? $html;
return substr($html, (int) strpos($html, '<main'));
}
beforeEach(function () {
$this->seed(\Database\Seeders\CmsBrandSeeder::class);
});
// ========================================
// Aufbau laut unterseite_marke.md
// ========================================
it('renders the page sections in the order from the spec', function () {
$html = markenBody($this->get('/marken')->assertSuccessful()->getContent());
$order = [
'Unsere <span class="text-secondary">Marken', // 1. Seitenkopf
'CABINET', // 2. Kernmarken
'Rotpunkt Küchen',
'Febrü',
'unser Netzwerk', // 3. Partnernetzwerk
'Markenwelt wächst', // 4. Marken werden ergänzt
'Lernen Sie unsere', // 5. Abschluss-CTA
];
$last = -1;
foreach ($order as $needle) {
$pos = strpos($html, $needle);
expect($pos)->not->toBeFalse("Nicht gefunden: {$needle}");
expect($pos)->toBeGreaterThan($last, "Falsche Reihenfolge bei: {$needle}");
$last = $pos;
}
});
it('shows a breadcrumb from home to brands', function () {
$this->get('/marken')
->assertSuccessful()
->assertSee('Breadcrumb')
->assertSee('Home');
});
it('carries the title and description from the spec', function () {
$this->get('/marken')
->assertSuccessful()
->assertSee('Unsere Marken – CABINET, Rotpunkt Küchen &amp; Febrü | B2in Interior', false)
->assertSee('Ausgewählte Hersteller statt beliebiger Kataloge', false);
});
// ========================================
// Marken-Abschnitt
// ========================================
it('shows the longer text and the three highlights per brand', function () {
$this->get('/marken')
->assertSuccessful()
->assertSee('Wo Standardmaße an ihre Grenzen kommen', false)
->assertSee('Millimetergenaue Planung')
->assertSee('Individuelle Fronten und Innenausstattung')
->assertSee('Persönliche Beratung vor Ort', false);
});
it('links every brand to its detail page', function () {
$this->get('/marken')
->assertSuccessful()
->assertSee('/marken/cabinet')
->assertSee('/marken/rotpunkt')
->assertSee('/marken/februe');
});
it('keeps the brand order from the CMS', function () {
$headings = function (string $html): array {
preg_match_all('#wire:key="brand-([a-z-]+)"#', $html, $matches);
return $matches[1];
};
expect($headings($this->get('/marken')->assertSuccessful()->getContent()))
->toBe(['cabinet', 'rotpunkt', 'februe']);
CmsBrand::where('slug', 'februe')->update(['order' => 0]);
expect($headings($this->get('/marken')->getContent()))
->toBe(['februe', 'cabinet', 'rotpunkt']);
});
it('hides unpublished brands', function () {
CmsBrand::where('slug', 'februe')->update(['is_published' => false]);
$this->get('/marken')
->assertSuccessful()
->assertSee('CABINET')
->assertDontSee('/marken/februe');
});
// ========================================
// WhatsApp
// ========================================
it('hides the whatsapp button while no number is stored', function () {
// Lieber kein Button als einer auf eine erfundene Nummer.
$this->get('/marken')
->assertSuccessful()
->assertSee('Termin anfragen')
->assertDontSee('wa.me');
});
it('shows the whatsapp button once a number is stored', function () {
CmsContent::create([
'group' => 'global',
'key' => 'contact',
'type' => 'json',
'value' => ['de' => ['whatsapp' => '+49 151 51002992']],
'order' => 0,
]);
$this->get('/marken')
->assertSuccessful()
->assertSee('https://wa.me/4915151002992')
->assertSee('Per WhatsApp schreiben');
});
// ========================================
// Abgrenzung der Blöcke
// ========================================
it('sets the page header on the same offset surface as the other heroes', function () {
$html = markenBody($this->get('/marken')->assertSuccessful()->getContent());
// Dieselbe Fläche wie Startseite und Netzwerkseite.
expect($html)->toContain('bg-hero-container');
});
it('frames every brand block so it reads as one unit', function () {
$html = markenBody($this->get('/marken')->assertSuccessful()->getContent());
expect(substr_count($html, 'card-elevated rounded-3xl'))->toBe(CmsBrand::query()->published()->count());
});
it('places a smaller image beside the page header text', function () {
$html = markenBody($this->get('/marken')->assertSuccessful()->getContent());
// Text 3, Bild 2 Spalten – das Bild begleitet, es dominiert nicht.
expect($html)->toContain('lg:col-span-3')
->and($html)->toContain('lg:col-span-2');
});
it('leaves out the image frame entirely when no motif is maintained', function () {
CmsContent::create([
'group' => 'marken',
'key' => 'hero',
'type' => 'json',
'value' => ['de' => ['title' => 'Unsere Marken', 'text' => 'Kurztext.', 'image' => '']],
'order' => 0,
]);
$html = markenBody($this->get('/marken')->assertSuccessful()->getContent());
expect($html)->toContain('Unsere Marken')
->and($html)->not->toContain('lg:col-span-2');
});