From ff6adecbdb23b340747467c9b74a41a48e71adcf Mon Sep 17 00:00:00 2001 From: Dharmendra Verma <64859911+dharmverma@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:58:57 +0100 Subject: [PATCH] Exclusions - Adding an exclusion - authority website (#675) * Exclusions - Adding an exclusion - authority website * Added unit test * PR comment fix * PR comment update * PR comment update * pr comments resolved --- .../Forms/FormElementUrlInputModelTests.cs | 133 ++ .../Models/DynamicForms.cs | 3 +- .../Pages/Forms/FormElementFileUploadModel.cs | 5 - .../Pages/Forms/FormElementTextInputModel.cs | 5 - .../Pages/Forms/FormElementUrlInputModel.cs | 67 + .../Forms/FormsAnswerSetSummary.cshtml.cs | 5 +- .../Pages/Forms/FormsQuestionPage.cshtml | 65 +- .../Pages/Forms/FormsQuestionPage.cshtml.cs | 33 +- .../Pages/Forms/_FormElementDateInput.cshtml | 38 +- .../Pages/Forms/_FormElementFileUpload.cshtml | 50 +- .../Pages/Forms/_FormElementTextInput.cshtml | 78 +- .../Pages/Forms/_FormElementUrlInput.cshtml | 108 + .../AutoMapper/DataSharingProfile.cs | 2 + .../Model/FormQuestionType.cs | 3 +- .../CO.CDP.Forms.WebApi/Model/FormQuestion.cs | 3 +- .../Forms/FormQuestion.cs | 3 +- ...34038_ExclusionWebsiteQuestion.Designer.cs | 1779 +++++++++++++++++ ...20241001134038_ExclusionWebsiteQuestion.cs | 63 + 18 files changed, 2312 insertions(+), 131 deletions(-) create mode 100644 Frontend/CO.CDP.OrganisationApp.Tests/Pages/Forms/FormElementUrlInputModelTests.cs create mode 100644 Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementUrlInputModel.cs create mode 100644 Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementUrlInput.cshtml create mode 100644 Services/CO.CDP.OrganisationInformation.Persistence/Migrations/20241001134038_ExclusionWebsiteQuestion.Designer.cs create mode 100644 Services/CO.CDP.OrganisationInformation.Persistence/Migrations/20241001134038_ExclusionWebsiteQuestion.cs diff --git a/Frontend/CO.CDP.OrganisationApp.Tests/Pages/Forms/FormElementUrlInputModelTests.cs b/Frontend/CO.CDP.OrganisationApp.Tests/Pages/Forms/FormElementUrlInputModelTests.cs new file mode 100644 index 000000000..69954dc5e --- /dev/null +++ b/Frontend/CO.CDP.OrganisationApp.Tests/Pages/Forms/FormElementUrlInputModelTests.cs @@ -0,0 +1,133 @@ +using CO.CDP.OrganisationApp.Models; +using CO.CDP.OrganisationApp.Pages.Forms; +using FluentAssertions; +using System.ComponentModel.DataAnnotations; + +namespace CO.CDP.OrganisationApp.Tests.Pages.Forms; + +public class FormElementUrlInputModelTests +{ + [Fact] + public void GetAnswer_ShouldReturnNull_WhenIsNotRequiredAndHasNoValue() + { + var model = new FormElementUrlInputModel + { + IsRequired = false, + HasValue = false + }; + + var result = model.GetAnswer(); + + result.Should().BeNull(); + } + + [Fact] + public void GetAnswer_ShouldReturnAnswer_WhenTextInputIsProvided() + { + var model = new FormElementUrlInputModel + { + IsRequired = true, + TextInput = "https://example.com", + HasValue = true + }; + + var result = model.GetAnswer(); + + result.Should().NotBeNull(); + result?.TextValue.Should().Be("https://example.com"); + } + + [Fact] + public void SetAnswer_ShouldSetTextInputAndHasValue_WhenAnswerIsProvided() + { + var model = new FormElementUrlInputModel(); + var answer = new FormAnswer { TextValue = "https://example.com" }; + + model.SetAnswer(answer); + + model.TextInput.Should().Be("https://example.com"); + model.HasValue.Should().BeTrue(); + } + + [Fact] + public void SetAnswer_ShouldSetHasValueToFalse_WhenAnswerIsNullAndRedirectFromCheckYourAnswerPage() + { + var model = new FormElementUrlInputModel(); + model.Initialize(new FormQuestion { IsRequired = false }, true); + + model.SetAnswer(null); + + model.HasValue.Should().BeFalse(); + } + + [Fact] + public void Validate_ShouldReturnError_WhenHasValueIsNullAndIsNotRequired() + { + var model = new FormElementUrlInputModel + { + IsRequired = false, + HasValue = null + }; + + var validationResults = new List(); + var context = new ValidationContext(model); + Validator.TryValidateObject(model, context, validationResults, true); + + validationResults.Should().ContainSingle(); + validationResults[0].ErrorMessage.Should().Be("Select an option"); + } + + [Fact] + public void Validate_ShouldReturnError_WhenTextInputIsMissingAndIsRequired() + { + var model = new FormElementUrlInputModel + { + IsRequired = true, + HasValue = true, + TextInput = null + }; + + var validationResults = new List(); + var context = new ValidationContext(model); + Validator.TryValidateObject(model, context, validationResults, true); + + validationResults.Should().ContainSingle(); + validationResults[0].ErrorMessage.Should().Be("Enter a website address"); + } + + [Fact] + public void Validate_ShouldReturnError_WhenTextInputIsInvalidUrl() + { + var model = new FormElementUrlInputModel + { + IsRequired = true, + HasValue = true, + TextInput = "invalid-url" + }; + + var validationResults = new List(); + var context = new ValidationContext(model); + Validator.TryValidateObject(model, context, validationResults, true); + + validationResults.Should().ContainSingle(); + validationResults[0].ErrorMessage.Should().Be("Enter a valid website address in the correct format"); + } + + [Fact] + public void Validate_ShouldPass_WhenValidUrlIsProvidedAndIsRequired() + { + var model = new FormElementUrlInputModel + { + IsRequired = true, + HasValue = true, + TextInput = "https://example.com" + }; + + var validationResults = new List(); + var context = new ValidationContext(model); + var isValid = Validator.TryValidateObject(model, context, validationResults, true); + + isValid.Should().BeTrue(); + validationResults.Should().BeEmpty(); + } +} \ No newline at end of file diff --git a/Frontend/CO.CDP.OrganisationApp/Models/DynamicForms.cs b/Frontend/CO.CDP.OrganisationApp/Models/DynamicForms.cs index 0785731fe..27a6ed227 100644 --- a/Frontend/CO.CDP.OrganisationApp/Models/DynamicForms.cs +++ b/Frontend/CO.CDP.OrganisationApp/Models/DynamicForms.cs @@ -78,5 +78,6 @@ public enum FormQuestionType Date, CheckBox, Address, - MultiLine + MultiLine, + Url } \ No newline at end of file diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementFileUploadModel.cs b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementFileUploadModel.cs index d213f3ea5..948a373f2 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementFileUploadModel.cs +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementFileUploadModel.cs @@ -59,11 +59,6 @@ public override void SetAnswer(FormAnswer? answer) public IEnumerable Validate(ValidationContext validationContext) { - if (CurrentFormQuestionType != FormQuestionType.FileUpload) - { - yield break; - } - var validateField = IsRequired; if (IsRequired == false) diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementTextInputModel.cs b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementTextInputModel.cs index 56ccf9b42..6bbd08e76 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementTextInputModel.cs +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementTextInputModel.cs @@ -39,11 +39,6 @@ public override void SetAnswer(FormAnswer? answer) public IEnumerable Validate(ValidationContext validationContext) { - if (CurrentFormQuestionType != FormQuestionType.Text) - { - yield break; - } - var validateTextField = IsRequired; if (IsRequired == false) diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementUrlInputModel.cs b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementUrlInputModel.cs new file mode 100644 index 000000000..7c8e8d580 --- /dev/null +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormElementUrlInputModel.cs @@ -0,0 +1,67 @@ +using CO.CDP.OrganisationApp.Models; +using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; + +namespace CO.CDP.OrganisationApp.Pages.Forms; + +public class FormElementUrlInputModel : FormElementModel, IValidatableObject +{ + [BindProperty] + public string? TextInput { get; set; } + + [BindProperty] + public bool? HasValue { get; set; } + + + public override FormAnswer? GetAnswer() + { + if (IsRequired == false && HasValue == false) + { + return null; + } + + return string.IsNullOrWhiteSpace(TextInput) ? null : new FormAnswer { TextValue = TextInput }; + } + + public override void SetAnswer(FormAnswer? answer) + { + if (answer?.TextValue != null) + { + TextInput = answer.TextValue; + HasValue = true; + } + else if (RedirectFromCheckYourAnswerPage && IsRequired == false) + { + HasValue = false; + } + } + + public IEnumerable Validate(ValidationContext validationContext) + { + var validateTextField = IsRequired; + + if (IsRequired == false) + { + if (HasValue == null) + { + yield return new ValidationResult("Select an option", [nameof(HasValue)]); + } + else if (HasValue == true) + { + validateTextField = true; + } + } + + if (validateTextField) + { + if (string.IsNullOrWhiteSpace(TextInput)) + { + yield return new ValidationResult("Enter a website address", [nameof(TextInput)]); + } + else if (Uri.TryCreate(TextInput, UriKind.Absolute, out var _) == false) + { + yield return new ValidationResult("Enter a valid website address in the correct format", [nameof(TextInput)]); + } + } + } +} \ No newline at end of file diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsAnswerSetSummary.cshtml.cs b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsAnswerSetSummary.cshtml.cs index 378992c78..e94168885 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsAnswerSetSummary.cshtml.cs +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsAnswerSetSummary.cshtml.cs @@ -181,12 +181,13 @@ private async Task InitAndVerifyPage() { FormQuestionType.Text => answer.TextValue ?? "", FormQuestionType.FileUpload => answer.TextValue ?? "", - FormQuestionType.YesOrNo => answer.BoolValue.HasValue ? (answer.BoolValue == true ? "yes" : "no") : "", + FormQuestionType.YesOrNo => answer.BoolValue.HasValue ? (answer.BoolValue == true ? "Yes" : "No") : "", + FormQuestionType.SingleChoice => answer.OptionValue ?? "", FormQuestionType.Date => answer.DateValue.HasValue ? answer.DateValue.Value.ToString("dd/MM/yyyy") : "", FormQuestionType.CheckBox => answer.BoolValue.HasValue ? question.Options.Choices?.FirstOrDefault()?.Title ?? "" : "", FormQuestionType.Address => answer.AddressValue != null ? $"{answer.AddressValue.StreetAddress}, {answer.AddressValue.Locality}, {answer.AddressValue.PostalCode}, {answer.AddressValue.CountryName}" : "", - FormQuestionType.SingleChoice => answer.OptionValue ?? "", FormQuestionType.MultiLine => answer.TextValue ?? "", + FormQuestionType.Url => answer.TextValue ?? "", _ => "" }; diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml index db3567d2d..20841bdc5 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml @@ -3,6 +3,7 @@ @model FormsQuestionPageModel @{ + var isCheckYourAnswersPage = Model.CurrentFormQuestionType == Models.FormQuestionType.CheckYourAnswers; var backUrl = $"/organisation/{Model.OrganisationId}/supplier-information"; var fromCheckAnswerPage = Model.RedirectFromCheckYourAnswerPage == true; @@ -28,45 +29,41 @@
-
+
-
- @if (Model.PartialViewName != null && Model.PartialViewModel != null) - { - @await Html.PartialAsync(Model.PartialViewName, Model.PartialViewModel) - } - - @if (Model.CurrentFormQuestionType == Models.FormQuestionType.CheckYourAnswers) - { - var answers = await Model.GetAnswers(); + @if (Model.PartialViewName != null && Model.PartialViewModel != null) + { + @await Html.PartialAsync(Model.PartialViewName, Model.PartialViewModel) + } -

Check your answers

-
- @foreach (var answer in answers) - { -
-
- @answer.Title -
-
- @Html.Raw(answer.Answer) -
-
- - Change - @($"the {answer.Title?.ToLower()}") - - -
-
- } -
- } -
- @if (Model.CurrentFormQuestionType == Models.FormQuestionType.CheckYourAnswers) + @if (isCheckYourAnswersPage) { + var answers = await Model.GetAnswers(); + +

Check your answers

+
+ @foreach (var answer in answers) + { +
+
+ @answer.Title +
+
+ @Html.Raw(answer.Answer) +
+
+ + Change + @($"the {answer.Title?.ToLower()}") + + +
+
+ } +
+ if (Model.FormSectionType == Models.FormSectionType.Declaration) { diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml.cs b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml.cs index 49112d6c2..eadc6b360 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml.cs +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/FormsQuestionPage.cshtml.cs @@ -29,24 +29,26 @@ public class FormsQuestionPageModel( [BindProperty(SupportsGet = true, Name = "frm-chk-answer")] public bool? RedirectFromCheckYourAnswerPage { get; set; } - [BindProperty] - public FormElementDateInputModel? DateInputModel { get; set; } - [BindProperty] - public FormElementFileUploadModel? FileUploadModel { get; set; } [BindProperty] public FormElementNoInputModel? NoInputModel { get; set; } [BindProperty] public FormElementTextInputModel? TextInputModel { get; set; } [BindProperty] - public FormElementMultiLineInputModel? MultiLineInputModel { get; set; } + public FormElementFileUploadModel? FileUploadModel { get; set; } [BindProperty] public FormElementYesNoInputModel? YesNoInputModel { get; set; } [BindProperty] - public FormElementAddressModel? AddressModel { get; set; } - [BindProperty] public FormElementSingleChoiceModel? SingleChoiceModel { get; set; } [BindProperty] + public FormElementDateInputModel? DateInputModel { get; set; } + [BindProperty] public FormElementCheckBoxInputModel? CheckBoxModel { get; set; } + [BindProperty] + public FormElementAddressModel? AddressModel { get; set; } + [BindProperty] + public FormElementMultiLineInputModel? MultiLineInputModel { get; set; } + [BindProperty] + public FormElementUrlInputModel? UrlInputModel { get; set; } [BindProperty(SupportsGet = true)] public string? UkOrNonUk { get; set; } @@ -154,13 +156,14 @@ public async Task> GetAnswers() string answerString = question.Type switch { FormQuestionType.Text => answer.Answer?.TextValue ?? "", - FormQuestionType.MultiLine => answer.Answer?.TextValue ?? "", - FormQuestionType.CheckBox => answer.Answer?.BoolValue == true ? question.Options.Choices?.FirstOrDefault() ?? "" : "", FormQuestionType.FileUpload => answer.Answer?.TextValue ?? "", - FormQuestionType.YesOrNo => answer.Answer?.BoolValue.HasValue == true ? (answer.Answer.BoolValue == true ? "yes" : "no") : "", + FormQuestionType.YesOrNo => answer.Answer?.BoolValue.HasValue == true ? (answer.Answer.BoolValue == true ? "Yes" : "No") : "", + FormQuestionType.SingleChoice => answer.Answer?.OptionValue ?? "", FormQuestionType.Date => answer.Answer?.DateValue.HasValue == true ? answer.Answer.DateValue.Value.ToString("dd/MM/yyyy") : "", + FormQuestionType.CheckBox => answer.Answer?.BoolValue == true ? question.Options.Choices?.FirstOrDefault() ?? "" : "", FormQuestionType.Address => answer.Answer?.AddressValue != null ? answer.Answer.AddressValue.ToHtmlString() : "", - FormQuestionType.SingleChoice => answer.Answer?.OptionValue ?? "", + FormQuestionType.MultiLine => answer.Answer?.TextValue ?? "", + FormQuestionType.Url => answer.Answer?.TextValue ?? "", _ => "" }; @@ -223,14 +226,15 @@ public bool PreviousQuestionHasNonUKAddressAnswer() Dictionary formQuestionPartials = new(){ { FormQuestionType.NoInput, "_FormElementNoInput" }, - { FormQuestionType.YesOrNo, "_FormElementYesNoInput" }, + { FormQuestionType.Text, "_FormElementTextInput" }, { FormQuestionType.FileUpload, "_FormElementFileUpload" }, + { FormQuestionType.YesOrNo, "_FormElementYesNoInput" }, + { FormQuestionType.SingleChoice, "_FormElementSingleChoice" }, { FormQuestionType.Date, "_FormElementDateInput" }, - { FormQuestionType.Text, "_FormElementTextInput" }, { FormQuestionType.CheckBox, "_FormElementCheckBoxInput" }, { FormQuestionType.Address, "_FormElementAddress" }, { FormQuestionType.MultiLine, "_FormElementMultiLineInput" }, - { FormQuestionType.SingleChoice, "_FormElementSingleChoice" }, + { FormQuestionType.Url, "_FormElementUrlInput" }, }; if (formQuestionPartials.TryGetValue(question.Type, out var partialView)) @@ -257,6 +261,7 @@ public bool PreviousQuestionHasNonUKAddressAnswer() FormQuestionType.Address => AddressModel ?? new FormElementAddressModel(), FormQuestionType.SingleChoice => SingleChoiceModel ?? new FormElementSingleChoiceModel(), FormQuestionType.MultiLine => MultiLineInputModel ?? new FormElementMultiLineInputModel(), + FormQuestionType.Url => UrlInputModel ?? new FormElementUrlInputModel(), _ => throw new NotImplementedException($"Forms question: {question.Type} is not supported"), }; diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementDateInput.cshtml b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementDateInput.cshtml index 32b8a58b2..8c8047afd 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementDateInput.cshtml +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementDateInput.cshtml @@ -107,31 +107,29 @@ } else { -
-
+
-
- - -
+
+ + +
-
-
- @{ - RenderField(); - } -
+
+
+ @{ + RenderField(); + }
+
-
- - -
-
+
+ + +
-
+
}
\ No newline at end of file diff --git a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementFileUpload.cshtml b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementFileUpload.cshtml index 0cbb0f13f..a8fd5b78e 100644 --- a/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementFileUpload.cshtml +++ b/Frontend/CO.CDP.OrganisationApp/Pages/Forms/_FormElementFileUpload.cshtml @@ -4,6 +4,22 @@ var uploadFileHasError = ((TagBuilder)Html.ValidationMessageFor(m => m.UploadedFile)).HasInnerHtml; var selectOptionHasError = ((TagBuilder)Html.ValidationMessageFor(m => m.HasValue)).HasInnerHtml; + void RenderHeading() + { + if (!string.IsNullOrWhiteSpace(Model.Heading)) + { +

@Model.Heading

+ } + } + + void RenderDescription() + { + if (!string.IsNullOrWhiteSpace(Model.Description)) + { + @Html.Raw(Model.Description) + } + } + void RenderField() {