- Look up the existing aria-describedby attribute with OrdinalIgnoreCase to match the casing rules used by HTML and TagHelperAttributeList
- Tokenize the existing value on all ASCII whitespace (space, tab, newline, carriage return, form feed) instead of just the literal space
- Cover the whitespace-separated case with a new test
Cover the new aria-describedby behaviour for <abp-input>:
- form-text rendered as <div>
- aria-describedby reaches the final HTML
- no-id case skips the InfoText id and aria-describedby
- caller-supplied aria-describedby is preserved (append + dedupe)
- [InputInfoText] attribute path produces a single aria-describedby
- Add TagHelperOutputExtensions.AppendAriaDescribedby helper that preserves caller-supplied tokens (space-separated id list) and dedupes
- Replace SetAttribute calls in AddInfoTextId/GetInfoAsHtml of abp-input/abp-select with the helper
- Cover the consumer-provided aria-describedby case with a new test
- 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
- Move inputTag.Render after GetInfoAsHtml so aria-describedby reaches the final HTML
- Replace Attributes.Add with Attributes.SetAttribute for aria-describedby to avoid duplicates when [InputInfoText] and info="..." are both present
- Apply the same fixes to AbpInputTagHelperService for consistency
- Cover the [InputInfoText] attribute path with an additional test