From dc7ed0728ab718a55ff0810d5c357dc90db78391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 16 Oct 2025 09:36:04 +0200 Subject: [PATCH] feat: readonly room selection for single room types --- .../Condition/SingleRoomTypeCondition.php | 67 +++++++++ src/Form/Service/CreateFieldStateProvider.php | 6 + .../Condition/SingleRoomTypeConditionTest.php | 140 ++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 src/Form/Service/Condition/SingleRoomTypeCondition.php create mode 100644 tests/Form/Service/Condition/SingleRoomTypeConditionTest.php diff --git a/src/Form/Service/Condition/SingleRoomTypeCondition.php b/src/Form/Service/Condition/SingleRoomTypeCondition.php new file mode 100644 index 0000000..b8d957f --- /dev/null +++ b/src/Form/Service/Condition/SingleRoomTypeCondition.php @@ -0,0 +1,67 @@ + $formData Current form data for condition evaluation + * + * @return bool True if exactly one room type is selected, false otherwise + */ + public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool + { + $selectedRoomCount = 0; + + foreach ($bookingDto->roomSelections as $roomSelection) { + if (null !== $roomSelection->quantity && $roomSelection->quantity > 0) { + ++$selectedRoomCount; + } + } + + return 1 === $selectedRoomCount; + } + + /** + * Returns field names that trigger re-evaluation of this condition. + * + * This condition depends on booking-level room selection data, not participant fields. + * Room selections are determined in step 1 and remain static during participant editing, + * so no participant-level field dependencies are needed. + * + * @return string[] Empty array (no participant-level dependencies) + */ + public function getDependentFields(): array + { + return []; + } + + /** + * Returns a human-readable description of this condition. + * + * @return string A brief description of the condition logic + */ + public function getDescription(): string + { + return 'Field readonly when exactly one room type is selected'; + } +} diff --git a/src/Form/Service/CreateFieldStateProvider.php b/src/Form/Service/CreateFieldStateProvider.php index e697e02..a9d8b4e 100644 --- a/src/Form/Service/CreateFieldStateProvider.php +++ b/src/Form/Service/CreateFieldStateProvider.php @@ -15,6 +15,7 @@ use App\Form\Service\Condition\FieldValueCondition; use App\Form\Service\Condition\RentalSelectionCondition; use App\Form\Service\Condition\RoomSelectionCondition; use App\Form\Service\Condition\ServiceSubTypeCondition; +use App\Form\Service\Condition\SingleRoomTypeCondition; use App\Form\Service\Condition\SkiPassSelectionCondition; use App\Service\ParticipantEligibilityService; @@ -204,6 +205,11 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider 'required' => new ApplicantCondition(), ]; + // Make assignedRoomId readonly when only one room type is selected + $this->fieldStateConditions['assignedRoomId'] = [ + 'readonly' => new SingleRoomTypeCondition(), + ]; + // Example field state conditions would be registered here // For demonstration purposes, here are some example patterns: diff --git a/tests/Form/Service/Condition/SingleRoomTypeConditionTest.php b/tests/Form/Service/Condition/SingleRoomTypeConditionTest.php new file mode 100644 index 0000000..7471f70 --- /dev/null +++ b/tests/Form/Service/Condition/SingleRoomTypeConditionTest.php @@ -0,0 +1,140 @@ +condition = new SingleRoomTypeCondition(); + } + + public function testReturnsFalseWhenNoRoomsSelected(): void + { + $bookingDto = $this->createBookingDto([]); + + $result = $this->condition->evaluate($bookingDto, 0, []); + + $this->assertFalse($result, 'Condition should return false when no rooms are selected'); + } + + public function testReturnsTrueWhenExactlyOneRoomSelected(): void + { + $bookingDto = $this->createBookingDto([ + ['roomId' => 1, 'quantity' => 2], + ]); + + $result = $this->condition->evaluate($bookingDto, 0, []); + + $this->assertTrue($result, 'Condition should return true when exactly one room type is selected'); + } + + public function testReturnsFalseWhenTwoRoomsSelected(): void + { + $bookingDto = $this->createBookingDto([ + ['roomId' => 1, 'quantity' => 2], + ['roomId' => 2, 'quantity' => 1], + ]); + + $result = $this->condition->evaluate($bookingDto, 0, []); + + $this->assertFalse($result, 'Condition should return false when two room types are selected'); + } + + public function testReturnsFalseWhenMultipleRoomsSelected(): void + { + $bookingDto = $this->createBookingDto([ + ['roomId' => 1, 'quantity' => 2], + ['roomId' => 2, 'quantity' => 1], + ['roomId' => 3, 'quantity' => 3], + ]); + + $result = $this->condition->evaluate($bookingDto, 0, []); + + $this->assertFalse($result, 'Condition should return false when multiple room types are selected'); + } + + public function testIgnoresRoomsWithZeroQuantity(): void + { + $bookingDto = $this->createBookingDto([ + ['roomId' => 1, 'quantity' => 2], + ['roomId' => 2, 'quantity' => 0], + ['roomId' => 3, 'quantity' => 0], + ]); + + $result = $this->condition->evaluate($bookingDto, 0, []); + + $this->assertTrue($result, 'Condition should ignore rooms with zero quantity'); + } + + public function testIgnoresRoomsWithNullQuantity(): void + { + $bookingDto = $this->createBookingDto([ + ['roomId' => 1, 'quantity' => 1], + ['roomId' => 2, 'quantity' => null], + ]); + + $result = $this->condition->evaluate($bookingDto, 0, []); + + $this->assertTrue($result, 'Condition should ignore rooms with null quantity'); + } + + public function testReturnsFalseWhenAllRoomsHaveZeroQuantity(): void + { + $bookingDto = $this->createBookingDto([ + ['roomId' => 1, 'quantity' => 0], + ['roomId' => 2, 'quantity' => 0], + ]); + + $result = $this->condition->evaluate($bookingDto, 0, []); + + $this->assertFalse($result, 'Condition should return false when all rooms have zero quantity'); + } + + public function testGetDependentFieldsReturnsEmptyArray(): void + { + $result = $this->condition->getDependentFields(); + + $this->assertIsArray($result); + $this->assertEmpty($result, 'Single room type condition has no participant-level field dependencies'); + } + + public function testGetDescriptionReturnsString(): void + { + $result = $this->condition->getDescription(); + + $this->assertIsString($result); + $this->assertStringContainsString('readonly', $result); + $this->assertStringContainsString('one room type', $result); + } + + /** + * Creates a BookingDto with specified room selections. + * + * @param array $rooms + */ + private function createBookingDto(array $rooms): BookingDto + { + $travel = new Travel(); + $bookingDto = new BookingDto($travel, 1); + + foreach ($rooms as $roomData) { + $roomSelection = new RoomSelectionDto(); + $roomSelection->roomId = $roomData['roomId']; + $roomSelection->quantity = $roomData['quantity']; + $bookingDto->roomSelections[] = $roomSelection; + } + + return $bookingDto; + } +}