Enable `CopyHtmlAttribute` to maintain copied attribute order.

- Updated implementation to do a best guess at copying an attribute back into `TagHelperOutput.Attributes`.
- Updated existing functional and unit tests to account for maintained attribute order.
- Added a set of complex order based unit tests to validate `CopyHtmlAttribute` properly maintains order.

#2639
This commit is contained in:
N. Taylor Mullen 2015-08-17 16:56:58 -07:00
parent 405d105bd7
commit f1eefdb650
19 changed files with 497 additions and 92 deletions

View File

@ -23,9 +23,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <param name="tagHelperOutput">The <see cref="TagHelperOutput"/> this method extends.</param>
/// <param name="attributeName">The name of the bound attribute.</param>
/// <param name="context">The <see cref="TagHelperContext"/>.</param>
/// <remarks>Only copies the attribute if <paramref name="tagHelperOutput"/>'s
/// <remarks>
/// <para>
/// Only copies the attribute if <paramref name="tagHelperOutput"/>'s
/// <see cref="TagHelperOutput.Attributes"/> does not contain an attribute with the given
/// <paramref name="attributeName"/>.</remarks>
/// <paramref name="attributeName"/>.
/// </para>
/// <para>
/// Duplicate attributes same name in <paramref name="context"/>'s <see cref="TagHelperContext.AllAttributes"/>
/// or <paramref name="tagHelperOutput"/>'s <see cref="TagHelperOutput.Attributes"/> may result in copied
/// attribute order not being maintained.
/// </para></remarks>
public static void CopyHtmlAttribute(
[NotNull] this TagHelperOutput tagHelperOutput,
[NotNull] string attributeName,
@ -33,21 +41,30 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
if (!tagHelperOutput.Attributes.ContainsName(attributeName))
{
IEnumerable<IReadOnlyTagHelperAttribute> entries;
var copiedAttribute = false;
// We look for the original attribute so we can restore the exact attribute name the user typed.
// Approach also ignores changes made to tagHelperOutput[attributeName].
if (!context.AllAttributes.TryGetAttributes(attributeName, out entries))
// We iterate context.AllAttributes backwards since we prioritize TagHelperOutput values occurring
// before the current context.AllAttribtes[i].
for (var i = context.AllAttributes.Count - 1; i >= 0; i--)
{
// We look for the original attribute so we can restore the exact attribute name the user typed in
// approximately the same position where the user wrote it in the Razor source.
if (string.Equals(
attributeName,
context.AllAttributes[i].Name,
StringComparison.OrdinalIgnoreCase))
{
CopyHtmlAttribute(i, tagHelperOutput, context);
copiedAttribute = true;
}
}
if (!copiedAttribute)
{
throw new ArgumentException(
Resources.FormatTagHelperOutput_AttributeDoesNotExist(attributeName, nameof(TagHelperContext)),
nameof(attributeName));
}
foreach (var entry in entries)
{
tagHelperOutput.Attributes.Add(entry.Name, entry.Value);
}
}
}
@ -100,5 +117,61 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
tagHelperOutput.Attributes.Remove(attribute);
}
}
private static void CopyHtmlAttribute(
int allAttributeIndex,
TagHelperOutput tagHelperOutput,
TagHelperContext context)
{
var existingAttribute = context.AllAttributes[allAttributeIndex];
var copiedAttribute = new TagHelperAttribute
{
Name = existingAttribute.Name,
Value = existingAttribute.Value,
Minimized = existingAttribute.Minimized
};
// Move backwards through context.AllAttributes from the provided index until we find a familiar attribute
// in tagHelperOutput where we can insert the copied value after the familiar one.
for (var i = allAttributeIndex - 1; i >= 0; i--)
{
var previousName = context.AllAttributes[i].Name;
var index = IndexOfFirstMatch(previousName, tagHelperOutput.Attributes);
if (index != -1)
{
tagHelperOutput.Attributes.Insert(index + 1, copiedAttribute);
return;
}
}
// Move forward through context.AllAttributes from the provided index until we find a familiar attribute in
// tagHelperOutput where we can insert the copied value.
for (var i = allAttributeIndex + 1; i < context.AllAttributes.Count; i++)
{
var nextName = context.AllAttributes[i].Name;
var index = IndexOfFirstMatch(nextName, tagHelperOutput.Attributes);
if (index != -1)
{
tagHelperOutput.Attributes.Insert(index, copiedAttribute);
return;
}
}
// Couldn't determine the attribute's location, add it to the end.
tagHelperOutput.Attributes.Add(copiedAttribute);
}
private static int IndexOfFirstMatch(string name, TagHelperAttributeList attributes)
{
for (var i = 0; i < attributes.Count; i++)
{
if (string.Equals(name, attributes[i].Name, StringComparison.OrdinalIgnoreCase))
{
return i;
}
}
return -1;
}
}
}

View File

@ -3,7 +3,7 @@
<form action="/Customer/HtmlGeneration_Customer" method="post">
<div>
<label class="order" for="Number">Number</label>
<input class="form-control input-validation-error" type="number" data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." id="Number" name="Number" value="" />
<input type="number" class="form-control input-validation-error" data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." id="Number" name="Number" value="" />
<span class="field-validation-error" data-valmsg-for="Number" data-valmsg-replace="true">The value &#x27;&#x27; is invalid.</span>
</div>
<div>
@ -21,7 +21,7 @@
</div>
<div>
<label class="order" for="Password">Password</label>
<input class="form-control input-validation-error" type="password" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" />
<input type="password" class="form-control input-validation-error" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" />
<span class="field-validation-error" data-valmsg-for="Password" data-valmsg-replace="true">The Password field is required.</span>
</div>
<div>

View File

@ -2,7 +2,7 @@
<body>
<form action="/HtmlGeneration_Home/CreateWarehouse" method="post"> <div>
<label class="warehouse" for="City">City</label>
<input size="50" type="text" data-val="true" data-val-minlength="The field City must be a string or array type with a minimum length of &#x27;2&#x27;." data-val-minlength-min="2" id="City" name="City" value="" />
<input type="text" size="50" data-val="true" data-val-minlength="The field City must be a string or array type with a minimum length of &#x27;2&#x27;." data-val-minlength-min="2" id="City" name="City" value="" />
<span class="field-validation-valid" data-valmsg-for="City" data-valmsg-replace="true"></span>
</div>
<div>

View File

@ -3,7 +3,7 @@
<form action="/Customer/HtmlGeneration_Customer" method="post">
<div>
<label class="order" for="Number">Number</label>
<input class="form-control" type="number" data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." id="Number" name="Number" value="" />
<input type="number" class="form-control" data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." id="Number" name="Number" value="" />
<span class="field-validation-valid" data-valmsg-for="Number" data-valmsg-replace="true"></span>
</div>
<div>
@ -21,7 +21,7 @@
</div>
<div>
<label class="order" for="Password">Password</label>
<input class="form-control" type="password" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" />
<input type="password" class="form-control" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" />
<span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span>
</div>
<div>

View File

@ -4,7 +4,7 @@
<div>
<label for="z0__Number">Number</label>
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z0__Number" name="[0].Number" readonly="readonly" type="text" value="0" />
<input class="form-control" type="number" id="z0__Number" name="[0].Number" value="0" />
<input type="number" class="form-control" id="z0__Number" name="[0].Number" value="0" />
</div>
<div>
<label class="employee" for="z0__Name">Name</label>
@ -37,7 +37,7 @@ EmployeeName_0</textarea>
<div>
<label for="z1__Number">Number</label>
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z1__Number" name="[1].Number" readonly="readonly" type="text" value="1" />
<input class="form-control" type="number" id="z1__Number" name="[1].Number" value="1" />
<input type="number" class="form-control" id="z1__Number" name="[1].Number" value="1" />
</div>
<div>
<label class="employee" for="z1__Name">Name</label>
@ -70,7 +70,7 @@ EmployeeName_1</textarea>
<div>
<label for="z2__Number">Number</label>
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z2__Number" name="[2].Number" readonly="readonly" type="text" value="2" />
<input class="form-control" type="number" id="z2__Number" name="[2].Number" value="2" />
<input type="number" class="form-control" id="z2__Number" name="[2].Number" value="2" />
</div>
<div>
<label class="employee" for="z2__Name">Name</label>

View File

@ -15,7 +15,7 @@
<img alt="Red versioned" title="Red versioned" src="/images/red.png?v=W2F5D366_nQ2fQqUk3URdgWy2ZekXjHzHJaY5yaiOOk" />
<!-- Plain image tag with file version set to false -->
<img alt="Red explicitly not versioned" title="Red versioned" src="/images/red.png">
<img src="/images/red.png" alt="Red explicitly not versioned" title="Red versioned">
<!-- Plain image tag with absolute path and file version -->
<img alt="Absolute path versioned" src="http://contoso.com/hello/world">

View File

@ -20,7 +20,7 @@
<link rel="stylesheet">
<!-- Globbed link tag missing include but with static href -->
<link rel="stylesheet" href="HtmlEncode[[/site.css]]" />
<link href="HtmlEncode[[/site.css]]" rel="stylesheet" />
<!-- Globbed link tag with missing file -->
@ -32,13 +32,13 @@
<!-- Globbed link tag with existing file and static href -->
<link rel="stylesheet" href="HtmlEncode[[/site.css]]" /><link rel="stylesheet" href="HtmlEncode[[/sub/site2.css]]" />
<link href="HtmlEncode[[/site.css]]" rel="stylesheet" /><link href="HtmlEncode[[/sub/site2.css]]" rel="stylesheet" />
<!-- Globbed link tag with existing file and static href should dedupe -->
<link rel="stylesheet" href="HtmlEncode[[/site.css]]" />
<link href="HtmlEncode[[/site.css]]" rel="stylesheet" />
<!-- Fallback to static href -->
<link rel="stylesheet" data-extra="test" title="&quot;the&quot; title" href="HtmlEncode[[/site.min.css?a=b&c=d]]" />
<link href="HtmlEncode[[/site.min.css?a=b&c=d]]" rel="stylesheet" data-extra="test" title="&quot;the&quot; title" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css?a=b&c=d]]"]);</script>
<!-- Fallback from globbed href to static href -->
@ -50,11 +50,11 @@
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback from globbed and static href to static href -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[site.min.css]]" /><link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.css]]" />
<link href="HtmlEncode[[site.min.css]]" rel="stylesheet" data-extra="test" /><link href="HtmlEncode[[/site.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback from globbed and static href with exclude to static href -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[site.min.css]]" />
<link href="HtmlEncode[[site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback to static href with no primary href -->
@ -62,19 +62,19 @@
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback to globbed href -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback to static and globbed href -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]","JavaScriptEncode[[/sub/site2.css]]"]);</script>
<!-- Fallback to static and globbed href should dedupe -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback to static and globbed href with exclude -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]","JavaScriptEncode[[/sub/site2.css]]"]);</script>
<!-- Fallback from globbed href to glbobed href -->
@ -86,44 +86,44 @@
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback from globbed and static href to globbed href -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[site.min.css]]" /><link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.css]]" />
<link href="HtmlEncode[[site.min.css]]" rel="stylesheet" data-extra="test" /><link href="HtmlEncode[[/site.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Fallback from globbed and static href with exclude to globbed href -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[site.min.css]]">
<link href="HtmlEncode[[site.min.css]]" rel="stylesheet" data-extra="test">
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]"]);</script>
<!-- Kitchen sink, all the attributes -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[site.min.css]]" />
<link href="HtmlEncode[[site.min.css]]" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css]]","JavaScriptEncode[[/sub/site2.css]]"]);</script>
<!-- Fallback to globbed href that doesn't exist -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<!-- Fallback to globbed href outside webroot -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<!-- Fallback with missing attribute -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<!-- Fallback with missing attribute -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<!-- Fallback with missing attribute -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]" />
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test" />
<!-- Plain link tag with file version -->
<link rel="stylesheet" href="HtmlEncode[[/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]" />
<link href="HtmlEncode[[/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]" rel="stylesheet" />
<!-- Globbed link tag with existing file and file version -->
<link rel="stylesheet" href="HtmlEncode[[/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]" />
<!-- Fallback with file version -->
<link rel="stylesheet" data-extra="test" href="HtmlEncode[[/site.min.css]]">
<link href="HtmlEncode[[/site.min.css]]" rel="stylesheet" data-extra="test">
<meta name="x-stylesheet-fallback-test" class="HtmlEncode[[hidden]]" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("JavaScriptEncode[[visibility]]","JavaScriptEncode[[hidden]]",["JavaScriptEncode[[/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]"]);</script>
<!-- Globbed link tag with existing file, static href and file version -->
<link rel="stylesheet" href="HtmlEncode[[/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]" /><link rel="stylesheet" href="HtmlEncode[[/sub/site2.css?v=30cxPex0tA9xEatW7f1Qhnn8tVLAHgE6xwIZhESq0y0]]" /><link rel="stylesheet" href="HtmlEncode[[/sub/site3.css?v=fSxxOr1Q4Dq2uPuzlju5UYGuK0SKABI-ghvaIGEsZDc]]" /><link rel="stylesheet" href="HtmlEncode[[/sub/site3.min.css?v=s8JMmAZxBn0dzuhRtQ0wgOvNBK4XRJRWEC2wfzsVF9M]]" />
<link href="HtmlEncode[[/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc]]" rel="stylesheet" /><link href="HtmlEncode[[/sub/site2.css?v=30cxPex0tA9xEatW7f1Qhnn8tVLAHgE6xwIZhESq0y0]]" rel="stylesheet" /><link href="HtmlEncode[[/sub/site3.css?v=fSxxOr1Q4Dq2uPuzlju5UYGuK0SKABI-ghvaIGEsZDc]]" rel="stylesheet" /><link href="HtmlEncode[[/sub/site3.min.css?v=s8JMmAZxBn0dzuhRtQ0wgOvNBK4XRJRWEC2wfzsVF9M]]" rel="stylesheet" />
</head>
<body>

View File

@ -20,7 +20,7 @@
<link rel="stylesheet">
<!-- Globbed link tag missing include but with static href -->
<link rel="stylesheet" href="/site.css" />
<link href="/site.css" rel="stylesheet" />
<!-- Globbed link tag with missing file -->
@ -32,13 +32,13 @@
<!-- Globbed link tag with existing file and static href -->
<link rel="stylesheet" href="/site.css" /><link rel="stylesheet" href="/sub/site2.css" />
<link href="/site.css" rel="stylesheet" /><link href="/sub/site2.css" rel="stylesheet" />
<!-- Globbed link tag with existing file and static href should dedupe -->
<link rel="stylesheet" href="/site.css" />
<link href="/site.css" rel="stylesheet" />
<!-- Fallback to static href -->
<link rel="stylesheet" data-extra="test" title="&quot;the&quot; title" href="/site.min.css?a=b&amp;c=d" />
<link href="/site.min.css?a=b&amp;c=d" rel="stylesheet" data-extra="test" title="&quot;the&quot; title" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css?a=b\u0026c=d"]);</script>
<!-- Fallback from globbed href to static href -->
@ -50,11 +50,11 @@
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback from globbed and static href to static href -->
<link rel="stylesheet" data-extra="test" href="site.min.css" /><link rel="stylesheet" data-extra="test" href="/site.css" />
<link href="site.min.css" rel="stylesheet" data-extra="test" /><link href="/site.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback from globbed and static href with exclude to static href -->
<link rel="stylesheet" data-extra="test" href="site.min.css" />
<link href="site.min.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback to static href with no primary href -->
@ -62,19 +62,19 @@
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback to globbed href -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback to static and globbed href -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css","\/sub\/site2.css"]);</script>
<!-- Fallback to static and globbed href should dedupe -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback to static and globbed href with exclude -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css","\/sub\/site2.css"]);</script>
<!-- Fallback from globbed href to glbobed href -->
@ -86,44 +86,44 @@
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback from globbed and static href to globbed href -->
<link rel="stylesheet" data-extra="test" href="site.min.css" /><link rel="stylesheet" data-extra="test" href="/site.css" />
<link href="site.min.css" rel="stylesheet" data-extra="test" /><link href="/site.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Fallback from globbed and static href with exclude to globbed href -->
<link rel="stylesheet" data-extra="test" href="site.min.css">
<link href="site.min.css" rel="stylesheet" data-extra="test">
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css"]);</script>
<!-- Kitchen sink, all the attributes -->
<link rel="stylesheet" data-extra="test" href="site.min.css" />
<link href="site.min.css" rel="stylesheet" data-extra="test" />
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css","\/sub\/site2.css"]);</script>
<!-- Fallback to globbed href that doesn't exist -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<!-- Fallback to globbed href outside webroot -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<!-- Fallback with missing attribute -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<!-- Fallback with missing attribute -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<!-- Fallback with missing attribute -->
<link rel="stylesheet" data-extra="test" href="/site.min.css" />
<link href="/site.min.css" rel="stylesheet" data-extra="test" />
<!-- Plain link tag with file version -->
<link rel="stylesheet" href="/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc" />
<link href="/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc" rel="stylesheet" />
<!-- Globbed link tag with existing file and file version -->
<link rel="stylesheet" href="/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc" />
<!-- Fallback with file version -->
<link rel="stylesheet" data-extra="test" href="/site.min.css">
<link href="/site.min.css" rel="stylesheet" data-extra="test">
<meta name="x-stylesheet-fallback-test" class="hidden" /><script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName("SCRIPT"),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}("visibility","hidden",["\/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc"]);</script>
<!-- Globbed link tag with existing file, static href and file version -->
<link rel="stylesheet" href="/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc" /><link rel="stylesheet" href="/sub/site2.css?v=30cxPex0tA9xEatW7f1Qhnn8tVLAHgE6xwIZhESq0y0" /><link rel="stylesheet" href="/sub/site3.css?v=fSxxOr1Q4Dq2uPuzlju5UYGuK0SKABI-ghvaIGEsZDc" /><link rel="stylesheet" href="/sub/site3.min.css?v=s8JMmAZxBn0dzuhRtQ0wgOvNBK4XRJRWEC2wfzsVF9M" />
<link href="/site.css?v=XY7YsMemPf8AGU4SIX9ED9eOjK1LOQWu2dmCNmh-pQc" rel="stylesheet" /><link href="/sub/site2.css?v=30cxPex0tA9xEatW7f1Qhnn8tVLAHgE6xwIZhESq0y0" rel="stylesheet" /><link href="/sub/site3.css?v=fSxxOr1Q4Dq2uPuzlju5UYGuK0SKABI-ghvaIGEsZDc" rel="stylesheet" /><link href="/sub/site3.min.css?v=s8JMmAZxBn0dzuhRtQ0wgOvNBK4XRJRWEC2wfzsVF9M" rel="stylesheet" />
</head>
<body>

View File

@ -1,4 +1,4 @@
<html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
@ -7,7 +7,7 @@
<form action="HtmlEncode[[/HtmlGeneration_Order/Submit]]" method="HtmlEncode[[post]]">
<div>
<label class="order" for="HtmlEncode[[Shipping]]">HtmlEncode[[Shipping]]</label>
<input size="50" type="HtmlEncode[[text]]" id="HtmlEncode[[Shipping]]" name="HtmlEncode[[Shipping]]" value="HtmlEncode[[Your shipping method is UPSP]]" />
<input type="HtmlEncode[[text]]" size="50" id="HtmlEncode[[Shipping]]" name="HtmlEncode[[Shipping]]" value="HtmlEncode[[Your shipping method is UPSP]]" />
</div>
<div>
<label class="order" for="HtmlEncode[[ShippingDateTime]]">HtmlEncode[[ShippingDateTime]]</label>
@ -45,7 +45,7 @@
</div>
<div>
<label class="order" for="HtmlEncode[[Customer_Number]]">HtmlEncode[[Number]]</label>
<input class="form-control" type="HtmlEncode[[number]]" data-val="HtmlEncode[[true]]" data-val-range="HtmlEncode[[The field Number must be between 1 and 100.]]" data-val-range-max="HtmlEncode[[100]]" data-val-range-min="HtmlEncode[[1]]" data-val-required="HtmlEncode[[The Number field is required.]]" id="HtmlEncode[[Customer_Number]]" name="HtmlEncode[[Customer.Number]]" value="HtmlEncode[[1]]" />
<input type="HtmlEncode[[number]]" class="form-control" data-val="HtmlEncode[[true]]" data-val-range="HtmlEncode[[The field Number must be between 1 and 100.]]" data-val-range-max="HtmlEncode[[100]]" data-val-range-min="HtmlEncode[[1]]" data-val-required="HtmlEncode[[The Number field is required.]]" id="HtmlEncode[[Customer_Number]]" name="HtmlEncode[[Customer.Number]]" value="HtmlEncode[[1]]" />
<span class="HtmlEncode[[field-validation-valid]]" data-valmsg-for="HtmlEncode[[Customer.Number]]" data-valmsg-replace="HtmlEncode[[true]]"></span>
</div>
<div>
@ -63,7 +63,7 @@
</div>
<div>
<label class="order" for="HtmlEncode[[Customer_Password]]">HtmlEncode[[Password]]</label>
<input class="form-control" type="HtmlEncode[[password]]" data-val="HtmlEncode[[true]]" data-val-required="HtmlEncode[[The Password field is required.]]" id="HtmlEncode[[Customer_Password]]" name="HtmlEncode[[Customer.Password]]" />
<input type="HtmlEncode[[password]]" class="form-control" data-val="HtmlEncode[[true]]" data-val-required="HtmlEncode[[The Password field is required.]]" id="HtmlEncode[[Customer_Password]]" name="HtmlEncode[[Customer.Password]]" />
<span class="HtmlEncode[[field-validation-valid]]" data-valmsg-for="HtmlEncode[[Customer.Password]]" data-valmsg-replace="HtmlEncode[[true]]"></span>
</div>
<div>

View File

@ -1,4 +1,4 @@
<html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
@ -7,7 +7,7 @@
<form action="/HtmlGeneration_Order/Submit" method="post">
<div>
<label class="order" for="Shipping">Shipping</label>
<input size="50" type="text" id="Shipping" name="Shipping" value="Your shipping method is UPSP" />
<input type="text" size="50" id="Shipping" name="Shipping" value="Your shipping method is UPSP" />
</div>
<div>
<label class="order" for="ShippingDateTime">ShippingDateTime</label>
@ -45,7 +45,7 @@
</div>
<div>
<label class="order" for="Customer_Number">Number</label>
<input class="form-control" type="number" data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." id="Customer_Number" name="Customer.Number" value="1" />
<input type="number" class="form-control" data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." id="Customer_Number" name="Customer.Number" value="1" />
<span class="field-validation-valid" data-valmsg-for="Customer.Number" data-valmsg-replace="true"></span>
</div>
<div>
@ -63,7 +63,7 @@
</div>
<div>
<label class="order" for="Customer_Password">Password</label>
<input class="form-control" type="password" data-val="true" data-val-required="The Password field is required." id="Customer_Password" name="Customer.Password" />
<input type="password" class="form-control" data-val="true" data-val-required="The Password field is required." id="Customer_Password" name="Customer.Password" />
<span class="field-validation-valid" data-valmsg-for="Customer.Password" data-valmsg-replace="true"></span>
</div>
<div>

View File

@ -1,4 +1,4 @@
<html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
@ -7,7 +7,7 @@
<form method="get" action="HtmlEncode[[/HtmlGeneration_Home/ProductSubmit]]">
<div>
<label class="product" for="HtmlEncode[[HomePage]]">HtmlEncode[[HomePage]]</label>
<input size="50" type="HtmlEncode[[url]]" id="HtmlEncode[[HomePage]]" name="HtmlEncode[[HomePage]]" value="HtmlEncode[[http://www.contoso.com/]]" />
<input type="HtmlEncode[[url]]" size="50" id="HtmlEncode[[HomePage]]" name="HtmlEncode[[HomePage]]" value="HtmlEncode[[http://www.contoso.com/]]" />
</div>
<div>
<label class="product" for="HtmlEncode[[Description]]">HtmlEncode[[Description]]</label>

View File

@ -1,4 +1,4 @@
<html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
@ -7,7 +7,7 @@
<form method="get" action="/HtmlGeneration_Home/ProductSubmit">
<div>
<label class="product" for="HomePage">HomePage</label>
<input size="50" type="url" id="HomePage" name="HomePage" value="http://www.contoso.com/" />
<input type="url" size="50" id="HomePage" name="HomePage" value="http://www.contoso.com/" />
</div>
<div>
<label class="product" for="Description">Description</label>

View File

@ -1,11 +1,11 @@
<html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<form action="/HtmlGeneration_Product" method="post"> <div>
<label class="product" for="z0__HomePage">HomePage</label>
<input size="50" disabled="disabled" readonly="readonly" type="url" id="z0__HomePage" name="[0].HomePage" value="http://www.contoso.com/" />
<input type="url" size="50" disabled="disabled" readonly="readonly" id="z0__HomePage" name="[0].HomePage" value="http://www.contoso.com/" />
</div>
<div>
@ -22,7 +22,7 @@
</textarea>
</div> <div>
<label class="product" for="z1__HomePage">HomePage</label>
<input size="50" disabled="disabled" readonly="readonly" type="url" id="z1__HomePage" name="[1].HomePage" value="" />
<input type="url" size="50" disabled="disabled" readonly="readonly" id="z1__HomePage" name="[1].HomePage" value="" />
</div>
<div>
@ -39,7 +39,7 @@
</textarea>
</div> <div>
<label class="product" for="z2__HomePage">HomePage</label>
<input size="50" disabled="disabled" readonly="readonly" type="url" id="z2__HomePage" name="[2].HomePage" value="" />
<input type="url" size="50" disabled="disabled" readonly="readonly" id="z2__HomePage" name="[2].HomePage" value="" />
</div>
<div>

View File

@ -10,15 +10,15 @@
// Regular script with comment in body, and extra properties.
</script>
<script data-foo="foo-data2" title="&lt;the title>" src="HtmlEncode[[/blank.js?a=b&c=d]]">
<script src="HtmlEncode[[/blank.js?a=b&c=d]]" data-foo="foo-data2" title="&lt;the title>">
// TagHelper script with comment in body, and extra properties.
</script>
<script>(false||document.write("<script JavaScriptEncode[[data-foo]]=\"JavaScriptEncode[[foo-data2]]\" JavaScriptEncode[[title]]=\"JavaScriptEncode[[&lt;the title>]]\" src=\"JavaScriptEncode[[/site.js?a=b&c=d]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/site.js?a=b&c=d]]\" JavaScriptEncode[[data-foo]]=\"JavaScriptEncode[[foo-data2]]\" JavaScriptEncode[[title]]=\"JavaScriptEncode[[&lt;the title>]]\"><\/script>"));</script>
<script title="&quot;the&quot; title" src="HtmlEncode[[/blank.js]]">
<script src="HtmlEncode[[/blank.js]]" title="&quot;the&quot; title">
// Fallback to globbed src
</script>
<script>(false||document.write("<script JavaScriptEncode[[title]]=\"JavaScriptEncode[["the" title]]\" src=\"JavaScriptEncode[[/site.js]]\"><\/script>"));</script>
<script>(false||document.write("<script src=\"JavaScriptEncode[[/site.js]]\" JavaScriptEncode[[title]]=\"JavaScriptEncode[["the" title]]\"><\/script>"));</script>
<script src="HtmlEncode[[/blank.js]]">
// Fallback to globbed src with exclude

View File

@ -10,15 +10,15 @@
// Regular script with comment in body, and extra properties.
</script>
<script data-foo="foo-data2" title="&lt;the title>" src="/blank.js?a=b&amp;c=d">
<script src="/blank.js?a=b&amp;c=d" data-foo="foo-data2" title="&lt;the title>">
// TagHelper script with comment in body, and extra properties.
</script>
<script>(false||document.write("<script data-foo=\"foo-data2\" title=\"\u0026lt;the title\u003E\" src=\"\/site.js?a=b\u0026c=d\"><\/script>"));</script>
<script>(false||document.write("<script src=\"\/site.js?a=b\u0026c=d\" data-foo=\"foo-data2\" title=\"\u0026lt;the title\u003E\"><\/script>"));</script>
<script title="&quot;the&quot; title" src="/blank.js">
<script src="/blank.js" title="&quot;the&quot; title">
// Fallback to globbed src
</script>
<script>(false||document.write("<script title=\"\u0022the\u0022 title\" src=\"\/site.js\"><\/script>"));</script>
<script>(false||document.write("<script src=\"\/site.js\" title=\"\u0022the\u0022 title\"><\/script>"));</script>
<script src="/blank.js">
// Fallback to globbed src with exclude

View File

@ -536,8 +536,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var expectedAttributes = new TagHelperAttributeList
{
{ "class", "form-control radio-control" },
{ "type", inputTypeName ?? "radio" }, // Generator restores type attribute; adds "radio" if none.
{ "value", value },
{ "type", inputTypeName ?? "radio" }, // Generator restores type attribute; adds "radio" if none.
};
var expectedPreContent = "original pre-content";
var expectedContent = "original content";

View File

@ -388,8 +388,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.Equal("rel", output.Attributes[0].Name);
Assert.Equal("data-extra", output.Attributes[1].Name);
Assert.Equal("href", output.Attributes[2].Name);
Assert.Equal("href", output.Attributes[1].Name);
Assert.Equal("data-extra", output.Attributes[2].Name);
}
public static TheoryData DoesNotRunWhenARequiredAttributeIsMissing_Data

View File

@ -589,8 +589,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.Equal("data-extra", output.Attributes[0].Name);
Assert.Equal("data-more", output.Attributes[1].Name);
Assert.Equal("src", output.Attributes[2].Name);
Assert.Equal("src", output.Attributes[1].Name);
Assert.Equal("data-more", output.Attributes[2].Name);
Assert.Empty(logger.Logged);
}

View File

@ -8,13 +8,345 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Testing;
using Microsoft.Framework.WebEncoders.Testing;
using Xunit;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
public class TagHelperOutputExtensionsTest
{
public static TheoryData CopyHtmlAttributeData_MaintainsOrder
{
get
{
// attributeNameToCopy, outputAttributes, allAttributes, expectedAttributes
return new TheoryData<
string,
TagHelperAttributeList,
TagHelperAttributeList,
IEnumerable<TagHelperAttribute>>
{
{
"first",
new TagHelperAttributeList
{
{ "second", "B" },
},
new TagHelperAttributeList
{
{ "first", "A" },
{ "second", "B" }
},
new[]
{
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("second", "B"),
}
},
{
"second",
new TagHelperAttributeList
{
{ "first", "A" },
},
new TagHelperAttributeList
{
{ "second", "B" },
{ "second", "Duplicate B" },
{ "first", "A" },
},
new[]
{
new TagHelperAttribute("second", "B"),
new TagHelperAttribute("second", "Duplicate B"),
new TagHelperAttribute("first", "A"),
}
},
{
"second",
new TagHelperAttributeList
{
{ "first", "A" },
},
new TagHelperAttributeList
{
{ "second", "B" },
{ "first", "A" },
{ "second", "Duplicate B" }
},
new[]
{
new TagHelperAttribute("second", "B"),
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("second", "Duplicate B"),
}
},
{
"dynamic",
new TagHelperAttributeList
{
{ "first", "A" },
{ "second", "B" },
},
new TagHelperAttributeList
{
{ "dynamic", "value" },
},
new[]
{
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("second", "B"),
new TagHelperAttribute("dynamic", "value"),
}
},
{
"second",
new TagHelperAttributeList
{
{ "first", "A" },
{ "dynamic", "value" },
{ "secondDynamic", "another value"}
},
new TagHelperAttributeList
{
{ "second", "B" }
},
new[]
{
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("dynamic", "value"),
new TagHelperAttribute("secondDynamic", "another value"),
new TagHelperAttribute("second", "B"),
}
},
{
"second",
new TagHelperAttributeList
{
{ "first", "A" },
{ "dynamic", "value" },
{ "secondDynamic", "another value"}
},
new TagHelperAttributeList
{
{ "first", "A" },
{ "second", "B" }
},
new[]
{
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("second", "B"),
new TagHelperAttribute("dynamic", "value"),
new TagHelperAttribute("secondDynamic", "another value"),
}
},
{
"third",
new TagHelperAttributeList
{
{ "first", "A" },
{ "dynamic", "value" },
{ "secondDynamic", "another value"}
},
new TagHelperAttributeList
{
{ "first", "A" },
{ "second", "B" },
{ "third", "C" }
},
new[]
{
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("third", "C"),
new TagHelperAttribute("dynamic", "value"),
new TagHelperAttribute("secondDynamic", "another value"),
}
},
{
"first",
new TagHelperAttributeList
{
{ "third", "C" },
{ "dynamic", "value" },
{ "secondDynamic", "another value"}
},
new TagHelperAttributeList
{
{ "first", "A" },
{ "second", "B" },
{ "third", "C" }
},
new[]
{
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("third", "C"),
new TagHelperAttribute("dynamic", "value"),
new TagHelperAttribute("secondDynamic", "another value"),
}
},
{
"first",
new TagHelperAttributeList
{
{ "third", "C" },
{ "dynamic", "value" },
{ "secondDynamic", "another value"}
},
new TagHelperAttributeList
{
{ "first", "A" },
{ "secondDynamic", "another value"},
{ "second", "B" },
{ "third", "C" },
},
new[]
{
new TagHelperAttribute("third", "C"),
new TagHelperAttribute("dynamic", "value"),
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("secondDynamic", "another value"),
}
},
{
"first",
new TagHelperAttributeList
{
{ "third", "C" },
{ "dynamic", "value" },
{ "secondDynamic", "another value"}
},
new TagHelperAttributeList
{
{ "first", "A" },
{ "secondDynamic", "another value"},
{ "first", "Second first" },
{ "second", "B" },
{ "third", "C" },
{ "first", "third first" },
},
new[]
{
new TagHelperAttribute("third", "C"),
new TagHelperAttribute("first", "third first"),
new TagHelperAttribute("dynamic", "value"),
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("secondDynamic", "another value"),
new TagHelperAttribute("first", "Second first"),
}
},
{
"first",
new TagHelperAttributeList
{
{ "third", "C" },
{ "third", "Duplicate Third" },
},
new TagHelperAttributeList
{
{ "third", "C" },
{ "first", "A" },
{ "third", "Duplicate Third" },
},
new[]
{
new TagHelperAttribute("third", "C"),
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("third", "Duplicate Third"),
}
},
{
"first",
new TagHelperAttributeList
{
{ "third", "C" },
{ "third", "Duplicate Third" },
},
new TagHelperAttributeList
{
{ "third", "C" },
{ "third", "Duplicate Third" },
{ "first", "A" },
},
new[]
{
new TagHelperAttribute("third", "C"),
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("third", "Duplicate Third"),
}
},
{
"first",
new TagHelperAttributeList
{
{ "third", "D" },
},
new TagHelperAttributeList
{
{ "first", "A" },
{ "first", "B" },
{ "first", "C" },
{ "third", "D" },
},
new[]
{
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("first", "C"),
new TagHelperAttribute("first", "B"),
new TagHelperAttribute("third", "D"),
}
},
{
"first",
new TagHelperAttributeList
{
{ "third", "D" },
{ "dynamic", "value" },
{ "third", "Duplicate Third" },
},
new TagHelperAttributeList
{
{ "third", "D" },
{ "first", "A" },
{ "third", "Duplicate Third" },
{ "first", "B" },
{ "first", "C" },
},
new[]
{
new TagHelperAttribute("third", "D"),
new TagHelperAttribute("first", "A"),
new TagHelperAttribute("first", "B"),
new TagHelperAttribute("first", "C"),
new TagHelperAttribute("dynamic", "value"),
new TagHelperAttribute("third", "Duplicate Third"),
}
},
};
}
}
[Theory]
[MemberData(nameof(CopyHtmlAttributeData_MaintainsOrder))]
public void CopyHtmlAttribute_MaintainsOrder(
string attributeNameToCopy,
TagHelperAttributeList outputAttributes,
TagHelperAttributeList allAttributes,
IEnumerable<TagHelperAttribute> expectedAttributes)
{
// Arrange
var output = new TagHelperOutput("p", attributes: new TagHelperAttributeList(outputAttributes));
var context = new TagHelperContext(
allAttributes,
items: new Dictionary<object, object>(),
uniqueId: "test",
getChildContentAsync: useCachedResult => Task.FromResult<TagHelperContent>(result: null));
// Act
output.CopyHtmlAttribute(attributeNameToCopy, context);
// Assert
Assert.Equal(expectedAttributes, output.Attributes, CaseSensitiveTagHelperAttributeComparer.Default);
}
public static TheoryData CopyHtmlAttributeData_MultipleAttributesSameName
{
get