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
This commit is contained in:
Doug Bunting 2014-10-14 16:47:33 -07:00
parent a9c2c4c5eb
commit ba8b3d14a4
5 changed files with 353 additions and 1 deletions

15
Mvc.sln
View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22115.0
VisualStudioVersion = 14.0.22209.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -102,6 +102,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TagHelperSample.Web", "samp
EndProject
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}"
EndProject
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -542,6 +544,16 @@ Global
{B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|x86.ActiveCfg = Release|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|x86.ActiveCfg = Debug|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.Build.0 = Release|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -591,5 +603,6 @@ Global
{5DE8E4D9-AACD-4B5F-819F-F091383FB996} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{2223120F-D675-40DA-8CD8-11DC14A0B2C7} = {DAAE4C74-D06F-4874-A166-33305D2643CE}
{B2347320-308E-4D2B-AEC8-005DFA68B0C9} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>860119ed-3db1-424d-8d0a-30132a8a7d96</ProjectGuid>
<RootNamespace>Microsoft.AspNet.Mvc.TagHelpers.Test</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,110 @@
// 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.IO;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.Framework.OptionsModel;
using Moq;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
public class TestableHtmlGenerator : DefaultHtmlGenerator
{
private IDictionary<string, object> _validationAttributes;
public TestableHtmlGenerator(IModelMetadataProvider metadataProvider)
: this(metadataProvider, Mock.Of<IUrlHelper>())
{
}
public TestableHtmlGenerator(IModelMetadataProvider metadataProvider, IUrlHelper urlHelper)
: this(
metadataProvider,
urlHelper,
validationAttributes: new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase))
{
}
public TestableHtmlGenerator(
IModelMetadataProvider metadataProvider,
IUrlHelper urlHelper,
IDictionary<string, object> validationAttributes)
: base(Mock.Of<IActionBindingContextProvider>(), GetAntiForgery(), metadataProvider, urlHelper)
{
_validationAttributes = validationAttributes;
}
public IDictionary<string, object> ValidationAttributes
{
get { return _validationAttributes; }
}
public static ViewContext GetViewContext(
object model,
IHtmlGenerator htmlGenerator,
IModelMetadataProvider metadataProvider)
{
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(provider => provider.GetService(typeof(IHtmlGenerator)))
.Returns(htmlGenerator);
var httpContext = new Mock<HttpContext>();
httpContext
.Setup(context => context.RequestServices)
.Returns(serviceProvider.Object);
var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary(metadataProvider)
{
Model = model,
};
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, new StringWriter());
return viewContext;
}
public override TagBuilder GenerateAntiForgery(ViewContext viewContext)
{
return new TagBuilder("input")
{
Attributes =
{
{ "name", "__RequestVerificationToken" },
{ "type", "hidden" },
{ "value", "olJlUDjrouRNWLen4tQJhauj1Z1rrvnb3QD65cmQU1Ykqi6S4" }, // 50 chars of a token.
},
};
}
protected override IDictionary<string, object> GetValidationAttributes(
ViewContext viewContext,
ModelMetadata metadata,
string name)
{
return ValidationAttributes;
}
private static AntiForgery GetAntiForgery()
{
// AntiForgery must be passed to TestableHtmlGenerator constructor but will never be called.
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
optionsAccessor
.SetupGet(o => o.Options)
.Returns(new MvcOptions());
var antiForgery = new AntiForgery(
Mock.Of<IClaimUidExtractor>(),
Mock.Of<IDataProtectionProvider>(),
Mock.Of<IAntiForgeryAdditionalDataProvider>(),
optionsAccessor.Object);
return antiForgery;
}
}
}

View File

@ -0,0 +1,194 @@
// 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 TextAreaTagHelperTest
{
// Model (List<Model> or Model instance), container type (Model or NestModel), model accessor,
// property path, expected content.
public static TheoryData<object, Type, Func<object>, string, string> 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, string>
{
{ null, typeof(Model), () => null, "Text",
Environment.NewLine },
{ modelWithNull, typeof(Model), () => modelWithNull.Text, "Text",
Environment.NewLine },
{ modelWithText, typeof(Model), () => modelWithText.Text, "Text",
Environment.NewLine + "outer text" },
{ modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text",
Environment.NewLine },
{ modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text",
Environment.NewLine + "inner text" },
// Top-level indexing does not work end-to-end due to code generation issue #1345.
// TODO: Remove above comment when #1345 is fixed.
{ models, typeof(Model), () => models[0].Text, "[0].Text",
Environment.NewLine },
{ models, typeof(Model), () => models[1].Text, "[1].Text",
Environment.NewLine + "outer text" },
{ models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text",
Environment.NewLine },
{ models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text",
Environment.NewLine + "inner text" },
};
}
}
[Theory]
[MemberData(nameof(TestDataSet))]
public async Task Process_GeneratesExpectedOutput(
object model,
Type containerType,
Func<object> modelAccessor,
string propertyPath,
string expectedContent)
{
// Arrange
var expectedAttributes = new Dictionary<string, string>
{
{ "class", "form-control" },
{ "id", propertyPath },
{ "name", propertyPath },
{ "valid", "from validation attributes" },
};
var expectedTagName = "textarea";
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 TextAreaTagHelper
{
For = modelExpression,
};
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
var htmlAttributes = new Dictionary<string, string>
{
{ "class", "form-control" },
};
var output = new TagHelperOutput("original tag name", htmlAttributes, "original content")
{
SelfClosing = true,
};
var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
{
ValidationAttributes =
{
{ "valid", "from validation attributes" },
}
};
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.False(output.SelfClosing);
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public async Task TagHelper_LeavesOutputUnchanged_IfForNotBound()
{
// 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 TextAreaTagHelper();
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>());
var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent)
{
SelfClosing = true,
};
var htmlGenerator = new TestableHtmlGenerator(metadataProvider)
{
ValidationAttributes =
{
{ "valid", "from validation attributes" },
}
};
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.True(output.SelfClosing);
Assert.Equal(expectedTagName, output.TagName);
}
private class Model
{
public string Text { get; set; }
public NestedModel NestedModel { get; set; }
}
private class NestedModel
{
public string Text { get; set; }
}
}
}

View File

@ -0,0 +1,17 @@
{
"commands": {
"test": "Xunit.KRunner"
},
"dependencies": {
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
"Microsoft.AspNet.Testing": "1.0.0-*",
"Xunit.KRunner": "1.0.0-*"
},
"frameworks": {
"aspnet50": {
"dependencies": {
"Moq": "4.2.1312.1622"
}
}
}
}