diff --git a/src/Components/Web/src/Forms/ValidationSummary.cs b/src/Components/Web/src/Forms/ValidationSummary.cs index e2580fd0cf97..565252ca85fc 100644 --- a/src/Components/Web/src/Forms/ValidationSummary.cs +++ b/src/Components/Web/src/Forms/ValidationSummary.cs @@ -73,8 +73,9 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) first = false; builder.OpenElement(0, "ul"); - builder.AddAttribute(1, "class", "validation-errors"); - builder.AddMultipleAttributes(2, AdditionalAttributes); + builder.AddMultipleAttributes(1, AdditionalAttributes); + builder.AddAttributeIfNotNullOrEmpty(2, "class", + AttributeUtilities.CombineClassNames(AdditionalAttributes, "validation-errors")); } builder.OpenElement(3, "li"); diff --git a/src/Components/test/E2ETest/Tests/FormsTest.cs b/src/Components/test/E2ETest/Tests/FormsTest.cs index 304ea1721abc..6b4f0be88b97 100644 --- a/src/Components/test/E2ETest/Tests/FormsTest.cs +++ b/src/Components/test/E2ETest/Tests/FormsTest.cs @@ -1096,4 +1096,32 @@ private bool ElementHasAttribute(IWebElement webElement, string attribute) var jsExecutor = (IJavaScriptExecutor)Browser; return (bool)jsExecutor.ExecuteScript($"return arguments[0].attributes['{attribute}'] !== undefined;", webElement); } + + [Fact] + public void ValidationSummaryCombinesAdditionalAttributesClassWithDefault() + { + var appElement = Browser.MountTestComponent(); + var nameInput = appElement.FindElement(By.ClassName("name")).FindElement(By.TagName("input")); + var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]")); + + // Make the form invalid to trigger rendering of the ValidationSummary elements + nameInput.SendKeys("01234567890123456789\t"); + submitButton.Click(); + + // Default ValidationSummary renders with only "validation-errors" class. + Browser.Equal("validation-errors", + () => appElement.FindElement(By.ClassName("all-errors")).FindElement(By.TagName("ul")).GetDomAttribute("class")); + + // ValidationSummary with class="pt-2" via AdditionalAttributes combines both: + // splatted class + fixed "validation-errors" + Browser.Equal("pt-2 validation-errors", + () => appElement.FindElement(By.ClassName("all-errors-with-class")).FindElement(By.TagName("ul")).GetDomAttribute("class")); + + // Additional non-class attributes (e.g. data-testid) are also splatted + Browser.Equal("validation-errors", + () => appElement.FindElement(By.ClassName("all-errors-with-attr")).FindElement(By.TagName("ul")).GetDomAttribute("class")); + Browser.Equal("my-summary", + () => appElement.FindElement(By.ClassName("all-errors-with-attr")).FindElement(By.TagName("ul")).GetDomAttribute("data-testid")); + } + } diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationSummaryWithCssClassComponent.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationSummaryWithCssClassComponent.razor new file mode 100644 index 000000000000..c668f4e6f61a --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/ValidationSummaryWithCssClassComponent.razor @@ -0,0 +1,40 @@ +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Components.Forms + + + + +

+ Name: +

+ + + +

+ +

+

+ +

+

+ +

+
+ +@code { + private EditContext editContext = default!; + private Person person = new(); + + protected override void OnInitialized() + { + editContext = new EditContext(person); + } + + private void HandleValidSubmit() { } + + private class Person + { + [Required, MaxLength(10)] + public string Name { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index cd3597b2a9a4..68cbb72c3c1f 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -50,6 +50,7 @@ +