412 lines
18 KiB
C#
412 lines
18 KiB
C#
// 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.ViewEngines;
|
||
using Moq;
|
||
using Xunit;
|
||
|
||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||
{
|
||
public class DefaultDisplayTemplatesTest
|
||
{
|
||
// Input value; HTML encode; expected value.
|
||
public static TheoryData<string, bool, string> HtmlEncodeData
|
||
{
|
||
get
|
||
{
|
||
return new TheoryData<string, bool, string>
|
||
{
|
||
{ "Simple Display Text", false, "Simple Display Text" },
|
||
{ "Simple Display Text", true, "HtmlEncode[[Simple Display Text]]" },
|
||
{ "<blink>text</blink>", false, "<blink>text</blink>" },
|
||
{ "<blink>text</blink>", true, "HtmlEncode[[<blink>text</blink>]]" },
|
||
{ "&'\"", false, "&'\"" },
|
||
{ "&'\"", true, "HtmlEncode[[&'\"]]" },
|
||
{ " ¡ÿĀ", false, " ¡ÿĀ" }, // high ASCII
|
||
{ " ¡ÿĀ", true, "HtmlEncode[[ ¡ÿĀ]]" },
|
||
{ "Chinese西雅图Chars", false, "Chinese西雅图Chars" },
|
||
{ "Chinese西雅图Chars", true, "HtmlEncode[[Chinese西雅图Chars]]" },
|
||
{ "UnicodeFormatChar", false, "UnicodeFormatChar" }, // class Cf
|
||
{ "UnicodeFormatChar", true, "HtmlEncode[[UnicodeFormatChar]]" },
|
||
{ "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 =
|
||
"<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[Property1]]</div>" + Environment.NewLine
|
||
+ "<div class=\"HtmlEncode[[display-field]]\">Model = p1, ModelType = System.String, PropertyName = Property1," +
|
||
" SimpleDisplayText = p1</div>" + Environment.NewLine
|
||
+ "<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[Prop2]]</div>" + Environment.NewLine
|
||
+ "<div class=\"HtmlEncode[[display-field]]\">Model = (null), ModelType = System.String, PropertyName = Property2," +
|
||
" SimpleDisplayText = (null)</div>" + 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<DefaultTemplatesUtilities.ObjectTemplateModel>().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<DefaultTemplatesUtilities.ObjectTemplateModel>().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 = "<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[Property1]]</div>" + Environment.NewLine +
|
||
"<div class=\"HtmlEncode[[display-field]]\"></div>" + Environment.NewLine +
|
||
"<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[Property3]]</div>" + Environment.NewLine +
|
||
"<div class=\"HtmlEncode[[display-field]]\"></div>" + Environment.NewLine;
|
||
|
||
var model = new DefaultTemplatesUtilities.ObjectWithScaffoldColumn();
|
||
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
|
||
viewEngine
|
||
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
viewEngine
|
||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
|
||
|
||
// Act
|
||
var result = DefaultDisplayTemplates.ObjectTemplate(htmlHelper);
|
||
|
||
// Assert
|
||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||
}
|
||
|
||
[Fact]
|
||
public void ObjectTemplate_HonorsHideSurroundingHtml()
|
||
{
|
||
// Arrange
|
||
var expected =
|
||
"Model = p1, ModelType = System.String, PropertyName = Property1, SimpleDisplayText = p1" +
|
||
"<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[Prop2]]</div>" + Environment.NewLine +
|
||
"<div class=\"HtmlEncode[[display-field]]\">Model = (null), ModelType = System.String, PropertyName = Property2," +
|
||
" SimpleDisplayText = (null)</div>" + Environment.NewLine;
|
||
|
||
var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null };
|
||
|
||
var provider = new TestModelMetadataProvider();
|
||
provider.ForProperty<DefaultTemplatesUtilities.ObjectTemplateModel>("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<string>
|
||
{
|
||
"OrderedProperty3",
|
||
"OrderedProperty2",
|
||
"OrderedProperty1",
|
||
"Property3",
|
||
"Property1",
|
||
"Property2",
|
||
"LastProperty",
|
||
};
|
||
|
||
var stringBuilder = new StringBuilder();
|
||
foreach (var property in expectedProperties)
|
||
{
|
||
var label = string.Format(
|
||
CultureInfo.InvariantCulture,
|
||
"<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[{0}]]</div>",
|
||
property);
|
||
stringBuilder.AppendLine(label);
|
||
|
||
var value = string.Format(
|
||
CultureInfo.InvariantCulture,
|
||
"<div class=\"HtmlEncode[[display-field]]\">Model = (null), ModelType = System.String, PropertyName = {0}, " +
|
||
"SimpleDisplayText = (null)</div>",
|
||
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_HonorsHideSurroundingHtml()
|
||
{
|
||
// Arrange
|
||
var model = "Model string";
|
||
|
||
var provider = new TestModelMetadataProvider();
|
||
provider.ForType<string>().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<ICompositeViewEngine>(MockBehavior.Strict);
|
||
viewEngine
|
||
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
viewEngine
|
||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
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<ICompositeViewEngine>(MockBehavior.Strict);
|
||
viewEngine
|
||
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
viewEngine
|
||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
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<ICompositeViewEngine>(MockBehavior.Strict);
|
||
viewEngine
|
||
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
viewEngine
|
||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
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<ICompositeViewEngine>(MockBehavior.Strict);
|
||
viewEngine
|
||
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
viewEngine
|
||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
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<IView>();
|
||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||
.Returns(Task.Run(() =>
|
||
{
|
||
throw new ArgumentException(expectedMessage);
|
||
}));
|
||
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
|
||
viewEngine
|
||
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
viewEngine
|
||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*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<ArgumentException>(() => helper.DisplayFor(m => m.Property1));
|
||
Assert.Equal(expectedMessage, ex.Message);
|
||
}
|
||
|
||
[Fact]
|
||
public void Display_CallsFindView_WithExpectedPath()
|
||
{
|
||
// Arrange
|
||
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
|
||
viewEngine
|
||
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
|
||
viewEngine
|
||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "DisplayTemplates/String", /*isMainPage*/ false))
|
||
.Returns(ViewEngineResult.Found(string.Empty, new Mock<IView>().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; }
|
||
}
|
||
}
|
||
}
|