// 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.IO; using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Testing; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public class DefaultEditorTemplatesTest { // Mappings from templateName to expected result when using StubbyHtmlHelper. public static TheoryData TemplateNameData { get { return new TheoryData { { null, "__TextBox__ class='text-box single-line'" }, { string.Empty, "__TextBox__ class='text-box single-line'" }, { "EmailAddress", "__TextBox__ class='text-box single-line' type='email'" }, { "emailaddress", "__TextBox__ class='text-box single-line' type='email'" }, { "HiddenInput", "HtmlEncode[[True]]__Hidden__" }, // Hidden also generates value by default. { "HIDDENINPUT", "HtmlEncode[[True]]__Hidden__" }, { "MultilineText", "__TextArea__ class='text-box multi-line'" }, { "multilinetext", "__TextArea__ class='text-box multi-line'" }, { "Password", "__Password__ class='text-box single-line password'" }, { "PASSWORD", "__Password__ class='text-box single-line password'" }, { "PhoneNumber", "__TextBox__ class='text-box single-line' type='tel'" }, { "phonenumber", "__TextBox__ class='text-box single-line' type='tel'" }, { "Text", "__TextBox__ class='text-box single-line'" }, { "TEXT", "__TextBox__ class='text-box single-line'" }, { "Url", "__TextBox__ class='text-box single-line' type='url'" }, { "url", "__TextBox__ class='text-box single-line' type='url'" }, { "Date", "__TextBox__ class='text-box single-line' type='date'" }, { "DATE", "__TextBox__ class='text-box single-line' type='date'" }, { "DateTime", "__TextBox__ class='text-box single-line' type='datetime-local'" }, { "datetime", "__TextBox__ class='text-box single-line' type='datetime-local'" }, { "DateTime-local", "__TextBox__ class='text-box single-line' type='datetime-local'" }, { "DATETIME-LOCAL", "__TextBox__ class='text-box single-line' type='datetime-local'" }, { "datetimeoffset", "__TextBox__ class='text-box single-line' type='text'" }, { "DateTimeOffset", "__TextBox__ class='text-box single-line' type='text'" }, { "Time", "__TextBox__ class='text-box single-line' type='time'" }, { "time", "__TextBox__ class='text-box single-line' type='time'" }, { "Month", "__TextBox__ class='text-box single-line' type='month'" }, { "month", "__TextBox__ class='text-box single-line' type='month'" }, { "Week", "__TextBox__ class='text-box single-line' type='week'" }, { "week", "__TextBox__ class='text-box single-line' type='week'" }, { "Byte", "__TextBox__ class='text-box single-line' type='number'" }, { "BYTE", "__TextBox__ class='text-box single-line' type='number'" }, { "SByte", "__TextBox__ class='text-box single-line' type='number'" }, { "sbyte", "__TextBox__ class='text-box single-line' type='number'" }, { "Int16", "__TextBox__ class='text-box single-line' type='number'" }, { "INT16", "__TextBox__ class='text-box single-line' type='number'" }, { "UInt16", "__TextBox__ class='text-box single-line' type='number'" }, { "uint16", "__TextBox__ class='text-box single-line' type='number'" }, { "Int32", "__TextBox__ class='text-box single-line' type='number'" }, { "INT32", "__TextBox__ class='text-box single-line' type='number'" }, { "UInt32", "__TextBox__ class='text-box single-line' type='number'" }, { "uint32", "__TextBox__ class='text-box single-line' type='number'" }, { "Int64", "__TextBox__ class='text-box single-line' type='number'" }, { "INT64", "__TextBox__ class='text-box single-line' type='number'" }, { "UInt64", "__TextBox__ class='text-box single-line' type='number'" }, { "uint64", "__TextBox__ class='text-box single-line' type='number'" }, { "Single", "__TextBox__ class='text-box single-line'" }, { "SINGLE", "__TextBox__ class='text-box single-line'" }, { "Double", "__TextBox__ class='text-box single-line'" }, { "double", "__TextBox__ class='text-box single-line'" }, { "Boolean", "__CheckBox__ class='check-box'" }, // Not tri-state b/c string is not a Nullable type. { "BOOLEAN", "__CheckBox__ class='check-box'" }, { "Decimal", "__TextBox__ class='text-box single-line'" }, { "decimal", "__TextBox__ class='text-box single-line'" }, { "String", "__TextBox__ class='text-box single-line'" }, { "STRING", "__TextBox__ class='text-box single-line'" }, { typeof(IFormFile).Name, "__TextBox__ class='text-box single-line' type='file'" }, { TemplateRenderer.IEnumerableOfIFormFileName, "__TextBox__ class='text-box single-line' type='file' multiple='multiple'" }, }; } } // label's IHtmlContent -> expected label text public static TheoryData ObjectTemplate_ChecksWriteTo_NotToStringData { get { // Similar to HtmlString.Empty today. var noopContentWithEmptyToString = new Mock(MockBehavior.Strict); noopContentWithEmptyToString .Setup(c => c.ToString()) .Returns(string.Empty); noopContentWithEmptyToString.Setup(c => c.WriteTo(It.IsAny(), It.IsAny())); // Similar to an empty StringHtmlContent today. var noopContentWithNonEmptyToString = new Mock(MockBehavior.Strict); noopContentWithNonEmptyToString .Setup(c => c.ToString()) .Returns(typeof(StringHtmlContent).FullName); noopContentWithNonEmptyToString.Setup(c => c.WriteTo(It.IsAny(), It.IsAny())); // Makes noop calls on the TextWriter. var busyNoopContentWithNonEmptyToString = new Mock(MockBehavior.Strict); busyNoopContentWithNonEmptyToString .Setup(c => c.ToString()) .Returns(typeof(StringHtmlContent).FullName); busyNoopContentWithNonEmptyToString .Setup(c => c.WriteTo(It.IsAny(), It.IsAny())) .Callback((writer, encoder) => { writer.Write(string.Empty); writer.Write(new char[0]); writer.Write((char[])null); writer.Write((object)null); writer.Write((string)null); writer.Write(format: "{0}", arg0: null); writer.Write(new char[] { 'a', 'b', 'c' }, index: 1, count: 0); }); // Unrealistic but covers all the bases. var writingContentWithEmptyToString = new Mock(MockBehavior.Strict); writingContentWithEmptyToString .Setup(c => c.ToString()) .Returns(string.Empty); writingContentWithEmptyToString .Setup(c => c.WriteTo(It.IsAny(), It.IsAny())) .Callback((writer, encoder) => writer.Write("Some string")); // Similar to TagBuilder today. var writingContentWithNonEmptyToString = new Mock(MockBehavior.Strict); writingContentWithNonEmptyToString .Setup(c => c.ToString()) .Returns(typeof(TagBuilder).FullName); writingContentWithNonEmptyToString .Setup(c => c.WriteTo(It.IsAny(), It.IsAny())) .Callback((writer, encoder) => writer.Write("Some string")); // label's IHtmlContent -> expected label text return new TheoryData { // Types HtmlHelper actually uses. { HtmlString.Empty, string.Empty }, { new TagBuilder("label"), "
" + Environment.NewLine }, // Another IHtmlContent implementation that does not override ToString(). { new StringHtmlContent(string.Empty), string.Empty }, // Mocks { noopContentWithEmptyToString.Object, string.Empty }, { noopContentWithNonEmptyToString.Object, string.Empty }, { busyNoopContentWithNonEmptyToString.Object, string.Empty }, { writingContentWithEmptyToString.Object, "
Some string
" + Environment.NewLine }, { writingContentWithNonEmptyToString.Object, "
Some string
" + Environment.NewLine }, }; } } [Fact] public void ObjectTemplateEditsSimplePropertiesOnObjectByDefault() { // Arrange var expected = "
" + Environment.NewLine + "
Model = p1, ModelType = System.String, PropertyName = Property1, SimpleDisplayText = p1 " + "" + "
" + Environment.NewLine + "
" + Environment.NewLine + "
Model = (null), ModelType = System.String, PropertyName = Property2, SimpleDisplayText = (null) " + "" + "
" + Environment.NewLine; var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "p1", Property2 = null }; var html = DefaultTemplatesUtilities.GetHtmlHelper(model); // Act var result = DefaultEditorTemplates.ObjectTemplate(html); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } // Prior to aspnet/Mvc#6638 fix, helper did not generate Property1