// Copyright (c) .NET Foundation. 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.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public class DefaultDisplayTemplatesTest { // Input value; HTML encode; expected value. public static TheoryData HtmlEncodeData { get { return new TheoryData { { "Simple Display Text", false, "Simple Display Text" }, { "Simple Display Text", true, "HtmlEncode[[Simple Display Text]]" }, { "text", false, "text" }, { "text", true, "HtmlEncode[[text]]" }, { "&'\"", false, "&'\"" }, { "&'\"", true, "HtmlEncode[[&'\"]]" }, { " ¡ÿĀ", false, " ¡ÿĀ" }, // high ASCII { " ¡ÿĀ", true, "HtmlEncode[[ ¡ÿĀ]]" }, { "Chinese西雅图Chars", false, "Chinese西雅图Chars" }, { "Chinese西雅图Chars", true, "HtmlEncode[[Chinese西雅图Chars]]" }, { "Unicode؃Format؃Char", false, "Unicode؃Format؃Char" }, // class Cf { "Unicode؃Format؃Char", true, "HtmlEncode[[Unicode؃Format؃Char]]" }, { "UnicodeῼTitlecaseῼChar", false, "UnicodeῼTitlecaseῼChar" }, // class Lt { "UnicodeῼTitlecaseῼChar", true, "HtmlEncode[[UnicodeῼTitlecaseῼChar]]" }, { "UnicodeःCombiningःChar", false, "UnicodeःCombiningःChar" }, // class Mc { "UnicodeःCombiningःChar", true, "HtmlEncode[[UnicodeःCombiningःChar]]" }, }; } } [Fact] public void ObjectTemplateDisplaysSimplePropertiesOnObjectByDefault() { var expected = "
HtmlEncode[[Property1]]
" + Environment.NewLine + "
Model = p1, ModelType = System.String, PropertyName = Property1," + " SimpleDisplayText = p1
" + Environment.NewLine + "
HtmlEncode[[Prop2]]
" + Environment.NewLine + "
Model = (null), ModelType = System.String, PropertyName = Property2," + " SimpleDisplayText = (null)
" + Environment.NewLine; // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; var html = DefaultTemplatesUtilities.GetHtmlHelper(model); // Act var result = DefaultDisplayTemplates.ObjectTemplate(html); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ObjectTemplateDisplaysNullDisplayTextWhenObjectIsNull() { // Arrange var provider = new TestModelMetadataProvider(); provider.ForType().DisplayDetails(dd => { dd.NullDisplayText = "(null value)"; }); var html = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); // Act var result = DefaultDisplayTemplates.ObjectTemplate(html); // Assert Assert.Equal("(null value)", HtmlContentUtilities.HtmlContentToString(result)); } [Theory] [MemberData(nameof(HtmlEncodeData))] public void ObjectTemplateDisplaysSimpleDisplayTextWhenTemplateDepthGreaterThanOne( string simpleDisplayText, bool htmlEncode, string expectedResult) { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel(); model.Property1 = simpleDisplayText; var provider = new TestModelMetadataProvider(); provider.ForType().DisplayDetails(dd => { dd.HtmlEncode = htmlEncode; dd.SimpleDisplayProperty = "Property1"; }); var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider); html.ViewData.TemplateInfo.AddVisited("foo"); html.ViewData.TemplateInfo.AddVisited("bar"); // Act var result = DefaultDisplayTemplates.ObjectTemplate(html); // Assert Assert.Equal(expectedResult, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ObjectTemplate_IgnoresPropertiesWith_ScaffoldColumnFalse() { // Arrange var expected = "
HtmlEncode[[Property1]]
" + Environment.NewLine + "
"+ Environment.NewLine + "
HtmlEncode[[Property3]]
"+ Environment.NewLine + "
"+ Environment.NewLine; var model = new DefaultTemplatesUtilities.ObjectWithScaffoldColumn(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); viewEngine .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); // Act var result = DefaultDisplayTemplates.ObjectTemplate(htmlHelper); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ObjectTemplate_HonoursHideSurroundingHtml() { // Arrange var expected = "Model = p1, ModelType = System.String, PropertyName = Property1, SimpleDisplayText = p1" + "
HtmlEncode[[Prop2]]
" + Environment.NewLine + "
Model = (null), ModelType = System.String, PropertyName = Property2," + " SimpleDisplayText = (null)
" + Environment.NewLine; var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; var provider = new TestModelMetadataProvider(); provider.ForProperty("Property1").DisplayDetails(dd => { dd.HideSurroundingHtml = true; }); var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider); // Act var result = DefaultDisplayTemplates.ObjectTemplate(html); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ObjectTemplate_OrdersProperties_AsExpected() { // Arrange var model = new OrderedModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); var expectedProperties = new List { "OrderedProperty3", "OrderedProperty2", "OrderedProperty1", "Property3", "Property1", "Property2", "LastProperty", }; var stringBuilder = new StringBuilder(); foreach (var property in expectedProperties) { var label = string.Format( CultureInfo.InvariantCulture, "
HtmlEncode[[{0}]]
", property); stringBuilder.AppendLine(label); var value = string.Format( CultureInfo.InvariantCulture, "
Model = (null), ModelType = System.String, PropertyName = {0}, " + "SimpleDisplayText = (null)
", property); stringBuilder.AppendLine(value); } var expected = stringBuilder.ToString(); // Act var result = DefaultDisplayTemplates.ObjectTemplate(html); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void HiddenInputTemplate_ReturnsValue() { // Arrange var model = "Model string"; var html = DefaultTemplatesUtilities.GetHtmlHelper(model); var templateInfo = html.ViewData.TemplateInfo; templateInfo.HtmlFieldPrefix = "FieldPrefix"; // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used below. templateInfo.FormattedModelValue = "Formatted string"; // Act var result = DefaultDisplayTemplates.HiddenInputTemplate(html); // Assert Assert.Equal("HtmlEncode[[Formatted string]]", HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void HiddenInputTemplate_HonoursHideSurroundingHtml() { // Arrange var model = "Model string"; var provider = new TestModelMetadataProvider(); provider.ForType().DisplayDetails(dd => { dd.HideSurroundingHtml = true; }); var html = DefaultTemplatesUtilities.GetHtmlHelper(model, provider: provider); var viewData = html.ViewData; var templateInfo = viewData.TemplateInfo; templateInfo.HtmlFieldPrefix = "FieldPrefix"; templateInfo.FormattedModelValue = "Formatted string"; // Act var result = DefaultDisplayTemplates.HiddenInputTemplate(html); // Assert Assert.Empty(HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void Display_FindsViewDataMember() { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); viewEngine .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; // Act var result = helper.Display("Property1"); // Assert Assert.Equal("HtmlEncode[[ViewData string]]", HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void DisplayFor_FindsModel() { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); viewEngine .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; // Act var result = helper.DisplayFor(m => m.Property1); // Assert Assert.Equal("HtmlEncode[[Model string]]", HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void Display_FindsModel_IfNoViewDataMember() { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); viewEngine .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); // Act var result = helper.Display("Property1"); // Assert Assert.Equal("HtmlEncode[[Model string]]", HtmlContentUtilities.HtmlContentToString(result)); } [Theory] [InlineData(null)] [InlineData("")] public void DisplayFor_FindsModel_EvenIfNullOrEmpty(string propertyValue) { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = propertyValue, }; var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); viewEngine .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; // Act var result = helper.DisplayFor(m => m.Property1); // Assert Assert.Equal(string.Empty, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void DisplayFor_DoesNotWrapExceptionThrowsDuringViewRendering() { // Arrange var expectedMessage = "my exception message"; var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Test string", }; var view = new Mock(); view.Setup(v => v.RenderAsync(It.IsAny())) .Returns(Task.Run(() => { throw new ArgumentException(expectedMessage); })); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); viewEngine .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.Found("test-view", view.Object)); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; // Act and Assert var ex = Assert.Throws(() => helper.DisplayFor(m => m.Property1)); Assert.Equal(expectedMessage, ex.Message); } [Fact] public void Display_CallsFindView_WithExpectedPath() { // Arrange var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isMainPage*/ false)) .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); viewEngine .Setup(v => v.FindView(It.IsAny(), "DisplayTemplates/String", /*isMainPage*/ false)) .Returns(ViewEngineResult.Found(string.Empty, new Mock().Object)) .Verifiable(); var html = DefaultTemplatesUtilities.GetHtmlHelper(new object(), viewEngine: viewEngine.Object); // Act & Assert html.Display(expression: string.Empty, templateName: null, htmlFieldName: null, additionalViewData: null); viewEngine.Verify(); } private class OrderedModel { [Display(Order = 10001)] public string LastProperty { get; set; } public string Property3 { get; set; } public string Property1 { get; set; } public string Property2 { get; set; } [Display(Order = 23)] public string OrderedProperty3 { get; set; } [Display(Order = 23)] public string OrderedProperty2 { get; set; } [Display(Order = 23)] public string OrderedProperty1 { get; set; } } } }