M2: Status & Wochentage – Schritt 2 (UI): Tagesansicht, htmx-CheckOff/Uncheck-Partials, Archiv-Tab + Feature-Tests
This commit is contained in:
@@ -10,6 +10,7 @@ use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Symfony\Component\BrowserKit\Cookie;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
/**
|
||||
* M1 Schritt 2: CRUD-Slice "Mahlzeit" über die Web-UI (eingeloggte Session).
|
||||
@@ -146,6 +147,137 @@ final class MahlzeitControllerTest extends WebTestCase
|
||||
self::assertSelectorExists('form[action$="/loeschen"]');
|
||||
}
|
||||
|
||||
// ---- M2 Schritt 2: Status (Check-Off/Toggle), Tagesansicht, Archiv ----
|
||||
|
||||
public function testIndexGroupsMealsByWeekday(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$this->anlegen($client, 'Curry', '', ['di']);
|
||||
$this->anlegen($client, 'Pizza', '', ['sa']);
|
||||
$this->anlegen($client, 'Bolognese', '', ['mo', 'mi']);
|
||||
|
||||
$client->request('GET', '/mahlzeiten');
|
||||
$crawler = $client->getCrawler();
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
// Curry exakt unter "Di", nicht unter "Sa".
|
||||
$diUl = $this->listeNachHeading($crawler, 'Di');
|
||||
self::assertStringContainsString('Curry', $diUl->html());
|
||||
self::assertStringNotContainsString('Pizza', $diUl->html());
|
||||
|
||||
// Pizza exakt unter "Sa".
|
||||
$saUl = $this->listeNachHeading($crawler, 'Sa');
|
||||
self::assertStringContainsString('Pizza', $saUl->html());
|
||||
self::assertStringNotContainsString('Curry', $saUl->html());
|
||||
|
||||
// Bolognese hat zwei Wochentage → erscheint unter "Mo" UND "Mi".
|
||||
self::assertStringContainsString('Bolognese', $this->listeNachHeading($crawler, 'Mo')->html());
|
||||
self::assertStringContainsString('Bolognese', $this->listeNachHeading($crawler, 'Mi')->html());
|
||||
}
|
||||
|
||||
public function testIndexGroupsMealWithoutWeekdayUnderOhneTag(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$this->anlegen($client, 'Reis', '', []);
|
||||
|
||||
$client->request('GET', '/mahlzeiten');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertStringContainsString('Reis', $this->listeNachHeading($client->getCrawler(), 'Ohne Tag')->html());
|
||||
}
|
||||
|
||||
public function testCheckOffReturnsGegessenPartial(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$id = $this->anlegen($client, 'Pasta', '', ['mo']);
|
||||
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/checkoff', $id));
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
// htmx-Fragment: genau das Mahlzeiten-Partial, keine komplette Seite.
|
||||
self::assertSelectorExists('li#mahlzeit-' . $id);
|
||||
self::assertSelectorTextContains('span.status', 'gegessen');
|
||||
$content = (string) $client->getResponse()->getContent();
|
||||
self::assertStringNotContainsString('<header', $content);
|
||||
self::assertStringNotContainsString('<!DOCTYPE', $content);
|
||||
|
||||
// Persistenz: Status auf "gegessen" + gegessenAm gesetzt.
|
||||
$this->em()->clear();
|
||||
$entity = $this->em()->getRepository(MahlzeitEntity::class)->find($id);
|
||||
self::assertSame('gegessen', $entity->getStatus());
|
||||
self::assertNotNull($entity->getGegessenAm());
|
||||
}
|
||||
|
||||
public function testUncheckRestoresGeplantStatus(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$id = $this->anlegen($client, 'Suppe', '', []);
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/checkoff', $id));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/uncheck', $id));
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('li#mahlzeit-' . $id);
|
||||
self::assertSelectorTextContains('span.status', 'geplant');
|
||||
|
||||
// Persistenz: Status zurück auf "geplant" + gegessenAm zurückgesetzt.
|
||||
$this->em()->clear();
|
||||
$entity = $this->em()->getRepository(MahlzeitEntity::class)->find($id);
|
||||
self::assertSame('geplant', $entity->getStatus());
|
||||
self::assertNull($entity->getGegessenAm());
|
||||
}
|
||||
|
||||
public function testArchivShowsOnlyGegessenMeals(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$this->anlegen($client, 'GeplanteMahlzeit', '', ['mo']);
|
||||
$gegessenId = $this->anlegen($client, 'GegesseneMahlzeit', '', ['di']);
|
||||
|
||||
$client->request('POST', sprintf('/mahlzeiten/%d/checkoff', $gegessenId));
|
||||
self::assertResponseIsSuccessful();
|
||||
|
||||
$client->request('GET', '/mahlzeiten/archiv');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('h1', 'Archiv');
|
||||
|
||||
// Nur die Ergebnisliste prüfen (Flash-Meldungen der Anleg-Requests ausblenden).
|
||||
$liste = $client->getCrawler()->filter('ul')->html();
|
||||
self::assertStringContainsString('GegesseneMahlzeit', $liste);
|
||||
self::assertStringNotContainsString('GeplanteMahlzeit', $liste);
|
||||
}
|
||||
|
||||
public function testCheckOffUnknownIdReturns404(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$client->request('POST', '/mahlzeiten/99999/checkoff');
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
public function testUncheckUnknownIdReturns404(): void
|
||||
{
|
||||
$client = $this->vorbereiten();
|
||||
$client->request('POST', '/mahlzeiten/99999/uncheck');
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert die <ul>-Liste, die direkt unter der angegebenen <h2>-Überschrift
|
||||
* (z. B. "Di", "Ohne Tag") in der Tagesansicht liegt.
|
||||
*/
|
||||
private function listeNachHeading(Crawler $crawler, string $heading): Crawler
|
||||
{
|
||||
$h2 = $crawler->filterXPath(sprintf('//h2[normalize-space()="%s"]', $heading));
|
||||
self::assertCount(1, $h2, sprintf('Genau ein <h2> "%s" erwartet.', $heading));
|
||||
|
||||
// Das <ul> ist der erste nächste Geschwister-Knoten der Überschrift.
|
||||
return $h2->nextAll()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Client + sauberes Schema (+ optional eingeloggte Session) bereitstellen.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user