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

245 lines
9.5 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\Livewire\Web\Components\Ui\AppointmentWidget;
use App\Models\CmsBrand;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(\Database\Seeders\CmsBrandSeeder::class);
$this->seed(\Database\Seeders\BrandPageTextSeeder::class);
});
// ========================================
// Abschnitte aus der Zulieferung
// ========================================
it('renders the delivered sections on the rotpunkt page', function () {
$this->get('/marken/rotpunkt')
->assertSuccessful()
->assertSee('Aus Ostwestfalen. Für Ihr Zuhause.', false)
->assertSee('Gestaltungsfreiheit bis ins Detail')
->assertSee('Nachhaltigkeit mit konkreter Substanz')
->assertSee('Mehr als Küchen', false)
->assertSee('Planung plus Handwerk', false);
});
it('renders the cabinet sections written to the same pattern', function () {
$this->get('/marken/cabinet')
->assertSuccessful()
->assertSee('Millimeter statt Standardmaß', false)
->assertSee('Ein Bild und ein grobes Maß reichen für den Anfang', false)
->assertSee('Warum CABINET bei uns?')
->assertSee('Ihr Raum. Persönlich geplant.', false);
});
it('renders the delivered sections on the februe page', function () {
$this->get('/marken/februe')
->assertSuccessful()
->assertSee('Aus Herford. Für moderne Arbeitswelten.', false)
->assertSee('Nicht drei Marken. Ein durchgängiger Raum.', false)
->assertSee('Ergonomie, die im Alltag funktioniert');
});
it('turns every marked deepening into a collapsible area', function () {
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
// Rotpunkt bringt vier Vertiefungen mit.
expect(substr_count($html, 'aria-controls="detail-'))->toBe(4)
->and($html)->toContain('FENIX und authentische Holzoptiken')
->and($html)->toContain('NEW DIMENSIONS');
});
it('renders every deepening collapsed and animates its height', function () {
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
// Der zugeklappte Zustand muss im statischen class-Attribut stehen, sonst blitzt der Inhalt
// vor dem Start von Alpine aufgeklappt auf.
expect(substr_count($html, 'grid grid-rows-[0fr] opacity-0 transition-all'))->toBe(4)
// grid-template-rows animiert die tatsaechliche Hoehe – die alte Bewegung blendete den
// Inhalt nur ein und sprang dabei auf die volle Hoehe.
->and($html)->not->toContain('x-transition:enter-start="opacity-0 -translate-y-2"')
->and($html)->toContain("'grid-rows-[1fr] opacity-100': open");
});
it('uses no full-surface hover on the accordion buttons', function () {
foreach (['/marken/rotpunkt', '/faq'] as $path) {
$html = $this->get($path)->assertSuccessful()->getContent();
expect($html)->not->toContain('hover:bg-muted/30');
}
});
it('merges the two sustainability sections as the corrected version requires', function () {
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
expect($html)->not->toContain('Greenline hält dem Vergleich stand')
->and($html)->toContain('ohne Verkaufsnebel')
// Der doppelte Satz steht nur noch einmal auf der Seite.
->and(substr_count($html, 'Nachhaltigkeit braucht keine großen Versprechen'))->toBe(1);
});
it('keeps the blauer engel claim tied to the listed product groups', function () {
$this->get('/marken/februe')
->assertSuccessful()
->assertSee('Ausgewählte Febrü-Tischsysteme und Stauraummöbel tragen den Blauen Engel.', false);
});
it('shows the pull quote from marcel on the februe page', function () {
$this->get('/marken/februe')
->assertSuccessful()
->assertSee('Mein Arzt sagt, ich solle weniger sitzen.', false)
->assertSee('Marcel Scheibe, Geschäftsführer von B2in', false);
});
it('shows the closing claims of a section', function () {
$this->get('/marken/februe')
->assertSuccessful()
->assertSee('Made in Herford. Geplant in Bielefeld.')
->assertSee('Ob im Kleinen oder im Großen', false);
});
it('never publishes the internal source notes', function () {
foreach (['rotpunkt', 'februe'] as $slug) {
$html = $this->get('/marken/'.$slug)->assertSuccessful()->getContent();
expect($html)->not->toContain('QUELLENHINWEISE')
->and($html)->not->toContain('NICHT VERÖFFENTLICHEN')
->and($html)->not->toContain('UMSETZUNGSHINWEIS');
}
});
// ========================================
// Abschlussblock
// ========================================
it('uses the brand specific closing block instead of the generic one', function () {
$this->get('/marken/rotpunkt')
->assertSuccessful()
->assertSee('Ihre Küche. Persönlich geplant.', false)
->assertSee('Rathausstraße 11 in Bielefeld', false)
->assertDontSee('Interesse an Rotpunkt Küchen?', false);
});
it('falls back to the generic closing block for a brand without one', function () {
// Beide Sprachen leeren: bei nur geleertem Deutsch greift die Uebersetzungs-Fallbackkette.
CmsBrand::where('slug', 'cabinet')->firstOrFail()->update(['cta_title' => [], 'cta_text' => []]);
$this->get('/marken/cabinet')
->assertSuccessful()
->assertSee('Interesse an CABINET?');
});
it('closes the page with a gradient band across the full width', function () {
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
expect($html)->toContain('cta-band')
// Die weichen Kreise setzten sichtbare Flecken in die Ecken statt eines Verlaufs.
->and($html)->not->toContain('blur-3xl');
});
it('exempts the closing band from the glass-flow section background', function () {
// `.variante-glass-flow section` faerbt jede Sektion hell ein und ist spezifischer als
// `.cta-band` – ohne diese Ausnahme steht der Abschluss hellgrau mit weisser Schrift da.
// `/marken/{slug}` haengt in genau diesem Wrapper.
expect(file_get_contents(resource_path('views/web/marken-show.blade.php')))
->toContain('variante-glass-flow');
expect(file_get_contents(resource_path('css/web/shared-styles.css')))
->toContain('section.cta-band');
});
// ========================================
// Beschriftung des Terminanfrage-Widgets
// ========================================
it('labels the widget for the brand of the page', function (string $slug, string $label) {
$this->get('/marken/'.$slug)
->assertSuccessful()
->assertSee($label, false);
})->with([
'rotpunkt' => ['rotpunkt', 'Persönliche Küchenberatung vereinbaren'],
'februe' => ['februe', 'Persönliche Arbeitsplatzberatung vereinbaren'],
'cabinet' => ['cabinet', 'Persönliche Schrankplanung vereinbaren'],
]);
it('keeps the general label outside the brand pages', function () {
$this->get('/')
->assertSuccessful()
->assertSee('Termin anfragen')
->assertDontSee('Persönliche Küchenberatung', false);
});
it('adds no second cta button, only the label changes', function () {
// Die Zulieferung verlangt ausdruecklich keinen zweiten Button.
$html = $this->get('/marken/rotpunkt')->assertSuccessful()->getContent();
expect(substr_count($html, 'Persönliche Küchenberatung vereinbaren'))->toBe(1);
});
it('leaves the label untouched for a brand without its own', function () {
$label = Livewire::test(AppointmentWidget::class)->get('content')['button_label'] ?? '';
expect($label)->toBe('Termin anfragen');
});
// ========================================
// Datenform
// ========================================
it('normalises sections into a fixed shape', function () {
$sections = CmsBrand::normalizeSections([
['title' => ' Titel ', 'paragraphs' => [' Erster ', '', 'Zweiter']],
['title' => '', 'paragraphs' => []],
'kein Array',
]);
expect($sections)->toHaveCount(1)
->and($sections[0]['title'])->toBe('Titel')
->and($sections[0]['paragraphs'])->toBe(['Erster', 'Zweiter'])
->and($sections[0]['detail'])->toBeNull();
});
it('keeps a deepening only when it has content', function () {
$sections = CmsBrand::normalizeSections([
['title' => 'A', 'detail' => ['title' => '', 'paragraphs' => []]],
['title' => 'B', 'detail' => ['title' => 'Vertiefung', 'paragraphs' => ['Text']]],
]);
expect($sections[0]['detail'])->toBeNull()
->and($sections[1]['detail']['title'])->toBe('Vertiefung');
});
it('accepts a text block instead of a paragraph list', function () {
$sections = CmsBrand::normalizeSections([
['title' => 'A', 'paragraphs' => "Erster Absatz.\n\nZweiter Absatz."],
]);
expect($sections[0]['paragraphs'])->toBe(['Erster Absatz.', 'Zweiter Absatz.']);
});
// ========================================
// Redaktionsschutz des Seeders
// ========================================
it('leaves images, gallery and services untouched', function () {
$brand = CmsBrand::where('slug', 'rotpunkt')->firstOrFail();
$brand->update([
'image' => 'gepflegt.webp',
'gallery' => [['image' => 'projekt.webp', 'category' => 'project', 'caption' => 'Küche']],
'order' => 42,
]);
$this->seed(\Database\Seeders\BrandPageTextSeeder::class);
$brand->refresh();
expect($brand->image)->toBe('gepflegt.webp')
->and($brand->gallery)->toHaveCount(1)
->and($brand->order)->toBe(42)
->and($brand->services)->not->toBeEmpty();
});