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

Commit fd26c23

Browse files
author
N. Taylor Mullen
committed
Add extensions for TagHelperOutput to copy from TagBuilder.
- Added tests to validate functionality of all TagHelperOutputExtension methods. #1319
1 parent 4208c8f commit fd26c23

File tree

2 files changed

+355
-0
lines changed

2 files changed

+355
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.Linq;
7+
using Microsoft.AspNet.Mvc.Rendering;
8+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
9+
10+
namespace Microsoft.AspNet.Mvc.TagHelpers
11+
{
12+
/// <summary>
13+
/// Utility related extensions for <see cref="TagHelperOutput"/>.
14+
/// </summary>
15+
public static class TagHelperOutputExtensions
16+
{
17+
/// <summary>
18+
/// Merges the given <paramref name="tagBuilder"/> into the <paramref name="tagHelperOutput"/>.
19+
/// </summary>
20+
/// <param name="tagHelperOutput">The <see cref="TagHelperOutput"/>.</param>
21+
/// <param name="tagBuilder">The <see cref="TagBuilder"/> to merge.</param>
22+
public static void Merge(this TagHelperOutput tagHelperOutput, TagBuilder tagBuilder)
23+
{
24+
tagHelperOutput.TagName = tagBuilder.TagName;
25+
tagHelperOutput.Content += tagBuilder.InnerHtml;
26+
27+
MergeAttributes(tagHelperOutput, tagBuilder);
28+
}
29+
30+
/// <summary>
31+
/// Merges the given <see cref="tagBuilder"/>'s <see cref="TagBuilder.Attributes"/> into the
32+
/// <paramref name="tagHelperOutput"/>.
33+
/// </summary>
34+
/// <param name="tagHelperOutput">The <see cref="TagHelperOutput"/>.</param>
35+
/// <param name="tagBuilder">The <see cref="TagBuilder"/> to merge attributes from.</param>
36+
public static void MergeAttributes(this TagHelperOutput tagHelperOutput, TagBuilder tagBuilder)
37+
{
38+
foreach (var attribute in tagBuilder.Attributes)
39+
{
40+
if (!tagHelperOutput.Attributes.ContainsKey(attribute.Key))
41+
{
42+
tagHelperOutput.Attributes.Add(attribute.Key, attribute.Value);
43+
}
44+
else if (attribute.Key.Equals("class", StringComparison.Ordinal))
45+
{
46+
tagHelperOutput.Attributes["class"] += " " + attribute.Value;
47+
}
48+
}
49+
}
50+
51+
/// <summary>
52+
/// Returns and removes all attributes from <paramref name="tagHelperOutput"/>'s
53+
/// <see cref="TagHelperOutput.Attributes"/> that have the given <paramref name="prefix"/>.
54+
/// </summary>
55+
/// <param name="tagHelperOutput">The <see cref="TagHelperOutput"/>.</param>
56+
/// <param name="prefix">The prefix to </param>
57+
/// <returns><see cref="KeyValuePair{string, string}"/>s whos <see cref="KeyValuePair{string, string}.Key"/>
58+
/// starts with the given <paramref name="prefix"/>.</returns>
59+
public static IEnumerable<KeyValuePair<string, string>> PullPrefixedAttributes(
60+
this TagHelperOutput tagHelperOutput, string prefix)
61+
{
62+
// TODO: We will not need this method once https://github.com/aspnet/Razor/issues/89 is completed.
63+
64+
var htmlAttributes = tagHelperOutput.Attributes;
65+
66+
// We're only interested in HTML attributes that have the desired prefix.
67+
var prefixedAttributes = htmlAttributes.Where(attribute =>
68+
attribute.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToArray();
69+
70+
// Since we're "pulling" the prefixed attribute values, we need to remove them.
71+
foreach (var attribute in prefixedAttributes)
72+
{
73+
htmlAttributes.Remove(attribute.Key);
74+
}
75+
76+
return prefixedAttributes;
77+
}
78+
79+
/// <summary>
80+
/// Restores a user provided bound attribute to the given <paramref name="tagHelperOutput"/>.
81+
/// </summary>
82+
/// <param name="tagHelperOutput">The <see cref="TagHelperOutput"/>.</param>
83+
/// <param name="boundAttributeName">The name of the bound attribute.</param>
84+
/// <param name="context">The <see cref="TagHelperContext"/>.</param>
85+
public static void RestoreBoundHtmlAttribute(this TagHelperOutput tagHelperOutput,
86+
string boundAttributeName,
87+
TagHelperContext context)
88+
{
89+
// We look for the original attribute so we can restore the exact attribute name the user typed.
90+
var entry = context.AllAttributes.Single(attribute =>
91+
attribute.Key.Equals(boundAttributeName, StringComparison.OrdinalIgnoreCase));
92+
var originalAttribute = new KeyValuePair<string, string>(entry.Key, entry.Value.ToString());
93+
94+
tagHelperOutput.Attributes.Add(originalAttribute);
95+
}
96+
}
97+
}
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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.Collections.Generic;
5+
using System.Linq;
6+
using Microsoft.AspNet.Mvc.Rendering;
7+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
8+
using Xunit;
9+
10+
namespace Microsoft.AspNet.Mvc.TagHelpers
11+
{
12+
public class TagHelperOutputExtensionsTest
13+
{
14+
[Theory]
15+
[InlineData("hello", "world")]
16+
[InlineData("HeLlO", "wOrLd")]
17+
public void RestoreBoundHtmlAttribute_RestoresOriginalAttributes(string attributeName, string attributeValue)
18+
{
19+
// Arrange
20+
var tagHelperOutput = new TagHelperOutput(
21+
"p",
22+
attributes: new Dictionary<string, string>(),
23+
content: string.Empty);
24+
var tagHelperContext = new TagHelperContext(
25+
new Dictionary<string, object>()
26+
{
27+
{ attributeName, attributeValue }
28+
});
29+
var expectedAttribute = new KeyValuePair<string, string>(attributeName, attributeValue);
30+
31+
// Act
32+
tagHelperOutput.RestoreBoundHtmlAttribute("hello", tagHelperContext);
33+
34+
// Assert
35+
var attribute = Assert.Single(tagHelperOutput.Attributes);
36+
Assert.Equal(expectedAttribute, attribute);
37+
}
38+
39+
[Fact]
40+
public void PullPrefixedAttributes_OnlyRemovesPrefixed_TagHelperOutputAttributeValues()
41+
{
42+
// Arrange
43+
var tagHelperOutput = new TagHelperOutput(
44+
"p",
45+
attributes: new Dictionary<string, string>()
46+
{
47+
{ "route-Hello", "World" },
48+
{ "Route-I", "Am" }
49+
},
50+
content: string.Empty);
51+
var expectedAttribute = new KeyValuePair<string, string>("type", "btn");
52+
tagHelperOutput.Attributes.Add(expectedAttribute);
53+
54+
// Act
55+
var pulledAttributes = tagHelperOutput.PullPrefixedAttributes("route-");
56+
57+
// Assert
58+
var attribute = Assert.Single(tagHelperOutput.Attributes);
59+
Assert.Equal(expectedAttribute, attribute);
60+
attribute = Assert.Single(pulledAttributes, kvp => kvp.Key.Equals("route-Hello"));
61+
Assert.Equal(attribute.Value, "World");
62+
attribute = Assert.Single(pulledAttributes, kvp => kvp.Key.Equals("Route-I"));
63+
Assert.Equal(attribute.Value, "Am");
64+
}
65+
66+
[Fact]
67+
public void PullPrefixedAttributes_ReturnsEmpty_AttributeListIfNoAttributesPrefixed()
68+
{
69+
// Arrange
70+
var tagHelperOutput = new TagHelperOutput(
71+
"p",
72+
attributes: new Dictionary<string, string>()
73+
{
74+
{ "routeHello", "World" },
75+
{ "Routee-I", "Am" }
76+
},
77+
content: string.Empty);
78+
79+
// Act
80+
var pulledAttributes = tagHelperOutput.PullPrefixedAttributes("route-");
81+
82+
// Assert
83+
Assert.Empty(pulledAttributes);
84+
var attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("routeHello"));
85+
Assert.Equal(attribute.Value, "World");
86+
attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("Routee-I"));
87+
Assert.Equal(attribute.Value, "Am");
88+
}
89+
90+
[Fact]
91+
public void MergeAttributes_DoesNotReplace_TagHelperOutputAttributeValues()
92+
{
93+
// Arrange
94+
var tagHelperOutput = new TagHelperOutput(
95+
"p",
96+
attributes: new Dictionary<string, string>(),
97+
content: string.Empty);
98+
var expectedAttribute = new KeyValuePair<string, string>("type", "btn");
99+
tagHelperOutput.Attributes.Add(expectedAttribute);
100+
101+
var tagBuilder = new TagBuilder("p");
102+
tagBuilder.Attributes.Add("type", "hello");
103+
104+
// Act
105+
tagHelperOutput.MergeAttributes(tagBuilder);
106+
107+
// Assert
108+
var attribute = Assert.Single(tagHelperOutput.Attributes);
109+
Assert.Equal(expectedAttribute, attribute);
110+
}
111+
112+
[Fact]
113+
public void MergeAttributes_AppendsClass_TagHelperOutputAttributeValues()
114+
{
115+
// Arrange
116+
var tagHelperOutput = new TagHelperOutput(
117+
"p",
118+
attributes: new Dictionary<string, string>(),
119+
content: string.Empty);
120+
tagHelperOutput.Attributes.Add("class", "Hello");
121+
122+
var tagBuilder = new TagBuilder("p");
123+
tagBuilder.Attributes.Add("class", "btn");
124+
125+
var expectedAttribute = new KeyValuePair<string, string>("class", "Hello btn");
126+
127+
// Act
128+
tagHelperOutput.MergeAttributes(tagBuilder);
129+
130+
// Assert
131+
var attribute = Assert.Single(tagHelperOutput.Attributes);
132+
Assert.Equal(expectedAttribute, attribute);
133+
}
134+
135+
[Fact]
136+
public void MergeAttributes_DoesNotEncode_TagHelperOutputAttributeValues()
137+
{
138+
// Arrange
139+
var tagHelperOutput = new TagHelperOutput(
140+
"p",
141+
attributes: new Dictionary<string, string>(),
142+
content: string.Empty);
143+
144+
var tagBuilder = new TagBuilder("p");
145+
var expectedAttribute = new KeyValuePair<string, string>("visible", "val < 3");
146+
tagBuilder.Attributes.Add(expectedAttribute);
147+
148+
// Act
149+
tagHelperOutput.MergeAttributes(tagBuilder);
150+
151+
// Assert
152+
var attribute = Assert.Single(tagHelperOutput.Attributes);
153+
Assert.Equal(expectedAttribute, attribute);
154+
}
155+
156+
[Fact]
157+
public void MergeAttributes_CopiesMultiple_TagHelperOutputAttributeValues()
158+
{
159+
// Arrange
160+
var tagHelperOutput = new TagHelperOutput(
161+
"p",
162+
attributes: new Dictionary<string, string>(),
163+
content: string.Empty);
164+
165+
var tagBuilder = new TagBuilder("p");
166+
var expectedAttribute1 = new KeyValuePair<string, string>("class", "btn");
167+
var expectedAttribute2 = new KeyValuePair<string, string>("class2", "btn");
168+
tagBuilder.Attributes.Add(expectedAttribute1);
169+
tagBuilder.Attributes.Add(expectedAttribute2);
170+
171+
// Act
172+
tagHelperOutput.MergeAttributes(tagBuilder);
173+
174+
// Assert
175+
Assert.Equal(2, tagHelperOutput.Attributes.Count);
176+
var attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class"));
177+
Assert.Equal(expectedAttribute1.Value, attribute.Value);
178+
attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class2"));
179+
Assert.Equal(expectedAttribute2.Value, attribute.Value);
180+
}
181+
182+
[Fact]
183+
public void MergeAttributes_Maintains_TagHelperOutputAttributeValues()
184+
{
185+
// Arrange
186+
var tagHelperOutput = new TagHelperOutput(
187+
"p",
188+
attributes: new Dictionary<string, string>(),
189+
content: string.Empty);
190+
var expectedAttribute = new KeyValuePair<string, string>("class", "btn");
191+
tagHelperOutput.Attributes.Add(expectedAttribute);
192+
193+
var tagBuilder = new TagBuilder("p");
194+
195+
// Act
196+
tagHelperOutput.MergeAttributes(tagBuilder);
197+
198+
// Assert
199+
var attribute = Assert.Single(tagHelperOutput.Attributes);
200+
Assert.Equal(expectedAttribute, attribute);
201+
}
202+
203+
[Fact]
204+
public void MergeAttributes_Combines_TagHelperOutputAttributeValues()
205+
{
206+
// Arrange
207+
var tagHelperOutput = new TagHelperOutput(
208+
"p",
209+
attributes: new Dictionary<string, string>(),
210+
content: string.Empty);
211+
var expectedOutputAttribute = new KeyValuePair<string, string>("class", "btn");
212+
tagHelperOutput.Attributes.Add(expectedOutputAttribute);
213+
214+
var tagBuilder = new TagBuilder("p");
215+
var expectedBuilderAttribute = new KeyValuePair<string, string>("for", "hello");
216+
tagBuilder.Attributes.Add(expectedBuilderAttribute);
217+
218+
// Act
219+
tagHelperOutput.MergeAttributes(tagBuilder);
220+
221+
// Assert
222+
Assert.Equal(tagHelperOutput.Attributes.Count, 2);
223+
var attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class"));
224+
Assert.Equal(expectedOutputAttribute.Value, attribute.Value);
225+
attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("for"));
226+
Assert.Equal(expectedBuilderAttribute.Value, attribute.Value);
227+
}
228+
229+
[Fact]
230+
public void Merge_CombinesAllTagHelperOutputAndTagBuilderProperties()
231+
{
232+
// Arrange
233+
var tagHelperOutput = new TagHelperOutput(
234+
"p",
235+
attributes: new Dictionary<string, string>(),
236+
content: "Hello from tagHelperOutput");
237+
var expectedOutputAttribute = new KeyValuePair<string, string>("class", "btn");
238+
tagHelperOutput.Attributes.Add(expectedOutputAttribute);
239+
240+
var tagBuilder = new TagBuilder("div");
241+
var expectedBuilderAttribute = new KeyValuePair<string, string>("for", "hello");
242+
tagBuilder.Attributes.Add(expectedBuilderAttribute);
243+
tagBuilder.InnerHtml = "Hello from tagBuilder.";
244+
245+
// Act
246+
tagHelperOutput.Merge(tagBuilder);
247+
248+
// Assert
249+
Assert.Equal("div", tagHelperOutput.TagName);
250+
Assert.Equal("Hello from tagHelperOutputHello from tagBuilder.", tagHelperOutput.Content);
251+
Assert.Equal(tagHelperOutput.Attributes.Count, 2);
252+
var attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class"));
253+
Assert.Equal(expectedOutputAttribute.Value, attribute.Value);
254+
attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("for"));
255+
Assert.Equal(expectedBuilderAttribute.Value, attribute.Value);
256+
}
257+
}
258+
}

0 commit comments

Comments
 (0)