feat: group resources into rooms on the floor plan
This commit is contained in:
1 parent
faef4d70a8
commit
8caf618950
14 files changed
+653
-34
No files matched your search
@@ -7,6 +7,7 @@ namespace App\Tests\Controller;
|
||||
use App\Booking\TimeRange;
|
||||
use App\Entity\Booking;
|
||||
use App\Entity\Resource;
|
||||
use App\Entity\ResourceGroup;
|
||||
use App\Entity\ResourceType;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -139,6 +140,107 @@ class FloorPlanTest extends WebTestCase
|
||||
self::assertSame('Schreibtisch 01 — Bea K. 09:00–13:00, frei 13:00–24:00', $label);
|
||||
}
|
||||
|
||||
public function testARoomIsDrawnWithItsNameAndHowManyAreFree(): void
|
||||
{
|
||||
$room = $this->room('Raum Nord', 'room-01');
|
||||
$this->deskIn($room, 'desk-01', 'Schreibtisch 01');
|
||||
$this->deskIn($room, 'desk-02', 'Schreibtisch 02');
|
||||
$this->deskIn($room, 'desk-03', 'Schreibtisch 03');
|
||||
|
||||
self::assertSame(['Raum Nord · 3 frei'], $this->roomsOf($this->board()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The badge counts what can still be had for the whole span, so a desk that is free only in
|
||||
* the afternoon does not count -- it is already amber on the plan, and counting it would
|
||||
* make the number disagree with the colour beside it.
|
||||
*/
|
||||
public function testAPartlyBookedDeskIsNotCountedAsFree(): void
|
||||
{
|
||||
$room = $this->room('Raum Nord', 'room-01');
|
||||
$partly = $this->deskIn($room, 'desk-01', 'Schreibtisch 01');
|
||||
$this->deskIn($room, 'desk-02', 'Schreibtisch 02');
|
||||
$this->book($partly, $this->user('[email protected]', 'Bea Kranz'), 540, 780);
|
||||
|
||||
self::assertSame(['Raum Nord · 1 frei'], $this->roomsOf($this->board()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The count means the same thing as the colours beside it, so it is relative to the board's
|
||||
* span: the desk that is unavailable for the whole day is free for an afternoon board.
|
||||
*/
|
||||
public function testTheFreeCountFollowsTheSelectedSpan(): void
|
||||
{
|
||||
$room = $this->room('Raum Nord', 'room-01');
|
||||
$desk = $this->deskIn($room, 'desk-01', 'Schreibtisch 01');
|
||||
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 540, 780);
|
||||
|
||||
self::assertSame(['Raum Nord · belegt'], $this->roomsOf($this->board()));
|
||||
self::assertSame(['Raum Nord · 1 frei'], $this->roomsOf($this->board(query: '&from=14:00&to=18:00')));
|
||||
}
|
||||
|
||||
public function testARoomWithNothingLeftSaysSoInsteadOfShowingAZero(): void
|
||||
{
|
||||
$room = $this->room('Raum Nord', 'room-01');
|
||||
$desk = $this->deskIn($room, 'desk-01', 'Schreibtisch 01');
|
||||
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 0, 1440);
|
||||
|
||||
self::assertSame(['Raum Nord · belegt'], $this->roomsOf($this->board()));
|
||||
}
|
||||
|
||||
public function testRoomsAreDrawnInTheirConfiguredOrder(): void
|
||||
{
|
||||
$this->deskIn($this->room('Zweiter Raum', 'room-02', 1), 'desk-04', 'Schreibtisch 04');
|
||||
$this->deskIn($this->room('Erster Raum', 'room-01', 0), 'desk-01', 'Schreibtisch 01');
|
||||
|
||||
self::assertSame(['Erster Raum · 1 frei', 'Zweiter Raum · 1 frei'], $this->roomsOf($this->board()));
|
||||
}
|
||||
|
||||
/**
|
||||
* A resource with no group is still bookable and still drawn -- the plan simply does not
|
||||
* frame it. That is the path the car park runs on.
|
||||
*/
|
||||
public function testAnUngroupedDeskIsDrawnWithoutARoom(): void
|
||||
{
|
||||
$this->desk('desk-01');
|
||||
|
||||
$crawler = $this->board();
|
||||
|
||||
self::assertSame([], $this->roomsOf($crawler));
|
||||
self::assertSame(['Schreibtisch 01'], $this->linesOf($crawler));
|
||||
}
|
||||
|
||||
/**
|
||||
* The room captions, which sit directly under the <svg> rather than inside a shape's <g>.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private function roomsOf(\Symfony\Component\DomCrawler\Crawler $crawler): array
|
||||
{
|
||||
return $crawler->filter('svg > text')->each(
|
||||
static fn (\Symfony\Component\DomCrawler\Crawler $node): string => trim(preg_replace('/\s+/', ' ', $node->text()) ?? ''),
|
||||
);
|
||||
}
|
||||
|
||||
private function room(string $label, string $svgElementId, int $position = 0): ResourceGroup
|
||||
{
|
||||
$group = new ResourceGroup(ResourceType::DESK, $label, $position, $svgElementId);
|
||||
$this->em->persist($group);
|
||||
$this->em->flush();
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
private function deskIn(ResourceGroup $group, string $svgElementId, string $label): Resource
|
||||
{
|
||||
$resource = new Resource(ResourceType::DESK, $label, $svgElementId);
|
||||
$resource->setGroup($group);
|
||||
$this->em->persist($resource);
|
||||
$this->em->flush();
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
@@ -149,11 +251,11 @@ class FloorPlanTest extends WebTestCase
|
||||
);
|
||||
}
|
||||
|
||||
private function board(?User $as = null): \Symfony\Component\DomCrawler\Crawler
|
||||
private function board(?User $as = null, string $query = ''): \Symfony\Component\DomCrawler\Crawler
|
||||
{
|
||||
$this->client->loginUser($as ?? $this->user('[email protected]', 'Anna Berger'));
|
||||
|
||||
return $this->client->request('GET', '/book/desk?date=2026-10-01');
|
||||
return $this->client->request('GET', '/book/desk?date=2026-10-01'.$query);
|
||||
}
|
||||
|
||||
private function desk(string $svgElementId): Resource
|
||||
|
||||
Reference in new issue
Block a user