[Fixes #2382] Use PostElement in Script and Link tag helpers

This commit is contained in:
Ajay Bhargav Baaskaran 2015-07-16 12:50:34 -07:00
parent 5ae6d029ce
commit 67474d8cbc
4 changed files with 343 additions and 244 deletions

View File

@ -221,23 +221,35 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
return;
}
// Get the highest matched mode
var mode = modeResult.FullMatches.Select(match => match.Mode).Max();
// NOTE: Values in TagHelperOutput.Attributes may already be HTML-encoded.
var attributes = new TagHelperAttributeList(output.Attributes);
if (AppendVersion == true)
{
EnsureFileVersionProvider();
var attributeStringValue = output.Attributes[HrefAttributeName]?.Value as string;
if (attributeStringValue != null)
{
output.Attributes[HrefAttributeName].Value =
_fileVersionProvider.AddFileVersionToPath(attributeStringValue);
}
}
var builder = new DefaultTagHelperContent();
if (mode == Mode.Fallback && string.IsNullOrEmpty(HrefInclude) || mode == Mode.AppendVersion)
{
// No globbing to do, just build a <link /> tag to match the original one in the source file.
// Or just add file version to the link tag.
BuildLinkTag(attributes, builder);
}
else
// Get the highest matched mode
var mode = modeResult.FullMatches.Select(match => match.Mode).Max();
if (mode == Mode.GlobbedHref || mode == Mode.Fallback && !string.IsNullOrEmpty(HrefInclude))
{
BuildGlobbedLinkTags(attributes, builder);
if (string.IsNullOrEmpty(Href))
{
// Only HrefInclude is specified. Don't render the original tag.
output.TagName = null;
output.Content.SetContent(string.Empty);
}
}
if (mode == Mode.Fallback)
@ -245,22 +257,26 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
BuildFallbackBlock(builder);
}
// We've taken over tag rendering, so prevent rendering the outer tag
output.TagName = null;
output.Content.SetContent(builder);
output.PostElement.SetContent(builder);
}
private void BuildGlobbedLinkTags(TagHelperAttributeList attributes, TagHelperContent builder)
{
EnsureGlobbingUrlBuilder();
// Build a <link /> tag for each matched href as well as the original one in the source file
var urls = GlobbingUrlBuilder.BuildUrlList(Href, HrefInclude, HrefExclude);
// Build a <link /> tag for each matched href.
var urls = GlobbingUrlBuilder.BuildUrlList(null, HrefInclude, HrefExclude);
foreach (var url in urls)
{
// "url" values come from bound attributes and globbing. Must always be non-null.
Debug.Assert(url != null);
if (string.Equals(Href, url, StringComparison.OrdinalIgnoreCase))
{
// Don't build duplicate link tag for the original href url.
continue;
}
attributes[HrefAttributeName] = url;
BuildLinkTag(attributes, builder);
}
@ -332,7 +348,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
private void BuildLinkTag(TagHelperAttributeList attributes, TagHelperContent builder)
{
EnsureFileVersionProvider();
builder.Append("<link ");
foreach (var attribute in attributes)

View File

@ -172,7 +172,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
protected internal GlobbingUrlBuilder GlobbingUrlBuilder { get; set; }
/// <inheritdoc />
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
public override void Process(TagHelperContext context, TagHelperOutput output)
{
// Pass through attribute that is also a well-known HTML attribute.
if (Src != null)
@ -190,24 +190,35 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
return;
}
// Get the highest matched mode
var mode = modeResult.FullMatches.Select(match => match.Mode).Max();
// NOTE: Values in TagHelperOutput.Attributes may already be HTML-encoded.
var attributes = new TagHelperAttributeList(output.Attributes);
var builder = new DefaultTagHelperContent();
var originalContent = await context.GetChildContentAsync();
if (AppendVersion == true)
{
EnsureFileVersionProvider();
if (mode == Mode.Fallback && string.IsNullOrEmpty(SrcInclude) || mode == Mode.AppendVersion)
{
// No globbing to do, just build a <script /> tag to match the original one in the source file
// Or just add file version to the script tag.
BuildScriptTag(originalContent, attributes, builder);
var attributeStringValue = output.Attributes[SrcAttributeName]?.Value as string;
if (attributeStringValue != null)
{
output.Attributes[SrcAttributeName].Value =
_fileVersionProvider.AddFileVersionToPath(attributeStringValue);
}
}
else
var builder = new DefaultTagHelperContent();
// Get the highest matched mode
var mode = modeResult.FullMatches.Select(match => match.Mode).Max();
if (mode == Mode.GlobbedSrc || mode == Mode.Fallback && !string.IsNullOrEmpty(SrcInclude))
{
BuildGlobbedScriptTags(originalContent, attributes, builder);
BuildGlobbedScriptTags(attributes, builder);
if (string.IsNullOrEmpty(Src))
{
// Only SrcInclude is specified. Don't render the original tag.
output.TagName = null;
output.Content.SetContent(string.Empty);
}
}
if (mode == Mode.Fallback)
@ -215,41 +226,36 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
BuildFallbackBlock(attributes, builder);
}
// We've taken over tag rendering, so prevent rendering the outer tag
output.TagName = null;
output.Content.SetContent(builder);
output.PostElement.SetContent(builder);
}
private void BuildGlobbedScriptTags(
TagHelperContent originalContent,
TagHelperAttributeList attributes,
TagHelperContent builder)
{
EnsureGlobbingUrlBuilder();
// Build a <script> tag for each matched src as well as the original one in the source file
var urls = GlobbingUrlBuilder.BuildUrlList(Src, SrcInclude, SrcExclude);
var urls = GlobbingUrlBuilder.BuildUrlList(null, SrcInclude, SrcExclude);
foreach (var url in urls)
{
// "url" values come from bound attributes and globbing. Must always be non-null.
Debug.Assert(url != null);
var content = originalContent;
if (!string.Equals(url, Src, StringComparison.OrdinalIgnoreCase))
if (string.Equals(url, Src, StringComparison.OrdinalIgnoreCase))
{
// Do not copy content into added <script/> elements.
content = null;
// Don't build duplicate script tag for the original source url.
continue;
}
attributes[SrcAttributeName] = url;
BuildScriptTag(content, attributes, builder);
BuildScriptTag(attributes, builder);
}
}
private void BuildFallbackBlock(TagHelperAttributeList attributes, DefaultTagHelperContent builder)
{
EnsureGlobbingUrlBuilder();
EnsureFileVersionProvider();
var fallbackSrcs = GlobbingUrlBuilder.BuildUrlList(FallbackSrc, FallbackSrcInclude, FallbackSrcExclude);
if (fallbackSrcs.Any())
@ -330,11 +336,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
private void BuildScriptTag(
TagHelperContent content,
TagHelperAttributeList attributes,
TagHelperContent builder)
{
EnsureFileVersionProvider();
builder.Append("<script");
foreach (var attribute in attributes)
@ -356,9 +360,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
AppendAttribute(builder, attribute.Name, attributeValue, escapeQuotes: false);
}
builder.Append(">")
.Append(content)
.Append("</script>");
builder.Append("></script>");
}
private void AppendAttribute(TagHelperContent content, string key, object value, bool escapeQuotes)

View File

@ -32,16 +32,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
get
{
// outputAttributes, expectedAttributeString
return new TheoryData<TagHelperAttributeList, string>
// outputAttributes
return new TheoryData<TagHelperAttributeList>
{
{
new TagHelperAttributeList
{
{ "hello", "world" },
{ "hello", "world2" }
},
"hello=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\""
}
},
{
new TagHelperAttributeList
@ -49,16 +48,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "hello", "world" },
{ "hello", "world2" },
{ "hello", "world3" }
},
"hello=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\" hello=\"HtmlEncode[[world3]]\""
}
},
{
new TagHelperAttributeList
{
{ "HelLO", "world" },
{ "HELLO", "world2" }
},
"HelLO=\"HtmlEncode[[world]]\" HELLO=\"HtmlEncode[[world2]]\""
}
},
{
new TagHelperAttributeList
@ -66,16 +63,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "Hello", "world" },
{ "HELLO", "world2" },
{ "hello", "world3" }
},
"Hello=\"HtmlEncode[[world]]\" HELLO=\"HtmlEncode[[world2]]\" hello=\"HtmlEncode[[world3]]\""
}
},
{
new TagHelperAttributeList
{
{ "HeLlO", "world" },
{ "hello", "world2" }
},
"HeLlO=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\""
}
},
};
}
@ -83,9 +78,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[Theory]
[MemberData(nameof(MultiAttributeSameNameData))]
public void HandlesMultipleAttributesSameNameCorrectly(
TagHelperAttributeList outputAttributes,
string expectedAttributeString)
public void HandlesMultipleAttributesSameNameCorrectly(TagHelperAttributeList outputAttributes)
{
// Arrange
var allAttributes = new TagHelperAttributeList(
@ -125,12 +118,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
FallbackTestValue = "hidden",
Href = "test.css",
};
var expectedAttributes = new TagHelperAttributeList(output.Attributes);
expectedAttributes.Add(new TagHelperAttribute("href", "test.css"));
// Act
helper.Process(context, output);
// Assert
Assert.StartsWith("<link " + expectedAttributeString + " rel=\"stylesheet\"", output.Content.GetContent());
Assert.Equal(expectedAttributes, output.Attributes);
}
public static TheoryData RunsWhenRequiredAttributesArePresent_Data
@ -139,28 +134,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
return new TheoryData<TagHelperAttributeList, Action<LinkTagHelper>>
{
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
}
},
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css",
["asp-href-exclude"] = "*.min.css"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
tagHelper.HrefExclude = "*.min.css";
}
},
{
new TagHelperAttributeList
{
@ -194,42 +167,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
},
// File Version
{
new TagHelperAttributeList
{
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css",
["asp-href-exclude"] = "*.min.css",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
tagHelper.HrefExclude = "*.min.css";
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
@ -282,6 +219,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(new[] { "/common.css" });
var helper = new LinkTagHelper(
logger.Object,
@ -291,6 +231,102 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
new CommonTestEncoder())
{
ViewContext = viewContext,
GlobbingUrlBuilder = globbingUrlBuilder.Object
};
setProperties(helper);
// Act
helper.Process(context, output);
// Assert
Assert.NotNull(output.TagName);
Assert.False(output.IsContentModified);
Assert.True(output.PostElement.IsModified);
}
public static TheoryData RunsWhenRequiredAttributesArePresent_NoHref_Data
{
get
{
return new TheoryData<TagHelperAttributeList, Action<LinkTagHelper>>
{
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
}
},
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css",
["asp-href-exclude"] = "*.min.css"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
tagHelper.HrefExclude = "*.min.css";
}
},
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
["asp-href-include"] = "*.css",
["asp-href-exclude"] = "*.min.css",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.HrefInclude = "*.css";
tagHelper.HrefExclude = "*.min.css";
tagHelper.AppendVersion = true;
}
}
};
}
}
[Theory]
[MemberData(nameof(RunsWhenRequiredAttributesArePresent_NoHref_Data))]
public void RunsWhenRequiredAttributesArePresent_NoHref(
TagHelperAttributeList attributes,
Action<LinkTagHelper> setProperties)
{
// Arrange
var context = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("link");
var logger = new Mock<ILogger<LinkTagHelper>>();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(new[] { "/common.css" });
var helper = new LinkTagHelper(
logger.Object,
hostingEnvironment,
MakeCache(),
new CommonTestEncoder(),
new CommonTestEncoder())
{
ViewContext = viewContext,
GlobbingUrlBuilder = globbingUrlBuilder.Object
};
setProperties(helper);
@ -299,20 +335,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.Null(output.TagName);
Assert.NotNull(output.Content);
Assert.True(output.IsContentModified);
Assert.True(output.PostElement.IsModified);
}
[Fact]
public void PreservesOrderOfSourceAttributesWhenRun()
public void PreservesOrderOfNonHrefAttributes()
{
// Arrange
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
{ "rel", new HtmlString("stylesheet") },
{ "data-extra", new HtmlString("something") },
{ "href", "test.css" },
{ "data-extra", new HtmlString("something") },
{ "asp-fallback-href", "test.css" },
{ "asp-fallback-test-class", "hidden" },
{ "asp-fallback-test-property", "visibility" },
@ -347,8 +383,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
helper.Process(context, output);
// Assert
Assert.StartsWith(
"<link rel=\"stylesheet\" data-extra=\"something\" href=\"HtmlEncode[[test.css]]\"", output.Content.GetContent());
Assert.Equal("rel", output.Attributes[0].Name);
Assert.Equal("data-extra", output.Attributes[1].Name);
Assert.Equal("href", output.Attributes[2].Name);
}
public static TheoryData DoesNotRunWhenARequiredAttributeIsMissing_Data
@ -453,6 +490,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.NotNull(output.TagName);
Assert.False(output.IsContentModified);
Assert.Empty(output.Attributes);
Assert.True(output.PostElement.IsEmpty);
}
[Fact]
@ -481,6 +520,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.NotNull(output.TagName);
Assert.False(output.IsContentModified);
Assert.Empty(output.Attributes);
Assert.True(output.PostElement.IsEmpty);
}
[Fact]
@ -502,8 +543,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList("/css/site.css", "**/*.css", null))
.Returns(new[] { "/css/site.css", "/base.css" });
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
.Returns(new[] { "/base.css" });
var helper = new LinkTagHelper(
logger.Object,
@ -522,10 +563,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
helper.Process(context, output);
// Assert
Assert.Equal(
"<link rel=\"stylesheet\" href=\"HtmlEncode[[/css/site.css]]\" />" +
"<link rel=\"stylesheet\" href=\"HtmlEncode[[/base.css]]\" />",
output.Content.GetContent());
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css", output.Attributes["href"].Value);
Assert.Equal("<link rel=\"stylesheet\" href=\"HtmlEncode[[/base.css]]\" />", output.PostElement.GetContent());
}
[Fact]
@ -547,8 +587,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList("/css/site.css", "**/*.css", null))
.Returns(new[] { "/css/site.css", "/base.css" });
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
.Returns(new[] { "/base.css" });
var helper = new LinkTagHelper(
logger.Object,
@ -567,10 +607,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
helper.Process(context, output);
// Assert
Assert.Equal(
"<link rel=\"HtmlEncode[[stylesheet]]\" href=\"HtmlEncode[[/css/site.css]]\" />" +
"<link rel=\"HtmlEncode[[stylesheet]]\" href=\"HtmlEncode[[/base.css]]\" />",
output.Content.GetContent());
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css", output.Attributes["href"].Value);
Assert.Equal("<link rel=\"HtmlEncode[[stylesheet]]\" href=\"HtmlEncode[[/base.css]]\" />",
output.PostElement.GetContent());
}
[ConditionalTheory]
@ -603,7 +643,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
ViewContext = viewContext,
Href = "/css/site.css",
HrefInclude = "**/*.css",
AppendVersion = true
};
@ -611,9 +650,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
helper.Process(context, output);
// Assert
Assert.Equal(
"<link rel=\"stylesheet\" href=\"HtmlEncode[[/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\" />",
output.Content.GetContent());
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["href"].Value);
}
[ConditionalTheory]
@ -646,7 +684,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
ViewContext = viewContext,
Href = "/bar/css/site.css",
HrefInclude = "**/*.css",
AppendVersion = true
};
@ -654,9 +691,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
helper.Process(context, output);
// Assert
Assert.Equal(
"<link rel=\"stylesheet\" href=\"HtmlEncode[[/bar/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\" />",
output.Content.GetContent());
Assert.Equal("link", output.TagName);
Assert.Equal("/bar/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["href"].Value);
}
[ConditionalTheory]
@ -681,8 +717,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList("/css/site.css", "**/*.css", null))
.Returns(new[] { "/css/site.css", "/base.css" });
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null))
.Returns(new[] { "/base.css" });
var helper = new LinkTagHelper(
logger.Object,
@ -702,10 +738,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
helper.Process(context, output);
// Assert
Assert.Equal(
"<link rel=\"stylesheet\" href=\"HtmlEncode[[/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\" />" +
"<link rel=\"stylesheet\" href=\"HtmlEncode[[/base.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\" />",
output.Content.GetContent());
Assert.Equal("link", output.TagName);
Assert.Equal("/css/site.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["href"].Value);
Assert.Equal("<link rel=\"stylesheet\" href=\"HtmlEncode[[/base.css?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\" />",
output.PostElement.GetContent());
}
private static ViewContext MakeViewContext(string requestPathBase = null)

View File

@ -31,9 +31,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
[Theory]
[MemberData(nameof(LinkTagHelperTest.MultiAttributeSameNameData), MemberType = typeof(LinkTagHelperTest))]
public async Task HandlesMultipleAttributesSameNameCorrectly(
TagHelperAttributeList outputAttributes,
string expectedAttributeString)
public async Task HandlesMultipleAttributesSameNameCorrectly(TagHelperAttributeList outputAttributes)
{
// Arrange
var allAttributes = new TagHelperAttributeList(
@ -68,15 +66,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
FallbackTestExpression = "http://www.example.com/blank.js",
Src = "/blank.js",
};
var expectedAttributes = new TagHelperAttributeList(output.Attributes);
expectedAttributes.Add(new TagHelperAttribute("src", "/blank.js"));
// Act
await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.StartsWith(
"<script " + expectedAttributeString + " data-extra=\"something\" " +
"src=\"HtmlEncode[[/blank.js]]\"",
output.Content.GetContent());
Assert.Equal(expectedAttributes, output.Attributes);
}
public static TheoryData RunsWhenRequiredAttributesArePresent_Data
@ -85,28 +82,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
return new TheoryData<TagHelperAttributeList, Action<ScriptTagHelper>>
{
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
}
},
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js",
["asp-src-exclude"] = "*.min.js"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
tagHelper.SrcExclude = "*.min.js";
}
},
{
new TagHelperAttributeList
{
@ -160,42 +135,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
},
// File Version
{
new TagHelperAttributeList
{
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js",
["asp-src-exclude"] = "*.min.js",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
tagHelper.SrcExclude = "*.min.js";
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
@ -272,6 +211,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var logger = CreateLogger();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
CreateLogger(),
@ -281,6 +223,103 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
new CommonTestEncoder())
{
ViewContext = viewContext,
GlobbingUrlBuilder = globbingUrlBuilder.Object
};
setProperties(helper);
// Act
await helper.ProcessAsync(context, output);
// Assert
Assert.NotNull(output.TagName);
Assert.False(output.IsContentModified);
Assert.True(output.PostElement.IsModified);
Assert.Empty(logger.Logged);
}
public static TheoryData RunsWhenRequiredAttributesArePresent_NoSrc_Data
{
get
{
return new TheoryData<TagHelperAttributeList, Action<ScriptTagHelper>>
{
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
}
},
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js",
["asp-src-exclude"] = "*.min.js"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
tagHelper.SrcExclude = "*.min.js";
}
},
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
tagHelper.AppendVersion = true;
}
},
{
new TagHelperAttributeList
{
["asp-src-include"] = "*.js",
["asp-src-exclude"] = "*.min.js",
["asp-append-version"] = "true"
},
tagHelper =>
{
tagHelper.SrcInclude = "*.js";
tagHelper.SrcExclude = "*.min.js";
tagHelper.AppendVersion = true;
}
}
};
}
}
[Theory]
[MemberData(nameof(RunsWhenRequiredAttributesArePresent_NoSrc_Data))]
public async Task RunsWhenRequiredAttributesArePresent_NoSrc(
TagHelperAttributeList attributes,
Action<ScriptTagHelper> setProperties)
{
// Arrange
var context = MakeTagHelperContext(attributes);
var output = MakeTagHelperOutput("script");
var logger = CreateLogger();
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
CreateLogger(),
hostingEnvironment,
MakeCache(),
new CommonTestEncoder(),
new CommonTestEncoder())
{
ViewContext = viewContext,
GlobbingUrlBuilder = globbingUrlBuilder.Object
};
setProperties(helper);
@ -290,6 +329,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.Null(output.TagName);
Assert.True(output.IsContentModified);
Assert.True(output.PostElement.IsModified);
Assert.Empty(logger.Logged);
}
@ -383,6 +423,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.NotNull(output.TagName);
Assert.False(output.IsContentModified);
Assert.Empty(output.Attributes);
Assert.True(output.PostElement.IsEmpty);
}
[Theory]
@ -415,6 +457,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.Equal("script", output.TagName);
Assert.False(output.IsContentModified);
Assert.Empty(output.Attributes);
Assert.True(output.PostElement.IsEmpty);
Assert.Equal(2, logger.Logged.Count);
@ -455,6 +499,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert
Assert.Equal("script", output.TagName);
Assert.False(output.IsContentModified);
Assert.Empty(output.Attributes);
Assert.True(output.PostElement.IsEmpty);
}
[Fact]
@ -492,7 +538,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
[Fact]
public async Task PreservesOrderOfSourceAttributesWhenRun()
public async Task PreservesOrderOfNonSrcAttributes()
{
// Arrange
var tagHelperContext = MakeTagHelperContext(
@ -534,9 +580,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await helper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.StartsWith(
"<script data-extra=\"HtmlEncode[[something]]\" data-more=\"HtmlEncode[[else]]\" src=\"HtmlEncode[[/blank.js]]\"",
output.Content.GetContent());
Assert.Equal("data-extra", output.Attributes[0].Name);
Assert.Equal("data-more", output.Attributes[1].Name);
Assert.Equal("src", output.Attributes[2].Name);
Assert.Empty(logger.Logged);
}
@ -555,8 +601,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList("/js/site.js", "**/*.js", null))
.Returns(new[] { "/js/site.js", "/common.js" });
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null))
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
logger.Object,
@ -575,8 +621,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("<script src=\"HtmlEncode[[/js/site.js]]\"></script>" +
"<script src=\"HtmlEncode[[/common.js]]\"></script>", output.Content.GetContent());
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js", output.Attributes["src"].Value);
Assert.Equal("<script src=\"HtmlEncode[[/common.js]]\"></script>", output.PostElement.GetContent());
}
[Fact]
@ -594,8 +641,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList("/js/site.js", "**/*.js", null))
.Returns(new[] { "/js/site.js", "/common.js" });
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null))
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
logger.Object,
@ -614,8 +661,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("<script src=\"HtmlEncode[[/js/site.js]]\"></script>" +
"<script src=\"HtmlEncode[[/common.js]]\"></script>", output.Content.GetContent());
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js", output.Attributes["src"].Value);
Assert.Equal("<script src=\"HtmlEncode[[/common.js]]\"></script>", output.PostElement.GetContent());
}
[ConditionalTheory]
@ -652,9 +700,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await helper.ProcessAsync(context, output);
// Assert
Assert.Equal(
"<script src=\"HtmlEncode[[/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\">" +
"</script>", output.Content.GetContent());
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["src"].Value);
}
[ConditionalTheory]
@ -691,9 +738,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await helper.ProcessAsync(context, output);
// Assert
Assert.Equal(
"<script src=\"HtmlEncode[[/bar/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\">" +
"</script>", output.Content.GetContent());
Assert.Equal("script", output.TagName);
Assert.Equal("/bar/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["src"].Value);
}
[ConditionalTheory]
@ -734,12 +780,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await helper.ProcessAsync(context, output);
// Assert
Assert.Equal(
"<script src=\"HtmlEncode[[/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\">" +
$"</script>{Environment.NewLine}" +
"<script>(isavailable()||document.write(\"<script src=\\\"JavaScriptStringEncode[[fallback.js" +
"?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\\\"><\\/script>\"));</script>",
output.Content.GetContent());
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["src"].Value);
Assert.Equal(Environment.NewLine + "<script>(isavailable()||document.write(\"<script " +
"src=\\\"JavaScriptStringEncode[[fallback.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\\\">" +
"<\\/script>\"));</script>", output.PostElement.GetContent());
}
[ConditionalTheory]
@ -760,8 +805,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var hostingEnvironment = MakeHostingEnvironment();
var viewContext = MakeViewContext();
var globbingUrlBuilder = new Mock<GlobbingUrlBuilder>();
globbingUrlBuilder.Setup(g => g.BuildUrlList("/js/site.js", "*.js", null))
.Returns(new[] { "/js/site.js", "/common.js" });
globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "*.js", null))
.Returns(new[] { "/common.js" });
var helper = new ScriptTagHelper(
logger.Object,
@ -781,9 +826,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await helper.ProcessAsync(context, output);
// Assert
Assert.Equal("<script src=\"HtmlEncode[[/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\">" +
"</script><script src=\"HtmlEncode[[/common.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\">" +
"</script>", output.Content.GetContent());
Assert.Equal("script", output.TagName);
Assert.Equal("/js/site.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", output.Attributes["src"].Value);
Assert.Equal("<script src=\"HtmlEncode[[/common.js?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk]]\">" +
"</script>", output.PostElement.GetContent());
}
private TagHelperContext MakeTagHelperContext(