Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 4d7da69

Browse files
author
N. Taylor Mullen
committed
Add Label TagHelper.
- Validated label TagHelper functionality. #1249
1 parent 3a0403c commit 4d7da69

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNet.Mvc.Rendering;
5+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
6+
using Microsoft.AspNet.Razor.TagHelpers;
7+
8+
namespace Microsoft.AspNet.Mvc.TagHelpers
9+
{
10+
/// <summary>
11+
/// <see cref="ITagHelper"/> implementation targeting &lt;label&gt; elements with <c>for</c> attributes.
12+
/// </summary>
13+
[ContentBehavior(ContentBehavior.Modify)]
14+
public class LabelTagHelper : TagHelper
15+
{
16+
[Activate]
17+
private ViewContext ViewContext { get; set; }
18+
19+
[Activate]
20+
private IHtmlGenerator Generator { get; set; }
21+
22+
/// <summary>
23+
/// An expression to be evaluated against the current model.
24+
/// </summary>
25+
public ModelExpression For { get; set; }
26+
27+
/// <inheritdoc />
28+
public override void Process(TagHelperContext context, TagHelperOutput output)
29+
{
30+
if (For != null)
31+
{
32+
var tagBuilder = Generator.GenerateLabel(ViewContext,
33+
For.Metadata,
34+
For.Name,
35+
labelText: null,
36+
htmlAttributes: null);
37+
38+
if (tagBuilder != null)
39+
{
40+
output.MergeAttributes(tagBuilder);
41+
42+
// We check for whitespace so detect scenarios such as:
43+
// <label for="Name">
44+
// </label>
45+
if (string.IsNullOrWhiteSpace(output.Content))
46+
{
47+
output.Content = tagBuilder.InnerHtml;
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNet.Mvc.ModelBinding;
8+
using Microsoft.AspNet.Mvc.Razor;
9+
using Microsoft.AspNet.Mvc.Rendering;
10+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
11+
using Xunit;
12+
13+
namespace Microsoft.AspNet.Mvc.TagHelpers
14+
{
15+
public class LabelTagHelperTest
16+
{
17+
// Model (List<Model> or Model instance), container type (Model or NestModel), model accessor,
18+
// property path, expected content.
19+
public static TheoryData<object, Type, Func<object>, string, ContentAssertion> TestDataSet
20+
{
21+
get
22+
{
23+
var modelWithNull = new Model
24+
{
25+
NestedModel = new NestedModel
26+
{
27+
Text = null,
28+
},
29+
Text = null,
30+
};
31+
var modelWithText = new Model
32+
{
33+
NestedModel = new NestedModel
34+
{
35+
Text = "inner text",
36+
},
37+
Text = "outer text",
38+
};
39+
var models = new List<Model>
40+
{
41+
modelWithNull,
42+
modelWithText,
43+
};
44+
45+
return new TheoryData<object, Type, Func<object>, string, ContentAssertion>
46+
{
47+
{ null, typeof(Model), () => null, "Text",
48+
new ContentAssertion(Environment.NewLine, "Text") },
49+
50+
{ modelWithNull, typeof(Model), () => modelWithNull.Text, "Text",
51+
new ContentAssertion(Environment.NewLine, "Text") },
52+
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
53+
new ContentAssertion(Environment.NewLine, "Text") },
54+
{ modelWithText, typeof(Model), () => modelWithNull.Text, "Text",
55+
new ContentAssertion("Hello World", "Hello World") },
56+
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
57+
new ContentAssertion("Hello World", "Hello World") },
58+
59+
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
60+
new ContentAssertion(Environment.NewLine, "Text") },
61+
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
62+
new ContentAssertion(Environment.NewLine, "Text") },
63+
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
64+
new ContentAssertion("Hello World", "Hello World") },
65+
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
66+
new ContentAssertion("Hello World", "Hello World") },
67+
68+
//// Note: Tests cases below here will not work in practice due to current limitations on indexing
69+
//// into ModelExpressions. Will be fixed in https://github.com/aspnet/Mvc/issues/1345.
70+
{ models, typeof(Model), () => models[0].Text, "[0].Text",
71+
new ContentAssertion(Environment.NewLine, "Text") },
72+
{ models, typeof(Model), () => models[1].Text, "[1].Text",
73+
new ContentAssertion(Environment.NewLine, "Text") },
74+
{ models, typeof(Model), () => models[0].Text, "[0].Text",
75+
new ContentAssertion("Hello World", "Hello World") },
76+
{ models, typeof(Model), () => models[1].Text, "[1].Text",
77+
new ContentAssertion("Hello World", "Hello World") },
78+
79+
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
80+
new ContentAssertion(Environment.NewLine, "Text") },
81+
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
82+
new ContentAssertion(Environment.NewLine, "Text") },
83+
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
84+
new ContentAssertion("Hello World", "Hello World") },
85+
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
86+
new ContentAssertion("Hello World", "Hello World") },
87+
};
88+
}
89+
}
90+
91+
[Theory]
92+
[MemberData(nameof(TestDataSet))]
93+
public async Task ProcessAsync_GeneratesExpectedOutput(
94+
object model,
95+
Type containerType,
96+
Func<object> modelAccessor,
97+
string propertyPath,
98+
ContentAssertion contentAssertion)
99+
{
100+
// Arrange
101+
var expectedAttributes = new Dictionary<string, string>
102+
{
103+
{ "class", "form-control" },
104+
{ "for", propertyPath }
105+
};
106+
var expectedTagName = "label";
107+
108+
var metadataProvider = new DataAnnotationsModelMetadataProvider();
109+
110+
// Property name is either nameof(Model.Text) or nameof(NestedModel.Text).
111+
var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text");
112+
var modelExpression = new ModelExpression(propertyPath, metadata);
113+
var tagHelper = new LabelTagHelper
114+
{
115+
For = modelExpression,
116+
};
117+
118+
var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary<string, object>());
119+
var htmlAttributes = new Dictionary<string, string>
120+
{
121+
{ "class", "form-control" },
122+
};
123+
var output = new TagHelperOutput(expectedTagName, htmlAttributes, contentAssertion.OutputContent);
124+
125+
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
126+
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
127+
var activator = new DefaultTagHelperActivator();
128+
activator.Activate(tagHelper, viewContext);
129+
130+
// Act
131+
await tagHelper.ProcessAsync(tagHelperContext, output);
132+
133+
// Assert
134+
Assert.Equal(expectedAttributes, output.Attributes);
135+
Assert.Equal(contentAssertion.ExpectedContent, output.Content);
136+
Assert.False(output.SelfClosing);
137+
Assert.Equal(expectedTagName, output.TagName);
138+
}
139+
140+
[Fact]
141+
public async Task TagHelper_LeavesOutputUnchanged_IfForNotBound2()
142+
{
143+
// Arrange
144+
var expectedAttributes = new Dictionary<string, string>
145+
{
146+
{ "class", "form-control" },
147+
};
148+
var expectedContent = "original content";
149+
var expectedTagName = "original tag name";
150+
151+
var metadataProvider = new DataAnnotationsModelMetadataProvider();
152+
var metadata = metadataProvider.GetMetadataForProperty(
153+
modelAccessor: () => null,
154+
containerType: typeof(Model),
155+
propertyName: nameof(Model.Text));
156+
var modelExpression = new ModelExpression(nameof(Model.Text), metadata);
157+
var tagHelper = new LabelTagHelper();
158+
159+
var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary<string, object>());
160+
var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent);
161+
162+
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
163+
Model model = null;
164+
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
165+
var activator = new DefaultTagHelperActivator();
166+
activator.Activate(tagHelper, viewContext);
167+
168+
// Act
169+
await tagHelper.ProcessAsync(tagHelperContext, output);
170+
171+
// Assert
172+
Assert.Equal(expectedAttributes, output.Attributes);
173+
Assert.Equal(expectedContent, output.Content);
174+
Assert.Equal(expectedTagName, output.TagName);
175+
}
176+
177+
public class ContentAssertion
178+
{
179+
public ContentAssertion(string outputContent, string expectedContent)
180+
{
181+
OutputContent = outputContent;
182+
ExpectedContent = expectedContent;
183+
}
184+
185+
public string OutputContent { get; set; }
186+
public string ExpectedContent { get; set; }
187+
}
188+
189+
private class Model
190+
{
191+
public string Text { get; set; }
192+
193+
public NestedModel NestedModel { get; set; }
194+
}
195+
196+
private class NestedModel
197+
{
198+
public string Text { get; set; }
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)