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

Commit 1b181d3

Browse files
committed
Add tests of TextAreaTagHelper
- new Microsoft.AspNet.Mvc.TagHelpers.Test project - bit of test infrastructure -- `TestableHtmlGenerator` - short-curcuits validation attribute and AntiForgery additions - should help when testing all MVC tag helpers
1 parent 408e108 commit 1b181d3

File tree

5 files changed

+353
-1
lines changed

5 files changed

+353
-1
lines changed

Mvc.sln

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 14
4-
VisualStudioVersion = 14.0.22115.0
4+
VisualStudioVersion = 14.0.22209.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
77
EndProject
@@ -102,6 +102,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TagHelperSample.Web", "samp
102102
EndProject
103103
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TagHelpers", "src\Microsoft.AspNet.Mvc.TagHelpers\Microsoft.AspNet.Mvc.TagHelpers.kproj", "{B2347320-308E-4D2B-AEC8-005DFA68B0C9}"
104104
EndProject
105+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TagHelpers.Test", "test\Microsoft.AspNet.Mvc.TagHelpers.Test\Microsoft.AspNet.Mvc.TagHelpers.Test.kproj", "{860119ED-3DB1-424D-8D0A-30132A8A7D96}"
106+
EndProject
105107
Global
106108
GlobalSection(SolutionConfigurationPlatforms) = preSolution
107109
Debug|Any CPU = Debug|Any CPU
@@ -542,6 +544,16 @@ Global
542544
{B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
543545
{B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
544546
{B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|x86.ActiveCfg = Release|Any CPU
547+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
548+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.Build.0 = Debug|Any CPU
549+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
550+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
551+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|x86.ActiveCfg = Debug|Any CPU
552+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.ActiveCfg = Release|Any CPU
553+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.Build.0 = Release|Any CPU
554+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
555+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.Build.0 = Release|Any CPU
556+
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|x86.ActiveCfg = Release|Any CPU
545557
EndGlobalSection
546558
GlobalSection(SolutionProperties) = preSolution
547559
HideSolutionNode = FALSE
@@ -591,5 +603,6 @@ Global
591603
{5DE8E4D9-AACD-4B5F-819F-F091383FB996} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
592604
{2223120F-D675-40DA-8CD8-11DC14A0B2C7} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
593605
{B2347320-308E-4D2B-AEC8-005DFA68B0C9} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
606+
{860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
594607
EndGlobalSection
595608
EndGlobal
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
5+
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
6+
</PropertyGroup>
7+
8+
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
9+
<PropertyGroup Label="Globals">
10+
<ProjectGuid>860119ed-3db1-424d-8d0a-30132a8a7d96</ProjectGuid>
11+
<RootNamespace>Microsoft.AspNet.Mvc.TagHelpers.Test</RootNamespace>
12+
</PropertyGroup>
13+
14+
<PropertyGroup>
15+
<SchemaVersion>2.0</SchemaVersion>
16+
</PropertyGroup>
17+
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
18+
</Project>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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.IO;
7+
using Microsoft.AspNet.Http;
8+
using Microsoft.AspNet.Mvc.ModelBinding;
9+
using Microsoft.AspNet.Mvc.Rendering;
10+
using Microsoft.AspNet.Routing;
11+
using Microsoft.AspNet.Security.DataProtection;
12+
using Microsoft.Framework.OptionsModel;
13+
using Moq;
14+
15+
namespace Microsoft.AspNet.Mvc.TagHelpers
16+
{
17+
public class TestableHtmlGenerator : DefaultHtmlGenerator
18+
{
19+
private IDictionary<string, object> _validationAttributes;
20+
21+
public TestableHtmlGenerator(IModelMetadataProvider metadataProvider)
22+
: this(metadataProvider, Mock.Of<IUrlHelper>())
23+
{
24+
}
25+
26+
public TestableHtmlGenerator(IModelMetadataProvider metadataProvider, IUrlHelper urlHelper)
27+
: this(
28+
metadataProvider,
29+
urlHelper,
30+
validationAttributes: new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase))
31+
{
32+
}
33+
34+
public TestableHtmlGenerator(
35+
IModelMetadataProvider metadataProvider,
36+
IUrlHelper urlHelper,
37+
IDictionary<string, object> validationAttributes)
38+
: base(Mock.Of<IActionBindingContextProvider>(), GetAntiForgery(), metadataProvider, urlHelper)
39+
{
40+
_validationAttributes = validationAttributes;
41+
}
42+
43+
public IDictionary<string, object> ValidationAttributes
44+
{
45+
get { return _validationAttributes; }
46+
}
47+
48+
public static ViewContext GetViewContext(
49+
object model,
50+
IHtmlGenerator htmlGenerator,
51+
IModelMetadataProvider metadataProvider)
52+
{
53+
var serviceProvider = new Mock<IServiceProvider>();
54+
serviceProvider
55+
.Setup(provider => provider.GetService(typeof(IHtmlGenerator)))
56+
.Returns(htmlGenerator);
57+
58+
var httpContext = new Mock<HttpContext>();
59+
httpContext
60+
.Setup(context => context.RequestServices)
61+
.Returns(serviceProvider.Object);
62+
63+
var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
64+
var viewData = new ViewDataDictionary(metadataProvider)
65+
{
66+
Model = model,
67+
};
68+
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, new StringWriter());
69+
70+
return viewContext;
71+
}
72+
73+
public override TagBuilder GenerateAntiForgery(ViewContext viewContext)
74+
{
75+
return new TagBuilder("input")
76+
{
77+
Attributes =
78+
{
79+
{ "name", "__RequestVerificationToken" },
80+
{ "type", "hidden" },
81+
{ "value", "olJlUDjrouRNWLen4tQJhauj1Z1rrvnb3QD65cmQU1Ykqi6S4" }, // 50 chars of a token.
82+
},
83+
};
84+
}
85+
86+
protected override IDictionary<string, object> GetValidationAttributes(
87+
ViewContext viewContext,
88+
ModelMetadata metadata,
89+
string name)
90+
{
91+
return ValidationAttributes;
92+
}
93+
94+
private static AntiForgery GetAntiForgery()
95+
{
96+
// AntiForgery must be passed to TestableHtmlGenerator constructor but will never be called.
97+
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
98+
optionsAccessor
99+
.SetupGet(o => o.Options)
100+
.Returns(new MvcOptions());
101+
var antiForgery = new AntiForgery(
102+
Mock.Of<IClaimUidExtractor>(),
103+
Mock.Of<IDataProtectionProvider>(),
104+
Mock.Of<IAntiForgeryAdditionalDataProvider>(),
105+
optionsAccessor.Object);
106+
107+
return antiForgery;
108+
}
109+
}
110+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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 TextAreaTagHelperTest
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, string> 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, string>
46+
{
47+
{ null, typeof(Model), () => null, "Text",
48+
Environment.NewLine },
49+
50+
{ modelWithNull, typeof(Model), () => modelWithNull.Text, "Text",
51+
Environment.NewLine },
52+
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
53+
Environment.NewLine + "outer text" },
54+
55+
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
56+
Environment.NewLine },
57+
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
58+
Environment.NewLine + "inner text" },
59+
60+
// Top-level indexing does not work end-to-end due to code generation issue #1345.
61+
// TODO: Remove above comment when #1345 is fixed.
62+
{ models, typeof(Model), () => models[0].Text, "[0].Text",
63+
Environment.NewLine },
64+
{ models, typeof(Model), () => models[1].Text, "[1].Text",
65+
Environment.NewLine + "outer text" },
66+
67+
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
68+
Environment.NewLine },
69+
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
70+
Environment.NewLine + "inner text" },
71+
};
72+
}
73+
}
74+
75+
[Theory]
76+
[MemberData(nameof(TestDataSet))]
77+
public async Task Process_GeneratesExpectedOutput(
78+
object model,
79+
Type containerType,
80+
Func<object> modelAccessor,
81+
string propertyPath,
82+
string expectedContent)
83+
{
84+
// Arrange
85+
var expectedAttributes = new Dictionary<string, string>
86+
{
87+
{ "class", "form-control" },
88+
{ "id", propertyPath },
89+
{ "name", propertyPath },
90+
{ "valid", "from validation attributes" },
91+
};
92+
var expectedTagName = "textarea";
93+
94+
var metadataProvider = new DataAnnotationsModelMetadataProvider();
95+
96+
// Property name is either nameof(Model.Text) or nameof(NestedModel.Text).
97+
var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text");
98+
var modelExpression = new ModelExpression(propertyPath, metadata);
99+
var tagHelper = new TextAreaTagHelper
100+
{
101+
For = modelExpression,
102+
};
103+
104+
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
105+
var htmlAttributes = new Dictionary<string, string>
106+
{
107+
{ "class", "form-control" },
108+
};
109+
var output = new TagHelperOutput("original tag name", htmlAttributes, "original content")
110+
{
111+
SelfClosing = true,
112+
};
113+
114+
var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
115+
{
116+
ValidationAttributes =
117+
{
118+
{ "valid", "from validation attributes" },
119+
}
120+
};
121+
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
122+
var activator = new DefaultTagHelperActivator();
123+
activator.Activate(tagHelper, viewContext);
124+
125+
// Act
126+
await tagHelper.ProcessAsync(tagHelperContext, output);
127+
128+
// Assert
129+
Assert.Equal(expectedAttributes, output.Attributes);
130+
Assert.Equal(expectedContent, output.Content);
131+
Assert.False(output.SelfClosing);
132+
Assert.Equal(expectedTagName, output.TagName);
133+
}
134+
135+
[Fact]
136+
public async Task TagHelper_LeavesOutputUnchanged_IfForNotBound()
137+
{
138+
// Arrange
139+
var expectedAttributes = new Dictionary<string, string>
140+
{
141+
{ "class", "form-control" },
142+
};
143+
var expectedContent = "original content";
144+
var expectedTagName = "original tag name";
145+
146+
var metadataProvider = new DataAnnotationsModelMetadataProvider();
147+
var metadata = metadataProvider.GetMetadataForProperty(
148+
modelAccessor: () => null,
149+
containerType: typeof(Model),
150+
propertyName: nameof(Model.Text));
151+
var modelExpression = new ModelExpression(nameof(Model.Text), metadata);
152+
var tagHelper = new TextAreaTagHelper();
153+
154+
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
155+
var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent)
156+
{
157+
SelfClosing = true,
158+
};
159+
160+
var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
161+
{
162+
ValidationAttributes =
163+
{
164+
{ "valid", "from validation attributes" },
165+
}
166+
};
167+
Model model = null;
168+
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
169+
var activator = new DefaultTagHelperActivator();
170+
activator.Activate(tagHelper, viewContext);
171+
172+
// Act
173+
await tagHelper.ProcessAsync(tagHelperContext, output);
174+
175+
// Assert
176+
Assert.Equal(expectedAttributes, output.Attributes);
177+
Assert.Equal(expectedContent, output.Content);
178+
Assert.True(output.SelfClosing);
179+
Assert.Equal(expectedTagName, output.TagName);
180+
}
181+
182+
private class Model
183+
{
184+
public string Text { get; set; }
185+
186+
public NestedModel NestedModel { get; set; }
187+
}
188+
189+
private class NestedModel
190+
{
191+
public string Text { get; set; }
192+
}
193+
}
194+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"commands": {
3+
"test": "Xunit.KRunner"
4+
},
5+
"dependencies": {
6+
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
7+
"Microsoft.AspNet.Testing": "1.0.0-*",
8+
"Xunit.KRunner": "1.0.0-*"
9+
},
10+
"frameworks": {
11+
"aspnet50": {
12+
"dependencies": {
13+
"Moq": "4.2.1312.1622"
14+
}
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)