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>
197 lines
6.8 KiB
PHP
197 lines
6.8 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
||
uses(RefreshDatabase::class);
|
||
|
||
// ========================================
|
||
// Neue Reihenfolge der Startseite
|
||
// ========================================
|
||
|
||
it('renders the homepage sections in the order from the concept', function () {
|
||
$html = $this->get('/')->assertSuccessful()->getContent();
|
||
|
||
$order = [
|
||
'Rotpunkt Küchen jetzt bei B2in Interior', // Ankündigungsleiste
|
||
'Premium-Einrichtung von starken Marken', // Hero
|
||
'B2in by Marcel Scheibe', // Marcel-Badge
|
||
'Einrichtung für Ihr Zuhause', // Weiche Privat/Geschäft (Karte 1)
|
||
'Unsere <span class="text-secondary">Kernmarken', // Kernmarken
|
||
'Gebaut auf Expertise und Vertrauen', // Über Marcel
|
||
'Regional', // Sponsoring
|
||
'Internationale Immobilien', // Immobilien-Teaser
|
||
'Ihr nächster', // Abschluss-CTA
|
||
];
|
||
|
||
$lastPosition = -1;
|
||
|
||
foreach ($order as $needle) {
|
||
$position = strpos($html, $needle);
|
||
|
||
expect($position)->not->toBeFalse("Sektion nicht gefunden: {$needle}");
|
||
expect($position)->toBeGreaterThan($lastPosition, "Falsche Reihenfolge bei: {$needle}");
|
||
|
||
$lastPosition = $position;
|
||
}
|
||
});
|
||
|
||
it('no longer shows the three-pillar ecosystem section', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertDontSee('Ein Ökosystem')
|
||
->assertDontSee('Local-for-Local Marktplatz')
|
||
->assertDontSee('Supply-Chain-Management');
|
||
});
|
||
|
||
it('no longer shows the old two-worlds section', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertDontSee('Zwei Welten');
|
||
});
|
||
|
||
// ========================================
|
||
// Hero
|
||
// ========================================
|
||
|
||
it('offers the two hero buttons from the concept', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('Unsere Marken entdecken')
|
||
->assertSee('Termin anfragen');
|
||
});
|
||
|
||
it('shows the three hero checkmarks', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('Premium-Marken')
|
||
->assertSee('Maßgefertigte Lösungen', false)
|
||
->assertSee('Persönliche Beratung', false);
|
||
});
|
||
|
||
// ========================================
|
||
// Kernmarken
|
||
// ========================================
|
||
|
||
it('shows all three core brands with an anchor the navigation can reach', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('id="marken"', false)
|
||
->assertSee('CABINET')
|
||
->assertSee('Rotpunkt Küchen', false)
|
||
->assertSee('Febrü', false);
|
||
});
|
||
|
||
it('describes Febrü with the home office first', function () {
|
||
$html = $this->get('/')->assertSuccessful()->getContent();
|
||
|
||
$homeOffice = strpos($html, 'Home-Office');
|
||
$office = strpos($html, 'Büroeinrichtung');
|
||
|
||
expect($homeOffice)->not->toBeFalse();
|
||
expect($office)->not->toBeFalse();
|
||
expect($homeOffice)->toBeLessThan($office);
|
||
});
|
||
|
||
// ========================================
|
||
// Navigation
|
||
// ========================================
|
||
|
||
it('puts brands before real estate in the navigation and drops the network entry', function () {
|
||
$html = $this->get('/')->assertSuccessful()->getContent();
|
||
|
||
$navStart = (int) strpos($html, 'aria-label="Hauptnavigation"');
|
||
$nav = substr($html, $navStart, (int) strpos($html, '</nav>', $navStart) - $navStart);
|
||
|
||
expect($nav)->toContain('Marken');
|
||
expect($nav)->toContain('/marken');
|
||
expect($nav)->toContain('Immobilien');
|
||
expect($nav)->not->toContain('Netzwerk');
|
||
expect(strpos($nav, 'Marken'))->toBeLessThan(strpos($nav, 'Immobilien'));
|
||
});
|
||
|
||
it('sends the retired network page to the brands page', function () {
|
||
$this->get('/netzwerk')->assertRedirect('/marken');
|
||
});
|
||
|
||
it('teases the brands on the homepage and links to the full overview', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('Alle Marken ansehen')
|
||
->assertSee('/marken');
|
||
});
|
||
|
||
// ========================================
|
||
// SEO
|
||
// ========================================
|
||
|
||
it('carries the new title and description', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('B2in Interior – Premium-Einrichtung', false)
|
||
->assertDontSee('Connecting Design and Property');
|
||
});
|
||
|
||
// ========================================
|
||
// Sponsoring
|
||
// ========================================
|
||
|
||
it('shows the three sponsoring logos', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('sponsoring/arminia-bielefeld.png')
|
||
->assertSee('sponsoring/tc-bad-waldliesborn-horn.jpg')
|
||
->assertSee('sponsoring/tc-hoevelhof.png');
|
||
});
|
||
|
||
it('puts every sponsor logo on a white stage inside its own tile', function () {
|
||
$html = $this->get('/')->assertSuccessful()->getContent();
|
||
|
||
expect(substr_count($html, 'bg-white flex items-center justify-center h-36'))->toBe(3);
|
||
});
|
||
|
||
it('names each sponsor and explains it in one line', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('DSC Arminia Bielefeld')
|
||
->assertSee('Traditionsverein aus Bielefeld', false)
|
||
->assertSee('Tennisclub aus Bad Waldliesborn')
|
||
->assertSee('Tennisclub aus Hövelhof', false);
|
||
});
|
||
|
||
it('does not turn the sponsor tiles into links', function () {
|
||
$html = $this->get('/')->assertSuccessful()->getContent();
|
||
|
||
$section = substr($html, (int) strpos($html, 'DSC Arminia Bielefeld'));
|
||
$section = substr($section, 0, (int) strpos($section, 'Tennisclub aus Hövelhof'));
|
||
|
||
expect($section)->not->toContain('<a ');
|
||
});
|
||
|
||
// ========================================
|
||
// Logo mit Zusatz „Interior"
|
||
// ========================================
|
||
|
||
it('uses the interior logo in the header and footer', function () {
|
||
$this->get('/')
|
||
->assertSuccessful()
|
||
->assertSee('b2in-interior-logo-positive.svg')
|
||
->assertSee('b2in-interior-logo-negative.svg');
|
||
});
|
||
|
||
it('ships the interior logo files in both variants', function () {
|
||
foreach (['positive', 'negative'] as $variant) {
|
||
expect(file_exists(public_path("img/logos/b2in-interior-logo-{$variant}.svg")))->toBeTrue();
|
||
expect(file_exists(public_path("img/logos/b2in-interior-logo-{$variant}.png")))->toBeTrue();
|
||
}
|
||
});
|
||
|
||
it('crops the logo viewBox to the artwork', function () {
|
||
// Die Lieferung lag auf einem A4-Artboard mit 74 % Weissraum. Ungeschnitten wuerde das
|
||
// Logo bei gleicher CSS-Hoehe rund halb so gross rendern wie das bisherige.
|
||
$svg = file_get_contents(public_path('img/logos/b2in-interior-logo-positive.svg'));
|
||
|
||
expect($svg)->toContain('viewBox="153 96 289.3 227.5"');
|
||
expect($svg)->not->toContain('595.3 419.5');
|
||
});
|