Markenseiten Rotpunkt und Febrü aus der Zulieferung vom 31.08.

Umsetzung der beiden finalen Webseitentexte aus dev/2026-08-28/.
Die angekündigte Aufgabenliste umsetzung-03-09-2026.md war leer, Grundlage
sind daher die Umsetzungshinweise in den PDFs selbst.

- Neue Felder in cms_brands: sections, widget_label, cta_title, cta_text.
  Einleitung und Leistungskacheln reichten für diese Texte nicht aus.
- Die als Vertiefung gelieferten Blöcke werden aufklappbar dargestellt,
  mit aria-expanded und aria-controls. Bewusst x-transition statt
  x-collapse, weil das Collapse-Plugin hier nicht registriert ist.
- Kein zweiter CTA-Button: das schwebende Terminanfrage-Widget trägt auf
  der Markenseite die Beschriftung der Marke und liest den Kontext aus
  der Route, da es im Layout und nicht je Seite eingebunden ist.
- Footer bekommt unten Platz und lange Beschriftungen brechen um, damit
  das Widget auf dem Smartphone nichts verdeckt.
- Abschnitte sind im Admin vollständig pflegbar, inklusive Sortierung.
- Interne Quellenhinweise der PDFs sind nicht übernommen; ein Test prüft,
  dass sie nicht im Ausgabe-HTML stehen.
- BrandPageTextSeeder fasst nur die Textfelder an, Bilder, Galerie und
  Leistungen bleiben unangetastet.

Vor der Veröffentlichung: Herstellerfreigabe einholen und die Aussage zur
erhaltenen Waldfläche nur mit ausdrücklicher Bestätigung verwenden. Beides
steht in der Deployment-Checkliste.

854 Tests grün (3150 Assertions), Pint sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-03 08:52:36 +00:00
parent 6d53243ea6
commit 4ef3a4d8f1
13 changed files with 1143 additions and 69 deletions

View file

@ -244,3 +244,86 @@ it('removes a gallery entry', function () {
expect($brand->fresh()->gallery)->toHaveCount(2);
});
// ========================================
// Abschnitte der Markenseite
// ========================================
it('saves sections with a deepening', function () {
$this->actingAs(User::factory()->create());
Volt::test('admin.cms.brands-index')
->call('openCreate')
->set('slug', 'mit-abschnitten')
->set('brandName', 'Mit Abschnitten')
->call('addSection')
->set('sections.0.title', 'Aus Ostwestfalen')
->set('sections.0.paragraphs', "Erster Absatz.\n\nZweiter Absatz.")
->set('sections.0.claim', 'Eine Schlusszeile.')
->set('sections.0.detail_title', 'Vertiefung')
->set('sections.0.detail_paragraphs', 'Mehr dazu.')
->call('save')
->assertHasNoErrors();
$sections = CmsBrand::where('slug', 'mit-abschnitten')->firstOrFail()->sections;
expect($sections)->toHaveCount(1)
->and($sections[0]['paragraphs'])->toBe(['Erster Absatz.', 'Zweiter Absatz.'])
->and($sections[0]['claim'])->toBe('Eine Schlusszeile.')
->and($sections[0]['detail']['title'])->toBe('Vertiefung')
->and($sections[0]['detail']['paragraphs'])->toBe(['Mehr dazu.']);
});
it('reorders sections', function () {
$brand = CmsBrand::factory()->create([
'slug' => 'sortieren',
'sections' => [
['title' => 'Erster', 'paragraphs' => ['A']],
['title' => 'Zweiter', 'paragraphs' => ['B']],
],
]);
$this->actingAs(User::factory()->create());
Volt::test('admin.cms.brands-index')
->call('openEdit', $brand->id)
->call('moveSection', 1, -1)
->call('save');
expect($brand->fresh()->sections[0]['title'])->toBe('Zweiter');
});
it('does not move a section beyond the ends', function () {
$brand = CmsBrand::factory()->create([
'slug' => 'raender',
'sections' => [['title' => 'Einziger', 'paragraphs' => ['A']]],
]);
$this->actingAs(User::factory()->create());
Volt::test('admin.cms.brands-index')
->call('openEdit', $brand->id)
->call('moveSection', 0, -1)
->call('moveSection', 0, 1)
->call('save');
expect($brand->fresh()->sections)->toHaveCount(1);
});
it('saves the widget label and the closing block', function () {
$this->actingAs(User::factory()->create());
Volt::test('admin.cms.brands-index')
->call('openCreate')
->set('slug', 'mit-cta')
->set('brandName', 'Mit CTA')
->set('widgetLabel', 'Persönliche Küchenberatung vereinbaren')
->set('ctaTitle', 'Ihre Küche. Persönlich geplant.')
->set('ctaText', 'Jeder Raum ist anders.')
->call('save')
->assertHasNoErrors();
$brand = CmsBrand::where('slug', 'mit-cta')->firstOrFail();
expect($brand->getTranslation('widget_label', 'de'))->toBe('Persönliche Küchenberatung vereinbaren')
->and($brand->getTranslation('cta_title', 'de'))->toBe('Ihre Küche. Persönlich geplant.')
->and($brand->getTranslation('cta_text', 'de'))->toBe('Jeder Raum ist anders.');
});