Files
ephub/tests/Booking/TimeRangeTest.php
T

101 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Booking;
use App\Booking\TimeRange;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
class TimeRangeTest extends TestCase
{
public function testAWholeDayRunsFromMidnightToMidnight(): void
{
$range = TimeRange::wholeDay();
self::assertTrue($range->isAllDay());
self::assertSame(0, $range->startMinute);
self::assertSame(1440, $range->endMinute);
}
public function testTheEndMustComeAfterTheStart(): void
{
$this->expectException(\InvalidArgumentException::class);
TimeRange::fromMinutes(13 * 60, 9 * 60);
}
public function testARangeCannotLeaveTheDay(): void
{
$this->expectException(\InvalidArgumentException::class);
TimeRange::fromMinutes(0, 1441);
}
public function testTouchingRangesDoNotOverlap(): void
{
$morning = TimeRange::fromMinutes(9 * 60, 13 * 60);
$afternoon = TimeRange::fromMinutes(13 * 60, 17 * 60);
// Half-open, so handing a desk over at 13:00 is not a collision.
self::assertFalse($morning->overlaps($afternoon));
self::assertFalse($afternoon->overlaps($morning));
}
public function testPartlyCoveringRangesOverlapBothWays(): void
{
$a = TimeRange::fromMinutes(9 * 60, 13 * 60);
$b = TimeRange::fromMinutes(12 * 60, 17 * 60);
self::assertTrue($a->overlaps($b));
self::assertTrue($b->overlaps($a));
}
public function testAWholeDayOverlapsEverything(): void
{
self::assertTrue(TimeRange::wholeDay()->overlaps(TimeRange::fromMinutes(0, 15)));
}
#[DataProvider('urlPairs')]
public function testParsingTheBoardsQueryParameters(?string $from, ?string $to, ?string $expected): void
{
$range = TimeRange::parse($from, $to);
self::assertSame($expected, null === $range ? null : $range->start().'-'.$range->end());
}
/**
* @return iterable<string, array{?string, ?string, ?string}>
*/
public static function urlPairs(): iterable
{
yield 'both given' => ['09:00', '13:00', '09:00-13:00'];
yield 'only a start is open to the end of the day' => ['09:00', null, '09:00-24:00'];
yield 'only an end is open from midnight' => [null, '13:00', '00:00-13:00'];
yield 'neither means the whole day, i.e. no filter' => [null, null, null];
yield 'empty strings are the same as absent' => ['', '', null];
yield 'reversed is rejected' => ['13:00', '09:00', null];
yield 'equal is rejected' => ['09:00', '09:00', null];
yield 'nonsense is rejected' => ['halb zehn', '13:00', null];
yield 'out of range is rejected' => ['25:00', '26:00', null];
}
/**
* Regression guard for a trap that cost real debugging time: Twig resolves `range.allDay` by
* looking for a method literally named `allDay` BEFORE it tries `isAllDay()`, and it happily
* calls a static one. While the factory was called allDay(), every template read back a
* freshly built all-day range -- always truthy -- so the whole board silently ignored the
* selected span. Renaming the factory to wholeDay() is what fixes it; this keeps it fixed.
*/
public function testTheAllDayAccessorIsNotShadowedInTwig(): void
{
$twig = new Environment(new ArrayLoader(['t' => '{{ range.allDay ? "yes" : "no" }}']));
self::assertSame('no', $twig->render('t', ['range' => TimeRange::fromMinutes(9 * 60, 13 * 60)]));
self::assertSame('yes', $twig->render('t', ['range' => TimeRange::wholeDay()]));
}
}