diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesContext.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesContext.cs
index ef62a6ed..2b9f5f14 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesContext.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesContext.cs
@@ -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}>.");
}
diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesErrorMessageTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesErrorMessageTagHelper.cs
index fc033290..a58751ac 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesErrorMessageTagHelper.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesErrorMessageTagHelper.cs
@@ -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);
diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesTagHelper.cs
index bb2cc24e..4b63d392 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesTagHelper.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/CheckboxesTagHelper.cs
@@ -107,7 +107,9 @@ internal CheckboxesTagHelper(IComponentGenerator componentGenerator, IModelHelpe
///
public override void Init(TagHelperContext context)
{
- context.SetContextItem(new CheckboxesContext(Name, For));
+ var checkboxesContext = new CheckboxesContext(Name, For);
+ context.SetContextItem(checkboxesContext);
+ context.SetContextItem(checkboxesContext);
}
///
diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputContext.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputContext.cs
index d51548d0..cbdb0abb 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputContext.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputContext.cs
@@ -128,7 +128,7 @@ public override void SetLabel(bool? isPageHeading, AttributeCollection attribute
public override void SetHint(AttributeCollection attributes, TemplateString? html, string tagName)
{
- if (Fieldset is not null)
+ if (Fieldset is not null && !_fieldsetIsOpen)
{
throw new InvalidOperationException($"<{tagName}> must be inside <{FieldsetTagName}>.");
}
@@ -159,7 +159,7 @@ public 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}>.");
}
diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputErrorMessageTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputErrorMessageTagHelper.cs
index 407598f9..13b17eac 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputErrorMessageTagHelper.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/DateInputErrorMessageTagHelper.cs
@@ -46,7 +46,7 @@ private protected override void SetErrorMessage(TagHelperContent? content, TagHe
dateInputContext.SetErrorMessage(
ErrorItems,
- VisuallyHiddenText,
+ VisuallyHiddenText is not null ? new TemplateString(VisuallyHiddenText) : null,
attributes,
content?.ToTemplateString(),
output.TagName);
diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupErrorMessageTagHelperBase.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupErrorMessageTagHelperBase.cs
index c2d5e8fd..a9f8a5df 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupErrorMessageTagHelperBase.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/FormGroupErrorMessageTagHelperBase.cs
@@ -52,7 +52,7 @@ private protected virtual void SetErrorMessage(TagHelperContent? content, TagHel
var formGroupContext3 = context.GetContextItem();
formGroupContext3.SetErrorMessage(
- VisuallyHiddenText,
+ VisuallyHiddenText is not null ? new TemplateString(VisuallyHiddenText) : null,
new AttributeCollection(output.Attributes),
content?.ToTemplateString(),
output.TagName);
diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosContext.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosContext.cs
index 99d6e38d..0d9371b6 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosContext.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosContext.cs
@@ -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}>.");
}
diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosErrorMessageTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosErrorMessageTagHelper.cs
index db25fbb1..b966a9e2 100644
--- a/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosErrorMessageTagHelper.cs
+++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/RadiosErrorMessageTagHelper.cs
@@ -31,7 +31,7 @@ private protected override void SetErrorMessage(TagHelperContent? content, TagHe
var attributes = new AttributeCollection(output.Attributes);
radiosContext.SetErrorMessage(
- VisuallyHiddenText,
+ VisuallyHiddenText is not null ? new TemplateString(VisuallyHiddenText) : null,
attributes,
content?.ToTemplateString(),
output.TagName);
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/GovUk.Frontend.AspNetCore.IntegrationTests.csproj b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/GovUk.Frontend.AspNetCore.IntegrationTests.csproj
index 1b08e71e..1b3b9139 100644
--- a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/GovUk.Frontend.AspNetCore.IntegrationTests.csproj
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/GovUk.Frontend.AspNetCore.IntegrationTests.csproj
@@ -12,6 +12,7 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CharacterCount.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CharacterCount.cshtml
new file mode 100644
index 00000000..cf0075f4
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CharacterCount.cshtml
@@ -0,0 +1,3 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.CharacterCountTestsModel
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CharacterCountOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CharacterCountOverridden.cshtml
new file mode 100644
index 00000000..fa06a4cf
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CharacterCountOverridden.cshtml
@@ -0,0 +1,8 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.CharacterCountTestsModel
+
+
+ Overridden label
+ Overridden hint
+ Overridden error message
+ Overridden value
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Checkboxes.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Checkboxes.cshtml
new file mode 100644
index 00000000..89715053
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Checkboxes.cshtml
@@ -0,0 +1,9 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.CheckboxesTestsModel
+
+
+
+
+ Option 1
+ Option 2
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CheckboxesOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CheckboxesOverridden.cshtml
new file mode 100644
index 00000000..5bcd163c
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/CheckboxesOverridden.cshtml
@@ -0,0 +1,13 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.CheckboxesTestsModel
+
+
+
+
+ Overridden legend
+
+ Overridden hint
+ Overridden error message
+ Option 1
+ Option 2
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInput.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInput.cshtml
new file mode 100644
index 00000000..58044507
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInput.cshtml
@@ -0,0 +1,7 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.DateInputTestsModel
+
+
+
+
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInputOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInputOverridden.cshtml
new file mode 100644
index 00000000..6542b494
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInputOverridden.cshtml
@@ -0,0 +1,9 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.DateInputTestsModel
+
+
+
+ Overridden legend
+ Overridden hint
+ Overridden error message
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInputOverriddenItems.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInputOverriddenItems.cshtml
new file mode 100644
index 00000000..338f2fde
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/DateInputOverriddenItems.cshtml
@@ -0,0 +1,7 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.DateInputTestsModel
+
+
+
+
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/FileUpload.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/FileUpload.cshtml
new file mode 100644
index 00000000..eb8a5909
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/FileUpload.cshtml
@@ -0,0 +1,3 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.FileUploadTestsModel
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/FileUploadOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/FileUploadOverridden.cshtml
new file mode 100644
index 00000000..427fa3c7
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/FileUploadOverridden.cshtml
@@ -0,0 +1,7 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.FileUploadTestsModel
+
+
+ Overridden label
+ Overridden hint
+ Overridden error message
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/PasswordInput.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/PasswordInput.cshtml
new file mode 100644
index 00000000..f0551632
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/PasswordInput.cshtml
@@ -0,0 +1,3 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.PasswordInputTestsModel
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/PasswordInputOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/PasswordInputOverridden.cshtml
new file mode 100644
index 00000000..97deb8b7
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/PasswordInputOverridden.cshtml
@@ -0,0 +1,7 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.PasswordInputTestsModel
+
+
+ Overridden label
+ Overridden hint
+ Overridden error message
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Radios.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Radios.cshtml
new file mode 100644
index 00000000..40ba8faa
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Radios.cshtml
@@ -0,0 +1,9 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.RadiosTestsModel
+
+
+
+
+ Option 1
+ Option 2
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/RadiosOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/RadiosOverridden.cshtml
new file mode 100644
index 00000000..5a6a77d9
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/RadiosOverridden.cshtml
@@ -0,0 +1,13 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.RadiosTestsModel
+
+
+
+
+ Overridden legend
+
+ Overridden hint
+ Overridden error message
+ Option 1
+ Option 2
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Select.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Select.cshtml
new file mode 100644
index 00000000..76755ca3
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Select.cshtml
@@ -0,0 +1,7 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.SelectTestsModel
+
+
+ Choose an option
+ Option 1
+ Option 2
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/SelectOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/SelectOverridden.cshtml
new file mode 100644
index 00000000..1b8b7dd3
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/SelectOverridden.cshtml
@@ -0,0 +1,10 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.SelectTestsModel
+
+
+ Overridden label
+ Overridden hint
+ Overridden error message
+ Choose an option
+ Option 1
+ Overridden option
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TagHelperModelBindingTestsFixture.cs b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TagHelperModelBindingTestsFixture.cs
new file mode 100644
index 00000000..fe63c81b
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TagHelperModelBindingTestsFixture.cs
@@ -0,0 +1,23 @@
+namespace GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests;
+
+public class TagHelperModelBindingTestsFixture : ServerFixture
+{
+ protected override void Configure(IApplicationBuilder app)
+ {
+ base.Configure(app);
+
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
+ }
+
+ protected override void ConfigureServices(IServiceCollection services)
+ {
+ services.AddGovUkFrontend();
+
+ services
+ .AddMvc()
+ .AddRazorOptions(options =>
+ {
+ options.ViewLocationFormats.Add("/TagHelperModelBindingTests/{0}.cshtml");
+ });
+ }
+}
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Tests.cs b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Tests.cs
new file mode 100644
index 00000000..0f99b6c0
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Tests.cs
@@ -0,0 +1,652 @@
+using System.ComponentModel.DataAnnotations;
+using Microsoft.AspNetCore.Mvc;
+
+namespace GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests;
+
+public class Tests(TagHelperModelBindingTestsFixture fixture) : IClassFixture
+{
+ [Fact]
+ public async Task TextInput_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/TextInput");
+
+ var input = page.Locator("input").First;
+ Assert.Equal("Text", await input.GetAttributeAsync("name"));
+ Assert.Equal("Text", await input.GetAttributeAsync("id"));
+ Assert.Equal("Model value", await input.GetAttributeAsync("value"));
+
+ var label = page.Locator("label").First;
+ Assert.Equal("Text", await label.GetAttributeAsync("for"));
+ Assert.Equal("ModelMetadata display name", await label.InnerTextAsync());
+
+ 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 TextInputOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/TextInputOverridden");
+
+ var input = page.Locator("input").First;
+ Assert.Equal("OverriddenName", await input.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId", await input.GetAttributeAsync("id"));
+ Assert.Equal("Overridden value", await input.GetAttributeAsync("value"));
+
+ var label = page.Locator("label").First;
+ Assert.Equal("OverriddenId", await label.GetAttributeAsync("for"));
+ Assert.Equal("Overridden label", await label.InnerTextAsync());
+
+ 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());
+ }
+
+ [Fact]
+ public async Task PasswordInput_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/PasswordInput");
+
+ var input = page.Locator("input[type='password']").First;
+ Assert.Equal("Password", await input.GetAttributeAsync("name"));
+ Assert.Equal("Password", await input.GetAttributeAsync("id"));
+ Assert.Equal("Model value", await input.GetAttributeAsync("value"));
+
+ var label = page.Locator("label").First;
+ Assert.Equal("Password", await label.GetAttributeAsync("for"));
+ Assert.Equal("ModelMetadata display name", await label.InnerTextAsync());
+
+ 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 PasswordInputOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/PasswordInputOverridden");
+
+ var input = page.Locator("input[type='password']").First;
+ Assert.Equal("OverriddenName", await input.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId", await input.GetAttributeAsync("id"));
+ Assert.Equal("Overridden value", await input.GetAttributeAsync("value"));
+
+ var label = page.Locator("label").First;
+ Assert.Equal("OverriddenId", await label.GetAttributeAsync("for"));
+ Assert.Equal("Overridden label", await label.InnerTextAsync());
+
+ 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());
+ }
+
+ [Fact]
+ public async Task DateInput_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/DateInput");
+
+ var dayInput = page.Locator("input[name='Date.Day']").First;
+ Assert.Equal("Date.Day", await dayInput.GetAttributeAsync("name"));
+ Assert.Equal("Date.Day", await dayInput.GetAttributeAsync("id"));
+ Assert.Equal("15", await dayInput.GetAttributeAsync("value"));
+
+ var monthInput = page.Locator("input[name='Date.Month']").First;
+ Assert.Equal("Date.Month", await monthInput.GetAttributeAsync("name"));
+ Assert.Equal("Date.Month", await monthInput.GetAttributeAsync("id"));
+ Assert.Equal("3", await monthInput.GetAttributeAsync("value"));
+
+ var yearInput = page.Locator("input[name='Date.Year']").First;
+ Assert.Equal("Date.Year", await yearInput.GetAttributeAsync("name"));
+ Assert.Equal("Date.Year", await yearInput.GetAttributeAsync("id"));
+ Assert.Equal("2024", await yearInput.GetAttributeAsync("value"));
+
+ var legend = page.Locator(".govuk-fieldset__legend").First;
+ Assert.Equal("ModelMetadata display name", await legend.InnerTextAsync());
+
+ 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 DateInputOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/DateInputOverridden");
+
+ var dayInput = page.Locator("input[name='OverriddenName.Day']").First;
+ Assert.Equal("OverriddenName.Day", await dayInput.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId.Day", await dayInput.GetAttributeAsync("id"));
+ Assert.Equal("15", await dayInput.GetAttributeAsync("value"));
+
+ var monthInput = page.Locator("input[name='OverriddenName.Month']").First;
+ Assert.Equal("OverriddenName.Month", await monthInput.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId.Month", await monthInput.GetAttributeAsync("id"));
+ Assert.Equal("3", await monthInput.GetAttributeAsync("value"));
+
+ var yearInput = page.Locator("input[name='OverriddenName.Year']").First;
+ Assert.Equal("OverriddenName.Year", await yearInput.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId.Year", await yearInput.GetAttributeAsync("id"));
+ Assert.Equal("2024", await yearInput.GetAttributeAsync("value"));
+
+ var legend = page.Locator(".govuk-fieldset__legend").First;
+ Assert.Equal("Overridden legend", await legend.InnerTextAsync());
+
+ 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());
+ }
+
+ [Fact]
+ public async Task DateInputOverriddenItems_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/DateInputOverriddenItems");
+
+ var dayInput = page.Locator("input[name='OverriddenDayName']").First;
+ Assert.Equal("OverriddenDayId", await dayInput.GetAttributeAsync("id"));
+ Assert.Equal("1", await dayInput.GetAttributeAsync("value"));
+
+ var monthInput = page.Locator("input[name='OverriddenMonthName']").First;
+ Assert.Equal("OverriddenMonthId", await monthInput.GetAttributeAsync("id"));
+ Assert.Equal("2", await monthInput.GetAttributeAsync("value"));
+
+ var yearInput = page.Locator("input[name='OverriddenYearName']").First;
+ Assert.Equal("OverriddenYearId", await yearInput.GetAttributeAsync("id"));
+ Assert.Equal("2020", await yearInput.GetAttributeAsync("value"));
+ }
+
+ [Fact]
+ public async Task Textarea_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/Textarea");
+
+ var textarea = page.Locator("textarea").First;
+ Assert.Equal("Text", await textarea.GetAttributeAsync("name"));
+ Assert.Equal("Text", await textarea.GetAttributeAsync("id"));
+ Assert.Equal("Model value", await textarea.InputValueAsync());
+
+ var label = page.Locator("label").First;
+ Assert.Equal("Text", await label.GetAttributeAsync("for"));
+ Assert.Equal("ModelMetadata display name", await label.InnerTextAsync());
+
+ 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 TextareaOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/TextareaOverridden");
+
+ var textarea = page.Locator("textarea").First;
+ Assert.Equal("OverriddenName", await textarea.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId", await textarea.GetAttributeAsync("id"));
+ Assert.Equal("Overridden value", await textarea.InputValueAsync());
+
+ var label = page.Locator("label").First;
+ Assert.Equal("OverriddenId", await label.GetAttributeAsync("for"));
+ Assert.Equal("Overridden label", await label.InnerTextAsync());
+
+ 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());
+ }
+
+ [Fact]
+ public async Task CharacterCount_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/CharacterCount");
+
+ var textarea = page.Locator("textarea").First;
+ Assert.Equal("Text", await textarea.GetAttributeAsync("name"));
+ Assert.Equal("Text", await textarea.GetAttributeAsync("id"));
+ Assert.Equal("Model value", await textarea.InputValueAsync());
+
+ var label = page.Locator("label").First;
+ Assert.Equal("Text", await label.GetAttributeAsync("for"));
+ Assert.Equal("ModelMetadata display name", await label.InnerTextAsync());
+
+ 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 CharacterCountOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/CharacterCountOverridden");
+
+ var textarea = page.Locator("textarea").First;
+ Assert.Equal("OverriddenName", await textarea.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId", await textarea.GetAttributeAsync("id"));
+ Assert.Equal("Overridden value", await textarea.InputValueAsync());
+
+ var label = page.Locator("label").First;
+ Assert.Equal("OverriddenId", await label.GetAttributeAsync("for"));
+ Assert.Equal("Overridden label", await label.InnerTextAsync());
+
+ 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());
+ }
+
+ [Fact]
+ public async Task FileUpload_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/FileUpload");
+
+ var input = page.Locator("input[type='file']").First;
+ Assert.Equal("File", await input.GetAttributeAsync("name"));
+ Assert.Equal("File", await input.GetAttributeAsync("id"));
+
+ var label = page.Locator("label").First;
+ Assert.Equal("File", await label.GetAttributeAsync("for"));
+ Assert.Equal("ModelMetadata display name", await label.InnerTextAsync());
+
+ 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 FileUploadOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/FileUploadOverridden");
+
+ var input = page.Locator("input[type='file']").First;
+ Assert.Equal("OverriddenName", await input.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId", await input.GetAttributeAsync("id"));
+
+ var label = page.Locator("label").First;
+ Assert.Equal("OverriddenId", await label.GetAttributeAsync("for"));
+ Assert.Equal("Overridden label", await label.InnerTextAsync());
+
+ 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());
+ }
+
+ [Fact]
+ public async Task Select_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/Select");
+
+ var select = page.Locator("select").First;
+ Assert.Equal("Option", await select.GetAttributeAsync("name"));
+ Assert.Equal("Option", await select.GetAttributeAsync("id"));
+ Assert.Equal("option2", await select.InputValueAsync());
+
+ var label = page.Locator("label").First;
+ Assert.Equal("Option", await label.GetAttributeAsync("for"));
+ Assert.Equal("ModelMetadata display name", await label.InnerTextAsync());
+
+ 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 SelectOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/SelectOverridden");
+
+ var select = page.Locator("select").First;
+ Assert.Equal("OverriddenName", await select.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenId", await select.GetAttributeAsync("id"));
+ Assert.Equal("overridden-value", await select.InputValueAsync());
+
+ var label = page.Locator("label").First;
+ Assert.Equal("OverriddenId", await label.GetAttributeAsync("for"));
+ Assert.Equal("Overridden label", await label.InnerTextAsync());
+
+ 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());
+ }
+
+ [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());
+ }
+
+ [Fact]
+ public async Task Radios_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/Radios");
+
+ var radio1 = page.Locator("input[value='option1']").First;
+ Assert.Equal("Option", await radio1.GetAttributeAsync("name"));
+ Assert.Equal("Option", await radio1.GetAttributeAsync("id"));
+ Assert.False(await radio1.IsCheckedAsync());
+
+ var radio2 = page.Locator("input[value='option2']").First;
+ Assert.Equal("Option", await radio2.GetAttributeAsync("name"));
+ Assert.Equal("Option-2", await radio2.GetAttributeAsync("id"));
+ Assert.True(await radio2.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 RadiosOverridden_RendersCorrectly()
+ {
+ var page = await fixture.Browser!.NewPageAsync();
+ await page.GotoAsync($"{ServerFixture.BaseUrl}/ModelBindingTests/RadiosOverridden");
+
+ var radio1 = page.Locator("input[value='option1']").First;
+ Assert.Equal("OverriddenName", await radio1.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenIdPrefix", await radio1.GetAttributeAsync("id"));
+ Assert.True(await radio1.IsCheckedAsync());
+
+ var radio2 = page.Locator("input[value='option2']").First;
+ Assert.Equal("OverriddenName", await radio2.GetAttributeAsync("name"));
+ Assert.Equal("OverriddenIdPrefix-2", await radio2.GetAttributeAsync("id"));
+ Assert.False(await radio2.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]")]
+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"] });
+ }
+
+ [HttpGet]
+ public IActionResult Radios()
+ {
+ ModelState.SetModelValue(nameof(RadiosTestsModel.Option), "option2", "option2");
+ ModelState.AddModelError(nameof(RadiosTestsModel.Option), "Model error message");
+ return View(new RadiosTestsModel { Option = "option2" });
+ }
+
+ [HttpGet]
+ public IActionResult RadiosOverridden()
+ {
+ ModelState.SetModelValue(nameof(RadiosTestsModel.Option), "option2", "option2");
+ ModelState.AddModelError(nameof(RadiosTestsModel.Option), "Model error message");
+ return View(new RadiosTestsModel { Option = "option2" });
+ }
+}
+
+public record TextInputTestsModel
+{
+ [Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
+ public string? Text { get; set; }
+}
+
+public record PasswordInputTestsModel
+{
+ [Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
+ public string? Password { get; set; }
+}
+
+public record DateInputTestsModel
+{
+ [Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
+ public DateOnly? Date { get; set; }
+}
+
+public record TextareaTestsModel
+{
+ [Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
+ public string? Text { get; set; }
+}
+
+public record CharacterCountTestsModel
+{
+ [Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
+ public string? Text { get; set; }
+}
+
+public record FileUploadTestsModel
+{
+ [Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
+ 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? Options { get; set; }
+}
+
+public record RadiosTestsModel
+{
+ [Display(Name = "ModelMetadata display name", Description = "ModelMetadata description")]
+ public string? Option { get; set; }
+}
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextInput.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextInput.cshtml
new file mode 100644
index 00000000..ab3f5913
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextInput.cshtml
@@ -0,0 +1,3 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.TextInputTestsModel
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextInputOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextInputOverridden.cshtml
new file mode 100644
index 00000000..badff6ae
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextInputOverridden.cshtml
@@ -0,0 +1,7 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.TextInputTestsModel
+
+
+ Overridden label
+ Overridden hint
+ Overridden error message
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Textarea.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Textarea.cshtml
new file mode 100644
index 00000000..be31000e
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/Textarea.cshtml
@@ -0,0 +1,3 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.TextareaTestsModel
+
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextareaOverridden.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextareaOverridden.cshtml
new file mode 100644
index 00000000..b7c3498d
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/TextareaOverridden.cshtml
@@ -0,0 +1,8 @@
+@model GovUk.Frontend.AspNetCore.IntegrationTests.TagHelperModelBindingTests.TextareaTestsModel
+
+
+ Overridden label
+ Overridden hint
+ Overridden error message
+ Overridden value
+
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/_ViewImports.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/_ViewImports.cshtml
new file mode 100644
index 00000000..8ced5bf7
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@addTagHelper *, GovUk.Frontend.AspNetCore
+@addTagHelper *, GovUk.Frontend.AspNetCore.IntegrationTests
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/_ViewStart.cshtml b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/_ViewStart.cshtml
new file mode 100644
index 00000000..36592afb
--- /dev/null
+++ b/tests/GovUk.Frontend.AspNetCore.IntegrationTests/TagHelperModelBindingTests/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_GovUkPageTemplate";
+}