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);
|
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)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -195,4 +195,7 @@
|
||||||
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace" xml:space="preserve">
|
<data name="TagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace" xml:space="preserve">
|
||||||
<value>Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.</value>
|
<value>Invalid '{0}' tag name for tag helper '{1}'. Name cannot be null or whitespace.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve">
|
||||||
|
<value>Parent Tag</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -79,5 +79,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public TagStructure TagStructure { get; set; }
|
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>(),
|
requiredAttributes: Enumerable.Empty<string>(),
|
||||||
allowedChildren: allowedChildren,
|
allowedChildren: allowedChildren,
|
||||||
tagStructure: default(TagStructure),
|
tagStructure: default(TagStructure),
|
||||||
|
parentTag: null,
|
||||||
designTimeDescriptor: typeDesignTimeDescriptor)
|
designTimeDescriptor: typeDesignTimeDescriptor)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +232,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
attributeDescriptors,
|
attributeDescriptors,
|
||||||
requiredAttributes,
|
requiredAttributes,
|
||||||
allowedChildren,
|
allowedChildren,
|
||||||
|
targetElementAttribute.ParentTag,
|
||||||
targetElementAttribute.TagStructure,
|
targetElementAttribute.TagStructure,
|
||||||
designTimeDescriptor);
|
designTimeDescriptor);
|
||||||
}
|
}
|
||||||
|
|
@ -242,6 +244,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
IEnumerable<string> requiredAttributes,
|
IEnumerable<string> requiredAttributes,
|
||||||
IEnumerable<string> allowedChildren,
|
IEnumerable<string> allowedChildren,
|
||||||
|
string parentTag,
|
||||||
TagStructure tagStructure,
|
TagStructure tagStructure,
|
||||||
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
TagHelperDesignTimeDescriptor designTimeDescriptor)
|
||||||
{
|
{
|
||||||
|
|
@ -253,6 +256,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Attributes = attributeDescriptors,
|
Attributes = attributeDescriptors,
|
||||||
RequiredAttributes = requiredAttributes,
|
RequiredAttributes = requiredAttributes,
|
||||||
AllowedChildren = allowedChildren,
|
AllowedChildren = allowedChildren,
|
||||||
|
RequiredParent = parentTag,
|
||||||
TagStructure = tagStructure,
|
TagStructure = tagStructure,
|
||||||
DesignTimeDescriptor = designTimeDescriptor
|
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(
|
private static bool ValidateName(
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Attributes = descriptor.Attributes,
|
Attributes = descriptor.Attributes,
|
||||||
RequiredAttributes = descriptor.RequiredAttributes,
|
RequiredAttributes = descriptor.RequiredAttributes,
|
||||||
AllowedChildren = descriptor.AllowedChildren,
|
AllowedChildren = descriptor.AllowedChildren,
|
||||||
|
RequiredParent = descriptor.RequiredParent,
|
||||||
TagStructure = descriptor.TagStructure,
|
TagStructure = descriptor.TagStructure,
|
||||||
DesignTimeDescriptor = descriptor.DesignTimeDescriptor
|
DesignTimeDescriptor = descriptor.DesignTimeDescriptor
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNet.Razor.Test.Internal
|
||||||
Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal);
|
Assert.Equal(descriptorX.TagName, descriptorY.TagName, StringComparer.Ordinal);
|
||||||
Assert.Equal(descriptorX.Prefix, descriptorY.Prefix, StringComparer.Ordinal);
|
Assert.Equal(descriptorX.Prefix, descriptorY.Prefix, StringComparer.Ordinal);
|
||||||
Assert.Equal(descriptorX.RequiredAttributes, descriptorY.RequiredAttributes, StringComparer.Ordinal);
|
Assert.Equal(descriptorX.RequiredAttributes, descriptorY.RequiredAttributes, StringComparer.Ordinal);
|
||||||
|
Assert.Equal(descriptorX.RequiredParent, descriptorY.RequiredParent, StringComparer.Ordinal);
|
||||||
|
|
||||||
if (descriptorX.AllowedChildren != descriptorY.AllowedChildren)
|
if (descriptorX.AllowedChildren != descriptorY.AllowedChildren)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
||||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||||
|
|
@ -15,16 +14,38 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
public class TagHelperParseTreeRewriter : ISyntaxTreeRewriter
|
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 TagHelperDescriptorProvider _provider;
|
||||||
private Stack<TagHelperBlockTracker> _trackerStack;
|
private Stack<TagBlockTracker> _trackerStack;
|
||||||
private TagHelperBlockTracker _currentTagHelperTracker;
|
private TagHelperBlockTracker _currentTagHelperTracker;
|
||||||
private Stack<BlockBuilder> _blockStack;
|
private Stack<BlockBuilder> _blockStack;
|
||||||
private BlockBuilder _currentBlock;
|
private BlockBuilder _currentBlock;
|
||||||
|
private string _currentParentTagName;
|
||||||
|
|
||||||
public TagHelperParseTreeRewriter(TagHelperDescriptorProvider provider)
|
public TagHelperParseTreeRewriter(TagHelperDescriptorProvider provider)
|
||||||
{
|
{
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
_trackerStack = new Stack<TagHelperBlockTracker>();
|
_trackerStack = new Stack<TagBlockTracker>();
|
||||||
_blockStack = new Stack<BlockBuilder>();
|
_blockStack = new Stack<BlockBuilder>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +65,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
ChunkGenerator = input.ChunkGenerator
|
ChunkGenerator = input.ChunkGenerator
|
||||||
});
|
});
|
||||||
|
|
||||||
var activeTagHelpers = _trackerStack.Count;
|
var activeTrackers = _trackerStack.Count;
|
||||||
|
|
||||||
foreach (var child in input.Children)
|
foreach (var child in input.Children)
|
||||||
{
|
{
|
||||||
|
|
@ -60,6 +81,8 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
TrackTagBlock(childBlock);
|
||||||
|
|
||||||
// Non-TagHelper tag.
|
// Non-TagHelper tag.
|
||||||
ValidateParentTagHelperAllowsPlainTag(childBlock, context.ErrorSink);
|
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
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// 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();
|
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)
|
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)
|
// 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 tracker = _currentTagHelperTracker;
|
||||||
var tagNameScope = tracker?.Builder.TagName ?? string.Empty;
|
var tagNameScope = tracker?.TagName ?? string.Empty;
|
||||||
|
|
||||||
if (!IsEndTag(tagBlock))
|
if (!IsEndTag(tagBlock))
|
||||||
{
|
{
|
||||||
// We're now in a start tag block, we first need to see if the tag block is a tag helper.
|
// 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);
|
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 there aren't any TagHelperDescriptors registered then we aren't a TagHelper
|
||||||
if (!descriptors.Any())
|
if (!descriptors.Any())
|
||||||
|
|
@ -193,7 +244,10 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
}
|
}
|
||||||
else
|
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
|
// 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.
|
// 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(
|
errorSink.OnError(
|
||||||
errorStart,
|
errorStart,
|
||||||
RazorResources.FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent(
|
RazorResources.FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent(
|
||||||
_currentTagHelperTracker.Builder.TagName,
|
_currentTagHelperTracker.TagName,
|
||||||
allowedChildrenString),
|
allowedChildrenString),
|
||||||
length);
|
length);
|
||||||
}
|
}
|
||||||
|
|
@ -343,7 +397,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
var allowedChildrenString = string.Join(", ", tracker.AllowedChildren);
|
var allowedChildrenString = string.Join(", ", tracker.AllowedChildren);
|
||||||
var errorMessage = RazorResources.FormatTagHelperParseTreeRewriter_InvalidNestedTag(
|
var errorMessage = RazorResources.FormatTagHelperParseTreeRewriter_InvalidNestedTag(
|
||||||
tagName,
|
tagName,
|
||||||
tracker.Builder.TagName,
|
tracker.TagName,
|
||||||
allowedChildrenString);
|
allowedChildrenString);
|
||||||
var errorStart = GetTagDeclarationErrorStart(tagBlock);
|
var errorStart = GetTagDeclarationErrorStart(tagBlock);
|
||||||
|
|
||||||
|
|
@ -450,11 +504,23 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
|
|
||||||
private void BuildCurrentlyTrackedTagHelperBlock(Block endTag)
|
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
|
// Track the original end tag so the editor knows where each piece of the TagHelperBlock lies
|
||||||
// for formatting.
|
// 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();
|
BuildCurrentlyTrackedBlock();
|
||||||
}
|
}
|
||||||
|
|
@ -481,7 +547,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
private void TrackTagHelperBlock(TagHelperBlockBuilder builder)
|
private void TrackTagHelperBlock(TagHelperBlockBuilder builder)
|
||||||
{
|
{
|
||||||
_currentTagHelperTracker = new TagHelperBlockTracker(builder);
|
_currentTagHelperTracker = new TagHelperBlockTracker(builder);
|
||||||
_trackerStack.Push(_currentTagHelperTracker);
|
PushTrackerStack(_currentTagHelperTracker);
|
||||||
|
|
||||||
TrackBlock(builder);
|
TrackBlock(builder);
|
||||||
}
|
}
|
||||||
|
|
@ -492,7 +558,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
|
|
||||||
foreach (var tracker in _trackerStack)
|
foreach (var tracker in _trackerStack)
|
||||||
{
|
{
|
||||||
if (tracker.Builder.TagName.Equals(tagName, StringComparison.OrdinalIgnoreCase))
|
if (tracker.IsTagHelper && tracker.TagName.Equals(tagName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -521,7 +587,16 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
{
|
{
|
||||||
for (var i = 0; i < count; i++)
|
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(
|
context.ErrorSink.OnError(
|
||||||
SourceLocation.Advance(malformedTagHelper.Start, "<"),
|
SourceLocation.Advance(malformedTagHelper.Start, "<"),
|
||||||
|
|
@ -570,11 +645,46 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
|
||||||
Debug.Assert(tagBlock.Children.First() is Span);
|
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;
|
private IEnumerable<string> _prefixedAllowedChildren;
|
||||||
|
|
||||||
public TagHelperBlockTracker(TagHelperBlockBuilder builder)
|
public TagHelperBlockTracker(TagHelperBlockBuilder builder)
|
||||||
|
: base(builder.TagName, isTagHelper: true)
|
||||||
{
|
{
|
||||||
Builder = builder;
|
Builder = builder;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,12 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
/// <remarks><c>null</c> indicates all children are allowed.</remarks>
|
/// <remarks><c>null</c> indicates all children are allowed.</remarks>
|
||||||
public IEnumerable<string> AllowedChildren { get; set; }
|
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>
|
/// <summary>
|
||||||
/// The expected tag structure.
|
/// The expected tag structure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) &&
|
string.Equals(descriptorX.TypeName, descriptorY.TypeName, StringComparison.Ordinal) &&
|
||||||
string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) &&
|
string.Equals(descriptorX.TagName, descriptorY.TagName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) &&
|
string.Equals(descriptorX.AssemblyName, descriptorY.AssemblyName, StringComparison.Ordinal) &&
|
||||||
|
string.Equals(
|
||||||
|
descriptorX.RequiredParent,
|
||||||
|
descriptorY.RequiredParent,
|
||||||
|
StringComparison.OrdinalIgnoreCase) &&
|
||||||
Enumerable.SequenceEqual(
|
Enumerable.SequenceEqual(
|
||||||
descriptorX.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
descriptorX.RequiredAttributes.OrderBy(attribute => attribute, StringComparer.OrdinalIgnoreCase),
|
||||||
descriptorY.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.TypeName, StringComparer.Ordinal);
|
||||||
hashCodeCombiner.Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase);
|
hashCodeCombiner.Add(descriptor.TagName, StringComparer.OrdinalIgnoreCase);
|
||||||
hashCodeCombiner.Add(descriptor.AssemblyName, StringComparer.Ordinal);
|
hashCodeCombiner.Add(descriptor.AssemblyName, StringComparer.Ordinal);
|
||||||
|
hashCodeCombiner.Add(descriptor.RequiredParent, StringComparer.OrdinalIgnoreCase);
|
||||||
hashCodeCombiner.Add(descriptor.TagStructure);
|
hashCodeCombiner.Add(descriptor.TagStructure);
|
||||||
|
|
||||||
var attributes = descriptor.RequiredAttributes.OrderBy(
|
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
|
/// <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>
|
/// 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="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"/>.
|
/// <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
|
/// Will return an empty <see cref="Enumerable" /> if no <see cref="TagHelperDescriptor"/>s are
|
||||||
/// found.</returns>
|
/// 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) &&
|
if (!string.IsNullOrEmpty(_tagHelperPrefix) &&
|
||||||
(tagName.Length <= _tagHelperPrefix.Length ||
|
(tagName.Length <= _tagHelperPrefix.Length ||
|
||||||
|
|
@ -75,10 +79,20 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributeNames);
|
var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributeNames);
|
||||||
|
applicableDescriptors = ApplyParentTagFilter(applicableDescriptors, parentTagName);
|
||||||
|
|
||||||
return applicableDescriptors;
|
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(
|
private IEnumerable<TagHelperDescriptor> ApplyRequiredAttributes(
|
||||||
IEnumerable<TagHelperDescriptor> descriptors,
|
IEnumerable<TagHelperDescriptor> descriptors,
|
||||||
IEnumerable<string> attributeNames)
|
IEnumerable<string> attributeNames)
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,100 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
{
|
{
|
||||||
public abstract class TagHelperDescriptorFactoryTest
|
public abstract class TagHelperDescriptorFactoryTest
|
||||||
{
|
{
|
||||||
protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly =
|
protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly =
|
||||||
typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName();
|
typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName();
|
||||||
|
|
||||||
protected static readonly string AssemblyName = TagHelperDescriptorFactoryTestAssembly.Name;
|
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
|
public static TheoryData RestrictChildrenData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -1713,6 +1802,41 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Assert.Equal(expectedErrors, errorSink.Errors);
|
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(
|
private static TheoryData<string, string[]> GetInvalidNameOrPrefixData(
|
||||||
Func<string, string, string> onNameError,
|
Func<string, string, string> onNameError,
|
||||||
string whitespaceErrorString,
|
string whitespaceErrorString,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,23 @@ using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||||
|
|
||||||
namespace 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")]
|
[RestrictChildren("p")]
|
||||||
public class RestrictChildrenTagHelper
|
public class RestrictChildrenTagHelper
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,94 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||||
|
using Microsoft.AspNet.Razor.Test.Internal;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.TagHelpers
|
namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
{
|
{
|
||||||
public class TagHelperDescriptorProviderTest
|
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
|
public static TheoryData RequiredAttributeData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -163,10 +245,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(availableDescriptors);
|
var provider = new TagHelperDescriptorProvider(availableDescriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes);
|
var resolvedDescriptors = provider.GetDescriptors(tagName, providedAttributes, parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedDescriptors, resolvedDescriptors, TagHelperDescriptorComparer.Default);
|
Assert.Equal(expectedDescriptors, resolvedDescriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -181,7 +263,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var resolvedDescriptors = provider.GetDescriptors("th", attributeNames: Enumerable.Empty<string>());
|
var resolvedDescriptors = provider.GetDescriptors(
|
||||||
|
tagName: "th",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(resolvedDescriptors);
|
Assert.Empty(resolvedDescriptors);
|
||||||
|
|
@ -197,8 +282,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptorsDiv = provider.GetDescriptors("th:div", attributeNames: Enumerable.Empty<string>());
|
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||||
var retrievedDescriptorsSpan = provider.GetDescriptors("th2:span", attributeNames: Enumerable.Empty<string>());
|
tagName: "th:div",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||||
|
tagName: "th2:span",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var descriptor = Assert.Single(retrievedDescriptorsDiv);
|
var descriptor = Assert.Single(retrievedDescriptorsDiv);
|
||||||
|
|
@ -215,8 +306,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptorsDiv = provider.GetDescriptors("th:div", attributeNames: Enumerable.Empty<string>());
|
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||||
var retrievedDescriptorsSpan = provider.GetDescriptors("th:span", attributeNames: Enumerable.Empty<string>());
|
tagName: "th:div",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
var retrievedDescriptorsSpan = provider.GetDescriptors(
|
||||||
|
tagName: "th:span",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var descriptor = Assert.Single(retrievedDescriptorsDiv);
|
var descriptor = Assert.Single(retrievedDescriptorsDiv);
|
||||||
|
|
@ -234,7 +331,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// 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
|
// Assert
|
||||||
var descriptor = Assert.Single(retrievedDescriptors);
|
var descriptor = Assert.Single(retrievedDescriptors);
|
||||||
|
|
@ -252,7 +352,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptorsDiv = provider.GetDescriptors("div", attributeNames: Enumerable.Empty<string>());
|
var retrievedDescriptorsDiv = provider.GetDescriptors(
|
||||||
|
tagName: "div",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(retrievedDescriptorsDiv);
|
Assert.Empty(retrievedDescriptorsDiv);
|
||||||
|
|
@ -278,7 +381,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptors = provider.GetDescriptors("foo", attributeNames: Enumerable.Empty<string>());
|
var retrievedDescriptors = provider.GetDescriptors(
|
||||||
|
tagName: "foo",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(retrievedDescriptors);
|
Assert.Empty(retrievedDescriptors);
|
||||||
|
|
@ -310,8 +416,14 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var divDescriptors = provider.GetDescriptors("div", attributeNames: Enumerable.Empty<string>());
|
var divDescriptors = provider.GetDescriptors(
|
||||||
var spanDescriptors = provider.GetDescriptors("span", attributeNames: Enumerable.Empty<string>());
|
tagName: "div",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
var spanDescriptors = provider.GetDescriptors(
|
||||||
|
tagName: "span",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// For divs
|
// For divs
|
||||||
|
|
@ -339,7 +451,10 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
var provider = new TagHelperDescriptorProvider(descriptors);
|
var provider = new TagHelperDescriptorProvider(descriptors);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var retrievedDescriptors = provider.GetDescriptors("div", attributeNames: Enumerable.Empty<string>());
|
var retrievedDescriptors = provider.GetDescriptors(
|
||||||
|
tagName: "div",
|
||||||
|
attributeNames: Enumerable.Empty<string>(),
|
||||||
|
parentTagName: "p");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var descriptor = Assert.Single(retrievedDescriptors);
|
var descriptor = Assert.Single(retrievedDescriptors);
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,13 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
AssemblyName = "assembly name",
|
AssemblyName = "assembly name",
|
||||||
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
||||||
AllowedChildren = new[] { "allowed child one" },
|
AllowedChildren = new[] { "allowed child one" },
|
||||||
|
RequiredParent = "parent name",
|
||||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||||
{
|
{
|
||||||
Summary = "usage summary",
|
Summary = "usage summary",
|
||||||
Remarks = "usage remarks",
|
Remarks = "usage remarks",
|
||||||
OutputElementHint = "some-tag"
|
OutputElementHint = "some-tag"
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedSerializedDescriptor =
|
var expectedSerializedDescriptor =
|
||||||
|
|
@ -42,6 +43,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" +
|
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":" +
|
||||||
"[\"required attribute one\",\"required attribute two\"]," +
|
"[\"required attribute one\",\"required attribute two\"]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\"]," +
|
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\"]," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{"+
|
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{"+
|
||||||
$"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," +
|
$"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," +
|
||||||
|
|
@ -105,6 +107,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":1," +
|
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":1," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||||
|
|
||||||
|
|
@ -143,7 +146,8 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
IsStringProperty = true
|
IsStringProperty = true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AllowedChildren = new[] { "allowed child one", "allowed child two" }
|
AllowedChildren = new[] { "allowed child one", "allowed child two" },
|
||||||
|
RequiredParent = "parent name"
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedSerializedDescriptor =
|
var expectedSerializedDescriptor =
|
||||||
|
|
@ -167,6 +171,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
$"\"{ nameof(TagHelperDescriptor.TagStructure) }\":0," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||||
|
|
||||||
|
|
@ -191,6 +196,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{nameof(TagHelperDescriptor.RequiredAttributes)}\":" +
|
$"\"{nameof(TagHelperDescriptor.RequiredAttributes)}\":" +
|
||||||
"[\"required attribute one\",\"required attribute two\"]," +
|
"[\"required attribute one\",\"required attribute two\"]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":[\"allowed child one\",\"allowed child two\"]," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":\"parent name\"," +
|
||||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":2," +
|
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":2," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{" +
|
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":{{" +
|
||||||
$"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," +
|
$"\"{ nameof(TagHelperDesignTimeDescriptor.Summary) }\":\"usage summary\"," +
|
||||||
|
|
@ -204,6 +210,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
AssemblyName = "assembly name",
|
AssemblyName = "assembly name",
|
||||||
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
RequiredAttributes = new[] { "required attribute one", "required attribute two" },
|
||||||
AllowedChildren = new[] { "allowed child one", "allowed child two" },
|
AllowedChildren = new[] { "allowed child one", "allowed child two" },
|
||||||
|
RequiredParent = "parent name",
|
||||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||||
{
|
{
|
||||||
Summary = "usage summary",
|
Summary = "usage summary",
|
||||||
|
|
@ -255,6 +262,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," +
|
||||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":0," +
|
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":0," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||||
var expectedDescriptor = new TagHelperDescriptor
|
var expectedDescriptor = new TagHelperDescriptor
|
||||||
|
|
@ -321,6 +329,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
$"\"{ nameof(TagHelperAttributeDescriptor.DesignTimeDescriptor) }\":null}}]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
$"\"{ nameof(TagHelperDescriptor.RequiredAttributes) }\":[]," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
$"\"{ nameof(TagHelperDescriptor.AllowedChildren) }\":null," +
|
||||||
|
$"\"{ nameof(TagHelperDescriptor.RequiredParent) }\":null," +
|
||||||
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":1," +
|
$"\"{nameof(TagHelperDescriptor.TagStructure)}\":1," +
|
||||||
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
$"\"{ nameof(TagHelperDescriptor.DesignTimeDescriptor) }\":null}}";
|
||||||
var expectedDescriptor = new TagHelperDescriptor
|
var expectedDescriptor = new TagHelperDescriptor
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,330 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
||||||
{
|
{
|
||||||
public class TagHelperParseTreeRewriterTest : TagHelperRewritingTestBase
|
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]
|
[Fact]
|
||||||
public void Rewrite_UnderstandsTagHelperPrefixAndAllowedChildren()
|
public void Rewrite_UnderstandsTagHelperPrefixAndAllowedChildren()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue