Files
ephub/tests/Model/SpotTest.php
T

167 lines
5.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Tests\Model;
use App\Entity\Booking;
use App\Entity\Resource;
use App\Entity\User;
use App\Enum\ResourceType;
use App\Enum\SpotState;
use App\Model\Spot;
use App\Model\TimeRange;
use PHPUnit\Framework\TestCase;
class SpotTest extends TestCase
{
public function testAResourceWithNothingBookedIsFree(): void
{
$spot = $this->spot([]);
self::assertSame(SpotState::FREE, $spot->state);
self::assertTrue($spot->isBookable());
self::assertNull($spot->own);
}
/**
* The reported bug: somebody else holds 09:00-13:00, the board shows the whole day, and the
* afternoon is still bookable. This used to come out as TAKEN and carried no link at all.
*/
public function testAResourceBookedForPartOfTheDayIsStillBookable(): void
{
$spot = $this->spot([$this->booking($this->someoneElse(), 540, 780)]);
self::assertSame(SpotState::PARTLY, $spot->state);
self::assertTrue($spot->isBookable());
self::assertTrue($spot->isActionable());
}
public function testTheSuggestedWindowIsTheLongestFreeOne(): void
{
// Taken 09:00-13:00 leaves 00:00-09:00 (9h) and 13:00-24:00 (11h).
$spot = $this->spot([$this->booking($this->someoneElse(), 540, 780)]);
self::assertSame('13:00–24:00', (string) $spot->suggested());
}
public function testTheSuggestedWindowIsAlwaysBookable(): void
{
$taken = $this->booking($this->someoneElse(), 540, 780);
$spot = $this->spot([$taken]);
// The whole point of suggesting a gap rather than the board's span: submitting it must
// not collide with what is already there.
self::assertFalse($spot->suggested()->overlaps($taken->getTimeRange()));
}
public function testAResourceBookedAllDayIsTaken(): void
{
$spot = $this->spot([$this->booking($this->someoneElse(), 0, 1440)]);
self::assertSame(SpotState::TAKEN, $spot->state);
self::assertFalse($spot->isBookable());
self::assertFalse($spot->isActionable());
self::assertNull($spot->suggested());
}
public function testSeveralBookingsCanLeaveNothingFree(): void
{
$spot = $this->spot([
$this->booking($this->someoneElse(), 0, 720),
$this->booking($this->someoneElse(), 720, 1440),
]);
self::assertSame(SpotState::TAKEN, $spot->state);
}
public function testSeveralBookingsCanStillLeaveAGapBetweenThem(): void
{
$spot = $this->spot([
$this->booking($this->someoneElse(), 0, 540),
$this->booking($this->someoneElse(), 780, 1440),
]);
self::assertSame(SpotState::PARTLY, $spot->state);
self::assertSame('09:00–13:00', (string) $spot->suggested());
}
public function testYourOwnBookingWinsOverEverythingElse(): void
{
$viewer = $this->viewer();
$spot = Spot::for(
$this->resource(),
[$this->booking($viewer, 540, 780)],
TimeRange::wholeDay(),
$viewer,
);
self::assertSame(SpotState::MINE, $spot->state);
// Clicking opens the edit modal rather than the create one, so it is actionable but not
// offered as a new booking.
self::assertTrue($spot->isActionable());
self::assertNotNull($spot->own);
}
/**
* A narrowed board only asks about its own window, so what happens outside it is irrelevant.
*/
public function testStateIsJudgedWithinTheBoardsSpanOnly(): void
{
$spot = Spot::for(
$this->resource(),
[],
TimeRange::fromMinutes(780, 1020),
$this->viewer(),
);
self::assertSame(SpotState::FREE, $spot->state);
self::assertSame('13:00–17:00', (string) $spot->suggested());
}
/**
* @param list<Booking> $bookings
*/
private function spot(array $bookings): Spot
{
return Spot::for($this->resource(), $bookings, TimeRange::wholeDay(), $this->viewer());
}
private function booking(User $user, int $start, int $end): Booking
{
return new Booking($user, $this->resource(), new \DateTimeImmutable('2026-10-01'), TimeRange::fromMinutes($start, $end));
}
private function resource(): Resource
{
return new Resource(ResourceType::DESK, 'D1');
}
private function viewer(): User
{
return new UserWithId('[email protected]', 'Anna', 1);
}
private function someoneElse(): User
{
return new UserWithId('[email protected]', 'Bea', 2);
}
}
/**
* Spot compares users by id, and an unpersisted User has none -- which would make every booking
* look like everyone's. This gives the fixtures stable ids without touching the database.
*/
class UserWithId extends User
{
public function __construct(string $email, string $name, private readonly int $fixedId)
{
parent::__construct($email, $name);
}
public function getId(): ?int
{
return $this->fixedId;
}
}