FAQ im Inhalte-Editor vollständig bearbeitbar: Bereich je Frage statt verschachtelter sections

Die Bereiche der FAQ-Seite lagen als verschachtelte Listen in sections, der Editor zeigte
davon nur JSON-Text. Jetzt stehen alle Fragen in questions mit dem Feld category, Überschrift
und Symbol der Bereiche in categories. FAQ::groups baut daraus die Seite (allgemeine Fragen
zuerst), alte Einträge werden weiter dargestellt. FaqCategoriesSeeder stellt gespeicherte
Einträge um (FAQ und Immobilien-FAQ), Texte bleiben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-09 09:50:44 +00:00
parent 69d50eed0f
commit dc31cdb401
8 changed files with 406 additions and 208 deletions

View file

@ -0,0 +1,103 @@
<?php
use App\Livewire\Web\Components\Sections\FAQ;
use Database\Seeders\FaqCategoriesSeeder;
use FluxCms\Core\Models\CmsContent;
/**
* FAQ mit Bereich je Frage statt verschachtelter `sections` (09.10.2026) – so kann der
* Inhalte-Editor alle Fragen als Liste zeigen.
*/
function legacyFaq(): array
{
return [
'title' => 'FAQ',
'questions' => [['question' => 'Allgemein?', 'answer' => 'A']],
'sections' => [
['title' => 'Für Ihr Zuhause', 'icon' => 'home-modern', 'questions' => [['question' => 'Zuhause?', 'answer' => 'B']]],
['title' => 'Für Projekte', 'icon' => 'briefcase', 'questions' => [['question' => 'Projekt?', 'answer' => 'C']]],
],
];
}
it('flattens the nested sections into one list with a category per question', function () {
expect(FAQ::flatten(legacyFaq()))->toBe([
'title' => 'FAQ',
'questions' => [
['question' => 'Allgemein?', 'answer' => 'A', 'category' => ''],
['question' => 'Zuhause?', 'answer' => 'B', 'category' => 'Für Ihr Zuhause'],
['question' => 'Projekt?', 'answer' => 'C', 'category' => 'Für Projekte'],
],
'categories' => [
['title' => 'Für Ihr Zuhause', 'icon' => 'home-modern'],
['title' => 'Für Projekte', 'icon' => 'briefcase'],
],
]);
});
it('groups the questions: general first, then the categories in their order, unknown ones last', function () {
$groups = FAQ::groups([
'questions' => [
['question' => 'Neu?', 'answer' => 'x', 'category' => 'Sonstiges'],
['question' => 'Projekt?', 'answer' => 'x', 'category' => 'Für Projekte'],
['question' => 'Allgemein?', 'answer' => 'x', 'category' => ''],
['question' => '', 'answer' => 'leer'],
],
'categories' => [
['title' => 'Für Ihr Zuhause', 'icon' => 'home-modern'],
['title' => 'Für Projekte', 'icon' => 'briefcase'],
],
]);
expect(array_column($groups, 'title'))->toBe([null, 'Für Projekte', 'Sonstiges'])
->and(array_column($groups, 'icon'))->toBe([null, 'briefcase', 'question-mark-circle']);
});
it('shows all thirteen questions with both headings on the faq page', function () {
$page = $this->get('/faq')->assertSuccessful()->getContent();
$html = substr($page, strpos($page, 'id="faq"'));
expect(substr_count($html, 'card-elevated rounded-xl overflow-hidden'))->toBe(13)
->and(strpos($html, 'Was ist B2in und welche Services'))->toBeLessThan(strpos($html, 'Für Ihr Zuhause'))
->and(strpos($html, 'Für Ihr Zuhause'))->toBeLessThan(strpos($html, 'Muss ich gleich die ganze Wohnung'))
->and(strpos($html, 'Muss ich gleich die ganze Wohnung'))->toBeLessThan(strpos($html, 'Für Projekte und Objekte'))
->and(strpos($html, 'Für Projekte und Objekte'))->toBeLessThan(strpos($html, 'Ab welcher Größe'));
});
it('converts stored faq entries once and keeps maintained texts', function () {
$legacy = legacyFaq();
$legacy['sections'][0]['questions'][0]['answer'] = 'Im CMS gepflegt';
$row = CmsContent::create(['group' => 'faq', 'key' => 'faq', 'type' => 'json', 'order' => 0, 'value' => ['de' => $legacy, 'en' => legacyFaq()]]);
$this->seed(FaqCategoriesSeeder::class);
$first = $row->refresh()->getTranslations('value');
$this->seed(FaqCategoriesSeeder::class);
expect($row->refresh()->getTranslations('value'))->toBe($first)
->and($first['de'])->not->toHaveKey('sections')
->and($first['de']['questions'][1])->toBe(['question' => 'Zuhause?', 'answer' => 'Im CMS gepflegt', 'category' => 'Für Ihr Zuhause']);
$this->get('/faq')->assertSuccessful()->assertSee('Im CMS gepflegt')->assertSee('Für Projekte');
});
it('still renders entries in the old form until the seeder ran', function () {
CmsContent::create(['group' => 'faq', 'key' => 'faq', 'type' => 'json', 'order' => 0, 'value' => ['de' => legacyFaq()]]);
$this->get('/faq')->assertSuccessful()->assertSee('Zuhause?')->assertSee('Für Ihr Zuhause');
});
it('opens every faq question as plain fields in the content editor', function () {
$this->actingAs(App\Models\User::factory()->create());
$this->seed(Database\Seeders\CmsContentSeeder::class);
$row = CmsContent::query()->where('group', 'faq')->where('key', 'faq')->first();
$component = Livewire\Volt\Volt::test('admin.cms.content-index')
->call('startFieldEdit', $row->id, 'questions');
$items = $component->get('jsonItems');
expect($items)->toHaveCount(13)
->and(array_keys($items[5]))->toBe(['question', 'answer', 'category'])
->and($items[5]['category'])->toBe('Für Ihr Zuhause')
->and(collect($items)->flatten()->filter(fn ($value) => str_starts_with((string) $value, '[{')))->toBeEmpty();
});