// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Web.Routing; using Microsoft.Web.UnitTestUtil; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Mvc.Test { public class HtmlHelperTest { public static readonly RouteValueDictionary AttributesDictionary = new RouteValueDictionary(new { baz = "BazValue" }); public static readonly object AttributesObjectDictionary = new { baz = "BazObjValue" }; public static readonly object AttributesObjectUnderscoresDictionary = new { foo_baz = "BazObjValue" }; // Constructor [Fact] public void ConstructorGuardClauses() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = MvcHelper.GetViewDataContainer(null); // Act & Assert Assert.ThrowsArgumentNull( () => new HtmlHelper(null, viewDataContainer), "viewContext" ); Assert.ThrowsArgumentNull( () => new HtmlHelper(viewContext, null), "viewDataContainer" ); Assert.ThrowsArgumentNull( () => new HtmlHelper(viewContext, viewDataContainer, null), "routeCollection" ); } [Fact] public void PropertiesAreSet() { // Arrange var viewContext = new Mock().Object; var viewData = new ViewDataDictionary("The Model"); var routes = new RouteCollection(); var mockViewDataContainer = new Mock(); mockViewDataContainer.Setup(vdc => vdc.ViewData).Returns(viewData); // Act var htmlHelper = new HtmlHelper(viewContext, mockViewDataContainer.Object, routes); // Assert Assert.Equal(viewContext, htmlHelper.ViewContext); Assert.Equal(mockViewDataContainer.Object, htmlHelper.ViewDataContainer); Assert.Equal(routes, htmlHelper.RouteCollection); Assert.Equal(viewData.Model, htmlHelper.ViewData.Model); } [Fact] public void DefaultRouteCollectionIsRouteTableRoutes() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = new Mock().Object; // Act var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); // Assert Assert.Equal(RouteTable.Routes, htmlHelper.RouteCollection); } // AnonymousObjectToHtmlAttributes tests [Fact] public void ConvertsUnderscoresInNamesToDashes() { // Arrange var attributes = new { foo = "Bar", baz_bif = "pow_wow" }; // Act RouteValueDictionary result = HtmlHelper.AnonymousObjectToHtmlAttributes(attributes); // Assert Assert.Equal(2, result.Count); Assert.Equal("Bar", result["foo"]); Assert.Equal("pow_wow", result["baz-bif"]); } // AttributeEncode [Fact] public void AttributeEncodeObject() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.AttributeEncode((object)@"<"">"); // Assert Assert.Equal(encodedHtml, "<">"); } [Fact] public void AttributeEncodeObjectNull() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.AttributeEncode((object)null); // Assert Assert.Equal("", encodedHtml); } [Fact] public void AttributeEncodeString() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.AttributeEncode(@"<"">"); // Assert Assert.Equal(encodedHtml, "<">"); } [Fact] public void AttributeEncodeStringNull() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.AttributeEncode((string)null); // Assert Assert.Equal("", encodedHtml); } // EnableClientValidation [Fact] public void EnableClientValidation() { // Arrange var mockViewContext = new Mock(); var viewDataContainer = new Mock().Object; var htmlHelper = new HtmlHelper(mockViewContext.Object, viewDataContainer); // Act htmlHelper.EnableClientValidation(); // Act & assert mockViewContext.VerifySet(vc => vc.ClientValidationEnabled = true); } // EnableUnobtrusiveJavaScript [Fact] public void EnableUnobtrusiveJavaScript() { // Arrange var mockViewContext = new Mock(); var viewDataContainer = new Mock().Object; var htmlHelper = new HtmlHelper(mockViewContext.Object, viewDataContainer); // Act htmlHelper.EnableUnobtrusiveJavaScript(); // Act & assert mockViewContext.VerifySet(vc => vc.UnobtrusiveJavaScriptEnabled = true); } // Encode [Fact] public void EncodeObject() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.Encode((object)"
"); // Assert Assert.Equal(encodedHtml, "<br />"); } [Fact] public void EncodeObjectNull() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.Encode((object)null); // Assert Assert.Equal("", encodedHtml); } [Fact] public void EncodeString() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.Encode("
"); // Assert Assert.Equal(encodedHtml, "<br />"); } [Fact] public void EncodeStringNull() { // Arrange HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(); // Act string encodedHtml = htmlHelper.Encode((string)null); // Assert Assert.Equal("", encodedHtml); } // GetModelStateValue [Fact] public void GetModelStateValueReturnsNullIfModelStateHasNoValue() { // Arrange ViewDataDictionary vdd = new ViewDataDictionary(); vdd.ModelState.AddModelError("foo", "some error text"); // didn't call SetModelValue() HtmlHelper helper = new HtmlHelper(new ViewContext(), new SimpleViewDataContainer(vdd)); // Act object retVal = helper.GetModelStateValue("foo", typeof(object)); // Assert Assert.Null(retVal); } [Fact] public void GetModelStateValueReturnsNullIfModelStateKeyNotPresent() { // Arrange ViewDataDictionary vdd = new ViewDataDictionary(); HtmlHelper helper = new HtmlHelper(new ViewContext(), new SimpleViewDataContainer(vdd)); // Act object retVal = helper.GetModelStateValue("key_not_present", typeof(object)); // Assert Assert.Null(retVal); } // GenerateIdFromName [Fact] public void GenerateIdFromNameTests() { // Guard clauses Assert.ThrowsArgumentNull( () => HtmlHelper.GenerateIdFromName(null), "name" ); Assert.ThrowsArgumentNull( () => HtmlHelper.GenerateIdFromName(null, "?"), "name" ); Assert.ThrowsArgumentNull( () => HtmlHelper.GenerateIdFromName("?", null), "idAttributeDotReplacement" ); // Default replacement tests Assert.Equal("", HtmlHelper.GenerateIdFromName("")); Assert.Equal("Foo", HtmlHelper.GenerateIdFromName("Foo")); Assert.Equal("Foo_Bar", HtmlHelper.GenerateIdFromName("Foo.Bar")); Assert.Equal("Foo_Bar_Baz", HtmlHelper.GenerateIdFromName("Foo.Bar.Baz")); Assert.Null(HtmlHelper.GenerateIdFromName("1Foo")); Assert.Equal("Foo_0_", HtmlHelper.GenerateIdFromName("Foo[0]")); // Custom replacement tests Assert.Equal("", HtmlHelper.GenerateIdFromName("", "?")); Assert.Equal("Foo", HtmlHelper.GenerateIdFromName("Foo", "?")); Assert.Equal("Foo?Bar", HtmlHelper.GenerateIdFromName("Foo.Bar", "?")); Assert.Equal("Foo?Bar?Baz", HtmlHelper.GenerateIdFromName("Foo.Bar.Baz", "?")); Assert.Equal("FooBarBaz", HtmlHelper.GenerateIdFromName("Foo.Bar.Baz", "")); Assert.Null(HtmlHelper.GenerateIdFromName("1Foo", "?")); Assert.Equal("Foo?0?", HtmlHelper.GenerateIdFromName("Foo[0]", "?")); } // RenderPartialInternal [Fact] public void NullPartialViewNameThrows() { // Arrange TestableHtmlHelper helper = TestableHtmlHelper.Create(); ViewDataDictionary viewData = new ViewDataDictionary(); // Act & Assert Assert.ThrowsArgumentNullOrEmpty( () => helper.RenderPartialInternal(null /* partialViewName */, null /* viewData */, null /* model */, TextWriter.Null), "partialViewName"); } [Fact] public void EmptyPartialViewNameThrows() { // Arrange TestableHtmlHelper helper = TestableHtmlHelper.Create(); ViewDataDictionary viewData = new ViewDataDictionary(); // Act & Assert Assert.ThrowsArgumentNullOrEmpty( () => helper.RenderPartialInternal(String.Empty /* partialViewName */, null /* viewData */, null /* model */, TextWriter.Null), "partialViewName"); } [Fact] public void EngineLookupSuccessCallsRender() { // Arrange TestableHtmlHelper helper = TestableHtmlHelper.Create(); TextWriter writer = helper.ViewContext.Writer; Mock engine = new Mock(MockBehavior.Strict); Mock view = new Mock(MockBehavior.Strict); engine .Setup(e => e.FindPartialView(It.IsAny(), "partial-view", It.IsAny())) .Returns(new ViewEngineResult(view.Object, engine.Object)) .Verifiable(); view .Setup(v => v.Render(It.IsAny(), writer)) .Callback( (viewContext, _) => { Assert.Same(helper.ViewContext.View, viewContext.View); Assert.Same(helper.ViewContext.TempData, viewContext.TempData); }) .Verifiable(); // Act helper.RenderPartialInternal("partial-view", null /* viewData */, null /* model */, writer, engine.Object); // Assert engine.Verify(); view.Verify(); } [Fact] public void EngineLookupFailureThrows() { // Arrange TestableHtmlHelper helper = TestableHtmlHelper.Create(); Mock engine = new Mock(MockBehavior.Strict); engine .Setup(e => e.FindPartialView(It.IsAny(), "partial-view", It.IsAny())) .Returns(new ViewEngineResult(new[] { "location1", "location2" })) .Verifiable(); // Act & Assert Assert.Throws( () => helper.RenderPartialInternal("partial-view", null /* viewData */, null /* model */, TextWriter.Null, engine.Object), @"The partial view 'partial-view' was not found or no view engine supports the searched locations. The following locations were searched: location1 location2"); engine.Verify(); } [Fact] public void RenderPartialInternalWithNullModelAndNullViewData() { // Arrange object model = new object(); TestableHtmlHelper helper = TestableHtmlHelper.Create(); helper.ViewData["Foo"] = "Bar"; helper.ViewData.Model = model; Mock engine = new Mock(MockBehavior.Strict); Mock view = new Mock(MockBehavior.Strict); engine .Setup(e => e.FindPartialView(It.IsAny(), "partial-view", It.IsAny())) .Returns(new ViewEngineResult(view.Object, engine.Object)) .Verifiable(); view .Setup(v => v.Render(It.IsAny(), TextWriter.Null)) .Callback( (viewContext, writer) => { Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance Assert.Equal("Bar", viewContext.ViewData["Foo"]); // Copy of the existing view data Assert.Same(model, viewContext.ViewData.Model); // Keep existing model }) .Verifiable(); // Act helper.RenderPartialInternal("partial-view", null /* viewData */, null /* model */, TextWriter.Null, engine.Object); // Assert engine.Verify(); view.Verify(); } [Fact] public void RenderPartialInternalWithNonNullModelAndNullViewData() { // Arrange object model = new object(); object newModel = new object(); TestableHtmlHelper helper = TestableHtmlHelper.Create(); helper.ViewData["Foo"] = "Bar"; helper.ViewData.Model = model; Mock engine = new Mock(MockBehavior.Strict); Mock view = new Mock(MockBehavior.Strict); engine .Setup(e => e.FindPartialView(It.IsAny(), "partial-view", It.IsAny())) .Returns(new ViewEngineResult(view.Object, engine.Object)) .Verifiable(); view .Setup(v => v.Render(It.IsAny(), TextWriter.Null)) .Callback( (viewContext, writer) => { Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance Assert.Empty(viewContext.ViewData); // Empty (not copied) Assert.Same(newModel, viewContext.ViewData.Model); // New model }) .Verifiable(); // Act helper.RenderPartialInternal("partial-view", null /* viewData */, newModel, TextWriter.Null, engine.Object); // Assert engine.Verify(); view.Verify(); } [Fact] public void RenderPartialInternalWithNullModelAndNonNullViewData() { // Arrange object model = new object(); object vddModel = new object(); ViewDataDictionary vdd = new ViewDataDictionary(); vdd["Baz"] = "Biff"; vdd.Model = vddModel; TestableHtmlHelper helper = TestableHtmlHelper.Create(); helper.ViewData["Foo"] = "Bar"; helper.ViewData.Model = model; Mock engine = new Mock(MockBehavior.Strict); Mock view = new Mock(MockBehavior.Strict); engine .Setup(e => e.FindPartialView(It.IsAny(), "partial-view", It.IsAny())) .Returns(new ViewEngineResult(view.Object, engine.Object)) .Verifiable(); view .Setup(v => v.Render(It.IsAny(), TextWriter.Null)) .Callback( (viewContext, writer) => { Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance Assert.Single(viewContext.ViewData); // Copy of the passed view data, not original view data Assert.Equal("Biff", viewContext.ViewData["Baz"]); Assert.Same(vddModel, viewContext.ViewData.Model); // Keep model from passed view data, not original view data }) .Verifiable(); // Act helper.RenderPartialInternal("partial-view", vdd, null /* model */, TextWriter.Null, engine.Object); // Assert engine.Verify(); view.Verify(); } [Fact] public void RenderPartialInternalWithNonNullModelAndNonNullViewData() { // Arrange object model = new object(); object vddModel = new object(); object newModel = new object(); ViewDataDictionary vdd = new ViewDataDictionary(); vdd["Baz"] = "Biff"; vdd.Model = vddModel; TestableHtmlHelper helper = TestableHtmlHelper.Create(); helper.ViewData["Foo"] = "Bar"; helper.ViewData.Model = model; Mock engine = new Mock(MockBehavior.Strict); Mock view = new Mock(MockBehavior.Strict); engine .Setup(e => e.FindPartialView(It.IsAny(), "partial-view", It.IsAny())) .Returns(new ViewEngineResult(view.Object, engine.Object)) .Verifiable(); view .Setup(v => v.Render(It.IsAny(), TextWriter.Null)) .Callback( (viewContext, writer) => { Assert.NotSame(helper.ViewData, viewContext.ViewData); // New view data instance Assert.Single(viewContext.ViewData); // Copy of the passed view data, not original view data Assert.Equal("Biff", viewContext.ViewData["Baz"]); Assert.Same(newModel, viewContext.ViewData.Model); // New model }) .Verifiable(); // Act helper.RenderPartialInternal("partial-view", vdd, newModel, TextWriter.Null, engine.Object); // Assert engine.Verify(); view.Verify(); } // HttpMethodOverride [Fact] public void HttpMethodOverrideGuardClauses() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = MvcHelper.GetViewDataContainer(null); var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); // Act & Assert Assert.ThrowsArgumentNullOrEmpty( () => htmlHelper.HttpMethodOverride(null), "httpMethod" ); Assert.Throws( () => htmlHelper.HttpMethodOverride((HttpVerbs)10000), @"The specified HttpVerbs value is not supported. The supported values are Delete, Head, Put, Options, and Patch. Parameter name: httpVerb" ); Assert.Throws( () => htmlHelper.HttpMethodOverride(HttpVerbs.Get), @"The specified HttpVerbs value is not supported. The supported values are Delete, Head, Put, Options, and Patch. Parameter name: httpVerb" ); Assert.Throws( () => htmlHelper.HttpMethodOverride(HttpVerbs.Post), @"The specified HttpVerbs value is not supported. The supported values are Delete, Head, Put, Options, and Patch. Parameter name: httpVerb" ); Assert.Throws( () => htmlHelper.HttpMethodOverride("gEt"), @"The GET and POST HTTP methods are not supported. Parameter name: httpMethod" ); Assert.Throws( () => htmlHelper.HttpMethodOverride("pOsT"), @"The GET and POST HTTP methods are not supported. Parameter name: httpMethod" ); } [Fact] public void HttpMethodOverrideWithMethodRendersHiddenField() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = MvcHelper.GetViewDataContainer(null); var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); // Act MvcHtmlString hiddenField = htmlHelper.HttpMethodOverride("PUT"); // Assert Assert.Equal(@"", hiddenField.ToHtmlString()); } [Fact] public void HttpMethodOverrideWithVerbRendersHiddenField() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = MvcHelper.GetViewDataContainer(null); var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); // Act MvcHtmlString hiddenField = htmlHelper.HttpMethodOverride(HttpVerbs.Delete); // Assert Assert.Equal(@"", hiddenField.ToHtmlString()); } [Fact] public void ViewBagProperty_ReflectsViewData() { // Arrange Mock viewDataContainer = new Mock(); ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } }; viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary); // Act HtmlHelper htmlHelper = new HtmlHelper(new Mock().Object, viewDataContainer.Object); // Assert Assert.Equal(1, htmlHelper.ViewBag.A); } [Fact] public void ViewBagProperty_ReflectsNewViewDataContainerInstance() { // Arrange ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } }; Mock viewDataContainer = new Mock(); viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary); ViewDataDictionary otherViewDataDictionary = new ViewDataDictionary() { { "A", 2 } }; Mock otherViewDataContainer = new Mock(); otherViewDataContainer.Setup(container => container.ViewData).Returns(otherViewDataDictionary); HtmlHelper htmlHelper = new HtmlHelper(new Mock().Object, viewDataContainer.Object, new RouteCollection()); // Act htmlHelper.ViewDataContainer = otherViewDataContainer.Object; // Assert Assert.Equal(2, htmlHelper.ViewBag.A); } [Fact] public void ViewBag_PropagatesChangesToViewData() { // Arrange ViewDataDictionary viewDataDictionary = new ViewDataDictionary() { { "A", 1 } }; Mock viewDataContainer = new Mock(); viewDataContainer.Setup(container => container.ViewData).Returns(viewDataDictionary); HtmlHelper htmlHelper = new HtmlHelper(new Mock().Object, viewDataContainer.Object, new RouteCollection()); // Act htmlHelper.ViewBag.A = "foo"; htmlHelper.ViewBag.B = 2; // Assert Assert.Equal("foo", htmlHelper.ViewData["A"]); Assert.Equal(2, htmlHelper.ViewData["B"]); } // Unobtrusive validation attributes [Fact] public void GetUnobtrusiveValidationAttributesReturnsEmptySetWhenClientValidationIsNotEnabled() { // Arrange var formContext = new FormContext(); formContext.RenderedField("foobar", true); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Empty(result); } [Fact] public void GetUnobtrusiveValidationAttributesReturnsEmptySetWhenUnobtrusiveJavaScriptIsNotEnabled() { // Arrange var formContext = new FormContext(); formContext.RenderedField("foobar", true); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Empty(result); } [Fact] public void GetUnobtrusiveValidationAttributesReturnsEmptySetWhenFieldHasAlreadyBeenRendered() { // Arrange var formContext = new FormContext(); formContext.RenderedField("foobar", true); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Empty(result); } [Fact] public void GetUnobtrusiveValidationAttributesReturnsEmptySetAndSetsFieldAsRenderedForFieldWithNoClientRules() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); htmlHelper.ClientValidationRuleFactory = delegate { return Enumerable.Empty(); }; // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Empty(result); Assert.True(formContext.RenderedField("foobar")); } [Fact] public void GetUnobtrusiveValidationAttributesIncludesDataValTrueWithNonEmptyClientRuleList() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); htmlHelper.ClientValidationRuleFactory = delegate { return new[] { new ModelClientValidationRule { ValidationType = "type" } }; }; // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Equal("true", result["data-val"]); } [Fact] public void GetUnobtrusiveValidationAttributesWithEmptyMessage() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); htmlHelper.ClientValidationRuleFactory = delegate { return new[] { new ModelClientValidationRule { ValidationType = "type" } }; }; // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Equal("", result["data-val-type"]); } [Fact] public void GetUnobtrusiveValidationAttributesMessageIsHtmlEncoded() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); htmlHelper.ClientValidationRuleFactory = delegate { return new[] { new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "" } }; }; // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Equal("<script>alert('xss')</script>", result["data-val-type"]); } [Fact] public void GetUnobtrusiveValidationAttributesWithMessageAndParameters() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); htmlHelper.ClientValidationRuleFactory = delegate { ModelClientValidationRule rule = new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" }; rule.ValidationParameters["foo"] = "bar"; rule.ValidationParameters["baz"] = "biff"; return new[] { rule }; }; // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Equal("error", result["data-val-type"]); Assert.Equal("bar", result["data-val-type-foo"]); Assert.Equal("biff", result["data-val-type-baz"]); } [Fact] public void GetUnobtrusiveValidationAttributesWithTwoClientRules() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); htmlHelper.ClientValidationRuleFactory = delegate { ModelClientValidationRule rule1 = new ModelClientValidationRule { ValidationType = "type", ErrorMessage = "error" }; rule1.ValidationParameters["foo"] = "bar"; rule1.ValidationParameters["baz"] = "biff"; ModelClientValidationRule rule2 = new ModelClientValidationRule { ValidationType = "othertype", ErrorMessage = "othererror" }; rule2.ValidationParameters["true3"] = "false4"; return new[] { rule1, rule2 }; }; // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Equal("error", result["data-val-type"]); Assert.Equal("bar", result["data-val-type-foo"]); Assert.Equal("biff", result["data-val-type-baz"]); Assert.Equal("othererror", result["data-val-othertype"]); Assert.Equal("false4", result["data-val-othertype-true3"]); } [Fact] public void GetUnobtrusiveValidationAttributesUsesShortNameForModelMetadataLookup() { // Arrange string passedName = null; var formContext = new FormContext(); var viewContext = new Mock(); var viewData = new ViewDataDictionary(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); viewData.TemplateInfo.HtmlFieldPrefix = "Prefix"; var viewDataContainer = MvcHelper.GetViewDataContainer(viewData); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); htmlHelper.ClientValidationRuleFactory = (name, _) => { passedName = name; return Enumerable.Empty(); }; // Act htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.Equal("foobar", passedName); } [Fact] public void GetUnobtrusiveValidationAttributeUsesViewDataForModelMetadataLookup() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); var viewData = new ViewDataDictionary(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); viewData.TemplateInfo.HtmlFieldPrefix = "Prefix"; var viewDataContainer = MvcHelper.GetViewDataContainer(viewData); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); // Act IDictionary result = htmlHelper.GetUnobtrusiveValidationAttributes("MyProperty"); // Assert Assert.Equal(2, result.Count); Assert.Equal("true", result["data-val"]); Assert.Equal("My required message", result["data-val-required"]); } class MyModel { [Required(ErrorMessage = "My required message")] public object MyProperty { get; set; } } [Fact] public void GetUnobtrusiveValidationAttributesMarksRenderedFieldsWithFullName() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); var viewData = new ViewDataDictionary(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); viewData.TemplateInfo.HtmlFieldPrefix = "Prefix"; var viewDataContainer = MvcHelper.GetViewDataContainer(viewData); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); // Act htmlHelper.GetUnobtrusiveValidationAttributes("foobar"); // Assert Assert.False(formContext.RenderedField("foobar")); Assert.True(formContext.RenderedField("Prefix.foobar")); } [Fact] public void GetUnobtrusiveValidationAttributesGuardClauses() { // Arrange var formContext = new FormContext(); var viewContext = new Mock(); viewContext.SetupGet(vc => vc.FormContext).Returns(formContext); viewContext.SetupGet(vc => vc.ClientValidationEnabled).Returns(true); viewContext.SetupGet(vc => vc.UnobtrusiveJavaScriptEnabled).Returns(true); var viewDataContainer = MvcHelper.GetViewDataContainer(new ViewDataDictionary()); var htmlHelper = new HtmlHelper(viewContext.Object, viewDataContainer); // Act & Assert AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules cannot be empty. Client rule type: System.Web.Mvc.ModelClientValidationRule", new ModelClientValidationRule()); AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: \"OnlyLowerCase\", client rule type: System.Web.Mvc.ModelClientValidationRule", new ModelClientValidationRule { ValidationType = "OnlyLowerCase" }); AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules must consist of only lowercase letters. Invalid name: \"nonumb3rs\", client rule type: System.Web.Mvc.ModelClientValidationRule", new ModelClientValidationRule { ValidationType = "nonumb3rs" }); AssertBadClientValidationRule(htmlHelper, "Validation type names in unobtrusive client validation rules must be unique. The following validation type was seen more than once: rule", new ModelClientValidationRule { ValidationType = "rule" }, new ModelClientValidationRule { ValidationType = "rule" }); var emptyParamName = new ModelClientValidationRule { ValidationType = "type" }; emptyParamName.ValidationParameters[""] = "foo"; AssertBadClientValidationRule(htmlHelper, "Validation parameter names in unobtrusive client validation rules cannot be empty. Client rule type: System.Web.Mvc.ModelClientValidationRule", emptyParamName); var paramNameMixedCase = new ModelClientValidationRule { ValidationType = "type" }; paramNameMixedCase.ValidationParameters["MixedCase"] = "foo"; AssertBadClientValidationRule(htmlHelper, "Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: MixedCase, client rule type: System.Web.Mvc.ModelClientValidationRule", paramNameMixedCase); var paramNameStartsWithNumber = new ModelClientValidationRule { ValidationType = "type" }; paramNameStartsWithNumber.ValidationParameters["2112"] = "foo"; AssertBadClientValidationRule(htmlHelper, "Validation parameter names in unobtrusive client validation rules must start with a lowercase letter and consist of only lowercase letters or digits. Validation parameter name: 2112, client rule type: System.Web.Mvc.ModelClientValidationRule", paramNameStartsWithNumber); } [Fact] public void RawReturnsWrapperMarkup() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = new Mock().Object; var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); string markup = "bold"; // Act IHtmlString markupHtml = htmlHelper.Raw(markup); // Assert Assert.Equal("bold", markupHtml.ToString()); Assert.Equal("bold", markupHtml.ToHtmlString()); } [Fact] public void RawAllowsNullValue() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = new Mock().Object; var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); // Act IHtmlString markupHtml = htmlHelper.Raw(null); // Assert Assert.Equal(null, markupHtml.ToString()); Assert.Equal(null, markupHtml.ToHtmlString()); } [Fact] public void RawAllowsNullObjectValue() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = new Mock().Object; var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); // Act IHtmlString markupHtml = htmlHelper.Raw((object)null); // Assert Assert.Equal(null, markupHtml.ToString()); Assert.Equal(null, markupHtml.ToHtmlString()); } [Fact] public void RawAllowsEmptyValue() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = new Mock().Object; var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); // Act IHtmlString markupHtml = htmlHelper.Raw(""); // Assert Assert.Equal("", markupHtml.ToString()); Assert.Equal("", markupHtml.ToHtmlString()); } [Fact] public void RawReturnsWrapperMarkupOfObject() { // Arrange var viewContext = new Mock().Object; var viewDataContainer = new Mock().Object; var htmlHelper = new HtmlHelper(viewContext, viewDataContainer); ObjectWithWrapperMarkup obj = new ObjectWithWrapperMarkup(); // Act IHtmlString markupHtml = htmlHelper.Raw(obj); // Assert Assert.Equal("boldFromObject", markupHtml.ToString()); Assert.Equal("boldFromObject", markupHtml.ToHtmlString()); } [Fact] public void EvalStringAndFormatValueWithNullValueReturnsEmptyString() { // Arrange var htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary()); // Act & Assert Assert.Equal(String.Empty, htmlHelper.FormatValue(null, "-{0}-")); Assert.Equal(String.Empty, htmlHelper.EvalString("nonExistant")); Assert.Equal(String.Empty, htmlHelper.EvalString("nonExistant", "-{0}-")); } [Fact] public void EvalStringAndFormatValueUseCurrentCulture() { // Arrange DateTime dt = new DateTime(1900, 1, 1, 0, 0, 0); HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary() { { "date", dt } }); string expectedFormattedDate = "-1900/01/01 12:00:00 AM-"; // Act && Assert using (ReplaceCulture("en-ZA", "en-US")) { Assert.Equal(expectedFormattedDate, htmlHelper.FormatValue(dt, "-{0}-")); Assert.Equal(expectedFormattedDate, htmlHelper.EvalString("date", "-{0}-")); } } [Fact] public void EvalStringAndFormatValueWithEmptyFormatConvertsValueToString() { // Arrange DateTime dt = new DateTime(1900, 1, 1, 0, 0, 0); HtmlHelper htmlHelper = MvcHelper.GetHtmlHelper(new ViewDataDictionary() { { "date", dt } }); string expectedUnformattedDate = "1900/01/01 12:00:00 AM"; // Act && Assert using (ReplaceCulture("en-ZA", "en-US")) { Assert.Equal(expectedUnformattedDate, htmlHelper.FormatValue(dt, String.Empty)); Assert.Equal(expectedUnformattedDate, htmlHelper.EvalString("date", String.Empty)); Assert.Equal(expectedUnformattedDate, htmlHelper.EvalString("date")); } } private class ObjectWithWrapperMarkup { public override string ToString() { return "boldFromObject"; } } // Helpers private static void AssertBadClientValidationRule(HtmlHelper htmlHelper, string expectedMessage, params ModelClientValidationRule[] rules) { htmlHelper.ClientValidationRuleFactory = delegate { return rules; }; Assert.Throws( () => htmlHelper.GetUnobtrusiveValidationAttributes(Guid.NewGuid().ToString()), expectedMessage ); } internal static ValueProviderResult GetValueProviderResult(object rawValue, string attemptedValue) { return new ValueProviderResult(rawValue, attemptedValue, CultureInfo.InvariantCulture); } internal static IDisposable ReplaceCulture(string currentCulture, string currentUICulture) { CultureInfo newCulture = CultureInfo.GetCultureInfo(currentCulture); CultureInfo newUICulture = CultureInfo.GetCultureInfo(currentUICulture); CultureInfo originalCulture = Thread.CurrentThread.CurrentCulture; CultureInfo originalUICulture = Thread.CurrentThread.CurrentUICulture; Thread.CurrentThread.CurrentCulture = newCulture; Thread.CurrentThread.CurrentUICulture = newUICulture; return new CultureReplacement { OriginalCulture = originalCulture, OriginalUICulture = originalUICulture }; } private class CultureReplacement : IDisposable { public CultureInfo OriginalCulture; public CultureInfo OriginalUICulture; public void Dispose() { Thread.CurrentThread.CurrentCulture = OriginalCulture; Thread.CurrentThread.CurrentUICulture = OriginalUICulture; } } private class TestableHtmlHelper : HtmlHelper { TestableHtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer) : base(viewContext, viewDataContainer) { } public static TestableHtmlHelper Create() { ViewDataDictionary viewData = new ViewDataDictionary(); Mock mockViewContext = new Mock() { DefaultValue = DefaultValue.Mock }; mockViewContext.Setup(c => c.HttpContext.Response.Output).Throws(new Exception("Response.Output should never be called.")); mockViewContext.Setup(c => c.ViewData).Returns(viewData); mockViewContext.Setup(c => c.Writer).Returns(new StringWriter()); Mock container = new Mock(); container.Setup(c => c.ViewData).Returns(viewData); return new TestableHtmlHelper(mockViewContext.Object, container.Object); } public void RenderPartialInternal(string partialViewName, ViewDataDictionary viewData, object model, TextWriter writer, params IViewEngine[] engines) { base.RenderPartialInternal(partialViewName, viewData, model, writer, new ViewEngineCollection(engines)); } } } }