Browse Source

Guard aria-describedby and InfoText id against missing/empty input id

- Stop using the localized text as the aria-describedby value in AddInfoTextId; reference the actual id directly
- Skip rendering the InfoText id and aria-describedby when the input/select has no id (or an empty one) so the form never renders a non-unique "InfoText" id
- Cover the no-id case with a new test
pull/25352/head
maliming 5 months ago
parent
commit
a8060b9566
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs
  2. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  3. 36
      framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs

15
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs

@ -259,15 +259,14 @@ public class AbpInputTagHelperService : AbpTagHelperService<AbpInputTagHelper>
}
var idAttr = inputTagHelperOutput.Attributes.FirstOrDefault(a => a.Name == "id");
var idValue = idAttr?.Value?.ToString();
if (idAttr == null)
if (string.IsNullOrEmpty(idValue))
{
return;
}
var infoText = _tagHelperLocalizer.GetLocalizedText(idAttr.Value + "InfoText", TagHelper.AspFor.ModelExplorer);
inputTagHelperOutput.Attributes.SetAttribute("aria-describedby", infoText);
inputTagHelperOutput.Attributes.SetAttribute("aria-describedby", idValue + "InfoText");
}
protected virtual bool IsInputCheckbox(TagHelperContext context, TagHelperOutput output, TagHelperAttributeList attributes)
@ -356,14 +355,18 @@ public class AbpInputTagHelperService : AbpTagHelperService<AbpInputTagHelper>
}
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
var idValue = idAttr?.Value?.ToString();
var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer);
var div = new TagBuilder("div");
div.Attributes.Add("id", idAttr?.Value + "InfoText");
div.AddCssClass("form-text");
div.InnerHtml.Append(localizedText);
inputTag.Attributes.SetAttribute("aria-describedby", idAttr?.Value + "InfoText");
if (!string.IsNullOrEmpty(idValue))
{
div.Attributes.Add("id", idValue + "InfoText");
inputTag.Attributes.SetAttribute("aria-describedby", idValue + "InfoText");
}
return div.ToHtmlString();
}

15
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs

@ -216,15 +216,14 @@ public class AbpSelectTagHelperService : AbpTagHelperService<AbpSelectTagHelper>
}
var idAttr = inputTagHelperOutput.Attributes.FirstOrDefault(a => a.Name == "id");
var idValue = idAttr?.Value?.ToString();
if (idAttr == null)
if (string.IsNullOrEmpty(idValue))
{
return;
}
var infoText = _tagHelperLocalizer.GetLocalizedText(idAttr.Value + "InfoText", TagHelper.AspFor.ModelExplorer);
inputTagHelperOutput.Attributes.SetAttribute("aria-describedby", infoText);
inputTagHelperOutput.Attributes.SetAttribute("aria-describedby", idValue + "InfoText");
}
protected virtual string GetInfoAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag)
@ -249,14 +248,18 @@ public class AbpSelectTagHelperService : AbpTagHelperService<AbpSelectTagHelper>
}
var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id");
var idValue = idAttr?.Value?.ToString();
var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer);
var div = new TagBuilder("div");
div.Attributes.Add("id", idAttr?.Value + "InfoText");
div.AddCssClass("form-text");
div.InnerHtml.Append(localizedText);
inputTag.Attributes.SetAttribute("aria-describedby", idAttr?.Value + "InfoText");
if (!string.IsNullOrEmpty(idValue))
{
div.Attributes.Add("id", idValue + "InfoText");
inputTag.Attributes.SetAttribute("aria-describedby", idValue + "InfoText");
}
return div.ToHtmlString();
}

36
framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs

@ -55,6 +55,29 @@ public class AbpSelectTagHelperService_Tests
service.LastGroupHtml.ShouldContain("aria-describedby=\"TestSelectInfoText\"");
}
[Fact]
public async Task Info_text_should_skip_id_and_aria_describedby_when_select_has_no_id()
{
var service = new TestAbpSelectTagHelperService(selectId: null);
var tagHelper = new AbpSelectTagHelper(service)
{
AspFor = CreateModelExpression(),
InfoText = "Description"
};
var output = CreateOutput();
await tagHelper.ProcessAsync(CreateContext(), output);
service.LastGroupHtml.ShouldContain("<div class=\"form-text\"");
service.LastGroupHtml.ShouldContain("Description");
service.LastGroupHtml.ShouldNotContain("id=\"InfoText\"");
service.LastGroupHtml.ShouldNotContain("aria-describedby=\"InfoText\"");
service.LastSelectTag.ShouldNotBeNull();
service.LastSelectTag!.Attributes.ContainsName("aria-describedby").ShouldBeFalse();
}
[Fact]
public async Task InputInfoText_attribute_should_render_info_text_with_single_aria_describedby()
{
@ -119,20 +142,29 @@ public class AbpSelectTagHelperService_Tests
private sealed class TestAbpSelectTagHelperService : AbpSelectTagHelperService
{
private readonly string? _selectId;
public string LastGroupHtml { get; private set; } = string.Empty;
public TagHelperOutput? LastSelectTag { get; private set; }
public TestAbpSelectTagHelperService()
public TestAbpSelectTagHelperService(string? selectId = "TestSelect")
: base(null!, HtmlEncoder.Default, new FakeTagHelperLocalizer(), null!, null!)
{
_selectId = selectId;
}
protected override Task<TagHelperOutput> GetSelectTagAsync(TagHelperContext context, TagHelperOutput output, TagHelperContent childContent)
{
var attributes = new TagHelperAttributeList();
if (!string.IsNullOrEmpty(_selectId))
{
attributes.Add("id", _selectId);
}
LastSelectTag = new TagHelperOutput(
"select",
new TagHelperAttributeList { { "id", "TestSelect" } },
attributes,
(_, _) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()))
{
TagMode = TagMode.StartTagAndEndTag

Loading…
Cancel
Save