Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public override void SetErrorMessage(
TemplateString? html,
string tagName)
{
if (Fieldset is not null)
if (Fieldset is not null && !_fieldsetIsOpen)
{
throw new InvalidOperationException($"<{tagName}> must be inside <{FieldsetTagName}>.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private protected override void SetErrorMessage(TagHelperContent? content, TagHe
var attributes = new AttributeCollection(output.Attributes);

checkboxesContext.SetErrorMessage(
VisuallyHiddenText,
VisuallyHiddenText is not null ? new TemplateString(VisuallyHiddenText) : null,
attributes,
content?.ToTemplateString(),
output.TagName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ internal CheckboxesTagHelper(IComponentGenerator componentGenerator, IModelHelpe
/// <inheritdoc/>
public override void Init(TagHelperContext context)
{
context.SetContextItem(new CheckboxesContext(Name, For));
var checkboxesContext = new CheckboxesContext(Name, For);
context.SetContextItem(checkboxesContext);
context.SetContextItem<FormGroupContext3>(checkboxesContext);
}

/// <inheritdoc/>
Expand Down
2 changes: 1 addition & 1 deletion src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public override void SetErrorMessage(
TemplateString? html,
string tagName)
{
if (Fieldset is not null)
if (Fieldset is not null && !_fieldsetIsOpen)
{
throw new InvalidOperationException($"<{tagName}> must be inside <{FieldsetTagName}>.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.CheckboxesTestsModel

<govuk-checkboxes for="Options">
<govuk-checkboxes-fieldset>
<govuk-checkboxes-fieldset-legend />
<govuk-checkboxes-item value="option1">Option 1</govuk-checkboxes-item>
<govuk-checkboxes-item value="option2">Option 2</govuk-checkboxes-item>
</govuk-checkboxes-fieldset>
</govuk-checkboxes>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.CheckboxesTestsModel

<govuk-checkboxes for="Options" id-prefix="OverriddenIdPrefix" name="OverriddenName">
<govuk-checkboxes-fieldset>
<govuk-checkboxes-fieldset-legend>
Overridden legend
</govuk-checkboxes-fieldset-legend>
<govuk-checkboxes-hint>Overridden hint</govuk-checkboxes-hint>
<govuk-checkboxes-error-message>Overridden error message</govuk-checkboxes-error-message>
<govuk-checkboxes-item value="option1" checked="true">Option 1</govuk-checkboxes-item>
<govuk-checkboxes-item value="option2" checked="false">Option 2</govuk-checkboxes-item>
</govuk-checkboxes-fieldset>
</govuk-checkboxes>
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,52 @@ public async Task SelectOverridden_RendersCorrectly()
var errorMessage = page.Locator(".govuk-error-message").First;
Assert.Equal("Error: Overridden error message", await errorMessage.TextContentAsync());
}

[Fact]
public async Task Checkboxes_RendersCorrectly()
{
var page = await fixture.Browser!.NewPageAsync();
await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/Checkboxes");

var checkbox1 = page.Locator("input[value='option1']").First;
Assert.Equal("Options", await checkbox1.GetAttributeAsync("name"));
Assert.Equal("Options", await checkbox1.GetAttributeAsync("id"));
Assert.False(await checkbox1.IsCheckedAsync());

var checkbox2 = page.Locator("input[value='option2']").First;
Assert.Equal("Options", await checkbox2.GetAttributeAsync("name"));
Assert.Equal("Options-2", await checkbox2.GetAttributeAsync("id"));
Assert.True(await checkbox2.IsCheckedAsync());

var hint = page.Locator(".govuk-hint").First;
Assert.Equal("ModelMetadata description", await hint.InnerTextAsync());

var errorMessage = page.Locator(".govuk-error-message").First;
Assert.Equal("Error: Model error message", await errorMessage.TextContentAsync());
}

[Fact]
public async Task CheckboxesOverridden_RendersCorrectly()
{
var page = await fixture.Browser!.NewPageAsync();
await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/CheckboxesOverridden");

var checkbox1 = page.Locator("input[value='option1']").First;
Assert.Equal("OverriddenName", await checkbox1.GetAttributeAsync("name"));
Assert.Equal("OverriddenIdPrefix", await checkbox1.GetAttributeAsync("id"));
Assert.True(await checkbox1.IsCheckedAsync());

var checkbox2 = page.Locator("input[value='option2']").First;
Assert.Equal("OverriddenName", await checkbox2.GetAttributeAsync("name"));
Assert.Equal("OverriddenIdPrefix-2", await checkbox2.GetAttributeAsync("id"));
Assert.False(await checkbox2.IsCheckedAsync());

var hint = page.Locator(".govuk-hint").First;
Assert.Equal("Overridden hint", await hint.InnerTextAsync());

var errorMessage = page.Locator(".govuk-error-message").First;
Assert.Equal("Error: Overridden error message", await errorMessage.TextContentAsync());
}
}

[Route("/[controller]/[action]")]
Expand All @@ -355,107 +401,138 @@ public class ModelBindingTestsController : Controller
[HttpGet]
public IActionResult TextInput()
{
ModelState.SetModelValue(nameof(TextInputTestsModel.Text), "Model value", "Model value");
ModelState.AddModelError(nameof(TextInputTestsModel.Text), "Model error message");
return View(new TextInputTestsModel { Text = "Model value" });
}

[HttpGet]
public IActionResult TextInputOverridden()
{
ModelState.SetModelValue(nameof(TextInputTestsModel.Text), "Model value", "Model value");
ModelState.AddModelError(nameof(TextInputTestsModel.Text), "Model error message");
return View(new TextInputTestsModel { Text = "Model value" });
}

[HttpGet]
public IActionResult PasswordInput()
{
ModelState.SetModelValue(nameof(PasswordInputTestsModel.Password), "Model value", "Model value");
ModelState.AddModelError(nameof(PasswordInputTestsModel.Password), "Model error message");
return View(new PasswordInputTestsModel { Password = "Model value" });
}

[HttpGet]
public IActionResult PasswordInputOverridden()
{
ModelState.SetModelValue(nameof(PasswordInputTestsModel.Password), "Model value", "Model value");
ModelState.AddModelError(nameof(PasswordInputTestsModel.Password), "Model error message");
return View(new PasswordInputTestsModel { Password = "Model value" });
}

[HttpGet]
public IActionResult DateInput()
{
ModelState.SetModelValue(nameof(DateInputTestsModel.Date), new DateOnly(2024, 3, 15), "15,3,2024");
ModelState.AddModelError(nameof(DateInputTestsModel.Date), "Model error message");
return View(new DateInputTestsModel { Date = new DateOnly(2024, 3, 15) });
}

[HttpGet]
public IActionResult DateInputOverridden()
{
ModelState.SetModelValue(nameof(DateInputTestsModel.Date), new DateOnly(2024, 3, 15), "15,3,2024");
ModelState.AddModelError(nameof(DateInputTestsModel.Date), "Model error message");
return View(new DateInputTestsModel { Date = new DateOnly(2024, 3, 15) });
}

[HttpGet]
public IActionResult DateInputOverriddenItems()
{
ModelState.SetModelValue(nameof(DateInputTestsModel.Date), new DateOnly(2024, 3, 15), "15,3,2024");
ModelState.AddModelError(nameof(DateInputTestsModel.Date), "Model error message");
return View(new DateInputTestsModel { Date = new DateOnly(2024, 3, 15) });
}

[HttpGet]
public IActionResult Textarea()
{
ModelState.SetModelValue(nameof(TextareaTestsModel.Text), "Model value", "Model value");
ModelState.AddModelError(nameof(TextareaTestsModel.Text), "Model error message");
return View(new TextareaTestsModel { Text = "Model value" });
}

[HttpGet]
public IActionResult TextareaOverridden()
{
ModelState.SetModelValue(nameof(TextareaTestsModel.Text), "Model value", "Model value");
ModelState.AddModelError(nameof(TextareaTestsModel.Text), "Model error message");
return View(new TextareaTestsModel { Text = "Model value" });
}

[HttpGet]
public IActionResult CharacterCount()
{
ModelState.SetModelValue(nameof(CharacterCountTestsModel.Text), "Model value", "Model value");
ModelState.AddModelError(nameof(CharacterCountTestsModel.Text), "Model error message");
return View(new CharacterCountTestsModel { Text = "Model value" });
}

[HttpGet]
public IActionResult CharacterCountOverridden()
{
ModelState.SetModelValue(nameof(CharacterCountTestsModel.Text), "Model value", "Model value");
ModelState.AddModelError(nameof(CharacterCountTestsModel.Text), "Model error message");
return View(new CharacterCountTestsModel { Text = "Model value" });
}

[HttpGet]
public IActionResult FileUpload()
{
ModelState.SetModelValue(nameof(FileUploadTestsModel.File), "", "");
ModelState.AddModelError(nameof(FileUploadTestsModel.File), "Model error message");
return View(new FileUploadTestsModel());
}

[HttpGet]
public IActionResult FileUploadOverridden()
{
ModelState.SetModelValue(nameof(FileUploadTestsModel.File), "", "");
ModelState.AddModelError(nameof(FileUploadTestsModel.File), "Model error message");
return View(new FileUploadTestsModel());
}

[HttpGet]
public IActionResult Select()
{
ModelState.SetModelValue(nameof(SelectTestsModel.Option), "option2", "option2");
ModelState.AddModelError(nameof(SelectTestsModel.Option), "Model error message");
return View(new SelectTestsModel { Option = "option2" });
}

[HttpGet]
public IActionResult SelectOverridden()
{
ModelState.SetModelValue(nameof(SelectTestsModel.Option), "option2", "option2");
ModelState.AddModelError(nameof(SelectTestsModel.Option), "Model error message");
return View(new SelectTestsModel { Option = "option2" });
}

[HttpGet]
public IActionResult Checkboxes()
{
ModelState.SetModelValue(nameof(CheckboxesTestsModel.Options), new[] { "option2" }, null);
ModelState.AddModelError(nameof(CheckboxesTestsModel.Options), "Model error message");
return View(new CheckboxesTestsModel { Options = ["option2"] });
}

[HttpGet]
public IActionResult CheckboxesOverridden()
{
ModelState.SetModelValue(nameof(CheckboxesTestsModel.Options), new[] { "option2" }, null);
ModelState.AddModelError(nameof(CheckboxesTestsModel.Options), "Model error message");
return View(new CheckboxesTestsModel { Options = ["option2"] });
}
}

public record TextInputTestsModel
Expand Down Expand Up @@ -491,11 +568,17 @@ public record CharacterCountTestsModel
public record FileUploadTestsModel
{
[Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
public Microsoft.AspNetCore.Http.IFormFile? File { get; set; }
public IFormFile? File { get; set; }
}

public record SelectTestsModel
{
[Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
public string? Option { get; set; }
}

public record CheckboxesTestsModel
{
[Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
public List<string>? Options { get; set; }
}