feat: list bookings inside the floor plan shapes

This commit is contained in:
fromme committed 2026-09-13 19:16:56 +02:00
1 parent 78839838f0
commit a17c61400a
4 files changed
+246 -41

No files matched your search

+188
View File
@@ -0,0 +1,188 @@
<?php
declare(strict_types=1);
namespace App\Tests\Controller;
use App\Booking\TimeRange;
use App\Entity\Booking;
use App\Entity\Resource;
use App\Entity\ResourceType;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* What a shape on the floor plan says.
*
* Who holds what is listed inside the box rather than in an SVG <title>: a tooltip only shows on
* hover, which is useless on touch and invisible when scanning the plan.
*/
class FloorPlanTest extends WebTestCase
{
private KernelBrowser $client;
private EntityManagerInterface $em;
protected function setUp(): void
{
$this->client = self::createClient();
$this->client->disableReboot();
$this->em = self::getContainer()->get(EntityManagerInterface::class);
$this->em->getConnection()->beginTransaction();
}
protected function tearDown(): void
{
$this->em->getConnection()->rollBack();
parent::tearDown();
}
public function testNoShapeCarriesATooltip(): void
{
$desk = $this->desk('desk-01');
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 540, 780);
$crawler = $this->board();
self::assertCount(0, $crawler->filter('svg title'));
self::assertCount(0, $crawler->filter('svg g title'));
}
public function testABookingIsListedInsideTheShape(): void
{
$desk = $this->desk('desk-01');
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 540, 780);
self::assertSame(
['Schreibtisch 01', 'Bea K. 09:00–13:00', 'frei 13:00–24:00'],
$this->linesOf($this->board()),
);
}
/**
* Full names do not fit a 140px box at this size, so they are shortened.
*/
public function testALongNameIsShortenedToAFirstNameAndInitial(): void
{
$desk = $this->desk('desk-01');
$this->book($desk, $this->user('[email protected]', 'Maximilian Wolkenstein'), 0, 1440);
self::assertContains('Maximilian W.', $this->linesOf($this->board()));
}
public function testASingleWordNameIsLeftAlone(): void
{
$desk = $this->desk('desk-01');
$this->book($desk, $this->user('[email protected]', 'Cher'), 0, 1440);
self::assertContains('Cher', $this->linesOf($this->board()));
}
public function testYourOwnBookingReadsAsDu(): void
{
$desk = $this->desk('desk-01');
$me = $this->user('[email protected]', 'Anna Berger');
$this->book($desk, $me, 540, 780);
self::assertContains('du 09:00–13:00', $this->linesOf($this->board($me)));
}
public function testAnAllDayBookingOmitsTheTimes(): void
{
$desk = $this->desk('desk-01');
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 0, 1440);
self::assertSame(['Schreibtisch 01', 'Bea K.'], $this->linesOf($this->board()));
}
/**
* A box fits three lines. More than that is summarised rather than allowed to spill out --
* and the counter has to account for the line the counter itself occupies.
*/
public function testTooManyBookingsAreSummarised(): void
{
$desk = $this->desk('desk-01');
$this->book($desk, $this->user('[email protected]', 'Ann Alpha'), 0, 360);
$this->book($desk, $this->user('[email protected]', 'Bea Beta'), 360, 720);
$this->book($desk, $this->user('[email protected]', 'Cem Gamma'), 720, 1080);
$this->book($desk, $this->user('[email protected]', 'Dia Delta'), 1080, 1440);
$lines = $this->linesOf($this->board());
self::assertSame(
['Schreibtisch 01', 'Ann A. 00:00–06:00', 'Bea B. 06:00–12:00', '+2 weitere'],
$lines,
);
// Label plus MAX_LINES, never more.
self::assertLessThanOrEqual(4, \count($lines));
}
public function testAFreeShapeShowsOnlyItsLabel(): void
{
$this->desk('desk-01');
self::assertSame(['Schreibtisch 01'], $this->linesOf($this->board()));
}
/**
* Screen readers get the same information the box shows, because role="img" on the <svg>
* would otherwise hide every one of those <text> elements.
*/
public function testTheShapeCarriesAnAccessibleLabel(): void
{
$desk = $this->desk('desk-01');
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 540, 780);
$label = $this->board()->filter('svg g[aria-label]')->attr('aria-label');
self::assertSame('Schreibtisch 01 — Bea K. 09:00–13:00, frei 13:00–24:00', $label);
}
/**
* @return list<string>
*/
private function linesOf(\Symfony\Component\DomCrawler\Crawler $crawler): array
{
return $crawler->filter('svg g text')->each(
static fn (\Symfony\Component\DomCrawler\Crawler $node): string => trim($node->text()),
);
}
private function board(?User $as = null): \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');
}
private function desk(string $svgElementId): Resource
{
$resource = new Resource(ResourceType::DESK, 'Schreibtisch 01', $svgElementId);
$this->em->persist($resource);
$this->em->flush();
return $resource;
}
private function book(Resource $resource, User $user, int $start, int $end): void
{
$this->em->persist(new Booking($user, $resource, new \DateTimeImmutable('2026-10-01'), TimeRange::fromMinutes($start, $end)));
$this->em->flush();
}
private function user(string $email, string $name): User
{
$existing = $this->em->getRepository(User::class)->findOneBy(['email' => $email]);
if (null !== $existing) {
return $existing;
}
$user = new User($email, $name);
$user->setRoles(['ROLE_EMPLOYEE']);
$this->em->persist($user);
$this->em->flush();
return $user;
}
}