// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Web.Routing; using System.Web.UI.WebControls; using Microsoft.Web.UnitTestUtil; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Mvc.Html.Test { public class TemplateHelpersTest { // ExecuteTemplate private class ExecuteTemplateModel { public string MyProperty { get; set; } public Nullable MyNullableProperty { get; set; } } [Fact] public void ExecuteTemplateCallsGetViewNamesWithProvidedTemplateNameAndMetadataInformation() { using (new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.TemplateHint = "templateHint"; metadata.DataTypeName = "dataType"; string[] hints = null; // Act TemplateHelpers.ExecuteTemplate( html, MakeViewData(html, metadata), "templateName", DataBoundControlMode.ReadOnly, (_metadata, _hints) => { hints = _hints; return new[] { "String" }; }, TemplateHelpers.GetDefaultActions); // Assert Assert.NotNull(hints); Assert.Equal(3, hints.Length); Assert.Equal("templateName", hints[0]); Assert.Equal("templateHint", hints[1]); Assert.Equal("dataType", hints[2]); } } [Fact] public void ExecuteTemplateUsesViewFromViewEngineInReadOnlyMode() { using (MockViewEngine engine = new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewContext callbackViewContext = null; engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "DisplayTemplates/String", true)) .Returns(new ViewEngineResult(engine.View.Object, engine.Engine.Object)) .Verifiable(); engine.View.Setup(v => v.Render(It.IsAny(), It.IsAny())) .Callback((vc, tw) => { callbackViewContext = vc; tw.Write("View Text"); }) .Verifiable(); ViewDataDictionary viewData = MakeViewData(html, metadata); // Act string result = TemplateHelpers.ExecuteTemplate( html, viewData, "templateName", DataBoundControlMode.ReadOnly, delegate { return new[] { "String" }; }, TemplateHelpers.GetDefaultActions); // Assert engine.Engine.Verify(); engine.View.Verify(); Assert.Equal("View Text", result); Assert.Same(engine.View.Object, callbackViewContext.View); Assert.Same(viewData, callbackViewContext.ViewData); Assert.Same(html.ViewContext.TempData, callbackViewContext.TempData); TemplateHelpers.ActionCacheViewItem cacheItem = TemplateHelpers.GetActionCache(html)["DisplayTemplates/String"] as TemplateHelpers.ActionCacheViewItem; Assert.NotNull(cacheItem); Assert.Equal("DisplayTemplates/String", cacheItem.ViewName); } } [Fact] public void ExecuteTemplateUsesViewFromViewEngineInEditMode() { using (MockViewEngine engine = new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewContext callbackViewContext = null; engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "EditorTemplates/String", true)) .Returns(new ViewEngineResult(engine.View.Object, engine.Engine.Object)) .Verifiable(); engine.View.Setup(v => v.Render(It.IsAny(), It.IsAny())) .Callback((vc, tw) => { callbackViewContext = vc; tw.Write("View Text"); }) .Verifiable(); ViewDataDictionary viewData = MakeViewData(html, metadata); // Act string result = TemplateHelpers.ExecuteTemplate( html, viewData, "templateName", DataBoundControlMode.Edit, delegate { return new[] { "String" }; }, TemplateHelpers.GetDefaultActions); // Assert engine.Engine.Verify(); engine.View.Verify(); Assert.Equal("View Text", result); Assert.Same(engine.View.Object, callbackViewContext.View); Assert.Same(viewData, callbackViewContext.ViewData); Assert.Same(html.ViewContext.TempData, callbackViewContext.TempData); TemplateHelpers.ActionCacheViewItem cacheItem = TemplateHelpers.GetActionCache(html)["EditorTemplates/String"] as TemplateHelpers.ActionCacheViewItem; Assert.NotNull(cacheItem); Assert.Equal("EditorTemplates/String", cacheItem.ViewName); } } [Fact] public void ExecuteTemplateUsesViewFromDefaultActionsInReadOnlyMode() { using (MockViewEngine engine = new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "DisplayTemplates/String", It.IsAny())) .Returns(new ViewEngineResult(new string[0])) .Verifiable(); ViewDataDictionary viewData = MakeViewData(html, metadata); // Act TemplateHelpers.ExecuteTemplate( html, viewData, "templateName", DataBoundControlMode.ReadOnly, delegate { return new[] { "String" }; }, TemplateHelpers.GetDefaultActions); // Assert engine.Engine.Verify(); TemplateHelpers.ActionCacheCodeItem cacheItem = TemplateHelpers.GetActionCache(html)["DisplayTemplates/String"] as TemplateHelpers.ActionCacheCodeItem; Assert.NotNull(cacheItem); Assert.Equal(DefaultDisplayTemplates.StringTemplate, cacheItem.Action); } } [Fact] public void ExecuteTemplateUsesViewFromDefaultActionsInEditMode() { using (MockViewEngine engine = new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); engine.Engine.Setup(e => e.FindPartialView(html.ViewContext, "EditorTemplates/String", It.IsAny())) .Returns(new ViewEngineResult(new string[0])) .Verifiable(); ViewDataDictionary viewData = MakeViewData(html, metadata); // Act TemplateHelpers.ExecuteTemplate( html, viewData, "templateName", DataBoundControlMode.Edit, delegate { return new[] { "String" }; }, TemplateHelpers.GetDefaultActions); // Assert engine.Engine.Verify(); TemplateHelpers.ActionCacheCodeItem cacheItem = TemplateHelpers.GetActionCache(html)["EditorTemplates/String"] as TemplateHelpers.ActionCacheCodeItem; Assert.NotNull(cacheItem); Assert.Equal(DefaultEditorTemplates.StringTemplate, cacheItem.Action); } } [Fact] public void ExecuteTemplatePrefersExistingActionCacheItem() { using (MockViewEngine engine = new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary viewData = MakeViewData(html, metadata); TemplateHelpers.GetActionCache(html).Add("EditorTemplates/String", new TemplateHelpers.ActionCacheCodeItem { Action = _ => "Action Text" }); // Act string result = TemplateHelpers.ExecuteTemplate( html, viewData, "templateName", DataBoundControlMode.Edit, delegate { return new[] { "String" }; }, TemplateHelpers.GetDefaultActions); // Assert engine.Engine.Verify(); engine.Engine.Verify(e => e.FindPartialView(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); Assert.Equal("Action Text", result); } } [Fact] public void ExecuteTemplateThrowsWhenNoTemplatesMatch() { using (new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary viewData = MakeViewData(html, metadata); // Act & Assert Assert.Throws( () => TemplateHelpers.ExecuteTemplate(html, viewData, "templateName", DataBoundControlMode.Edit, delegate { return new string[0]; }, TemplateHelpers.GetDefaultActions), "Unable to locate an appropriate template for type System.String."); } } [Fact] public void ExecuteTemplateCreatesNewHtmlHelperWithCorrectViewDataForDefaultAction() { using (MockViewEngine engine = new MockViewEngine(false)) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary viewData = MakeViewData(html, metadata); HtmlHelper passedHtmlHelper = null; // Act TemplateHelpers.ExecuteTemplate( html, viewData, "templateName", DataBoundControlMode.Edit, delegate { return new[] { "String" }; }, delegate { return new Dictionary> { { "String", _htmlHelper => { passedHtmlHelper = _htmlHelper; return "content"; } } }; }); // Assert Assert.NotNull(passedHtmlHelper); Assert.Same(passedHtmlHelper.ViewData, passedHtmlHelper.ViewContext.ViewData); Assert.NotSame(html.ViewData, passedHtmlHelper.ViewData); } } [Fact] public void ExecuteTemplateCreatesNewHtmlHelperWithCorrectViewDataForCachedAction() { using (MockViewEngine engine = new MockViewEngine()) { // Arrange HtmlHelper html = MakeHtmlHelper(new ExecuteTemplateModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary viewData = MakeViewData(html, metadata); HtmlHelper passedHtmlHelper = null; TemplateHelpers.GetActionCache(html).Add( "EditorTemplates/String", new TemplateHelpers.ActionCacheCodeItem { Action = _htmlHelper => { passedHtmlHelper = _htmlHelper; return "content"; } }); // Act string result = TemplateHelpers.ExecuteTemplate( html, viewData, "templateName", DataBoundControlMode.Edit, delegate { return new[] { "String" }; }, TemplateHelpers.GetDefaultActions); // Assert Assert.NotNull(passedHtmlHelper); Assert.Same(passedHtmlHelper.ViewData, passedHtmlHelper.ViewContext.ViewData); Assert.NotSame(html.ViewData, passedHtmlHelper.ViewData); } } // GetActionCache [Fact] public void CacheIsCreatedIfNotPresent() { // Arrange Hashtable items = new Hashtable(); Mock context = new Mock(); context.Setup(c => c.Items).Returns(items); Mock viewContext = new Mock(); viewContext.Setup(c => c.HttpContext).Returns(context.Object); Mock viewDataContainer = new Mock(); HtmlHelper helper = new HtmlHelper(viewContext.Object, viewDataContainer.Object); // Act Dictionary cache = TemplateHelpers.GetActionCache(helper); // Assert Assert.NotNull(cache); Assert.Empty(cache); Assert.Contains((object)cache, items.Values.OfType()); } [Fact] public void CacheIsReusedIfPresent() { // Arrange Hashtable items = new Hashtable(); Mock context = new Mock(); context.Setup(c => c.Items).Returns(items); Mock viewContext = new Mock(); viewContext.Setup(c => c.HttpContext).Returns(context.Object); Mock viewDataContainer = new Mock(); HtmlHelper helper = new HtmlHelper(viewContext.Object, viewDataContainer.Object); // Act Dictionary cache1 = TemplateHelpers.GetActionCache(helper); Dictionary cache2 = TemplateHelpers.GetActionCache(helper); // Assert Assert.NotNull(cache1); Assert.Same(cache1, cache2); } // GetViewNames [Fact] public void GetViewNamesFullOrderingOfBuiltInValueType() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(double)); // Act List result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList(); // Assert Assert.Equal(4, result.Count); Assert.Equal("UIHint", result[0]); Assert.Equal("DataType", result[1]); Assert.Equal("Double", result[2]); Assert.Equal("String", result[3]); } [Fact] public void GetViewNamesFullOrderingOfComplexType() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(HttpWebRequest)); // Act List result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList(); // Assert Assert.Equal(6, result.Count); Assert.Equal("UIHint", result[0]); Assert.Equal("DataType", result[1]); Assert.Equal("HttpWebRequest", result[2]); Assert.Equal("WebRequest", result[3]); Assert.Equal("MarshalByRefObject", result[4]); Assert.Equal("Object", result[5]); } [Fact] public void GetViewNamesFullOrderingOfInterface() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IDisposable)); // Act List result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList(); // Assert Assert.Equal(4, result.Count); Assert.Equal("UIHint", result[0]); Assert.Equal("DataType", result[1]); Assert.Equal("IDisposable", result[2]); Assert.Equal("Object", result[3]); } [Fact] public void GetViewNamesFullOrderingOfComplexTypeThatImplementsIEnumerable() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(List)); // Act List result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList(); // Assert Assert.Equal(5, result.Count); Assert.Equal("UIHint", result[0]); Assert.Equal("DataType", result[1]); Assert.Equal("List`1", result[2]); Assert.Equal("Collection", result[3]); Assert.Equal("Object", result[4]); } [Fact] public void GetViewNamesFullOrderingOfInterfaceThatRequiresIEnumerable() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(IList)); // Act List result = TemplateHelpers.GetViewNames(metadata, "UIHint", "DataType").ToList(); // Assert Assert.Equal(5, result.Count); Assert.Equal("UIHint", result[0]); Assert.Equal("DataType", result[1]); Assert.Equal("IList`1", result[2]); Assert.Equal("Collection", result[3]); Assert.Equal("Object", result[4]); } [Fact] public void GetViewNamesNullUIHintNotIncludedInList() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Object)); // Act List result = TemplateHelpers.GetViewNames(metadata, null, "DataType").ToList(); // Assert Assert.Equal(2, result.Count); Assert.Equal("DataType", result[0]); Assert.Equal("Object", result[1]); } [Fact] public void GetViewNamesNullDataTypeNotIncludedInList() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Object)); // Act List result = TemplateHelpers.GetViewNames(metadata, "UIHint", null).ToList(); // Assert Assert.Equal(2, result.Count); Assert.Equal("UIHint", result[0]); Assert.Equal("Object", result[1]); } [Fact] public void GetViewNamesConvertsNullableOfTIntoT() { // Arrange ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Nullable)); // Act List result = TemplateHelpers.GetViewNames(metadata, null, null).ToList(); // Assert Assert.Equal(2, result.Count); Assert.Equal("Int32", result[0]); Assert.Equal("String", result[1]); } // Template private class TemplateModel { public object MyProperty { get; set; } } [Fact] public void TemplateNullExpressionThrows() { // Arrange HtmlHelper html = MakeHtmlHelper(null); // Act & Assert Assert.ThrowsArgumentNull( () => TemplateHelpers.Template(html, null, "templateName", "htmlFieldName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy), "expression"); } [Fact] public void TemplateDataNotFound() { // Arrange HtmlHelper html = MakeHtmlHelper(null); // Act string result = TemplateHelpers.Template(html, "UnknownObject", "templateName", null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = (null), HtmlFieldName = UnknownObject, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateHtmlFieldNameReplacesExpression() { // Arrange HtmlHelper html = MakeHtmlHelper(null); // Act string result = TemplateHelpers.Template(html, "UnknownObject", "templateName", "htmlFieldName", DataBoundControlMode.ReadOnly, new { foo = "Bar" }, TemplateHelperSpy); // Assert Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = (null), HtmlFieldName = htmlFieldName, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = { foo: Bar }", result); } [Fact] public void TemplateDataFoundInViewDataDictionaryHasNoPropertyName() { // Arrange HtmlHelper html = MakeHtmlHelper(null); html.ViewContext.ViewData["Key"] = 42; // Act string result = TemplateHelpers.Template(html, "Key", null, null, DataBoundControlMode.Edit, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = 42, ModelType = System.Int32, RealModelType = System.Int32, PropertyName = (null), HtmlFieldName = Key, TemplateName = (null), Mode = Edit, AdditionalViewData = (null)", result); } [Fact] public void TemplateDataFoundInViewDataDictionarySubPropertyHasPropertyName() { // Arrange HtmlHelper html = MakeHtmlHelper(null); html.ViewContext.ViewData["Key"] = new TemplateModel { MyProperty = "Hello!" }; // Act string result = TemplateHelpers.Template(html, "Key.MyProperty", null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = Key.MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateDataFoundInModelHasPropertyName() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateModel { MyProperty = "Hello!" }); // Act string result = TemplateHelpers.Template(html, "MyProperty", null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateNullDataFoundInModelHasPropertyTypeInsteadOfActualModelType() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateModel()); // Act string result = TemplateHelpers.Template(html, "MyProperty", null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = (null), ModelType = System.Object, RealModelType = System.Object, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } // TemplateFor private class TemplateForModel { public int MyField = 0; public object MyProperty { get; set; } public Nullable MyNullableProperty { get; set; } } [Fact] public void TemplateForNonUnsupportedExpressionTypeThrows() { // Arrange HtmlHelper html = MakeHtmlHelper(null); // Act & Assert Assert.Throws( () => TemplateHelpers.TemplateFor(html, model => new Object(), "templateName", "htmlFieldName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy), "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions."); } [Fact] public void TemplateForWithNonNullExpression() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateForModel { MyProperty = "Hello!" }); // Act string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, "templateName", null, DataBoundControlMode.ReadOnly, new { foo = "Bar" }, TemplateHelperSpy); // Assert Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = { foo: Bar }", result); } [Fact] public void TemplateForHtmlFieldNameReplacesExpression() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateForModel { MyProperty = "Hello!" }); // Act string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, "templateName", "htmlFieldName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = Hello!, ModelType = System.Object, RealModelType = System.String, PropertyName = MyProperty, HtmlFieldName = htmlFieldName, TemplateName = templateName, Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateForNullModelStillRetainsTypeInformation() { // Arrange HtmlHelper html = MakeHtmlHelper(null); // Act string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = (null), ModelType = System.Object, RealModelType = System.Object, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateForNullPropertyStillRetainsTypeInformation() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateForModel()); // Act string result = TemplateHelpers.TemplateFor(html, model => model.MyProperty, null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = (null), ModelType = System.Object, RealModelType = System.Object, PropertyName = MyProperty, HtmlFieldName = MyProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateForNullableValueTYpePropertyRetainsNullableValueTYpeForNullPropertyValue() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateForModel()); // Act string result = TemplateHelpers.TemplateFor(html, model => model.MyNullableProperty, null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = (null), ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], RealModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = MyNullableProperty, HtmlFieldName = MyNullableProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateForNullableValueTypePropertyRetainsNullableValueTypeForNonNullPropertyValue() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateForModel { MyNullableProperty = 42 }); // Act string result = TemplateHelpers.TemplateFor(html, model => model.MyNullableProperty, null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = 42, ModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], RealModelType = System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], PropertyName = MyNullableProperty, HtmlFieldName = MyNullableProperty, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateForWithParameterExpression() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateForModel()); // Act string result = TemplateHelpers.TemplateFor(html, model => model, null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = System.Web.Mvc.Html.Test.TemplateHelpersTest+TemplateForModel, ModelType = System.Web.Mvc.Html.Test.TemplateHelpersTest+TemplateForModel, RealModelType = System.Web.Mvc.Html.Test.TemplateHelpersTest+TemplateForModel, PropertyName = (null), HtmlFieldName = (empty), TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } [Fact] public void TemplateForWithFieldExpression() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateForModel { MyField = 42 }); // Act string result = TemplateHelpers.TemplateFor(html, model => model.MyField, null, null, DataBoundControlMode.ReadOnly, null /* additionalViewData */, TemplateHelperSpy); // Assert Assert.Equal("Model = 42, ModelType = System.Int32, RealModelType = System.Int32, PropertyName = (null), HtmlFieldName = MyField, TemplateName = (null), Mode = ReadOnly, AdditionalViewData = (null)", result); } // TemplateHelper private class TemplateHelperModel { public string MyProperty { get; set; } } [Fact] public void TemplateHelperNonNullNonEmptyStringModel() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = Hello, ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = Hello, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result); } [Fact] public void TemplateHelperEmptyStringModel() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.ConvertEmptyStringToNull = false; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = , ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result); } [Fact] public void TemplateHelperConvertsEmptyStringsToNull() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.ConvertEmptyStringToNull = true; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result); } [Fact] public void TemplateHelperConvertsNullModelsToNullDisplayTextInReadOnlyMode() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.NullDisplayText = "NullDisplayText"; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = NullDisplayText, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result); } [Fact] public void TemplateHelperDoesNotConvertNullModelsToNullDisplayTextInEditMode() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.NullDisplayText = "NullDisplayText"; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.Edit, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = Edit", result); } [Fact] public void TemplateHelperAppliesDisplayFormatStringInReadOnlyMode() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.DisplayFormatString = "{0} world!"; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = Hello, ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = Hello world!, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result); } [Fact] public void TemplateHelperDoesNotApplyDisplayFormatStringInReadOnlyModeForNullModel() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.DisplayFormatString = "{0} world!"; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result); } [Fact] public void TemplateHelperAppliesEditFormatStringInEditMode() { HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.EditFormatString = "{0} world!"; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.Edit, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = Hello, ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = Hello world!, HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = Edit", result); } [Fact] public void TemplateHelperDoesNotApplyEditFormatStringInEditModeForNullModel() { // Arrange HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.EditFormatString = "{0} world!"; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.Edit, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = (null), ModelType = System.String, RealModelType = System.String, PropertyName = MyProperty, FormattedModelValue = , HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = Edit", result); } [Fact] public void TemplateHelperAddsNonNullModelToVisitedObjects() { // DDB #224750 HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary viewData = null; // Act TemplateHelpers.TemplateHelper( html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, (_html, _viewData, _templateName, _mode, _getViews, _getDefaultActions) => { viewData = _viewData; return String.Empty; }); // Assert Assert.NotNull(viewData); Assert.True(viewData.TemplateInfo.VisitedObjects.Contains("Hello")); } [Fact] public void TemplateHelperAddsNullModelsTypeToVisitedObjects() { // DDB #224750 HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary viewData = null; // Act TemplateHelpers.TemplateHelper( html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, (_html, _viewData, _templateName, _mode, _getViews, _getDefaultActions) => { viewData = _viewData; return String.Empty; }); // Assert Assert.NotNull(viewData); Assert.True(viewData.TemplateInfo.VisitedObjects.Contains(typeof(string))); } [Fact] public void TemplateHelperReturnsEmptyStringForAlreadyVisitedObject() { // DDB #224750 HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel { MyProperty = "Hello" }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); html.ViewData.TemplateInfo.VisitedObjects.Add("Hello"); // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */); // Assert Assert.Equal(String.Empty, result); } [Fact] public void TemplateHelperReturnsEmptyStringForAlreadyVisitedType() { // DDB #224750 HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); html.ViewData.TemplateInfo.VisitedObjects.Add(typeof(string)); // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */); // Assert Assert.Equal(String.Empty, result); } [Fact] public void TemplateHelperPreservesSameInstanceOfModelMetadata() { // DDB #225858 HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary callbackViewData = null; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, (_html, _viewData, _templateName, _mode, _getViewNames, _getDefaultActions) => { callbackViewData = _viewData; return String.Empty; }); // Assert Assert.NotNull(callbackViewData); Assert.Same(metadata, callbackViewData.ModelMetadata); } [Fact] public void TemplateHelperFormatsValuesUsingCurrentCulture() { CultureInfo existingCulture = Thread.CurrentThread.CurrentCulture; try { // Arrange Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("es-PR"); HtmlHelper html = MakeHtmlHelper(new { MyProperty = new DateTime(2009, 11, 18, 16, 12, 8, DateTimeKind.Utc) }); ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); metadata.DisplayFormatString = "{0:F}"; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, ExecuteTemplateSpy); // Assert Assert.Equal("Model = 18/11/2009 04:12:08 p.m., ModelType = System.DateTime, RealModelType = System.DateTime, PropertyName = MyProperty, FormattedModelValue = miércoles, 18 de noviembre de 2009 04:12:08 p.m., HtmlFieldPrefix = FieldPrefix.htmlFieldName, TemplateName = templateName, Mode = ReadOnly", result); } finally { Thread.CurrentThread.CurrentCulture = existingCulture; } } [Fact] public void TemplateHelperPreservesExistingViewData() { HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); html.ViewData["Foo"] = "Bar"; html.ViewData["Baz"] = 42; ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary callbackViewData = null; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, null /* additionalViewData */, (_html, _viewData, _templateName, _mode, _getViewNames, _getDefaultActions) => { callbackViewData = _viewData; return String.Empty; }); // Assert Assert.NotSame(html.ViewData, callbackViewData); Assert.Equal(2, callbackViewData.Count); Assert.Equal("Bar", callbackViewData["Foo"]); Assert.Equal(42, callbackViewData["Baz"]); } [Fact] public void TemplateHelperMergesAdditionalViewData() { HtmlHelper html = MakeHtmlHelper(new TemplateHelperModel()); html.ViewData["Foo"] = "Bar"; html.ViewData["Baz"] = 42; ModelMetadata metadata = ModelMetadata.FromStringExpression("MyProperty", html.ViewData); ViewDataDictionary callbackViewData = null; // Act string result = TemplateHelpers.TemplateHelper(html, metadata, "htmlFieldName", "templateName", DataBoundControlMode.ReadOnly, new { foo = "New Foo", hello = "World!" }, (_html, _viewData, _templateName, _mode, _getViewNames, _getDefaultActions) => { callbackViewData = _viewData; return String.Empty; }); // Assert Assert.NotSame(html.ViewData, callbackViewData); Assert.Equal(3, callbackViewData.Count); Assert.Equal("New Foo", callbackViewData["Foo"]); Assert.Equal(42, callbackViewData["Baz"]); Assert.Equal("World!", callbackViewData["Hello"]); } // Helpers private static string ExecuteTemplateSpy(HtmlHelper html, ViewDataDictionary viewData, string templateName, DataBoundControlMode mode, TemplateHelpers.GetViewNamesDelegate getViewNames, TemplateHelpers.GetDefaultActionsDelegate getDefaultActions) { Assert.Same(viewData.Model, viewData.ModelMetadata.Model); Assert.Equal(TemplateHelpers.GetViewNames, getViewNames); Assert.Equal(TemplateHelpers.GetDefaultActions, getDefaultActions); return String.Format("Model = {0}, ModelType = {1}, RealModelType = {2}, PropertyName = {3}, FormattedModelValue = {4}, HtmlFieldPrefix = {5}, TemplateName = {6}, Mode = {7}", viewData.ModelMetadata.Model ?? "(null)", viewData.ModelMetadata.ModelType == null ? "(null)" : viewData.ModelMetadata.ModelType.FullName, viewData.ModelMetadata.RealModelType == null ? "(null)" : viewData.ModelMetadata.RealModelType.FullName, viewData.ModelMetadata.PropertyName ?? "(null)", viewData.TemplateInfo.FormattedModelValue ?? "(null)", viewData.TemplateInfo.HtmlFieldPrefix == "" ? "(empty)" : viewData.TemplateInfo.HtmlFieldPrefix ?? "(null)", templateName ?? "(null)", mode); } private static string TemplateHelperSpy(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData) { return String.Format("Model = {0}, ModelType = {1}, RealModelType = {2}, PropertyName = {3}, HtmlFieldName = {4}, TemplateName = {5}, Mode = {6}, AdditionalViewData = {7}", metadata.Model ?? "(null)", metadata.ModelType == null ? "(null)" : metadata.ModelType.FullName, metadata.RealModelType == null ? "(null)" : metadata.RealModelType.FullName, metadata.PropertyName ?? "(null)", htmlFieldName == String.Empty ? "(empty)" : htmlFieldName ?? "(null)", templateName ?? "(null)", mode, AnonymousObject.Inspect(additionalViewData)); } private HtmlHelper MakeHtmlHelper(TModel model) { return MakeHtmlHelper(model, model); } private HtmlHelper MakeHtmlHelper(TModel model, object formattedModelValue) { ViewDataDictionary viewData = new ViewDataDictionary(model); viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix"; viewData.TemplateInfo.FormattedModelValue = formattedModelValue; viewData.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(() => model, typeof(TModel)); Mock httpContext = new Mock(); httpContext.Setup(c => c.Items).Returns(new Hashtable()); Mock viewContext = new Mock { CallBase = true }; viewContext.Setup(c => c.View).Returns(new DummyView()); viewContext.Setup(c => c.ViewData).Returns(viewData); viewContext.Setup(c => c.HttpContext).Returns(httpContext.Object); viewContext.Setup(c => c.RouteData).Returns(new RouteData()); viewContext.Setup(c => c.TempData).Returns(new TempDataDictionary()); viewContext.Setup(c => c.Writer).Returns(TextWriter.Null); return new HtmlHelper(viewContext.Object, new SimpleViewDataContainer(viewData)); } private ViewDataDictionary MakeViewData(HtmlHelper html, ModelMetadata metadata) { return new ViewDataDictionary(html.ViewDataContainer.ViewData) { Model = metadata.Model, ModelMetadata = metadata, TemplateInfo = new TemplateInfo { FormattedModelValue = metadata.Model, HtmlFieldPrefix = "FieldPrefix", } }; } private class DummyView : IView { public void Render(ViewContext viewContext, TextWriter writer) { throw new NotImplementedException(); } } private class MockViewEngine : IDisposable { List oldEngines; public MockViewEngine(bool returnView = true) { oldEngines = ViewEngines.Engines.ToList(); View = new Mock(); Engine = new Mock(); Engine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(returnView ? new ViewEngineResult(View.Object, Engine.Object) : new ViewEngineResult(new string[0])); ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(Engine.Object); } public void Dispose() { ViewEngines.Engines.Clear(); foreach (IViewEngine engine in oldEngines) { ViewEngines.Engines.Add(engine); } } public Mock Engine { get; set; } public Mock View { get; set; } } } }