Add `ParentTag` to `[HtmlTargetElement]`.
- `ParentTag` allows `TagHelper`s to restrict where they apply based on their immediate parent tag. - Changed the `TagHelperParseTreeRewriter` to understand non-`TagHelper` HTML elements to properly determine a parent tag when applying `TagHelperDescriptor.RequiredParent`. This change will also enable `[RestrictChildren]` to apply to more than just `TagHelper`s. - Added tests to validate that partial and well formed tags properly discover `TagHelper`s. Also added tests that validate that descriptors are properly created based on `TagHelper` types. #474
This commit is contained in:
parent
18799d2944
commit
67739ea565
|
|
@ -426,6 +426,22 @@ namespace Microsoft.AspNet.Razor.Runtime
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent Tag
|
||||
/// </summary>
|
||||
internal static string TagHelperDescriptorFactory_ParentTag
|
||||
{
|
||||
get { return GetString("TagHelperDescriptorFactory_ParentTag"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parent Tag
|
||||
/// </summary>
|
||||
internal static string FormatTagHelperDescriptorFactory_ParentTag()
|
||||
{
|
||||
return GetString("TagHelperDescriptorFactory_ParentTag");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -195,4 +195,7 @@
|
|||
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace" xml:space="preserve">
|
||||
<value>Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.</value>
|
||||
</data>
|
||||
<data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve">
|
||||
<value>Parent Tag</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -79,5 +79,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
/// </para>
|
||||
/// </remarks>
|
||||
public TagStructure TagStructure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The required HTML element name of the direct parent.
|
||||
/// </summary>
|
||||
/// <remarks>A <c>null</c> value indicates any HTML element name is appropriate.</remarks>
|
||||
public string ParentTag { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -142,6 +142,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
requiredAttributes: Enumerable.Empty<string>(),
|
||||
allowedChildren: allowedChildren,
|
||||
tagStructure: default(TagStructure),
|
||||
parentTag: null,
|
||||
designTimeDescriptor: typeDesignTimeDescriptor)
|
||||
};
|
||||
}
|
||||
|
|
@ -231,6 +232,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
attributeDescriptors,
|
||||
requiredAttributes,
|
||||
allowedChildren,
|
||||
targetElementAttribute.ParentTag,
|
||||
targetElementAttribute.TagStructure,
|
||||
designTimeDescriptor);
|
||||
}
|
||||
|
|
@ -242,6 +244,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||
IEnumerable<string> requiredAttributes,
|
||||
IEnumerable<string> allowedChildren,
|
||||
string parentTag,
|
||||
TagStructure tagStructure,
|
||||
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||
{
|
||||
|
|
@ -253,6 +256,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
Attributes = attributeDescriptors,
|
||||
RequiredAttributes = requiredAttributes,
|
||||
AllowedChildren = allowedChildren,
|
||||
RequiredParent = parentTag,
|
||||
TagStructure = tagStructure,
|
||||
DesignTimeDescriptor = designTimeDescriptor
|
||||
};
|
||||
|
|
@ -286,7 +290,27 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
}
|
||||
}
|
||||
|
||||
return validTagName && validAttributeNames;
|
||||
var validParentTagName = ValidateParentTagName(attribute.ParentTag, errorSink);
|
||||
|
||||
return validTagName && validAttributeNames && validParentTagName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal for unit testing.
|
||||
/// </summary>
|
||||
internal static bool ValidateParentTagName(string parentTag, ErrorSink errorSink)
|
||||
{
|
||||
return parentTag == null ||
|
||||
TryValidateName(
|
||||
parentTag,
|
||||
Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(
|
||||
Resources.TagHelperDescriptorFactory_ParentTag),
|
||||
characterErrorBuilder: (invalidCharacter) =>
|
||||
Resources.FormatHtmlTargetElementAttribute_InvalidName(
|
||||
Resources.TagHelperDescriptorFactory_ParentTag.ToLower(),
|
||||
parentTag,
|
||||
invalidCharacter),
|
||||
errorSink: errorSink);
|
||||
}
|
||||
|
||||
private static bool ValidateName(
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
Attributes = descriptor.Attributes,
|
||||
RequiredAttributes = descriptor.RequiredAttributes,
|
||||
AllowedChildren = descriptor.AllowedChildren,
|
||||
RequiredParent = descriptor.RequiredParent,
|
||||
TagStructure = descriptor.TagStructure,
|
||||
DesignTimeDescriptor = descriptor.DesignTimeDescriptor
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNet.Razor.Test.Internal
|
|||
Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal);
|
||||
Assert.Equal(descriptorX.Prefix, descriptorY.Prefix, StringComparer.Ordinal);
|
||||
Assert.Equal(descriptorX.RequiredAttributes, descriptorY.RequiredAttributes, StringComparer.Ordinal);
|
||||
Assert.Equal(descriptorX.RequiredParent, descriptorY.RequiredParent, StringComparer.Ordinal);
|
||||
|
||||
if (descriptorX.AllowedChildren != descriptorY.AllowedChildren)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
|
|
@ -15,16 +14,38 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
{
|
||||
public class TagHelperParseTreeRewriter : ISyntaxTreeRewriter
|
||||
{
|
||||
// From http://dev.w3.org/html5/spec/Overview.html#elements-0
|
||||
private static readonly HashSet<string> VoidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
"command",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"keygen",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr"
|
||||
};
|
||||
|
||||
private TagHelperDescriptorProvider _provider;
|
||||
private Stack<TagHelperBlockTracker> _trackerStack;
|
||||
private Stack<TagBlockTracker> _trackerStack;
|
||||
private TagHelperBlockTracker _currentTagHelperTracker;
|
||||
private Stack<BlockBuilder> _blockStack;
|
||||
private BlockBuilder _currentBlock;
|
||||
private string _currentParentTagName;
|
||||
|
||||
public TagHelperParseTreeRewriter(TagHelperDescriptorProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
_trackerStack = new Stack<TagHelperBlockTracker>();
|
||||
_trackerStack = new Stack<TagBlockTracker>();
|
||||
_blockStack = new Stack<BlockBuilder>();
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +65,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
ChunkGenerator = input.ChunkGenerator
|
||||
});
|
||||
|
||||
var activeTagHelpers = _trackerStack.Count;
|
||||
var activeTrackers = _trackerStack.Count;
|
||||
|
||||
foreach (var child in input.Children)
|
||||
{
|
||||
|
|
@ -60,6 +81,8 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
}
|
||||
else
|
||||
{
|
||||
TrackTagBlock(childBlock);
|
||||
|
||||
// Non-TagHelper tag.
|
||||
ValidateParentTagHelperAllowsPlainTag(childBlock, context.ErrorSink);
|
||||
}
|
||||
|
|
@ -88,19 +111,48 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
|
||||
// We captured the number of active tag helpers at the start of our logic, it should be the same. If not
|
||||
// it means that there are malformed tag helpers at the top of our stack.
|
||||
if (activeTagHelpers != _trackerStack.Count)
|
||||
if (activeTrackers != _trackerStack.Count)
|
||||
{
|
||||
// Malformed tag helpers built here will be tag helpers that do not have end tags in the current block
|
||||
// scope. Block scopes are special cases in Razor such as @<p> would cause an error because there's no
|
||||
// matching end </p> tag in the template block scope and therefore doesn't make sense as a tag helper.
|
||||
BuildMalformedTagHelpers(_trackerStack.Count - activeTagHelpers, context);
|
||||
BuildMalformedTagHelpers(_trackerStack.Count - activeTrackers, context);
|
||||
|
||||
Debug.Assert(activeTagHelpers == _trackerStack.Count);
|
||||
Debug.Assert(activeTrackers == _trackerStack.Count);
|
||||
}
|
||||
|
||||
BuildCurrentlyTrackedBlock();
|
||||
}
|
||||
|
||||
private void TrackTagBlock(Block childBlock)
|
||||
{
|
||||
var tagName = GetTagName(childBlock);
|
||||
|
||||
// Don't want to track incomplete tags that have no tag name.
|
||||
if (string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsEndTag(childBlock))
|
||||
{
|
||||
var parentTracker = _trackerStack.Count > 0 ? _trackerStack.Peek() : null;
|
||||
if (parentTracker != null &&
|
||||
!parentTracker.IsTagHelper &&
|
||||
string.Equals(parentTracker.TagName, tagName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
PopTrackerStack();
|
||||
}
|
||||
}
|
||||
else if (!VoidElements.Contains(tagName) && !IsSelfClosing(childBlock))
|
||||
{
|
||||
// If it's not a void element and it's not self-closing then we need to create a tag
|
||||
// tracker for it.
|
||||
var tracker = new TagBlockTracker(tagName, isTagHelper: false);
|
||||
PushTrackerStack(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryRewriteTagHelper(Block tagBlock, RewritingContext context)
|
||||
{
|
||||
// Get tag name of the current block (doesn't matter if it's an end or start tag)
|
||||
|
|
@ -120,15 +172,14 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
}
|
||||
|
||||
var tracker = _currentTagHelperTracker;
|
||||
var tagNameScope = tracker?.Builder.TagName ?? string.Empty;
|
||||
var tagNameScope = tracker?.TagName ?? string.Empty;
|
||||
|
||||
if (!IsEndTag(tagBlock))
|
||||
{
|
||||
// We're now in a start tag block, we first need to see if the tag block is a tag helper.
|
||||
var providedAttributes = GetAttributeNames(tagBlock);
|
||||
|
||||
descriptors = _provider.GetDescriptors(tagName, providedAttributes);
|
||||
|
||||
descriptors = _provider.GetDescriptors(tagName, providedAttributes, _currentParentTagName);
|
||||
|
||||
// If there aren't any TagHelperDescriptors registered then we aren't a TagHelper
|
||||
if (!descriptors.Any())
|
||||
|
|
@ -193,7 +244,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
}
|
||||
else
|
||||
{
|
||||
descriptors = _provider.GetDescriptors(tagName, attributeNames: Enumerable.Empty<string>());
|
||||
descriptors = _provider.GetDescriptors(
|
||||
tagName,
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: _currentParentTagName);
|
||||
|
||||
// If there are not TagHelperDescriptors associated with the end tag block that also have no
|
||||
// required attributes then it means we can't be a TagHelper, bail out.
|
||||
|
|
@ -297,7 +351,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
errorSink.OnError(
|
||||
errorStart,
|
||||
RazorResources.FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent(
|
||||
_currentTagHelperTracker.Builder.TagName,
|
||||
_currentTagHelperTracker.TagName,
|
||||
allowedChildrenString),
|
||||
length);
|
||||
}
|
||||
|
|
@ -343,7 +397,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
var allowedChildrenString = string.Join(", ", tracker.AllowedChildren);
|
||||
var errorMessage = RazorResources.FormatTagHelperParseTreeRewriter_InvalidNestedTag(
|
||||
tagName,
|
||||
tracker.Builder.TagName,
|
||||
tracker.TagName,
|
||||
allowedChildrenString);
|
||||
var errorStart = GetTagDeclarationErrorStart(tagBlock);
|
||||
|
||||
|
|
@ -450,11 +504,23 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
|
||||
private void BuildCurrentlyTrackedTagHelperBlock(Block endTag)
|
||||
{
|
||||
Debug.Assert(_trackerStack.Any(tracker => tracker.IsTagHelper));
|
||||
|
||||
// We need to pop all trackers until we reach our TagHelperBlock. We can throw away any non-TagHelper
|
||||
// trackers because they don't need to be well-formed.
|
||||
TagHelperBlockTracker tagHelperTracker;
|
||||
do
|
||||
{
|
||||
tagHelperTracker = PopTrackerStack() as TagHelperBlockTracker;
|
||||
}
|
||||
while (tagHelperTracker == null);
|
||||
|
||||
// Track the original end tag so the editor knows where each piece of the TagHelperBlock lies
|
||||
// for formatting.
|
||||
_trackerStack.Pop().Builder.SourceEndTag = endTag;
|
||||
tagHelperTracker.Builder.SourceEndTag = endTag;
|
||||
|
||||
_currentTagHelperTracker = _trackerStack.Count > 0 ? _trackerStack.Peek() : null;
|
||||
_currentTagHelperTracker =
|
||||
(TagHelperBlockTracker)_trackerStack.FirstOrDefault(tagBlockTracker => tagBlockTracker.IsTagHelper);
|
||||
|
||||
BuildCurrentlyTrackedBlock();
|
||||
}
|
||||
|
|
@ -481,7 +547,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
private void TrackTagHelperBlock(TagHelperBlockBuilder builder)
|
||||
{
|
||||
_currentTagHelperTracker = new TagHelperBlockTracker(builder);
|
||||
_trackerStack.Push(_currentTagHelperTracker);
|
||||
PushTrackerStack(_currentTagHelperTracker);
|
||||
|
||||
TrackBlock(builder);
|
||||
}
|
||||
|
|
@ -492,7 +558,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
|
||||
foreach (var tracker in _trackerStack)
|
||||
{
|
||||
if (tracker.Builder.TagName.Equals(tagName, StringComparison.OrdinalIgnoreCase))
|
||||
if (tracker.IsTagHelper && tracker.TagName.Equals(tagName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
@ -521,7 +587,16 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var malformedTagHelper = _trackerStack.Peek().Builder;
|
||||
var tracker = _trackerStack.Peek();
|
||||
|
||||
// Skip all non-TagHelper entries. Non TagHelper trackers do not need to represent well-formed HTML.
|
||||
if (!tracker.IsTagHelper)
|
||||
{
|
||||
PopTrackerStack();
|
||||
continue;
|
||||
}
|
||||
|
||||
var malformedTagHelper = ((TagHelperBlockTracker)tracker).Builder;
|
||||
|
||||
context.ErrorSink.OnError(
|
||||
SourceLocation.Advance(malformedTagHelper.Start, "<"),
|
||||
|
|
@ -570,11 +645,46 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
|||
Debug.Assert(tagBlock.Children.First() is Span);
|
||||
}
|
||||
|
||||
private class TagHelperBlockTracker
|
||||
private static bool IsSelfClosing(Block childBlock)
|
||||
{
|
||||
var childSpan = childBlock.FindLastDescendentSpan();
|
||||
|
||||
return childSpan?.Content.EndsWith("/>") ?? false;
|
||||
}
|
||||
|
||||
private void PushTrackerStack(TagBlockTracker tracker)
|
||||
{
|
||||
_currentParentTagName = tracker.TagName;
|
||||
_trackerStack.Push(tracker);
|
||||
}
|
||||
|
||||
private TagBlockTracker PopTrackerStack()
|
||||
{
|
||||
var poppedTracker = _trackerStack.Pop();
|
||||
_currentParentTagName = _trackerStack.Count > 0 ? _trackerStack.Peek().TagName : null;
|
||||
|
||||
return poppedTracker;
|
||||
}
|
||||
|
||||
private class TagBlockTracker
|
||||
{
|
||||
public TagBlockTracker(string tagName, bool isTagHelper)
|
||||
{
|
||||
TagName = tagName;
|
||||
IsTagHelper = isTagHelper;
|
||||
}
|
||||
|
||||
public string TagName { get; }
|
||||
|
||||
public bool IsTagHelper { get; }
|
||||
}
|
||||
|
||||
private class TagHelperBlockTracker : TagBlockTracker
|
||||
{
|
||||
private IEnumerable<string> _prefixedAllowedChildren;
|
||||
|
||||
public TagHelperBlockTracker(TagHelperBlockBuilder builder)
|
||||
: base(builder.TagName, isTagHelper: true)
|
||||
{
|
||||
Builder = builder;
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,12 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// <remarks><c>null</c> indicates all children are allowed.</remarks>
|
||||
public IEnumerable<string> AllowedChildren { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the name of the HTML element required as the immediate parent.
|
||||
/// </summary>
|
||||
/// <remarks><c>null</c> indicates no restriction on parent tag.</remarks>
|
||||
public string RequiredParent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The expected tag structure.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) &&
|
||||
string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) &&
|
||||
string.Equals(
|
||||
descriptorX.RequiredParent,
|
||||
descriptorY.RequiredParent,
|
||||
StringComparison.OrdinalIgnoreCase) &&
|
||||
Enumerable.SequenceEqual(
|
||||
descriptorX.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
||||
descriptorY.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
||||
|
|
@ -72,6 +76,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
hashCodeCombiner.Add(descriptor.TypeName, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase);
|
||||
hashCodeCombiner.Add(descriptor.AssemblyName, StringComparer.Ordinal);
|
||||
hashCodeCombiner.Add(descriptor.RequiredParent, StringComparer.OrdinalIgnoreCase);
|
||||
hashCodeCombiner.Add(descriptor.TagStructure);
|
||||
|
||||
var attributes = descriptor.RequiredAttributes.OrderBy(
|
||||
|
|
|
|||
|
|
@ -40,10 +40,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
/// <param name="tagName">The name of the HTML tag to match. Providing a '*' tag name
|
||||
/// retrieves catch-all <see cref="TagHelperDescriptor"/>s (descriptors that target every tag).</param>
|
||||
/// <param name="attributeNames">Attributes the HTML element must contain to match.</param>
|
||||
/// <param name="parentTagName">The parent tag name of the given <paramref name="tagName"/> tag.</param>
|
||||
/// <returns><see cref="TagHelperDescriptor"/>s that apply to the given <paramref name="tagName"/>.
|
||||
/// Will return an empty <see cref="Enumerable" /> if no <see cref="TagHelperDescriptor"/>s are
|
||||
/// found.</returns>
|
||||
public IEnumerable<TagHelperDescriptor> GetDescriptors(string tagName, IEnumerable<string> attributeNames)
|
||||
public IEnumerable<TagHelperDescriptor> GetDescriptors(
|
||||
string tagName,
|
||||
IEnumerable<string> attributeNames,
|
||||
string parentTagName)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_tagHelperPrefix) &&
|
||||
(tagName.Length <= _tagHelperPrefix.Length ||
|
||||
|
|
@ -75,10 +79,20 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
}
|
||||
|
||||
var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributeNames);
|
||||
applicableDescriptors = ApplyParentTagFilter(applicableDescriptors, parentTagName);
|
||||
|
||||
return applicableDescriptors;
|
||||
}
|
||||
|
||||
private IEnumerable<TagHelperDescriptor> ApplyParentTagFilter(
|
||||
IEnumerable<TagHelperDescriptor> descriptors,
|
||||
string parentTagName)
|
||||
{
|
||||
return descriptors.Where(descriptor =>
|
||||
descriptor.RequiredParent == null ||
|
||||
string.Equals(parentTagName, descriptor.RequiredParent, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private IEnumerable<TagHelperDescriptor> ApplyRequiredAttributes(
|
||||
IEnumerable<TagHelperDescriptor> descriptors,
|
||||
IEnumerable<string> attributeNames)
|
||||
|
|
|
|||
|
|
@ -13,11 +13,100 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
{
|
||||
public abstract class TagHelperDescriptorFactoryTest
|
||||
{
|
||||
protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly =
|
||||
protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly =
|
||||
typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName();
|
||||
|
||||
protected static readonly string AssemblyName = TagHelperDescriptorFactoryTestAssembly.Name;
|
||||
|
||||
public static TheoryData RequiredParentData
|
||||
{
|
||||
get
|
||||
{
|
||||
// tagHelperType, expectedDescriptors
|
||||
return new TheoryData<Type, TagHelperDescriptor[]>
|
||||
{
|
||||
{
|
||||
typeof(RequiredParentTagHelper),
|
||||
new[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = typeof(RequiredParentTagHelper).FullName,
|
||||
AssemblyName = AssemblyName,
|
||||
RequiredParent = "div"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
typeof(MultiSpecifiedRequiredParentTagHelper),
|
||||
new[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = typeof(MultiSpecifiedRequiredParentTagHelper).FullName,
|
||||
AssemblyName = AssemblyName,
|
||||
RequiredParent = "section"
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "p",
|
||||
TypeName = typeof(MultiSpecifiedRequiredParentTagHelper).FullName,
|
||||
AssemblyName = AssemblyName,
|
||||
RequiredParent = "div"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
typeof(MultiWithUnspecifiedRequiredParentTagHelper),
|
||||
new[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = typeof(MultiWithUnspecifiedRequiredParentTagHelper).FullName,
|
||||
AssemblyName = AssemblyName,
|
||||
RequiredParent = "div"
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "p",
|
||||
TypeName = typeof(MultiWithUnspecifiedRequiredParentTagHelper).FullName,
|
||||
AssemblyName = AssemblyName
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequiredParentData))]
|
||||
public void CreateDescriptors_CreatesDesignTimeDescriptorsWithRequiredParent(
|
||||
Type tagHelperType,
|
||||
TagHelperDescriptor[] expectedDescriptors)
|
||||
{
|
||||
// Arrange
|
||||
var errorSink = new ErrorSink();
|
||||
|
||||
// Act
|
||||
var descriptors = TagHelperDescriptorFactory.CreateDescriptors(
|
||||
AssemblyName,
|
||||
GetTypeInfo(tagHelperType),
|
||||
designTime: false,
|
||||
errorSink: errorSink);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(errorSink.Errors);
|
||||
|
||||
// We don't care about order. Mono returns reflected attributes differently so we need to ensure order
|
||||
// doesn't matter by sorting.
|
||||
descriptors = descriptors.OrderBy(descriptor => descriptor.TagName);
|
||||
|
||||
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
public static TheoryData RestrictChildrenData
|
||||
{
|
||||
get
|
||||
|
|
@ -1713,6 +1802,41 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|||
Assert.Equal(expectedErrors, errorSink.Errors);
|
||||
}
|
||||
|
||||
public static TheoryData<string, string[]> InvalidParentTagData
|
||||
{
|
||||
get
|
||||
{
|
||||
var nullOrWhiteSpaceError =
|
||||
Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(
|
||||
Resources.TagHelperDescriptorFactory_ParentTag);
|
||||
|
||||
return GetInvalidNameOrPrefixData(
|
||||
onNameError: (invalidInput, invalidCharacter) =>
|
||||
Resources.FormatHtmlTargetElementAttribute_InvalidName(
|
||||
Resources.TagHelperDescriptorFactory_ParentTag.ToLower(),
|
||||
invalidInput,
|
||||
invalidCharacter),
|
||||
whitespaceErrorString: nullOrWhiteSpaceError,
|
||||
onDataError: null);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidParentTagData))]
|
||||
public void ValidateParentTagName_AddsExpectedErrors(string name, string[] expectedErrorMessages)
|
||||
{
|
||||
// Arrange
|
||||
var errorSink = new ErrorSink();
|
||||
var expectedErrors = expectedErrorMessages.Select(
|
||||
message => new RazorError(message, SourceLocation.Zero, 0));
|
||||
|
||||
// Act
|
||||
TagHelperDescriptorFactory.ValidateParentTagName(name, errorSink);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedErrors, errorSink.Errors);
|
||||
}
|
||||
|
||||
private static TheoryData<string, string[]> GetInvalidNameOrPrefixData(
|
||||
Func<string, string, string> onNameError,
|
||||
string whitespaceErrorString,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,23 @@ using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
|||
|
||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||
{
|
||||
[HtmlTargetElement("input", ParentTag = "div")]
|
||||
public class RequiredParentTagHelper : TagHelper
|
||||
{
|
||||
}
|
||||
|
||||
[HtmlTargetElement("p", ParentTag = "div")]
|
||||
[HtmlTargetElement("input", ParentTag = "section")]
|
||||
public class MultiSpecifiedRequiredParentTagHelper : TagHelper
|
||||
{
|
||||
}
|
||||
|
||||
[HtmlTargetElement("p")]
|
||||
[HtmlTargetElement("input", ParentTag = "div")]
|
||||
public class MultiWithUnspecifiedRequiredParentTagHelper : TagHelper
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[RestrictChildren("p")]
|
||||
public class RestrictChildrenTagHelper
|
||||
|
|
|
|||
|
|
@ -4,12 +4,94 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
using Microsoft.AspNet.Razor.Test.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||
{
|
||||
public class TagHelperDescriptorProviderTest
|
||||
{
|
||||
public static TheoryData RequiredParentData
|
||||
{
|
||||
get
|
||||
{
|
||||
var strongPParent = new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "p",
|
||||
};
|
||||
var strongDivParent = new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "div",
|
||||
};
|
||||
var catchAllPParent = new TagHelperDescriptor
|
||||
{
|
||||
TagName = "*",
|
||||
TypeName = "CatchAllTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "p",
|
||||
};
|
||||
|
||||
return new TheoryData<
|
||||
string, // tagName
|
||||
string, // parentTagName
|
||||
IEnumerable<TagHelperDescriptor>, // availableDescriptors
|
||||
IEnumerable<TagHelperDescriptor>> // expectedDescriptors
|
||||
{
|
||||
{
|
||||
"strong",
|
||||
"p",
|
||||
new[] { strongPParent, strongDivParent },
|
||||
new[] { strongPParent }
|
||||
},
|
||||
{
|
||||
"strong",
|
||||
"div",
|
||||
new[] { strongPParent, strongDivParent, catchAllPParent },
|
||||
new[] { strongDivParent }
|
||||
},
|
||||
{
|
||||
"strong",
|
||||
"p",
|
||||
new[] { strongPParent, strongDivParent, catchAllPParent },
|
||||
new[] { strongPParent, catchAllPParent }
|
||||
},
|
||||
{
|
||||
"custom",
|
||||
"p",
|
||||
new[] { strongPParent, strongDivParent, catchAllPParent },
|
||||
new[] { catchAllPParent }
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RequiredParentData))]
|
||||
public void GetDescriptors_ReturnsDescriptorsWithRequiredAttributes(
|
||||
string tagName,
|
||||
string parentTagName,
|
||||
IEnumerable<TagHelperDescriptor> availableDescriptors,
|
||||
IEnumerable<TagHelperDescriptor> expectedDescriptors)
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TagHelperDescriptorProvider(availableDescriptors);
|
||||
|
||||
// Act
|
||||
var resolvedDescriptors = provider.GetDescriptors(
|
||||
tagName,
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: parentTagName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
public static TheoryData RequiredAttributeData
|
||||
{
|
||||
get
|
||||
|
|
@ -163,10 +245,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(availableDescriptors);
|
||||
|
||||
// Act
|
||||
var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes);
|
||||
var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDescriptors, resolvedDescriptors, TagHelperDescriptorComparer.Default);
|
||||
Assert.Equal(expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -181,7 +263,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var resolvedDescriptors = provider.GetDescriptors("th", attributeNames: Enumerable.Empty<string>());
|
||||
var resolvedDescriptors = provider.GetDescriptors(
|
||||
tagName: "th",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
Assert.Empty(resolvedDescriptors);
|
||||
|
|
@ -197,8 +282,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors("th:div", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptorsSpan = provider.GetDescriptors("th2:span", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||
tagName: "th:div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||
tagName: "th2:span",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
var descriptor = Assert.Single(retrievedDescriptorsDiv);
|
||||
|
|
@ -215,8 +306,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors("th:div", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptorsSpan = provider.GetDescriptors("th:span", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||
tagName: "th:div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||
tagName: "th:span",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
var descriptor = Assert.Single(retrievedDescriptorsDiv);
|
||||
|
|
@ -234,7 +331,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var retrievedDescriptors = provider.GetDescriptors("th:div", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptors = provider.GetDescriptors(
|
||||
tagName: "th:div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
var descriptor = Assert.Single(retrievedDescriptors);
|
||||
|
|
@ -252,7 +352,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors("div", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||
tagName: "div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
Assert.Empty(retrievedDescriptorsDiv);
|
||||
|
|
@ -278,7 +381,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var retrievedDescriptors = provider.GetDescriptors("foo", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptors = provider.GetDescriptors(
|
||||
tagName: "foo",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
Assert.Empty(retrievedDescriptors);
|
||||
|
|
@ -310,8 +416,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var divDescriptors = provider.GetDescriptors("div", attributeNames: Enumerable.Empty<string>());
|
||||
var spanDescriptors = provider.GetDescriptors("span", attributeNames: Enumerable.Empty<string>());
|
||||
var divDescriptors = provider.GetDescriptors(
|
||||
tagName: "div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
var spanDescriptors = provider.GetDescriptors(
|
||||
tagName: "span",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
// For divs
|
||||
|
|
@ -339,7 +451,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act
|
||||
var retrievedDescriptors = provider.GetDescriptors("div", attributeNames: Enumerable.Empty<string>());
|
||||
var retrievedDescriptors = provider.GetDescriptors(
|
||||
tagName: "div",
|
||||
attributeNames: Enumerable.Empty<string>(),
|
||||
parentTagName: "p");
|
||||
|
||||
// Assert
|
||||
var descriptor = Assert.Single(retrievedDescriptors);
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
AssemblyName = "assembly name",
|
||||
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
||||
AllowedChildren = new[] { "allowed child one" },
|
||||
RequiredParent = "parent name",
|
||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||
{
|
||||
Summary = "usage summary",
|
||||
Remarks = "usage remarks",
|
||||
OutputElementHint = "some-tag"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var expectedSerializedDescriptor =
|
||||
|
|
@ -42,6 +43,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" +
|
||||
"[\"required attribute one\",\"required attribute two\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{"+
|
||||
$"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," +
|
||||
|
|
@ -105,6 +107,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," +
|
||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":1," +
|
||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||
|
||||
|
|
@ -143,7 +146,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
IsStringProperty = true
|
||||
},
|
||||
},
|
||||
AllowedChildren = new[] { "allowed child one", "allowed child two" }
|
||||
AllowedChildren = new[] { "allowed child one", "allowed child two" },
|
||||
RequiredParent = "parent name"
|
||||
};
|
||||
|
||||
var expectedSerializedDescriptor =
|
||||
|
|
@ -167,6 +171,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||
|
||||
|
|
@ -191,6 +196,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
$"\"{nameof(TagHelperDescriptor.RequiredAttributes)}\":" +
|
||||
"[\"required attribute one\",\"required attribute two\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":2," +
|
||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{" +
|
||||
$"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," +
|
||||
|
|
@ -204,6 +210,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
AssemblyName = "assembly name",
|
||||
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
||||
AllowedChildren = new[] { "allowed child one", "allowed child two" },
|
||||
RequiredParent = "parent name",
|
||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||
{
|
||||
Summary = "usage summary",
|
||||
|
|
@ -255,6 +262,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," +
|
||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":0," +
|
||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||
var expectedDescriptor = new TagHelperDescriptor
|
||||
|
|
@ -321,6 +329,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
|||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
||||
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," +
|
||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":1," +
|
||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||
var expectedDescriptor = new TagHelperDescriptor
|
||||
|
|
|
|||
|
|
@ -18,6 +18,330 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
{
|
||||
public class TagHelperParseTreeRewriterTest : TagHelperRewritingTestBase
|
||||
{
|
||||
public static TheoryData PartialRequiredParentData
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = CreateDefaultSpanFactory();
|
||||
var blockFactory = new BlockFactory(factory);
|
||||
Func<int, string, RazorError> errorFormatUnclosed = (location, tagName) =>
|
||||
new RazorError(
|
||||
$"Found a malformed '{tagName}' tag helper. Tag helpers must have a start and end tag or be " +
|
||||
"self closing.",
|
||||
new SourceLocation(location, 0, location),
|
||||
tagName.Length);
|
||||
Func<int, string, RazorError> errorFormatNoCloseAngle = (location, tagName) =>
|
||||
new RazorError(
|
||||
$"Missing close angle for tag helper '{tagName}'.",
|
||||
new SourceLocation(location, 0, location),
|
||||
tagName.Length);
|
||||
|
||||
// documentContent, expectedOutput, expectedErrors
|
||||
return new TheoryData<string, MarkupBlock, RazorError[]>
|
||||
{
|
||||
{
|
||||
"<p><strong>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("strong"))),
|
||||
new[] { errorFormatUnclosed(1, "p"), errorFormatUnclosed(4, "strong") }
|
||||
},
|
||||
{
|
||||
"<p><strong></strong>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("strong"))),
|
||||
new[] { errorFormatUnclosed(1, "p") }
|
||||
},
|
||||
{
|
||||
"<p><strong></p><strong>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("strong")),
|
||||
blockFactory.MarkupTagBlock("<strong>")),
|
||||
new[] { errorFormatUnclosed(4, "strong") }
|
||||
},
|
||||
{
|
||||
"<<p><<strong></</strong</strong></p>",
|
||||
new MarkupBlock(
|
||||
blockFactory.MarkupTagBlock("<"),
|
||||
new MarkupTagHelperBlock("p",
|
||||
blockFactory.MarkupTagBlock("<"),
|
||||
new MarkupTagHelperBlock("strong",
|
||||
blockFactory.MarkupTagBlock("</")),
|
||||
blockFactory.MarkupTagBlock("</strong>"))),
|
||||
new[] { errorFormatNoCloseAngle(17, "strong"), errorFormatUnclosed(25, "strong") }
|
||||
},
|
||||
{
|
||||
"<<p><<strong></</strong></strong></p>",
|
||||
new MarkupBlock(
|
||||
blockFactory.MarkupTagBlock("<"),
|
||||
new MarkupTagHelperBlock("p",
|
||||
blockFactory.MarkupTagBlock("<"),
|
||||
new MarkupTagHelperBlock("strong",
|
||||
blockFactory.MarkupTagBlock("</")),
|
||||
blockFactory.MarkupTagBlock("</strong>"))),
|
||||
new[] { errorFormatUnclosed(26, "strong") }
|
||||
},
|
||||
|
||||
{
|
||||
"<<p><<custom></<</custom></custom></p>",
|
||||
new MarkupBlock(
|
||||
blockFactory.MarkupTagBlock("<"),
|
||||
new MarkupTagHelperBlock("p",
|
||||
blockFactory.MarkupTagBlock("<"),
|
||||
new MarkupTagHelperBlock("custom",
|
||||
blockFactory.MarkupTagBlock("</"),
|
||||
blockFactory.MarkupTagBlock("<")),
|
||||
blockFactory.MarkupTagBlock("</custom>"))),
|
||||
new[] { errorFormatUnclosed(27, "custom") }
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PartialRequiredParentData))]
|
||||
public void Rewrite_UnderstandsPartialRequiredParentTags(
|
||||
string documentContent,
|
||||
MarkupBlock expectedOutput,
|
||||
RazorError[] expectedErrors)
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "p",
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "div",
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "*",
|
||||
TypeName = "CatchALlTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "p",
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "p",
|
||||
TypeName = "PTagHelper",
|
||||
AssemblyName = "SomeAssembly"
|
||||
}
|
||||
};
|
||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act & Assert
|
||||
EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors);
|
||||
}
|
||||
|
||||
public static TheoryData NestedVoidSelfClosingRequiredParentData
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = CreateDefaultSpanFactory();
|
||||
var blockFactory = new BlockFactory(factory);
|
||||
|
||||
// documentContent, expectedOutput
|
||||
return new TheoryData<string, MarkupBlock>
|
||||
{
|
||||
{
|
||||
"<input><strong></strong>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("input", TagMode.StartTagOnly),
|
||||
blockFactory.MarkupTagBlock("<strong>"),
|
||||
blockFactory.MarkupTagBlock("</strong>"))
|
||||
},
|
||||
{
|
||||
"<p><input><strong></strong></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("input", TagMode.StartTagOnly),
|
||||
new MarkupTagHelperBlock("strong")))
|
||||
},
|
||||
{
|
||||
"<p><br><strong></strong></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
blockFactory.MarkupTagBlock("<br>"),
|
||||
new MarkupTagHelperBlock("strong")))
|
||||
},
|
||||
{
|
||||
"<p><p><br></p><strong></strong></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("p",
|
||||
blockFactory.MarkupTagBlock("<br>")),
|
||||
new MarkupTagHelperBlock("strong")))
|
||||
},
|
||||
{
|
||||
"<input><strong></strong>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("input", TagMode.StartTagOnly),
|
||||
blockFactory.MarkupTagBlock("<strong>"),
|
||||
blockFactory.MarkupTagBlock("</strong>"))
|
||||
},
|
||||
{
|
||||
"<p><input /><strong /></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("input", TagMode.SelfClosing),
|
||||
new MarkupTagHelperBlock("strong", TagMode.SelfClosing)))
|
||||
},
|
||||
{
|
||||
"<p><br /><strong /></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
blockFactory.MarkupTagBlock("<br />"),
|
||||
new MarkupTagHelperBlock("strong", TagMode.SelfClosing)))
|
||||
},
|
||||
{
|
||||
"<p><p><br /></p><strong /></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("p",
|
||||
blockFactory.MarkupTagBlock("<br />")),
|
||||
new MarkupTagHelperBlock("strong", TagMode.SelfClosing)))
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NestedVoidSelfClosingRequiredParentData))]
|
||||
public void Rewrite_UnderstandsNestedVoidSelfClosingRequiredParent(
|
||||
string documentContent,
|
||||
MarkupBlock expectedOutput)
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "input",
|
||||
TypeName = "InputTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
TagStructure = TagStructure.WithoutEndTag,
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "p",
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "input",
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "p",
|
||||
TypeName = "PTagHelper",
|
||||
AssemblyName = "SomeAssembly"
|
||||
}
|
||||
};
|
||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act & Assert
|
||||
EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors: new RazorError[0]);
|
||||
}
|
||||
|
||||
public static TheoryData NestedRequiredParentData
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = CreateDefaultSpanFactory();
|
||||
var blockFactory = new BlockFactory(factory);
|
||||
|
||||
// documentContent, expectedOutput
|
||||
return new TheoryData<string, MarkupBlock>
|
||||
{
|
||||
{
|
||||
"<strong></strong>",
|
||||
new MarkupBlock(
|
||||
blockFactory.MarkupTagBlock("<strong>"),
|
||||
blockFactory.MarkupTagBlock("</strong>"))
|
||||
},
|
||||
{
|
||||
"<p><strong></strong></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("strong")))
|
||||
},
|
||||
{
|
||||
"<div><strong></strong></div>",
|
||||
new MarkupBlock(
|
||||
blockFactory.MarkupTagBlock("<div>"),
|
||||
new MarkupTagHelperBlock("strong"),
|
||||
blockFactory.MarkupTagBlock("</div>"))
|
||||
},
|
||||
{
|
||||
"<strong><strong></strong></strong>",
|
||||
new MarkupBlock(
|
||||
blockFactory.MarkupTagBlock("<strong>"),
|
||||
blockFactory.MarkupTagBlock("<strong>"),
|
||||
blockFactory.MarkupTagBlock("</strong>"),
|
||||
blockFactory.MarkupTagBlock("</strong>"))
|
||||
},
|
||||
{
|
||||
"<p><strong><strong></strong></strong></p>",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("p",
|
||||
new MarkupTagHelperBlock("strong",
|
||||
blockFactory.MarkupTagBlock("<strong>"),
|
||||
blockFactory.MarkupTagBlock("</strong>"))))
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NestedRequiredParentData))]
|
||||
public void Rewrite_UnderstandsNestedRequiredParent(string documentContent, MarkupBlock expectedOutput)
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "p",
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "strong",
|
||||
TypeName = "StrongTagHelper",
|
||||
AssemblyName = "SomeAssembly",
|
||||
RequiredParent = "div",
|
||||
},
|
||||
new TagHelperDescriptor
|
||||
{
|
||||
TagName = "p",
|
||||
TypeName = "PTagHelper",
|
||||
AssemblyName = "SomeAssembly"
|
||||
}
|
||||
};
|
||||
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act & Assert
|
||||
EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors: new RazorError[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rewrite_UnderstandsTagHelperPrefixAndAllowedChildren()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue