MVC tag helpers should not override tag name in Razor source

- #1523
- remove `TagHelperOutput.Merge()` extension method entirely
- test tag name preservation with all MVC tag helpers
 - `<input/>` tag helper generation of a checkbox wasn't previously tested

nits:
- fix argument order in a couple of `Assert.Equal()` calls
- remove use of "original tag name"
This commit is contained in:
Doug Bunting 2014-11-17 08:50:27 -08:00
parent 99a1848598
commit 1b28e19114
16 changed files with 43 additions and 87 deletions

View File

@ -97,8 +97,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
if (tagBuilder != null)
{
// We don't want to do a full merge because we want the TagHelper content to take precedence.
output.Merge(tagBuilder);
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
output.SelfClosing = false;
}
}

View File

@ -199,8 +199,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
// This TagBuilder contains the one <input/> element of interest. Since this is not the "checkbox"
// special-case, output is a self-closing element and can merge the TagBuilder in directly.
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
output.SelfClosing = true;
output.Merge(tagBuilder);
}
}
}

View File

@ -48,8 +48,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
output.Content = tagBuilder.InnerHtml;
}
output.TagName = tagBuilder.TagName;
}
}
}

View File

@ -137,8 +137,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
output.SelfClosing = false;
output.Merge(tagBuilder);
}
// Whether or not (not being highly unlikely) we generate anything, could update contained <option/>

View File

@ -60,23 +60,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
return prefixedAttributes;
}
/// <summary>
/// Merges the given <paramref name="tagBuilder"/> into the <paramref name="tagHelperOutput"/>.
/// </summary>
/// <param name="tagHelperOutput">The <see cref="TagHelperOutput"/> this method extends.</param>
/// <param name="tagBuilder">The <see cref="TagBuilder"/> to merge.</param>
/// <remarks><paramref name="tagHelperOutput"/>'s <see cref="TagHelperOutput.Content"/> has the given
/// <paramref name="tagBuilder"/>s <see cref="TagBuilder.InnerHtml"/> appended to it. This is to ensure
/// multiple <see cref="ITagHelper"/>s running on the same HTML tag don't overwrite each other; therefore,
/// this method may not be appropriate for all <see cref="ITagHelper"/> scenarios.</remarks>
public static void Merge(this TagHelperOutput tagHelperOutput, TagBuilder tagBuilder)
{
tagHelperOutput.TagName = tagBuilder.TagName;
tagHelperOutput.Content += tagBuilder.InnerHtml;
MergeAttributes(tagHelperOutput, tagBuilder);
}
/// <summary>
/// Merges the given <paramref name="tagBuilder"/>'s <see cref="TagBuilder.Attributes"/> into the
/// <paramref name="tagHelperOutput"/>.

View File

@ -47,7 +47,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
output.MergeAttributes(tagBuilder);
output.SelfClosing = false;
output.TagName = tagBuilder.TagName;
}
}
}

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_GeneratesExpectedOutput()
{
// Arrange
var expectedTagName = "not-a";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var anchorTagHelper = new AnchorTagHelper
{
@ -40,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "protocol", "http" }
});
var output = new TagHelperOutput(
"a",
expectedTagName,
attributes: new Dictionary<string, string>
{
{ "id", "myanchor" },
@ -75,7 +76,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("href"));
Assert.Equal("home/index", attribute.Value);
Assert.Equal("Something", output.Content);
Assert.Equal("a", output.TagName);
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_GeneratesExpectedOutput()
{
// Arrange
var expectedTagName = "not-form";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var formTagHelper = new FormTagHelper
{
@ -40,7 +41,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "anti-forgery", true }
});
var output = new TagHelperOutput(
"form",
expectedTagName,
attributes: new Dictionary<string, string>
{
{ "id", "myform" },
@ -78,7 +79,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("action"));
Assert.Equal("home/index", attribute.Value);
Assert.Equal(expectedContent, output.Content);
Assert.Equal("form", output.TagName);
Assert.Equal(expectedTagName, output.TagName);
}
[Theory]

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "value", expectedValue },
};
var expectedContent = "original content";
var expectedTagName = "input";
var expectedTagName = "not-input";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
@ -104,7 +104,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "class", "form-control" },
};
var output = new TagHelperOutput("original tag name", htmlAttributes, expectedContent)
var output = new TagHelperOutput(expectedTagName, htmlAttributes, expectedContent)
{
SelfClosing = false,
};
@ -145,7 +145,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "value", "2014-10-15T23:24:19.000-7:00" },
};
var expectedContent = "original content";
var expectedTagName = "original tag name";
var expectedTagName = "input";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForProperty(

View File

@ -97,6 +97,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
TagHelperOutputContent tagHelperOutputContent)
{
// Arrange
var expectedTagName = "not-label";
var expectedAttributes = new Dictionary<string, string>
{
{ "class", "form-control" },
@ -117,8 +118,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "class", "form-control" },
};
var output = new TagHelperOutput("A random tag name", htmlAttributes, tagHelperOutputContent.OriginalContent);
var expectedTagName = "label";
var output = new TagHelperOutput(expectedTagName, htmlAttributes, tagHelperOutputContent.OriginalContent);
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
tagHelper.ViewContext = viewContext;
@ -143,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "class", "form-control" },
};
var expectedContent = "original content";
var expectedTagName = "original tag name";
var expectedTagName = "label";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForProperty(

View File

@ -128,7 +128,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "label", "my-label" },
};
var originalTagName = "not-option";
var expectedTagName = "not-option";
var contextAttributes = new Dictionary<string, object>
{
@ -137,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "value", value },
};
var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(originalTagName, originalAttributes, originalContent)
var output = new TagHelperOutput(expectedTagName, originalAttributes, originalContent)
{
SelfClosing = false,
};

View File

@ -167,7 +167,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "class", "form-control" },
};
var originalContent = "original content";
var originalTagName = "not-select";
var expectedAttributes = new Dictionary<string, string>(originalAttributes)
{
@ -176,7 +175,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "valid", "from validation attributes" },
};
var expectedContent = originalContent;
var expectedTagName = "select";
var expectedTagName = "not-select";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
@ -185,7 +184,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var modelExpression = new ModelExpression(nameAndId.Name, metadata);
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
var output = new TagHelperOutput(originalTagName, originalAttributes, expectedContent)
var output = new TagHelperOutput(expectedTagName, originalAttributes, expectedContent)
{
SelfClosing = true,
};
@ -238,7 +237,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "class", "form-control" },
};
var originalContent = "original content";
var originalTagName = "not-select";
var expectedAttributes = new Dictionary<string, string>(originalAttributes)
{
@ -256,7 +254,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var modelExpression = new ModelExpression(nameAndId.Name, metadata);
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
var output = new TagHelperOutput(originalTagName, originalAttributes, originalContent)
var output = new TagHelperOutput(expectedTagName, originalAttributes, originalContent)
{
SelfClosing = true,
};
@ -313,10 +311,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var originalAttributes = new Dictionary<string, string>();
var content = "original content";
var propertyName = "Property1";
var tagName = "not-select";
var expectedTagName = "select";
var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(tagName, originalAttributes, content);
var output = new TagHelperOutput(expectedTagName, originalAttributes, content);
// TODO: In real (model => model) scenario, ModelExpression should have name "" and
// TemplateInfo.HtmlFieldPrefix should be "Property1" but empty ModelExpression name is not currently
@ -376,7 +374,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var originalAttributes = new Dictionary<string, string>();
var content = "original content";
var propertyName = "Property1";
var tagName = "not-select";
var tagName = "select";
var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(tagName, originalAttributes, content);
@ -440,7 +438,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var expectedAttributes = new Dictionary<string, string>(originalAttributes);
expectedAttributes[attributeName] = (string)contextAttributes[attributeName];
var expectedContent = "original content";
var expectedTagName = "not-select";
var expectedTagName = "select";
var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(expectedTagName, originalAttributes, expectedContent)
@ -470,11 +468,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var contextAttributes = new Dictionary<string, object>();
var originalAttributes = new Dictionary<string, string>();
var content = "original content";
var tagName = "not-select";
var expectedTagName = "select";
var expectedMessage = "Cannot determine body for <select>. 'items' must be null if 'for' is null.";
var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(tagName, originalAttributes, content);
var output = new TagHelperOutput(expectedTagName, originalAttributes, content);
var tagHelper = new SelectTagHelper
{
Items = Enumerable.Empty<SelectListItem>(),
@ -500,12 +498,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var contextAttributes = new Dictionary<string, object>();
var originalAttributes = new Dictionary<string, string>();
var content = "original content";
var tagName = "not-select";
var expectedTagName = "select";
var expectedMessage = "Cannot parse 'multiple' value '" + multiple +
"' for <select>. Acceptable values are 'false', 'true' and 'multiple'.";
var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(tagName, originalAttributes, content);
var output = new TagHelperOutput(expectedTagName, originalAttributes, content);
var metadataProvider = new EmptyModelMetadataProvider();
string model = null;

View File

@ -275,34 +275,5 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("for"));
Assert.Equal(expectedBuilderAttribute.Value, attribute.Value);
}
[Fact]
public void Merge_CombinesAllTagHelperOutputAndTagBuilderProperties()
{
// Arrange
var tagHelperOutput = new TagHelperOutput(
"p",
attributes: new Dictionary<string, string>(),
content: "Hello from tagHelperOutput");
var expectedOutputAttribute = new KeyValuePair<string, string>("class", "btn");
tagHelperOutput.Attributes.Add(expectedOutputAttribute);
var tagBuilder = new TagBuilder("div");
var expectedBuilderAttribute = new KeyValuePair<string, string>("for", "hello");
tagBuilder.Attributes.Add(expectedBuilderAttribute);
tagBuilder.InnerHtml = "Hello from tagBuilder.";
// Act
tagHelperOutput.Merge(tagBuilder);
// Assert
Assert.Equal("div", tagHelperOutput.TagName);
Assert.Equal("Hello from tagHelperOutputHello from tagBuilder.", tagHelperOutput.Content);
Assert.Equal(tagHelperOutput.Attributes.Count, 2);
var attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class"));
Assert.Equal(expectedOutputAttribute.Value, attribute.Value);
attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("for"));
Assert.Equal(expectedBuilderAttribute.Value, attribute.Value);
}
}
}

View File

@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "name", nameAndId.Name },
{ "valid", "from validation attributes" },
};
var expectedTagName = "textarea";
var expectedTagName = "not-textarea";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
@ -114,7 +114,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "class", "form-control" },
};
var output = new TagHelperOutput("original tag name", htmlAttributes, "original content")
var output = new TagHelperOutput(expectedTagName, htmlAttributes, "original content")
{
SelfClosing = true,
};
@ -149,7 +149,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "class", "form-control" },
};
var expectedContent = "original content";
var expectedTagName = "original tag name";
var expectedTagName = "textarea";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForProperty(

View File

@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_GeneratesExpectedOutput()
{
// Arrange
var expectedTagName = "not-span";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var modelExpression = CreateModelExpression("Name");
var validationMessageTagHelper = new ValidationMessageTagHelper
@ -34,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "for", modelExpression },
});
var output = new TagHelperOutput(
"original tag name",
expectedTagName,
attributes: new Dictionary<string, string>
{
{ "id", "myvalidationmessage" }
@ -61,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("data-valmsg-replace"));
Assert.Equal("true", attribute.Value);
Assert.Equal("Something", output.Content);
Assert.Equal("original tag name", output.TagName);
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
@ -134,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await validationMessageTagHelper.ProcessAsync(context: null, output: output);
// Assert
Assert.Equal(output.TagName, "span");
Assert.Equal("span", output.TagName);
Assert.Equal(2, output.Attributes.Count);
var attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("data-foo"));
Assert.Equal("bar", attribute.Value);

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_GeneratesExpectedOutput()
{
// Arrange
var expectedTagName = "not-div";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
@ -29,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
var output = new TagHelperOutput(
"div",
expectedTagName,
attributes: new Dictionary<string, string>
{
{ "class", "form-control" }
@ -53,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal("true", attribute.Value);
Assert.Equal("Custom Content<ul><li style=\"display:none\"></li>" + Environment.NewLine + "</ul>",
output.Content);
Assert.Equal("div", output.TagName);
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
@ -124,7 +125,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
await validationSummaryTagHelper.ProcessAsync(context: null, output: output);
// Assert
Assert.Equal(output.TagName, "div");
Assert.Equal("div", output.TagName);
Assert.Equal(3, output.Attributes.Count);
var attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("data-foo"));
Assert.Equal("bar", attribute.Value);