Add Label TagHelper.

- Validated label TagHelper functionality.

#1249
This commit is contained in:
N. Taylor Mullen 2014-10-14 15:13:20 -07:00 committed by Doug Bunting
parent 9c07055ac7
commit f8b0249918
2 changed files with 256 additions and 0 deletions

View File

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;label&gt; elements with <c>for</c> attributes.
/// </summary>
[ContentBehavior(ContentBehavior.Modify)]
public class LabelTagHelper : TagHelper
{
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate]
protected internal ViewContext ViewContext { get; set; }
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate]
protected internal IHtmlGenerator Generator { get; set; }
/// <summary>
/// An expression to be evaluated against the current model.
/// </summary>
public ModelExpression For { get; set; }
/// <inheritdoc />
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (For != null)
{
var tagBuilder = Generator.GenerateLabel(ViewContext,
For.Metadata,
For.Name,
labelText: null,
htmlAttributes: null);
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
// We check for whitespace to detect scenarios such as:
// <label for="Name">
// </label>
if (string.IsNullOrWhiteSpace(output.Content))
{
output.Content = tagBuilder.InnerHtml;
}
output.TagName = tagBuilder.TagName;
}
}
}
}
}

View File

@ -0,0 +1,199 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Xunit;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
public class LabelTagHelperTest
{
// Model (List<Model> or Model instance), container type (Model or NestModel), model accessor,
// property path, TagHelperOutput.Content values.
public static TheoryData<object, Type, Func<object>, string, TagHelperOutputContent> TestDataSet
{
get
{
var modelWithNull = new Model
{
NestedModel = new NestedModel
{
Text = null,
},
Text = null,
};
var modelWithText = new Model
{
NestedModel = new NestedModel
{
Text = "inner text",
},
Text = "outer text",
};
var models = new List<Model>
{
modelWithNull,
modelWithText,
};
return new TheoryData<object, Type, Func<object>, string, TagHelperOutputContent>
{
{ null, typeof(Model), () => null, "Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ modelWithNull, typeof(Model), () => modelWithNull.Text, "Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ modelWithText, typeof(Model), () => modelWithNull.Text, "Text",
new TagHelperOutputContent("Hello World", "Hello World") },
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
new TagHelperOutputContent("Hello World", "Hello World") },
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
new TagHelperOutputContent("Hello World", "Hello World") },
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
new TagHelperOutputContent("Hello World", "Hello World") },
// Note: Tests cases below here will not work in practice due to current limitations on indexing
// into ModelExpressions. Will be fixed in https://github.com/aspnet/Mvc/issues/1345.
{ models, typeof(Model), () => models[0].Text, "[0].Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ models, typeof(Model), () => models[1].Text, "[1].Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ models, typeof(Model), () => models[0].Text, "[0].Text",
new TagHelperOutputContent("Hello World", "Hello World") },
{ models, typeof(Model), () => models[1].Text, "[1].Text",
new TagHelperOutputContent("Hello World", "Hello World") },
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
new TagHelperOutputContent(Environment.NewLine, "Text") },
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
new TagHelperOutputContent("Hello World", "Hello World") },
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
new TagHelperOutputContent("Hello World", "Hello World") },
};
}
}
[Theory]
[MemberData(nameof(TestDataSet))]
public async Task ProcessAsync_GeneratesExpectedOutput(
object model,
Type containerType,
Func<object> modelAccessor,
string propertyPath,
TagHelperOutputContent tagHelperOutputContent)
{
// Arrange
var expectedAttributes = new Dictionary<string, string>
{
{ "class", "form-control" },
{ "for", propertyPath }
};
var metadataProvider = new DataAnnotationsModelMetadataProvider();
// Property name is either nameof(Model.Text) or nameof(NestedModel.Text).
var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text");
var modelExpression = new ModelExpression(propertyPath, metadata);
var tagHelper = new LabelTagHelper
{
For = modelExpression,
};
var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary<string, object>());
var htmlAttributes = new Dictionary<string, string>
{
{ "class", "form-control" },
};
var output = new TagHelperOutput("A random tag name", htmlAttributes, tagHelperOutputContent.OriginalContent);
var expectedTagName = "label";
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
tagHelper.ViewContext = viewContext;
tagHelper.Generator = htmlGenerator;
// Act
await tagHelper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(tagHelperOutputContent.ExpectedContent, output.Content);
Assert.False(output.SelfClosing);
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public async Task TagHelper_LeavesOutputUnchanged_IfForNotBound2()
{
// Arrange
var expectedAttributes = new Dictionary<string, string>
{
{ "class", "form-control" },
};
var expectedContent = "original content";
var expectedTagName = "original tag name";
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForProperty(
modelAccessor: () => null,
containerType: typeof(Model),
propertyName: nameof(Model.Text));
var modelExpression = new ModelExpression(nameof(Model.Text), metadata);
var tagHelper = new LabelTagHelper();
var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary<string, object>());
var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent);
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
Model model = null;
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
var activator = new DefaultTagHelperActivator();
activator.Activate(tagHelper, viewContext);
// Act
await tagHelper.ProcessAsync(tagHelperContext, output);
// Assert
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedContent, output.Content);
Assert.Equal(expectedTagName, output.TagName);
}
public class TagHelperOutputContent
{
public TagHelperOutputContent(string outputContent, string expectedContent)
{
OriginalContent = outputContent;
ExpectedContent = expectedContent;
}
public string OriginalContent { get; set; }
public string ExpectedContent { get; set; }
}
private class Model
{
public string Text { get; set; }
public NestedModel NestedModel { get; set; }
}
private class NestedModel
{
public string Text { get; set; }
}
}
}