b2in/tests/Feature/HomePageStructureTest.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

197 lines
6.8 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 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');
});