fix: allow booking the free part of a partly booked day

This commit is contained in:
fromme committed 2026-09-13 19:11:51 +02:00
1 parent a88ff7ff24
commit 78839838f0
15 files changed
+541 -60

No files matched your search

+61
View File
@@ -83,6 +83,57 @@ class TimeRangeTest extends TestCase
yield 'out of range is rejected' => ['25:00', '26:00', null];
}
public function testRemainingIsTheWholeSpanWhenNothingIsTaken(): void
{
$free = TimeRange::remaining(TimeRange::wholeDay(), []);
self::assertSame(['00:00–24:00'], self::render($free));
}
public function testRemainingSplitsAroundABookingInTheMiddle(): void
{
$free = TimeRange::remaining(TimeRange::wholeDay(), [TimeRange::fromMinutes(540, 780)]);
self::assertSame(['00:00–09:00', '13:00–24:00'], self::render($free));
}
public function testRemainingIsEmptyWhenTheSpanIsFullyCovered(): void
{
self::assertSame([], TimeRange::remaining(TimeRange::wholeDay(), [TimeRange::wholeDay()]));
}
public function testRemainingHandlesOverlappingAndUnsortedInput(): void
{
// Bookings on one resource cannot overlap, but the span may be covered by several that
// touch, and nothing guarantees the order they arrive in.
$free = TimeRange::remaining(TimeRange::wholeDay(), [
TimeRange::fromMinutes(780, 1020),
TimeRange::fromMinutes(540, 900),
]);
self::assertSame(['00:00–09:00', '17:00–24:00'], self::render($free));
}
public function testRemainingIgnoresWhatFallsOutsideTheSpan(): void
{
$free = TimeRange::remaining(TimeRange::fromMinutes(780, 1020), [TimeRange::fromMinutes(0, 540)]);
self::assertSame(['13:00–17:00'], self::render($free));
}
public function testRemainingClipsABookingThatOverhangsTheSpan(): void
{
$free = TimeRange::remaining(TimeRange::fromMinutes(480, 1080), [TimeRange::fromMinutes(0, 600)]);
self::assertSame(['10:00–18:00'], self::render($free));
}
public function testDurationIsInMinutes(): void
{
self::assertSame(240, TimeRange::fromMinutes(540, 780)->durationInMinutes());
self::assertSame(1440, TimeRange::wholeDay()->durationInMinutes());
}
/**
* 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
@@ -97,4 +148,14 @@ class TimeRangeTest extends TestCase
self::assertSame('no', $twig->render('t', ['range' => TimeRange::fromMinutes(9 * 60, 13 * 60)]));
self::assertSame('yes', $twig->render('t', ['range' => TimeRange::wholeDay()]));
}
/**
* @param list<TimeRange> $ranges
*
* @return list<string>
*/
private static function render(array $ranges): array
{
return array_map(static fn (TimeRange $range): string => $range->start().'–'.$range->end(), $ranges);
}
}