M3: Reorder + Inspiration – Schritt 2 (UI): Planungsliste mit SortableJS, Tagesansicht /mahlzeiten/tage, Inspiration /inspiration mit 'Weitere 5' + 1-Klick-Adopt, Feature-Tests
This commit is contained in:
@@ -149,14 +149,14 @@ final class MahlzeitControllerTest extends WebTestCase
|
||||
|
||||
// ---- M2 Schritt 2: Status (Check-Off/Toggle), Tagesansicht, Archiv ----
|
||||
|
||||
public function testIndexGroupsMealsByWeekday(): void
|
||||
public function testTageViewGroupsMealsByWeekday(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$this->anlegen($client, 'Curry', '', ['di']);
|
||||
$this->anlegen($client, 'Pizza', '', ['sa']);
|
||||
$this->anlegen($client, 'Bolognese', '', ['mo', 'mi']);
|
||||
|
||||
$client->request('GET', '/mahlzeiten');
|
||||
$client->request('GET', '/mahlzeiten/tage');
|
||||
$crawler = $client->getCrawler();
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
@@ -176,12 +176,12 @@ final class MahlzeitControllerTest extends WebTestCase
|
||||
self::assertStringContainsString('Bolognese', $this->listeNachHeading($crawler, 'Mi')->html());
|
||||
}
|
||||
|
||||
public function testIndexGroupsMealWithoutWeekdayUnderOhneTag(): void
|
||||
public function testTageViewGroupsMealWithoutWeekdayUnderOhneTag(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$this->anlegen($client, 'Reis', '', []);
|
||||
|
||||
$client->request('GET', '/mahlzeiten');
|
||||
$client->request('GET', '/mahlzeiten/tage');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertStringContainsString('Reis', $this->listeNachHeading($client->getCrawler(), 'Ohne Tag')->html());
|
||||
@@ -265,6 +265,174 @@ final class MahlzeitControllerTest extends WebTestCase
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
// ---- M3: Planungsliste (flach/sortierbar), Reorder, Inspiration, Adopt ----
|
||||
|
||||
public function testIndexRendersFlatPlannedListOnly(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$geplantId = $this->anlegen($client, 'GeplanteMahlzeit', '', []);
|
||||
$gegessenId = $this->anlegen($client, 'GegesseneMahlzeit', '', []);
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/checkoff', $gegessenId));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$client->request('GET', '/mahlzeiten');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
// Sortierbare Planungsliste mit Drag-Handle + data-id; gegessene Mahlzeit fehlt.
|
||||
self::assertSelectorExists('ul.sortierbar');
|
||||
self::assertSelectorExists('li[data-id="' . $geplantId . '"]');
|
||||
self::assertSelectorExists('li[data-id="' . $geplantId . '"] .drag-handle');
|
||||
self::assertSelectorNotExists('li[data-id="' . $gegessenId . '"]');
|
||||
}
|
||||
|
||||
public function testIndexKeepsPersistedSortOrder(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$a = $this->anlegen($client, 'Erste', '', []);
|
||||
$b = $this->anlegen($client, 'Zweite', '', []);
|
||||
$c = $this->anlegen($client, 'Dritte', '', []);
|
||||
|
||||
// Anordnung ändern: C, A, B (Formularfeld `ids[]`).
|
||||
$client->request('POST', '/mahlzeiten/reorder', ['ids' => [$c, $a, $b]]);
|
||||
self::assertResponseStatusCodeSame(204);
|
||||
|
||||
// Persistenz: sortOrder neu nummeriert.
|
||||
$this->em()->clear();
|
||||
$repo = $this->em()->getRepository(MahlzeitEntity::class);
|
||||
self::assertSame(1, $repo->find($c)->getSortOrder());
|
||||
self::assertSame(2, $repo->find($a)->getSortOrder());
|
||||
self::assertSame(3, $repo->find($b)->getSortOrder());
|
||||
|
||||
// Index rendert die neue Reihenfolge.
|
||||
$client->request('GET', '/mahlzeiten');
|
||||
$reihenfolge = $client->getCrawler()
|
||||
->filter('li[data-id]')
|
||||
->each(static fn (Crawler $node): int => (int) $node->attr('data-id'));
|
||||
self::assertSame([$c, $a, $b], $reihenfolge);
|
||||
}
|
||||
|
||||
public function testReorderUnbekannteIdLiefert404(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$client->request('POST', '/mahlzeiten/reorder', ['ids' => [99999]]);
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function testReorderAkzeptiertJsonIds(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$a = $this->anlegen($client, 'Erste', '', []);
|
||||
$b = $this->anlegen($client, 'Zweite', '', []);
|
||||
|
||||
// SortableJS schickt JSON: {"ids": [...]} – hier als roher Body.
|
||||
$client->request('POST', '/mahlzeiten/reorder', [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
'HTTP_ACCEPT' => 'application/json',
|
||||
], json_encode(['ids' => [$b, $a]]));
|
||||
|
||||
self::assertResponseStatusCodeSame(204);
|
||||
|
||||
$this->em()->clear();
|
||||
$repo = $this->em()->getRepository(MahlzeitEntity::class);
|
||||
self::assertSame(1, $repo->find($b)->getSortOrder());
|
||||
self::assertSame(2, $repo->find($a)->getSortOrder());
|
||||
}
|
||||
|
||||
public function testInspirationListetArchivGerichteMitAdopt(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$id = $this->anlegen($client, 'Bolognese', '', ['mo']);
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/checkoff', $id));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$client->request('GET', '/inspiration');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('h1', 'Inspiration');
|
||||
self::assertSelectorTextContains('strong', 'Bolognese');
|
||||
self::assertSelectorExists('form[action$="/adopt"] button[type="submit"]');
|
||||
}
|
||||
|
||||
public function testInspirationLeerOhneArchivEintraege(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$this->anlegen($client, 'NurGeplant', '', []);
|
||||
|
||||
$client->request('GET', '/inspiration');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('p', 'Noch keine Inspiration');
|
||||
self::assertSelectorNotExists('li[id^="vorschlag-"]');
|
||||
}
|
||||
|
||||
public function testInspirationWeitere5ExkludiertBereitsGezeigte(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
foreach (['A', 'B', 'C', 'D', 'E', 'F'] as $buchstabe) {
|
||||
$id = $this->anlegen($client, sprintf('Gericht%s', $buchstabe), '', []);
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/checkoff', $id));
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
$client->request('GET', '/inspiration');
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$ersteSeite = $client->getCrawler()->filter('li[id^="vorschlag-"]');
|
||||
self::assertCount(5, $ersteSeite);
|
||||
$ersteSeiteNamen = $ersteSeite->each(static fn (Crawler $n): string => $n->filter('strong')->text());
|
||||
|
||||
// "Weitere 5" führt mit Ausschluss-Liste zurück zur Inspiration.
|
||||
$link = $client->getCrawler()->filter('a:contains("Weitere 5 Vorschläge")');
|
||||
self::assertCount(1, $link);
|
||||
self::assertStringContainsString('ausschluss=', (string) $link->link()->getUri());
|
||||
|
||||
$client->request('GET', (string) $link->link()->getUri());
|
||||
$zweiteSeite = $client->getCrawler()->filter('li[id^="vorschlag-"]');
|
||||
self::assertCount(1, $zweiteSeite);
|
||||
self::assertNotContains($zweiteSeite->filter('strong')->text(), $ersteSeiteNamen);
|
||||
}
|
||||
|
||||
public function testAdoptErstelltNeuenGeplantEintrag(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$quellId = $this->anlegen($client, 'Pasta', 'mit Knoblauch', ['di']);
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/checkoff', $quellId));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/adopt', $quellId));
|
||||
self::assertResponseRedirects('/inspiration');
|
||||
|
||||
$this->em()->clear();
|
||||
$repo = $this->em()->getRepository(MahlzeitEntity::class);
|
||||
|
||||
// Quelle bleibt unverändert im Archiv.
|
||||
self::assertSame('gegessen', $repo->find($quellId)->getStatus());
|
||||
|
||||
// Neue Kopie: gleicher Name/Notiz/Wochentag, aber geplant + ohne gegessenAm.
|
||||
$kopien = $repo->findBy(['name' => 'Pasta']);
|
||||
self::assertCount(2, $kopien);
|
||||
$neue = null;
|
||||
foreach ($kopien as $kandidat) {
|
||||
if ((int) $kandidat->getId() !== $quellId) {
|
||||
$neue = $kandidat;
|
||||
}
|
||||
}
|
||||
self::assertNotNull($neue);
|
||||
self::assertSame('geplant', $neue->getStatus());
|
||||
self::assertNull($neue->getGegessenAm());
|
||||
self::assertSame('mit Knoblauch', $neue->getNotiz());
|
||||
self::assertSame('di', $neue->getWochentage());
|
||||
}
|
||||
|
||||
public function testAdoptUnbekannteIdLiefert404(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$client->request('POST', '/mahlzeiten/99999/adopt');
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert die <ul>-Liste, die direkt unter der angegebenen <h2>-Überschrift
|
||||
* (z. B. "Di", "Ohne Tag") in der Tagesansicht liegt.
|
||||
|
||||
Reference in New Issue
Block a user