Skip to content

Commit

Permalink
Merge pull request #668 from cabinetoffice/feature/single-choice-fiel…
Browse files Browse the repository at this point in the history
…d-type

DP-544 - Add single choice question type
  • Loading branch information
andymantell authored Sep 27, 2024
2 parents fbfa297 + 9666446 commit bd36d24
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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 FormElementSingleChoiceModelTest
{
private readonly FormElementSingleChoiceModel _model;

public FormElementSingleChoiceModelTest()
{
_model = new FormElementSingleChoiceModel();
_model.Options = new FormQuestionOptions() { Choices = ["Option 1", "Option 2", "Option 3"] } ;
}

[Theory]
[InlineData(null, null)]
[InlineData(" ", null)]
[InlineData("yes", null)]
[InlineData("no", null)]
[InlineData("Option 1", "Option 1")]
[InlineData("Option 2", "Option 2")]
[InlineData("Option 3", "Option 3")]
public void GetAnswer_GetsExpectedFormAnswer(string? input, string? expected)
{
_model.SelectedOption = input;

var answer = _model.GetAnswer();

if (expected == null)
{
answer.Should().BeNull();
}
else
{
answer.Should().NotBeNull();
answer!.OptionValue.Should().Be(expected);
}
}

[Theory]
[InlineData(null, null)]
[InlineData(" ", null)]
[InlineData("yes", null)]
[InlineData("no", null)]
[InlineData("Option 1", "Option 1")]
[InlineData("Option 2", "Option 2")]
[InlineData("Option 3", "Option 3")]
public void SetAnswer_SetsExpectedOption(string? selectedOption, string? expected)
{
var answer = new FormAnswer { OptionValue = selectedOption };

_model.SetAnswer(answer);

if (expected == null)
{
_model.SelectedOption.Should().BeNull();
}
else
{
_model.SelectedOption.Should().NotBeNull();
_model.SelectedOption.Should().Be(expected);
}
}

[Theory]
[InlineData(null, "Select an option")]
[InlineData(" ", "Select an option")]
[InlineData("yes", "Invalid option selected")]
[InlineData("no", "Invalid option selected")]
[InlineData("Option 1", null)]
[InlineData("Option 2", null)]
[InlineData("Option 3", null)]
public void Validate_ReturnsExpectedResults(string? selectedOption, string? expectedErrorMessage)
{
_model.SelectedOption = selectedOption;

var validationResults = _model.Validate(new ValidationContext(_model)).ToList();

if (expectedErrorMessage != null)
{
validationResults.Should().ContainSingle();
validationResults.First().ErrorMessage.Should().Be(expectedErrorMessage);
}
else
{
validationResults.Should().BeEmpty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using CO.CDP.OrganisationApp.Models;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace CO.CDP.OrganisationApp.Pages.Forms;

public class FormElementSingleChoiceModel : FormElementModel, IValidatableObject
{
[BindProperty]
public string? SelectedOption { get; set; }

public override FormAnswer? GetAnswer()
{
if(
SelectedOption != null
&& Options?.Choices != null
&& Options.Choices.Contains(SelectedOption)
)
{
return new FormAnswer { OptionValue = SelectedOption };
}

return null;
}

public override void SetAnswer(FormAnswer? answer)
{
if (answer?.OptionValue != null && Options?.Choices != null && Options.Choices.Contains(answer.OptionValue))
{
SelectedOption = answer.OptionValue;
}
}

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{

if (string.IsNullOrWhiteSpace(SelectedOption))
{
yield return new ValidationResult("Select an option", new[] { nameof(SelectedOption) });
yield break;
}

if(Options?.Choices == null || (SelectedOption != null && !Options.Choices.Contains(SelectedOption)))
{
yield return new ValidationResult("Invalid option selected", new[] { nameof(SelectedOption) });
yield break;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ private async Task<bool> InitAndVerifyPage()
FormQuestionType.YesOrNo => answer.BoolValue.HasValue ? (answer.BoolValue == true ? "yes" : "no") : "",
FormQuestionType.Date => answer.DateValue.HasValue ? answer.DateValue.Value.ToString("dd/MM/yyyy") : "",
FormQuestionType.Address => answer.AddressValue != null ? $"{answer.AddressValue.StreetAddress}, {answer.AddressValue.Locality}, {answer.AddressValue.PostalCode}, {answer.AddressValue.CountryName}" : "",
FormQuestionType.SingleChoice => answer.OptionValue ?? "",
_ => ""
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class FormsQuestionPageModel(
[BindProperty]
public FormElementAddressModel? AddressModel { get; set; }
[BindProperty]
public FormElementSingleChoiceModel? SingleChoiceModel { get; set; }
[BindProperty]
public FormElementCheckBoxInputModel? CheckBoxModel { get; set; }

[BindProperty(SupportsGet = true)]
Expand Down Expand Up @@ -158,6 +160,7 @@ public async Task<IEnumerable<AnswerSummary>> GetAnswers()
FormQuestionType.YesOrNo => answer.Answer?.BoolValue.HasValue == true ? (answer.Answer.BoolValue == true ? "yes" : "no") : "",
FormQuestionType.Date => answer.Answer?.DateValue.HasValue == true ? answer.Answer.DateValue.Value.ToString("dd/MM/yyyy") : "",
FormQuestionType.Address => answer.Answer?.AddressValue != null ? answer.Answer.AddressValue.ToHtmlString() : "",
FormQuestionType.SingleChoice => answer.Answer?.OptionValue ?? "",
_ => ""
};

Expand Down Expand Up @@ -227,6 +230,7 @@ public bool PreviousQuestionHasNonUKAddressAnswer()
{ FormQuestionType.CheckBox, "_FormElementCheckBoxInput" },
{ FormQuestionType.Address, "_FormElementAddress" },
{ FormQuestionType.MultiLine, "_FormElementMultiLineInput" },
{ FormQuestionType.SingleChoice, "_FormElementSingleChoice" },
};

if (formQuestionPartials.TryGetValue(question.Type, out var partialView))
Expand All @@ -252,6 +256,7 @@ public bool PreviousQuestionHasNonUKAddressAnswer()
FormQuestionType.Date => DateInputModel ?? new FormElementDateInputModel(),
FormQuestionType.CheckBox => CheckBoxModel ?? new FormElementCheckBoxInputModel(),
FormQuestionType.Address => AddressModel ?? new FormElementAddressModel(),
FormQuestionType.SingleChoice => SingleChoiceModel ?? new FormElementSingleChoiceModel(),
_ => throw new NotImplementedException($"Forms question: {question.Type} is not supported"),
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@model FormElementSingleChoiceModel

@{
var hasError = ((TagBuilder)Html.ValidationMessageFor(m => m.SelectedOption)).HasInnerHtml;
string ariaDescribedby = "";

if (!string.IsNullOrWhiteSpace(Model.Description))
{
ariaDescribedby += "SelectedOption-description";
}

if (hasError)
{
ariaDescribedby += " SelectedOption-error";
}
}

<div class="govuk-form-group">
<fieldset class="govuk-fieldset" aria-describedby="@ariaDescribedby">
@if (!string.IsNullOrWhiteSpace(Model.Heading))
{
<legend class="govuk-fieldset__legend govuk-fieldset__legend--l">
<h1 class="govuk-fieldset__heading">
@Model.Heading
@if (!string.IsNullOrWhiteSpace(Model.Caption))
{
<span class="govuk-caption-l govuk-!-margin-bottom-3">@Model.Caption</span>
}
</h1>
</legend>
}

@if (!string.IsNullOrWhiteSpace(Model.Description))
{
<div id="SelectedOption-description">
@Html.Raw(Model.Description)
</div>
}

@if (hasError)
{
<p class="govuk-error-message" id="SelectedOption-error">
<span class="govuk-visually-hidden">Error:</span>
@Html.ValidationMessageFor(m => m.SelectedOption)
</p>
}

<div class="govuk-radios" data-module="govuk-radios">
@{
var index = 0;
if (Model.Options?.Choices != null ) {
foreach(var value in Model.Options.Choices)
{
var id = index == 0 ? "SelectedOption" : $"SelectedOption_{index}";

<div class="govuk-radios__item">
<input class="govuk-radios__input" id="@id" name="SelectedOption" type="radio" value="@value" @(Model.SelectedOption == value ? "checked": "")>
<label class="govuk-label govuk-radios__label" for="@id">@value</label>
</div>

index++;
}
}
}
</div>
</fieldset>
</div>

0 comments on commit bd36d24

Please sign in to comment.