Add tests to validate minimized attributes.

- Added parse level rewriting tests to validate new TagHelper rewritten structures for minimized attributes.
- Updated existing parser tests to understand minimized attributes.
- Added codegen test to validate understanding of minimized attributes.
- Added TagHelperExecutionContext tests to validate maintaining of runtime TagHelperOutput tests.
- Refactored part of the TagHelperParseTreeRewriterTest file into a base class file so we can make better rewriting tests.

#220
This commit is contained in:
N. Taylor Mullen 2015-05-15 10:56:02 -07:00
parent 6fa3e405af
commit 0882ff4a13
13 changed files with 1198 additions and 96 deletions

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
{
@ -26,16 +25,13 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Normal comparer (TagHelperAttribute.Equals()) doesn't care about the Name case, in tests we do.
return attributeX != null &&
string.Equals(attributeX.Name, attributeY.Name, StringComparison.Ordinal) &&
Equals(attributeX.Value, attributeY.Value);
attributeX.Minimized == attributeY.Minimized &&
(attributeX.Minimized || Equals(attributeX.Value, attributeY.Value));
}
public int GetHashCode(IReadOnlyTagHelperAttribute attribute)
{
return HashCodeCombiner
.Start()
.Add(attribute.Name, StringComparer.Ordinal)
.Add(attribute.Value)
.CombinedHash;
return attribute.GetHashCode();
}
}
}

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
// Arrange & Act
var executionContext = new TagHelperExecutionContext("p", selfClosing);
// Assert
// Assert
Assert.Equal(selfClosing, executionContext.SelfClosing);
}
@ -202,6 +202,54 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
CaseSensitiveTagHelperAttributeComparer.Default);
}
[Fact]
public void AddMinimizedHtmlAttribute_MaintainsHTMLAttributes()
{
// Arrange
var executionContext = new TagHelperExecutionContext("input", selfClosing: true);
var expectedAttributes = new TagHelperAttributeList
{
["checked"] = new TagHelperAttribute { Name = "checked", Minimized = true },
["visible"] = new TagHelperAttribute { Name = "visible", Minimized = true }
};
// Act
executionContext.AddMinimizedHtmlAttribute("checked");
executionContext.AddMinimizedHtmlAttribute("visible");
// Assert
Assert.Equal(
expectedAttributes,
executionContext.HTMLAttributes,
CaseSensitiveTagHelperAttributeComparer.Default);
}
[Fact]
public void AddMinimizedHtmlAttribute_MaintainsHTMLAttributes_SomeMinimized()
{
// Arrange
var executionContext = new TagHelperExecutionContext("input", selfClosing: true);
var expectedAttributes = new TagHelperAttributeList
{
{ "class", "btn" },
{ "foo", "bar" }
};
expectedAttributes.Add(new TagHelperAttribute { Name = "checked", Minimized = true });
expectedAttributes.Add(new TagHelperAttribute { Name = "visible", Minimized = true });
// Act
executionContext.AddHtmlAttribute("class", "btn");
executionContext.AddHtmlAttribute("foo", "bar");
executionContext.AddMinimizedHtmlAttribute("checked");
executionContext.AddMinimizedHtmlAttribute("visible");
// Assert
Assert.Equal(
expectedAttributes,
executionContext.HTMLAttributes,
CaseSensitiveTagHelperAttributeComparer.Default);
}
[Fact]
public void TagHelperExecutionContext_MaintainsAllAttributes()
{

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Test.Framework
@ -19,12 +20,28 @@ namespace Microsoft.AspNet.Razor.Test.Framework
return EscapedMarkupTagBlock(prefix, suffix, AcceptedCharacters.Any);
}
public Block EscapedMarkupTagBlock(string prefix, string suffix, AcceptedCharacters acceptedCharacters)
public Block EscapedMarkupTagBlock(string prefix, string suffix, params SyntaxTreeNode[] children)
{
return new MarkupTagBlock(
_factory.Markup(prefix),
_factory.BangEscape(),
_factory.Markup(suffix).Accepts(acceptedCharacters));
return EscapedMarkupTagBlock(prefix, suffix, AcceptedCharacters.Any, children);
}
public Block EscapedMarkupTagBlock(
string prefix,
string suffix,
AcceptedCharacters acceptedCharacters,
params SyntaxTreeNode[] children)
{
var newChildren = new List<SyntaxTreeNode>(
new SyntaxTreeNode[]
{
_factory.Markup(prefix),
_factory.BangEscape(),
_factory.Markup(suffix).Accepts(acceptedCharacters)
});
newChildren.AddRange(children);
return new MarkupTagBlock(newChildren.ToArray());
}
public Block MarkupTagBlock(string content)

View File

@ -303,6 +303,7 @@ namespace Microsoft.AspNet.Razor.Test.Framework
if (actual == null)
{
AddNullActualError(collector, actual, expected);
return;
}
if (actual.IsBlock != expected.IsBlock)
@ -335,7 +336,14 @@ namespace Microsoft.AspNet.Razor.Test.Framework
collector.AddMessage("{0} - PASSED :: Attribute names match", expected.Key);
}
EvaluateSyntaxTreeNode(collector, actual.Value, expected.Value);
if (actual.Value == null && expected.Value == null)
{
collector.AddMessage("{0} - PASSED :: Minimized attribute values match", expected.Key);
}
else
{
EvaluateSyntaxTreeNode(collector, actual.Value, expected.Value);
}
}
private static void EvaluateSpan(ErrorCollector collector, Span actual, Span expected)

View File

@ -19,6 +19,44 @@ namespace Microsoft.AspNet.Razor.Test.Generator
private static IEnumerable<TagHelperDescriptor> PrefixedPAndInputTagHelperDescriptors
=> BuildPAndInputTagHelperDescriptors("THS");
private static IEnumerable<TagHelperDescriptor> MinimizedTagHelpers_Descriptors
{
get
{
return new[]
{
new TagHelperDescriptor(
tagName: "*",
typeName: "CatchAllTagHelper",
assemblyName: "SomeAssembly",
attributes: new[]
{
new TagHelperAttributeDescriptor(
"catchall-bound-string",
"BoundRequiredString",
typeof(string).FullName),
},
requiredAttributes: new[] { "catchall-unbound-required" }),
new TagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "SomeAssembly",
attributes: new[]
{
new TagHelperAttributeDescriptor(
"input-bound-required-string",
"BoundRequiredString",
typeof(string).FullName),
new TagHelperAttributeDescriptor(
"input-bound-string",
"BoundString",
typeof(string).FullName)
},
requiredAttributes: new[] { "input-bound-required-string", "input-unbound-required" }),
};
}
}
private static IEnumerable<TagHelperDescriptor> DuplicateTargetTagHelperDescriptors
{
get
@ -207,6 +245,20 @@ namespace Microsoft.AspNet.Razor.Test.Generator
AttributeTargetingTagHelperDescriptors,
AttributeTargetingTagHelperDescriptors,
true
},
{
"MinimizedTagHelpers",
"MinimizedTagHelpers",
MinimizedTagHelpers_Descriptors,
MinimizedTagHelpers_Descriptors,
false
},
{
"MinimizedTagHelpers",
"MinimizedTagHelpers.DesignTime",
MinimizedTagHelpers_Descriptors,
MinimizedTagHelpers_Descriptors,
true
}
};
}

View File

@ -70,7 +70,9 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged<string>(" href=", 2, 0, 2), suffix: new LocationTagged<string>(string.Empty, 11, 0, 11)),
Factory.Markup(" href=").With(SpanCodeGenerator.Null),
Factory.Markup("Foo").With(new LiteralAttributeCodeGenerator(prefix: new LocationTagged<string>(string.Empty, 8, 0, 8), value: new LocationTagged<string>("Foo", 8, 0, 8)))),
Factory.Markup(" Bar Baz />").Accepts(AcceptedCharacters.None))));
new MarkupBlock(Factory.Markup(" Bar")),
new MarkupBlock(Factory.Markup(" Baz")),
Factory.Markup(" />").Accepts(AcceptedCharacters.None))));
}
[Fact]

View File

@ -72,7 +72,11 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
ParseDocumentTest("<div ><p class = 'bar'> Foo </p></div >",
new MarkupBlock(
BlockFactory.MarkupTagBlock("<div >"),
BlockFactory.MarkupTagBlock("<p class = 'bar'>"),
new MarkupTagBlock(
Factory.Markup("<p"),
new MarkupBlock(
Factory.Markup(" class")),
Factory.Markup(" = 'bar'>")),
Factory.Markup(" Foo "),
BlockFactory.MarkupTagBlock("</p>"),
BlockFactory.MarkupTagBlock("</div >")));

View File

@ -0,0 +1,766 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Test.Framework;
using Microsoft.AspNet.Razor.Test.TagHelpers;
using Xunit;
namespace Microsoft.AspNet.Razor.TagHelpers
{
public class TagHelperBlockRewriterTest : TagHelperRewritingTestBase
{
public static TheoryData MinimizedAttributeData_Document
{
get
{
var factory = CreateDefaultSpanFactory();
var noErrors = new RazorError[0];
var errorFormat = "Attribute '{0}' on tag helper element '{1}' requires a value. Tag helper bound " +
"attributes of type '{2}' cannot be empty or contain only whitespace.";
var stringType = typeof(string).FullName;
var intType = typeof(int).FullName;
var expressionString = "@DateTime.Now + 1";
var expression = new MarkupBlock(
new MarkupBlock(
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace))),
factory.Markup(" +"),
factory.Markup(" 1"));
// documentContent, expectedOutput, expectedErrors
return new TheoryData<string, MarkupBlock, RazorError[]>
{
{
"<input unbound-required />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
})),
noErrors
},
{
"<p bound-string></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-string", "p", stringType), 3, 0, 3, 12)
}
},
{
"<input bound-required-string />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-string", "input", stringType), 7, 0, 7, 21)
}
},
{
"<input bound-required-int />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-int", "input", intType), 7, 0, 7, 18)
}
},
{
"<p bound-int></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
})),
new[] { new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 3, 0, 3, 9) }
},
{
"<input unbound-required bound-required-string />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-string", "input", stringType),
absoluteIndex: 24,
lineIndex: 0,
columnIndex: 24,
length: 21)
}
},
{
"<p bound-int bound-string></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
})),
new[]
{
new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 3, 0, 3, 9),
new RazorError(string.Format(errorFormat, "bound-string", "p", stringType), 13, 0, 13, 12),
}
},
{
"<input bound-required-int unbound-required bound-required-string />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-int", "input", intType), 7, 0, 7, 18),
new RazorError(
string.Format(errorFormat, "bound-required-string", "input", stringType),
absoluteIndex: 43,
lineIndex: 0,
columnIndex: 43,
length: 21)
}
},
{
"<p bound-int bound-string bound-string></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
})),
new[]
{
new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 3, 0, 3, 9),
new RazorError(string.Format(errorFormat, "bound-string", "p", stringType), 13, 0, 13, 12),
new RazorError(string.Format(errorFormat, "bound-string", "p", stringType), 26, 0, 26, 12),
}
},
{
"<input unbound-required class='btn' />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
})),
noErrors
},
{
"<p bound-string class='btn'></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-string", "p", stringType),
absoluteIndex: 3,
lineIndex: 0,
columnIndex: 3,
length: 12)
}
},
{
"<input class='btn' unbound-required />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
})),
noErrors
},
{
"<p class='btn' bound-string></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-string", "p", stringType),
absoluteIndex: 15,
lineIndex: 0,
columnIndex: 15,
length: 12)
}
},
{
"<input bound-required-string class='btn' />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-string", "input", stringType),
absoluteIndex: 7,
lineIndex: 0,
columnIndex: 7,
length: 21)
}
},
{
"<input class='btn' bound-required-string />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-string", "input", stringType),
absoluteIndex: 19,
lineIndex: 0,
columnIndex: 19,
length: 21)
}
},
{
"<input bound-required-int class='btn' />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-int", "input", intType), 7, 0, 7, 18)
}
},
{
"<p bound-int class='btn'></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
})),
new[]
{
new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 3, 0, 3, 9)
}
},
{
"<input class='btn' bound-required-int />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
})),
new[]
{
new RazorError(string.Format(errorFormat, "bound-required-int", "input", intType), 19, 0, 19, 18)
}
},
{
"<p class='btn' bound-int></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("class", factory.Markup("btn")),
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
})),
new[]
{
new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 15, 0, 15, 9)
}
},
{
$"<input class='{expressionString}' bound-required-int />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("class", expression),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-int", "input", intType), 33, 0, 33, 18)
}
},
{
$"<p class='{expressionString}' bound-int></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("class", expression),
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
})),
new[]
{
new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 29, 0, 29, 9)
}
},
{
$"<input bound-required-int class='{expressionString}' bound-required-string " +
$"class='{expressionString}' unbound-required />",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: true,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
new KeyValuePair<string, SyntaxTreeNode>("class", expression),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
new KeyValuePair<string, SyntaxTreeNode>("class", expression),
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
})),
new[]
{
new RazorError(
string.Format(errorFormat, "bound-required-int", "input", intType), 10, 0, 10, 18),
new RazorError(
string.Format(errorFormat, "bound-required-string", "input", stringType),
absoluteIndex: 57,
lineIndex: 0,
columnIndex: 57,
length: 21),
}
},
{
$"<p bound-int class='{expressionString}' bound-string " +
$"class='{expressionString}' bound-string></p>",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
new KeyValuePair<string, SyntaxTreeNode>("class", expression),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
new KeyValuePair<string, SyntaxTreeNode>("class", expression),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
})),
new[]
{
new RazorError(string.Format(errorFormat, "bound-int", "p", intType), 6, 0, 6, 9),
new RazorError(string.Format(errorFormat, "bound-string", "p", stringType), 44, 0, 44, 12),
new RazorError(string.Format(errorFormat, "bound-string", "p", stringType), 84, 0, 84, 12),
}
},
};
}
}
public static TheoryData MinimizedAttributeData_CSharpBlock
{
get
{
var factory = CreateDefaultSpanFactory();
var documentData = MinimizedAttributeData_Document;
Func<Func<MarkupBlock>, MarkupBlock> buildStatementBlock = (insideBuilder) =>
{
return new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharacters.None),
insideBuilder(),
factory.EmptyCSharp().AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharacters.None)),
factory.EmptyHtml());
};
foreach (var data in documentData)
{
data[0] = $"@{{{data[0]}}}";
data[1] = buildStatementBlock(() => data[1] as MarkupBlock);
var errors = data[2] as RazorError[];
for (var i = 0; i < errors.Length; i++)
{
var error = errors[i];
error.Location = SourceLocation.Advance(error.Location, "@{");
}
}
return documentData;
}
}
public static TheoryData MinimizedAttributeData_PartialTags
{
get
{
var factory = CreateDefaultSpanFactory();
var noErrors = new RazorError[0];
var errorFormatUnclosed = "Found a malformed '{0}' tag helper. Tag helpers must have a start and " +
"end tag or be self closing.";
var errorFormatNoCloseAngle = "Missing close angle for tag helper '{0}'.";
var errorFormatNoValue = "Attribute '{0}' on tag helper element '{1}' requires a value. Tag helper bound " +
"attributes of type '{2}' cannot be empty or contain only whitespace.";
var stringType = typeof(string).FullName;
var intType = typeof(int).FullName;
// documentContent, expectedOutput, expectedErrors
return new TheoryData<string, MarkupBlock, RazorError[]>
{
{
"<input unbound-required",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
})),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "input"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "input"), SourceLocation.Zero),
}
},
{
"<input bound-required-string",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
})),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "input"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "input"), SourceLocation.Zero),
new RazorError(
string.Format(errorFormatNoValue, "bound-required-string", "input", stringType),
absoluteIndex: 7,
lineIndex: 0,
columnIndex: 7,
length: 21),
}
},
{
"<input bound-required-int",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
})),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "input"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "input"), SourceLocation.Zero),
new RazorError(
string.Format(errorFormatNoValue, "bound-required-int", "input", intType),
absoluteIndex: 7,
lineIndex: 0,
columnIndex: 7,
length: 18),
}
},
{
"<input bound-required-int unbound-required bound-required-string",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
})),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "input"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "input"), SourceLocation.Zero),
new RazorError(
string.Format(errorFormatNoValue, "bound-required-int", "input", intType),
absoluteIndex: 7,
lineIndex: 0,
columnIndex: 7,
length: 18),
new RazorError(
string.Format(errorFormatNoValue, "bound-required-string", "input", stringType),
absoluteIndex: 43,
lineIndex: 0,
columnIndex: 43,
length: 21),
}
},
{
"<p bound-string",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
})),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "p"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "p"), SourceLocation.Zero),
new RazorError(
string.Format(errorFormatNoValue, "bound-string", "p", stringType), 3, 0, 3, 12),
}
},
{
"<p bound-int",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
})),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "p"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "p"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatNoValue, "bound-int", "p", intType), 3, 0, 3, 9),
}
},
{
"<p bound-int bound-string",
new MarkupBlock(
new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
})),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "p"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "p"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatNoValue, "bound-int", "p", intType), 3, 0, 3, 9),
new RazorError(
string.Format(errorFormatNoValue, "bound-string", "p", stringType), 13, 0, 13, 12),
}
},
{
"<input bound-required-int unbound-required bound-required-string<p bound-int bound-string",
new MarkupBlock(
new MarkupTagHelperBlock(
"input",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-required-int", null),
new KeyValuePair<string, SyntaxTreeNode>("unbound-required", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-required-string", null),
},
children: new MarkupTagHelperBlock(
"p",
selfClosing: false,
attributes: new List<KeyValuePair<string, SyntaxTreeNode>>()
{
new KeyValuePair<string, SyntaxTreeNode>("bound-int", null),
new KeyValuePair<string, SyntaxTreeNode>("bound-string", null),
}))),
new[]
{
new RazorError(string.Format(errorFormatNoCloseAngle, "input"), SourceLocation.Zero),
new RazorError(string.Format(errorFormatUnclosed, "input"), SourceLocation.Zero),
new RazorError(
string.Format(errorFormatNoValue, "bound-required-int", "input", intType), 7, 0, 7, 18),
new RazorError(
string.Format(errorFormatNoValue, "bound-required-string", "input", stringType),
absoluteIndex: 43,
lineIndex: 0,
columnIndex: 43,
length: 21),
new RazorError(string.Format(errorFormatNoCloseAngle, "p"), 64, 0, 64),
new RazorError(string.Format(errorFormatUnclosed, "p"), 64, 0, 64),
new RazorError(string.Format(errorFormatNoValue, "bound-int", "p", intType), 67, 0, 67, 9),
new RazorError(
string.Format(errorFormatNoValue, "bound-string", "p", stringType),
absoluteIndex: 77,
lineIndex: 0,
columnIndex: 77,
length: 12),
}
},
};
}
}
[Theory]
[MemberData(nameof(MinimizedAttributeData_Document))]
[MemberData(nameof(MinimizedAttributeData_CSharpBlock))]
[MemberData(nameof(MinimizedAttributeData_PartialTags))]
public void Rewrite_UnderstandsMinimizedAttributes(
string documentContent,
MarkupBlock expectedOutput,
RazorError[] expectedErrors)
{
// Arrange
var descriptors = new TagHelperDescriptor[]
{
new TagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "SomeAssembly",
attributes: new TagHelperAttributeDescriptor[0],
requiredAttributes: new[] { "unbound-required" }),
new TagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "SomeAssembly",
attributes: new[]
{
new TagHelperAttributeDescriptor(
"bound-required-string",
"BoundRequiredString",
typeof(string).FullName)
},
requiredAttributes: new[] { "bound-required-string" }),
new TagHelperDescriptor(
tagName: "input",
typeName: "InputTagHelper",
assemblyName: "SomeAssembly",
attributes: new[]
{
new TagHelperAttributeDescriptor(
"bound-required-int",
"BoundRequiredInt",
typeof(int).FullName)
},
requiredAttributes: new[] { "bound-required-int" }),
new TagHelperDescriptor(
tagName: "p",
typeName: "PTagHelper",
assemblyName: "SomeAssembly",
attributes: new[]
{
new TagHelperAttributeDescriptor(
"bound-string",
"BoundRequiredString",
typeof(string).FullName),
new TagHelperAttributeDescriptor(
"bound-int",
"BoundRequiredString",
typeof(int).FullName)
},
requiredAttributes: Enumerable.Empty<string>()),
};
var descriptorProvider = new TagHelperDescriptorProvider(descriptors);
// Act & Assert
EvaluateData(descriptorProvider, documentContent, expectedOutput, expectedErrors);
}
}
}

View File

@ -8,16 +8,14 @@ using System.Linq;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Parser.TagHelpers.Internal;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Test.Framework;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Xunit;
namespace Microsoft.AspNet.Razor.Test.TagHelpers
{
public class TagHelperParseTreeRewriterTest : CsHtmlMarkupParserTestBase
public class TagHelperParseTreeRewriterTest : TagHelperRewritingTestBase
{
public static TheoryData RequiredAttributeData
{
@ -1949,7 +1947,11 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
{
"@{<!text /}",
buildPartialStatementBlock(
() => new MarkupBlock(blockFactory.EscapedMarkupTagBlock("<", "text /}"))),
() => new MarkupBlock(
blockFactory.EscapedMarkupTagBlock(
"<",
"text /",
new MarkupBlock(factory.Markup("}"))))),
new []
{
new RazorError(
@ -2036,7 +2038,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
prefix: new LocationTagged<string>(string.Empty, 16, 0, 16),
value: new LocationTagged<string>("btn", 16, 0, 16))),
factory.Markup("\"").With(SpanCodeGenerator.Null)),
factory.Markup("}")))),
new MarkupBlock(factory.Markup("}"))))),
new []
{
new RazorError(
@ -2067,7 +2069,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
prefix: new LocationTagged<string>(string.Empty, 16, 0, 16),
value: new LocationTagged<string>("btn", 16, 0, 16))),
factory.Markup("\"").With(SpanCodeGenerator.Null)),
factory.Markup(" /}")))),
factory.Markup(" /"),
new MarkupBlock(factory.Markup("}"))))),
new []
{
new RazorError(
@ -2152,7 +2155,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
{
"@{<!p /}",
buildPartialStatementBlock(
() => new MarkupBlock(blockFactory.EscapedMarkupTagBlock("<", "p /}"))),
() => new MarkupBlock(
blockFactory.EscapedMarkupTagBlock("<", "p /", new MarkupBlock(factory.Markup("}"))))),
new []
{
new RazorError(
@ -2274,7 +2278,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
prefix: new LocationTagged<string>(string.Empty, 13, 0, 13),
value: new LocationTagged<string>("btn", 13, 0, 13))),
factory.Markup("\"").With(SpanCodeGenerator.Null)),
factory.Markup("}")))),
new MarkupBlock(factory.Markup("}"))))),
new []
{
new RazorError(
@ -2305,7 +2309,9 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
prefix: new LocationTagged<string>(string.Empty, 13, 0, 13),
value: new LocationTagged<string>("btn", 13, 0, 13))),
factory.Markup("\"").With(SpanCodeGenerator.Null)),
factory.Markup(" /}")))),
factory.Markup(" /"),
new MarkupBlock(
factory.Markup("}"))))),
new []
{
new RazorError(
@ -3353,6 +3359,11 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
"<p foo bar<strong>",
new MarkupBlock(
new MarkupTagHelperBlock("p",
new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>("foo", null),
new KeyValuePair<string, SyntaxTreeNode>("bar", null)
},
new MarkupTagHelperBlock("strong"))),
new []
{
@ -3406,7 +3417,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
new KeyValuePair<string, SyntaxTreeNode>(
"class",
new MarkupBlock(factory.Markup("btn"), factory.Markup(" bar="))),
new KeyValuePair<string, SyntaxTreeNode>("foo", factory.Markup(string.Empty))
new KeyValuePair<string, SyntaxTreeNode>("foo", null)
},
new MarkupTagHelperBlock("strong"))),
new []
@ -3429,6 +3440,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
new List<KeyValuePair<string, SyntaxTreeNode>>
{
new KeyValuePair<string, SyntaxTreeNode>("class", new MarkupBlock(factory.Markup("btn"), factory.Markup(" bar="))),
new KeyValuePair<string, SyntaxTreeNode>("foo", null),
})),
new RazorError[0]
},
@ -4013,7 +4025,11 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
{
"< p />",
new MarkupBlock(
blockFactory.MarkupTagBlock("< p />"))
new MarkupTagBlock(
factory.Markup("<"),
new MarkupBlock(
factory.Markup(" p")),
factory.Markup(" />")))
},
{
"<input <p />",
@ -5076,74 +5092,5 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
{
RunParseTreeRewriterTest(documentContent, expectedOutput, "p", "div");
}
private void RunParseTreeRewriterTest(string documentContent,
MarkupBlock expectedOutput,
params string[] tagNames)
{
RunParseTreeRewriterTest(documentContent,
expectedOutput,
errors: Enumerable.Empty<RazorError>(),
tagNames: tagNames);
}
private void RunParseTreeRewriterTest(string documentContent,
MarkupBlock expectedOutput,
IEnumerable<RazorError> errors,
params string[] tagNames)
{
// Arrange
var providerContext = BuildProviderContext(tagNames);
// Act & Assert
EvaluateData(providerContext, documentContent, expectedOutput, errors);
}
private TagHelperDescriptorProvider BuildProviderContext(params string[] tagNames)
{
var descriptors = new List<TagHelperDescriptor>();
foreach (var tagName in tagNames)
{
descriptors.Add(
new TagHelperDescriptor(tagName, tagName + "taghelper", "SomeAssembly"));
}
return new TagHelperDescriptorProvider(descriptors);
}
public override ParserContext CreateParserContext(ITextDocument input,
ParserBase codeParser,
ParserBase markupParser,
ErrorSink errorSink)
{
return base.CreateParserContext(input, codeParser, markupParser, errorSink);
}
private void EvaluateData(TagHelperDescriptorProvider provider,
string documentContent,
MarkupBlock expectedOutput,
IEnumerable<RazorError> expectedErrors)
{
var errorSink = new ErrorSink();
var results = ParseDocument(documentContent, errorSink);
var rewritingContext = new RewritingContext(results.Document, errorSink);
new TagHelperParseTreeRewriter(provider).Rewrite(rewritingContext);
var rewritten = rewritingContext.SyntaxTree;
var actualErrors = errorSink.Errors.OrderBy(error => error.Location.AbsoluteIndex)
.ToList();
EvaluateRazorErrors(actualErrors, expectedErrors.ToList());
EvaluateParseTree(rewritten, expectedOutput);
}
private static SpanFactory CreateDefaultSpanFactory()
{
return new SpanFactory
{
MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
};
}
}
}

View File

@ -0,0 +1,89 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.TagHelpers.Internal;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Test.Framework;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
namespace Microsoft.AspNet.Razor.Test.TagHelpers
{
public class TagHelperRewritingTestBase : CsHtmlMarkupParserTestBase
{
public void RunParseTreeRewriterTest(
string documentContent,
MarkupBlock expectedOutput,
params string[] tagNames)
{
RunParseTreeRewriterTest(
documentContent,
expectedOutput,
errors: Enumerable.Empty<RazorError>(),
tagNames: tagNames);
}
public void RunParseTreeRewriterTest(
string documentContent,
MarkupBlock expectedOutput,
IEnumerable<RazorError> errors,
params string[] tagNames)
{
var providerContext = BuildProviderContext(tagNames);
EvaluateData(providerContext, documentContent, expectedOutput, errors);
}
public TagHelperDescriptorProvider BuildProviderContext(params string[] tagNames)
{
var descriptors = new List<TagHelperDescriptor>();
foreach (var tagName in tagNames)
{
descriptors.Add(
new TagHelperDescriptor(tagName, tagName + "taghelper", "SomeAssembly"));
}
return new TagHelperDescriptorProvider(descriptors);
}
public override ParserContext CreateParserContext(
ITextDocument input,
ParserBase codeParser,
ParserBase markupParser,
ErrorSink errorSink)
{
return base.CreateParserContext(input, codeParser, markupParser, errorSink);
}
public void EvaluateData(
TagHelperDescriptorProvider provider,
string documentContent,
MarkupBlock expectedOutput,
IEnumerable<RazorError> expectedErrors)
{
var errorSink = new ErrorSink();
var results = ParseDocument(documentContent, errorSink);
var rewritingContext = new RewritingContext(results.Document, errorSink);
new TagHelperParseTreeRewriter(provider).Rewrite(rewritingContext);
var rewritten = rewritingContext.SyntaxTree;
var actualErrors = errorSink.Errors.OrderBy(error => error.Location.AbsoluteIndex)
.ToList();
EvaluateRazorErrors(actualErrors, expectedErrors.ToList());
EvaluateParseTree(rewritten, expectedOutput);
}
public static SpanFactory CreateDefaultSpanFactory()
{
return new SpanFactory
{
MarkupTokenizerFactory = doc => new HtmlTokenizer(doc),
CodeTokenizerFactory = doc => new CSharpTokenizer(doc)
};
}
}
}

View File

@ -0,0 +1,49 @@
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;
public class MinimizedTagHelpers
{
private static object @__o;
private void @__RazorDesignTimeHelpers__()
{
#pragma warning disable 219
string __tagHelperDirectiveSyntaxHelper = null;
__tagHelperDirectiveSyntaxHelper =
#line 1 "MinimizedTagHelpers.cshtml"
"something, nice"
#line default
#line hidden
;
#pragma warning restore 219
}
#line hidden
private CatchAllTagHelper __CatchAllTagHelper = null;
private InputTagHelper __InputTagHelper = null;
#line hidden
public MinimizedTagHelpers()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__InputTagHelper.BoundRequiredString = "hello";
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__InputTagHelper.BoundRequiredString = "hello2";
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__CatchAllTagHelper.BoundRequiredString = "world";
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__InputTagHelper.BoundRequiredString = "world";
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,106 @@
#pragma checksum "MinimizedTagHelpers.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "07839be4304797e30b19b50b95e2247c93cdff06"
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;
public class MinimizedTagHelpers
{
#line hidden
#pragma warning disable 0414
private TagHelperContent __tagHelperStringValueBuffer = null;
#pragma warning restore 0414
private TagHelperExecutionContext __tagHelperExecutionContext = null;
private TagHelperRunner __tagHelperRunner = null;
private TagHelperScopeManager __tagHelperScopeManager = new TagHelperScopeManager();
private CatchAllTagHelper __CatchAllTagHelper = null;
private InputTagHelper __InputTagHelper = null;
#line hidden
public MinimizedTagHelpers()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
__tagHelperRunner = __tagHelperRunner ?? new TagHelperRunner();
Instrumentation.BeginContext(33, 2, true);
WriteLiteral("\r\n");
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("p", false, "test", async() => {
WriteLiteral("\r\n <input nottaghelper />\r\n ");
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__tagHelperExecutionContext.Add(__CatchAllTagHelper);
__tagHelperExecutionContext.AddHtmlAttribute("class", Html.Raw("btn"));
__tagHelperExecutionContext.AddMinimizedHtmlAttribute("catchall-unbound-required");
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
await WriteTagHelperAsync(__tagHelperExecutionContext);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
WriteLiteral("\r\n ");
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__tagHelperExecutionContext.Add(__InputTagHelper);
__InputTagHelper.BoundRequiredString = "hello";
__tagHelperExecutionContext.AddTagHelperAttribute("input-bound-required-string", __InputTagHelper.BoundRequiredString);
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__tagHelperExecutionContext.Add(__CatchAllTagHelper);
__tagHelperExecutionContext.AddHtmlAttribute("class", Html.Raw("btn"));
__tagHelperExecutionContext.AddMinimizedHtmlAttribute("catchall-unbound-required");
__tagHelperExecutionContext.AddMinimizedHtmlAttribute("input-unbound-required");
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
await WriteTagHelperAsync(__tagHelperExecutionContext);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
WriteLiteral("\r\n ");
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__tagHelperExecutionContext.Add(__InputTagHelper);
__InputTagHelper.BoundRequiredString = "hello2";
__tagHelperExecutionContext.AddTagHelperAttribute("input-bound-required-string", __InputTagHelper.BoundRequiredString);
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__tagHelperExecutionContext.Add(__CatchAllTagHelper);
__CatchAllTagHelper.BoundRequiredString = "world";
__tagHelperExecutionContext.AddTagHelperAttribute("catchall-bound-string", __CatchAllTagHelper.BoundRequiredString);
__tagHelperExecutionContext.AddHtmlAttribute("class", Html.Raw("btn"));
__tagHelperExecutionContext.AddMinimizedHtmlAttribute("catchall-unbound-required");
__tagHelperExecutionContext.AddMinimizedHtmlAttribute("input-unbound-required");
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
await WriteTagHelperAsync(__tagHelperExecutionContext);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
WriteLiteral("\r\n ");
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", true, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__tagHelperExecutionContext.Add(__InputTagHelper);
__InputTagHelper.BoundRequiredString = "world";
__tagHelperExecutionContext.AddTagHelperAttribute("input-bound-required-string", __InputTagHelper.BoundRequiredString);
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__tagHelperExecutionContext.Add(__CatchAllTagHelper);
__tagHelperExecutionContext.AddHtmlAttribute("class", Html.Raw("btn"));
__tagHelperExecutionContext.AddHtmlAttribute("catchall-unbound-required", Html.Raw("hello"));
__tagHelperExecutionContext.AddHtmlAttribute("input-unbound-required", Html.Raw("hello2"));
__tagHelperExecutionContext.AddMinimizedHtmlAttribute("catchall-unbound-required");
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
await WriteTagHelperAsync(__tagHelperExecutionContext);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
WriteLiteral("\r\n");
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__CatchAllTagHelper = CreateTagHelper<CatchAllTagHelper>();
__tagHelperExecutionContext.Add(__CatchAllTagHelper);
__tagHelperExecutionContext.AddMinimizedHtmlAttribute("catchall-unbound-required");
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
await WriteTagHelperAsync(__tagHelperExecutionContext);
__tagHelperExecutionContext = __tagHelperScopeManager.End();
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,18 @@
@addTagHelper "something, nice"
<p catchall-unbound-required>
<input nottaghelper />
<input class="btn"
catchall-unbound-required />
<input
class="btn" catchall-unbound-required input-unbound-required input-bound-required-string="hello" />
<input
class="btn"
catchall-unbound-required
input-unbound-required catchall-bound-string="world" input-bound-required-string="hello2" />
<input class="btn"
catchall-unbound-required="hello"
input-unbound-required="hello2"
catchall-unbound-required
input-bound-required-string="world" />
</p>