From e2c6af69e69236ca4c9a8ab57d518c45541d9031 Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Thu, 28 Nov 2024 10:22:33 +0100 Subject: [PATCH] [ESC-389]form-requires-to-answer --- api/app/Http/Requests/AnswerFormRequest.php | 12 +- api/tests/Feature/Forms/FormLogicTest.php | 235 ++++++++++++++++++++ 2 files changed, 245 insertions(+), 2 deletions(-) diff --git a/api/app/Http/Requests/AnswerFormRequest.php b/api/app/Http/Requests/AnswerFormRequest.php index 8bfc10ee9..61d67f4c8 100644 --- a/api/app/Http/Requests/AnswerFormRequest.php +++ b/api/app/Http/Requests/AnswerFormRequest.php @@ -66,12 +66,20 @@ public function rules() foreach ($selectionFields as $field) { if (isset($data[$field['id']]) && is_array($data[$field['id']])) { $data[$field['id']] = array_map(function ($val) use ($field) { + // Find the option by exact ID match first $tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($val) { - return $op['id'] ?? $op['value'] === $val; + return $op['id'] === $val || $op['name'] === $val; }); - return isset($tmpop['name']) ? $tmpop['name'] : ''; + // Return the original value if no match found + return isset($tmpop['name']) ? $tmpop['name'] : $val; }, $data[$field['id']]); + } elseif (isset($data[$field['id']])) { + // Handle single select values + $tmpop = collect($field[$field['type']]['options'])->first(function ($op) use ($field, $data) { + return $op['id'] === $data[$field['id']] || $op['name'] === $data[$field['id']]; + }); + $data[$field['id']] = isset($tmpop['name']) ? $tmpop['name'] : $data[$field['id']]; } } if (FormLogicPropertyResolver::isRequired($property, $data)) { diff --git a/api/tests/Feature/Forms/FormLogicTest.php b/api/tests/Feature/Forms/FormLogicTest.php index 456824547..81d038c4d 100644 --- a/api/tests/Feature/Forms/FormLogicTest.php +++ b/api/tests/Feature/Forms/FormLogicTest.php @@ -113,3 +113,238 @@ ], ]); }); + +it('preserves multi-select values during validation with logic conditions', function () { + $user = $this->actingAsUser(); + $workspace = $this->createUserWorkspace($user); + + // Create a form with a multi-select field and a text field that has logic based on the multi-select + $form = $this->createForm($user, $workspace, [ + 'properties' => [ + [ + 'id' => 'multi_select_field', + 'name' => 'Multi Select Field', + 'type' => 'multi_select', + 'hidden' => false, + 'required' => true, + 'multi_select' => [ + 'options' => [ + ['id' => 'option1', 'name' => 'Option 1'], + ['id' => 'option2', 'name' => 'Option 2'], + ['id' => 'option3', 'name' => 'Option 3'] + ] + ] + ], + [ + 'id' => 'text_field', + 'name' => 'Text Field', + 'type' => 'text', + 'hidden' => false, + 'required' => false, + 'logic' => [ + 'conditions' => [ + 'operatorIdentifier' => 'and', + 'children' => [ + [ + 'identifier' => 'multi_select', + 'value' => [ + 'operator' => 'contains', + 'property_meta' => [ + 'id' => 'multi_select_field', + 'type' => 'multi_select' + ], + 'value' => 'Option 1' + ] + ] + ] + ], + 'actions' => ['require-answer'] + ] + ] + ] + ]); + + // Submit form data with multi-select values + $formData = [ + 'multi_select_field' => ['Option 1', 'Option 2'] + ]; + + ray($formData)->blue('Original form data'); + + $response = $this->postJson(route('forms.answer', $form->slug), $formData); + + // The validation should fail because text_field is required when Option 1 is selected + $response->assertStatus(422) + ->assertJson([ + 'message' => 'The Text Field field is required.', + 'errors' => [ + 'text_field' => ['The Text Field field is required.'] + ] + ]); + + // Check that the multi-select values were preserved in the validation data + ray($response->json())->purple('Response data'); + expect($response->json('errors.multi_select_field'))->toBeNull(); +}); + +it('correctly handles multi-select values with complex form logic', function () { + $user = $this->actingAsUser(); + $workspace = $this->createUserWorkspace($user); + + // Create form with the specific fields from your example + $form = $this->createForm($user, $workspace, [ + 'properties' => [ + [ + 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b', + 'name' => 'Which event would you like to join?', + 'type' => 'multi_select', + 'hidden' => false, + 'required' => true, + 'multi_select' => [ + 'options' => [ + ['id' => 'Ashkelon Run (March 21)', 'name' => 'Ashkelon Run (March 21)'], + ['id' => 'Jerusalem Marathon (April 4)', 'name' => 'Jerusalem Marathon (April 4)'], + ['id' => 'Neither', 'name' => 'Neither'] + ] + ] + ], + [ + 'id' => '0ca51469-6bda-40f4-831c-084f066643d7', + 'name' => 'Jerusalem Marathon - Run Options', + 'type' => 'select', + 'hidden' => true, + 'required' => false, + 'select' => [ + 'options' => [ + ['id' => '10km (Most popular)', 'name' => '10km (Most popular)'] + ] + ], + 'logic' => [ + 'conditions' => [ + 'operatorIdentifier' => 'and', + 'children' => [ + [ + 'identifier' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b', + 'value' => [ + 'operator' => 'contains', + 'property_meta' => [ + 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b', + 'type' => 'multi_select' + ], + 'value' => 'Jerusalem Marathon (April 4)' + ] + ] + ] + ], + 'actions' => ['require-answer', 'show-block'] + ] + ] + ] + ]); + + // Submit form data matching your payload + $formData = [ + '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Jerusalem Marathon (April 4)'], + '0ca51469-6bda-40f4-831c-084f066643d7' => '10km (Most popular)' + ]; + + ray($formData)->blue('Original form data'); + + $response = $this->postJson(route('forms.answer', $form->slug), $formData); + + ray($response->json())->purple('Response data'); + + // Should be successful since all required fields are filled + $response->assertSuccessful() + ->assertJson([ + 'type' => 'success', + 'message' => 'Form submission saved.' + ]); + + // Now let's verify the saved submission data + $submission = $form->submissions()->first(); + expect($submission->data['93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b'])->toBe(['Jerusalem Marathon (April 4)']); +}); + +it('preserves multi-select values when building validation rules', function () { + $user = $this->actingAsUser(); + $workspace = $this->createUserWorkspace($user); + + // Create form with the exact fields from your real form + $form = $this->createForm($user, $workspace, [ + 'properties' => [ + [ + 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b', + 'name' => 'Which event would you like to join?', + 'type' => 'multi_select', + 'required' => true, + 'multi_select' => [ + 'options' => [ + ['id' => 'Jerusalem Marathon (April 4)', 'name' => 'Jerusalem Marathon (April 4)'], + ['id' => 'Ashkelon Run (March 21)', 'name' => 'Ashkelon Run (March 21)'] + ] + ] + ], + [ + 'id' => '72565901-c345-427a-b988-0ce3de9ad9f4', + 'name' => 'Additional Days', + 'type' => 'multi_select', + 'required' => false, + 'multi_select' => [ + 'options' => [ + ['id' => 'Thursday', 'name' => 'Thursday'], + ['id' => 'Sunday', 'name' => 'Sunday'] + ] + ], + 'logic' => [ + 'conditions' => [ + 'operatorIdentifier' => 'and', + 'children' => [ + [ + 'identifier' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b', + 'value' => [ + 'operator' => 'contains', + 'property_meta' => [ + 'id' => '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b', + 'type' => 'multi_select' + ], + 'value' => 'Ashkelon Run (March 21)' + ] + ] + ] + ], + 'actions' => ['require-answer'] + ] + ] + ] + ]); + + // Submit form data with Jerusalem Marathon + $formData = [ + '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Jerusalem Marathon (April 4)'] + ]; + + ray($formData)->blue('Original form data'); + + $response = $this->postJson(route('forms.answer', $form->slug), $formData); + + ray($response->json())->purple('Response data'); + + // Should be successful since Jerusalem Marathon doesn't require Additional Days + $response->assertSuccessful(); + + // Now try with Ashkelon Run which requires Additional Days + $formData = [ + '93c8ebe9-b1ba-42ce-841c-bf3b9be1ca4b' => ['Ashkelon Run (March 21)'] + ]; + + $response = $this->postJson(route('forms.answer', $form->slug), $formData); + + // Should fail because Additional Days is required when Ashkelon Run is selected + $response->assertStatus(422) + ->assertJson([ + 'errors' => [ + '72565901-c345-427a-b988-0ce3de9ad9f4' => ['The Additional Days field is required.'] + ] + ]); +});