b2in/tests/Feature/BrandPageTest.php
Kevin Adametz 4b07bbdcae Startseite ruhiger, Otterpohl mit Montage, Marken-Bilder und Farbwelt-Entwurf
- 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>
2026-10-01 10:34:21 +00:00

313 lines
12 KiB
PHP
Raw Permalink 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 Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(\Database\Seeders\CmsBrandSeeder::class);
});
// ========================================
// Kacheln auf der Startseite
// ========================================
it('shows the maintained brands on the homepage', function () {
$this->get('/')
->assertSuccessful()
->assertSee('CABINET')
->assertSee('Rotpunkt Küchen', false)
->assertSee('Febrü', false);
});
it('links each tile to its detail page', function () {
$this->get('/')
->assertSuccessful()
->assertSee('/marken/cabinet')
->assertSee('/marken/rotpunkt')
->assertSee('/marken/februe');
});
it('hides unpublished brands from the homepage', function () {
CmsBrand::where('slug', 'februe')->update(['is_published' => false]);
$this->get('/')
->assertSuccessful()
->assertSee('CABINET')
->assertDontSee('/marken/februe');
});
// ========================================
// Detailseite
// ========================================
it('serves a detail page for every brand', function () {
foreach (['cabinet', 'rotpunkt', 'februe'] as $slug) {
$this->get('/marken/'.$slug)->assertSuccessful();
}
});
it('renders the template sections from the concept', function () {
$this->get('/marken/cabinet')
->assertSuccessful()
->assertSee('CABINET') // Header
->assertSee('Einbauschränke nach Maß', false) // Claim
->assertSee('Showroom Bielefeld')
->assertSee('Leistungen und Besonderheiten') // Leistungen
->assertSee('Aufmaß vor Ort', false)
->assertSee('Termin anfragen'); // CTA
});
it('links to the other brands from a detail page', function () {
$this->get('/marken/cabinet')
->assertSuccessful()
->assertSee('/marken/rotpunkt')
->assertSee('/marken/februe');
});
it('returns 404 for an unknown brand', function () {
$this->get('/marken/gibt-es-nicht')->assertNotFound();
});
it('returns 404 for an unpublished brand', function () {
CmsBrand::where('slug', 'februe')->update(['is_published' => false]);
$this->get('/marken/februe')->assertNotFound();
});
it('carries a brand specific title and description', function () {
$this->get('/marken/cabinet')
->assertSuccessful()
->assertSee('CABINET – Einbauschränke nach Maß. | B2in Interior', false)
->assertDontSee('Connecting Design and Property');
});
// ========================================
// CMS-Anbindung
// ========================================
it('renders what is maintained in the CMS', function () {
$cabinet = CmsBrand::where('slug', 'cabinet')->firstOrFail();
$cabinet->setTranslation('tagline', 'de', 'Im CMS geänderter Claim');
$cabinet->save();
$this->get('/')->assertSuccessful()->assertSee('Im CMS geänderter Claim');
$this->get('/marken/cabinet')->assertSuccessful()->assertSee('Im CMS geänderter Claim');
});
it('picks up a brand that was added in the CMS, without a code change', function () {
CmsBrand::factory()->create([
'slug' => 'neue-marke',
'name' => ['de' => 'Neue Marke', 'en' => 'New Brand'],
'order' => 9,
]);
$this->get('/')->assertSuccessful()->assertSee('Neue Marke');
$this->get('/marken/neue-marke')->assertSuccessful()->assertSee('Neue Marke');
});
// ========================================
// Galerie: ein Raster, Badge je Bild (Rubrikwechsel ausgesetzt, Nachtrag 17)
// ========================================
it('shows no gallery section while no images are maintained', function () {
$this->get('/marken/cabinet')
->assertSuccessful()
->assertDontSee('id="galerie"', false)
->assertDontSee('Inspirationen und Entwürfe', false);
});
it('shows projects and visualisations in one grid with a badge each and projects first', function () {
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update([
'gallery' => [
['image' => 'entwurf.webp', 'category' => 'inspiration', 'caption' => 'Entwurf Garderobe'],
['image' => 'projekt.webp', 'category' => 'project', 'caption' => 'Ankleide in Bielefeld'],
],
]);
$html = $this->get('/marken/cabinet')->assertSuccessful()->getContent();
// Gemischte Galerie: Überschrift nennt beides (Nachtrag 19).
expect($html)->toContain('Projekte und Entwürfe')
->toContain('was in einem Raum möglich ist')
->not->toContain('Inspirationen und Entwürfe')
->toContain('Realisiertes Projekt')
->toContain('Visualisierung')
->toContain('Ankleide in Bielefeld')
->toContain('Entwurf Garderobe')
->not->toContain('role="tablist"')
->not->toContain('Realisierte Projekte');
// Ohne Livewire-Snapshot vergleichen: er trägt die Galerie in Datenreihenfolge.
$visible = preg_replace('/wire:snapshot="[^"]*"/', '', $html) ?? $html;
expect(strpos($visible, 'Ankleide in Bielefeld'))->toBeLessThan((int) strpos($visible, 'Entwurf Garderobe'));
});
it('shows exactly one line below the gallery title', function () {
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update([
'gallery' => [['image' => 'entwurf.webp', 'category' => 'inspiration', 'caption' => 'Entwurf']],
]);
$html = $this->get('/marken/cabinet')->assertSuccessful()->getContent();
$section = substr($html, (int) strpos($html, 'id="galerie"'));
$section = substr($section, 0, (int) strpos($section, '<div class="grid'));
expect(substr_count($section, '<p '))->toBe(1);
expect($section)->not->toContain('Entwürfe und Visualisierungen. Sie zeigen');
});
it('marks an image without a category as visualisation, never as a completed project', function () {
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update([
'gallery' => [
['image' => 'ohne-kategorie.webp'],
],
]);
$this->get('/marken/cabinet')
->assertSuccessful()
->assertSee('Visualisierung')
->assertDontSee('Realisiertes Projekt');
});
it('still separates inspirations from completed projects after the rewording', function () {
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update([
'gallery' => [
['image' => 'entwurf.webp', 'category' => 'inspiration'],
],
]);
$this->get('/marken/cabinet')
->assertSuccessful()
->assertSee('Visualisierung')
->assertDontSee('Realisiertes Projekt');
});
it('removes the demo images from every gallery and keeps the real ones', function () {
$this->seed(\Database\Seeders\DemoBrandGallerySeeder::class);
$this->seed(\Database\Seeders\BrandGalleryCaptionNormalizerSeeder::class);
$this->seed(\Database\Seeders\BrandImageSeeder::class);
$this->seed(\Database\Seeders\DemoBrandGalleryCleanupSeeder::class);
$this->seed(\Database\Seeders\DemoBrandGalleryCleanupSeeder::class);
expect(CmsBrand::where('slug', 'cabinet')->firstOrFail()->gallery)->toHaveCount(11)
->and(CmsBrand::where('slug', 'rotpunkt')->firstOrFail()->gallery)->toHaveCount(13)
->and(CmsBrand::where('slug', 'februe')->firstOrFail()->gallery)->toHaveCount(15)
->and(CmsBrand::where('slug', 'tischlerei')->firstOrFail()->gallery)->toHaveCount(12);
foreach (CmsBrand::all() as $brand) {
foreach ($brand->gallery ?? [] as $item) {
expect($item['image'])->toStartWith('marken/');
}
}
});
it('fills the demo galleries with both categories', function () {
$this->seed(\Database\Seeders\DemoBrandGallerySeeder::class);
$grouped = CmsBrand::groupGallery(CmsBrand::where('slug', 'cabinet')->firstOrFail()->gallery);
expect($grouped['projects'])->not->toBeEmpty()
->and($grouped['inspirations'])->not->toBeEmpty();
// Der Vermerk „BEISPIEL“ ist auf Kundenwunsch entfallen. Die Unterscheidung muss deshalb
// an der Kategorie haengen – jedes Demobild braucht eine ausdrueckliche.
foreach (CmsBrand::all() as $brand) {
foreach ($brand->gallery ?? [] as $item) {
expect($item['caption'])->not->toContain('BEISPIEL')
->and($item['category'])->toBeIn(CmsBrand::GALLERY_CATEGORIES);
}
}
});
it('strips the old example prefix without touching image, category or order', function () {
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update([
'gallery' => [
['image' => 'a.webp', 'category' => 'project', 'caption' => 'BEISPIEL – Ankleide, Bielefeld'],
['image' => 'b.webp', 'category' => 'inspiration', 'caption' => 'Entwurf Garderobe'],
],
]);
$this->seed(\Database\Seeders\BrandGalleryCaptionNormalizerSeeder::class);
$gallery = CmsBrand::where('slug', 'cabinet')->firstOrFail()->gallery;
expect($gallery)->toHaveCount(2)
->and($gallery[0]['caption'])->toBe('Ankleide, Bielefeld')
->and($gallery[0]['image'])->toBe('a.webp')
->and($gallery[0]['category'])->toBe('project')
->and($gallery[1]['caption'])->toBe('Entwurf Garderobe');
});
// ========================================
// Markenlogos
// ========================================
it('shows the logo instead of the wordmark at the top of a brand page', function () {
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
// Das Logo sitzt im <h1>, damit die Ueberschrift erhalten bleibt.
expect($html)->toMatch('/<h1>\s*<img[^>]*marken\/rotpunkt\.svg/')
->and($html)->toContain('alt="Rotpunkt Küchen"');
});
it('falls back to the wordmark for a brand without a logo', function () {
CmsBrand::where('slug', 'rotpunkt')->firstOrFail()->update(['logo' => '']);
$this->get('/marken/rotpunkt')
->assertSuccessful()
->assertSee('<h1 class="text-section-title">Rotpunkt Küchen</h1>', false);
});
it('shows the logo on every tile that leads to a brand page', function (string $path) {
$html = $this->get($path)->assertSuccessful()->getContent();
foreach (['cabinet.svg', 'rotpunkt.svg', 'februe.png'] as $file) {
expect($html)->toContain('marken/'.$file);
}
})->with([
'Startseite' => ['/'],
'Markenuebersicht' => ['/marken'],
]);
it('shows the logo on the other-brands tiles of a detail page', function () {
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
// Auf der Rotpunkt-Seite verweisen die Kacheln auf CABINET und Februe.
expect($html)->toContain('marken/cabinet.svg')
->and($html)->toContain('marken/februe.png');
});
it('ships the logo files the seeder points at', function () {
foreach (['cabinet.svg', 'rotpunkt.svg', 'februe.png'] as $file) {
expect(public_path('img/assets/marken/'.$file))->toBeFile();
}
});
it('ships the logos cropped to their content, not as delivered', function (string $file) {
// Die Zulieferungen haben viel leeren Rand in der Datei – CABINET nutzte nur 29 Prozent der
// Hoehe. In einer Logo-Box wirkt so ein Logo winzig, egal wie gross die Box ist. Die
// beschnittenen Fassungen erkennt man am Ursprung: der ist nach dem Beschnitt nicht mehr 0 0.
$viewBox = (string) simplexml_load_file(public_path('img/assets/marken/'.$file))['viewBox'];
expect($viewBox)->not->toStartWith('0 0 ');
})->with(['cabinet.svg', 'rotpunkt.svg']);
it('keeps a logo that was uploaded in the admin', function () {
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update(['logo' => 'eigenes-logo.svg']);
$this->seed(\Database\Seeders\BrandLogoSeeder::class);
expect(CmsBrand::where('slug', 'cabinet')->firstOrFail()->logo)->toBe('eigenes-logo.svg');
});
it('adds the logo to a brand that has none yet', function () {
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update(['logo' => '']);
$this->seed(\Database\Seeders\BrandLogoSeeder::class);
expect(CmsBrand::where('slug', 'cabinet')->firstOrFail()->logo)->toBe('marken/cabinet.svg');
});