Browse Source

Fix DateOnly value for Input Tag Helper (#47957)

Doug Waldron 2 years ago
parent
commit
7f4ee4ac2f

+ 2 - 1
src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs

@@ -464,7 +464,8 @@ public class InputTagHelper : TagHelper
         else if (ViewContext.Html5DateRenderingMode == Html5DateRenderingMode.Rfc3339 &&
             !modelExplorer.Metadata.HasNonDefaultEditFormat &&
             (typeof(DateTime) == modelExplorer.Metadata.UnderlyingOrModelType ||
-             typeof(DateTimeOffset) == modelExplorer.Metadata.UnderlyingOrModelType))
+             typeof(DateTimeOffset) == modelExplorer.Metadata.UnderlyingOrModelType ||
+             typeof(DateOnly) == modelExplorer.Metadata.UnderlyingOrModelType))
         {
             // Rfc3339 mode _may_ override EditFormatString in a limited number of cases. Happens only when
             // EditFormatString has a default format i.e. came from a [DataType] attribute.

+ 203 - 0
src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs

@@ -1819,11 +1819,84 @@ public class InputTagHelperTest
         Assert.Equal(expectedTagName, output.TagName);
     }
 
+    [Fact]
+    public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateOnly_RendersAsDate()
+    {
+        // Arrange
+        var expectedAttributes = new TagHelperAttributeList
+            {
+                { "type", "date" },                   // Calculated; not passed to HtmlGenerator.
+            };
+        var expectedTagName = "not-input";
+
+        var context = new TagHelperContext(
+            tagName: "input",
+            allAttributes: new TagHelperAttributeList()
+            {
+                    {"type", "date" }
+            },
+            items: new Dictionary<object, object>(),
+            uniqueId: "test");
+
+        var output = new TagHelperOutput(
+            expectedTagName,
+            attributes: new TagHelperAttributeList(),
+            getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
+                new DefaultTagHelperContent()))
+        {
+            TagMode = TagMode.SelfClosing,
+        };
+
+        var htmlAttributes = new Dictionary<string, object>
+            {
+                { "type", "date" }
+            };
+
+        var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
+
+        var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
+        var tagHelper = GetTagHelper(
+            htmlGenerator.Object,
+            model: null,
+            propertyName: "DateOnly",
+            metadataProvider: metadataProvider);
+        tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
+        tagHelper.InputTypeName = "date";
+        var tagBuilder = new TagBuilder("input");
+        htmlGenerator
+            .Setup(mock => mock.GenerateTextBox(
+                tagHelper.ViewContext,
+                tagHelper.For.ModelExplorer,
+                tagHelper.For.Name,
+                null,                                   // value
+                @"{0:yyyy-MM-dd}",
+                htmlAttributes))                    // htmlAttributes
+            .Returns(tagBuilder)
+            .Verifiable();
+
+        // Act
+        await tagHelper.ProcessAsync(context, output);
+
+        // Assert
+        htmlGenerator.Verify();
+
+        Assert.Equal(TagMode.SelfClosing, output.TagMode);
+        Assert.Equal(expectedAttributes, output.Attributes);
+        Assert.Empty(output.PreContent.GetContent());
+        Assert.Equal(string.Empty, output.Content.GetContent());
+        Assert.Empty(output.PostContent.GetContent());
+        Assert.Equal(expectedTagName, output.TagName);
+    }
+
     [Theory]
     [InlineData("Date", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")]    // Format from [DataType].
     [InlineData("Date", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
     [InlineData("DateTime", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
     [InlineData("DateTime", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}", "datetime-local")]
+    [InlineData("DateOnlyDate", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")]    // Format from [DataType].
+    [InlineData("DateOnlyDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
+    [InlineData("DateOnly", Html5DateRenderingMode.CurrentCulture, null, "text")]    // Format from [DataType].
+    [InlineData("DateOnly", Html5DateRenderingMode.Rfc3339, null, "text")]
     [InlineData("DateTimeOffset", Html5DateRenderingMode.CurrentCulture, null, "text")]
     [InlineData("DateTimeOffset", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}", "text")]
     [InlineData("DateTimeLocal", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
@@ -1836,6 +1909,8 @@ public class InputTagHelperTest
     [InlineData("Week", Html5DateRenderingMode.Rfc3339, null, "week")]
     [InlineData("NullableDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
     [InlineData("NullableDateTime", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}", "datetime-local")]
+    [InlineData("NullableDateOnlyDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
+    [InlineData("NullableDateOnly", Html5DateRenderingMode.Rfc3339, null, "text")]
     [InlineData("NullableDateTimeOffset", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}", "text")]
     public async Task ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339(
         string propertyName,
@@ -1973,6 +2048,63 @@ public class InputTagHelperTest
         Assert.Equal(expectedType, output.TagName);
     }
 
+    // Html5DateRenderingMode.Rfc3339 is enabled by default.
+    [Theory]
+    [InlineData("DateOnlyDate", "2000-01-02", "date")]
+    [InlineData("DateOnly", "02/01/2000", "text")]
+    [ReplaceCulture]
+    public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateOnly(
+        string propertyName,
+        string expectedValue,
+        string expectedType)
+    {
+        // Arrange
+        var expectedAttributes = new TagHelperAttributeList
+            {
+                { "type", expectedType },
+                { "id", propertyName },
+                { "name", propertyName },
+                { "value", expectedValue },
+            };
+
+        var context = new TagHelperContext(
+            tagName: "input",
+            allAttributes: new TagHelperAttributeList(
+                Enumerable.Empty<TagHelperAttribute>()),
+            items: new Dictionary<object, object>(),
+            uniqueId: "test");
+
+        var output = new TagHelperOutput(
+            expectedType,
+            attributes: new TagHelperAttributeList(),
+            getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
+                new DefaultTagHelperContent()))
+        {
+            TagMode = TagMode.SelfClosing,
+        };
+
+        var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
+        var model = new DateOnly(
+            year: 2000,
+            month: 1,
+            day: 2);
+
+        var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
+        var tagHelper = GetTagHelper(
+            htmlGenerator,
+            model: model,
+            propertyName: propertyName,
+            metadataProvider: metadataProvider);
+        tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
+
+        // Act
+        await tagHelper.ProcessAsync(context, output);
+
+        // Assert
+        Assert.Equal(expectedAttributes, output.Attributes);
+        Assert.Equal(expectedType, output.TagName);
+    }
+
     // Html5DateRenderingMode.Rfc3339 can be disabled.
     [Theory]
     [InlineData("Date", null, "02/01/2000", "date")]
@@ -2048,6 +2180,67 @@ public class InputTagHelperTest
         Assert.Equal(expectedType, output.TagName);
     }
 
+    // Html5DateRenderingMode.Rfc3339 can be disabled.
+    [Theory]
+    [InlineData("DateOnlyDate", null, "02/01/2000", "date")]
+    [InlineData("DateOnlyDate", "{0:d}", "02/01/2000", "date")]
+    [InlineData("DateOnly", null, "02/01/2000", "text")]
+    [ReplaceCulture]
+    public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateOnlyNotRfc3339(
+        string propertyName,
+        string editFormatString,
+        string expectedValue,
+        string expectedType)
+    {
+        // Arrange
+        var expectedAttributes = new TagHelperAttributeList
+            {
+                { "type", expectedType },
+                { "id", propertyName },
+                { "name", propertyName },
+                { "value", expectedValue },
+            };
+
+        var context = new TagHelperContext(
+            tagName: "input",
+            allAttributes: new TagHelperAttributeList(
+                Enumerable.Empty<TagHelperAttribute>()),
+            items: new Dictionary<object, object>(),
+            uniqueId: "test");
+
+        var output = new TagHelperOutput(
+            expectedType,
+            attributes: new TagHelperAttributeList(),
+            getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
+                new DefaultTagHelperContent()))
+        {
+            TagMode = TagMode.SelfClosing,
+        };
+
+        var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
+
+        var model = new DateOnly(
+            year: 2000,
+            month: 1,
+            day: 2);
+
+        var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
+        var tagHelper = GetTagHelper(
+            htmlGenerator,
+            model: model,
+            propertyName: propertyName,
+            metadataProvider: metadataProvider);
+        tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.CurrentCulture;
+        tagHelper.Format = editFormatString;
+
+        // Act
+        await tagHelper.ProcessAsync(context, output);
+
+        // Assert
+        Assert.Equal(expectedAttributes, output.Attributes);
+        Assert.Equal(expectedType, output.TagName);
+    }
+
     // Html5DateRenderingMode.Rfc3339 can be disabled.
     [Theory]
     [InlineData("Month", "month")]
@@ -2184,6 +2377,11 @@ public class InputTagHelperTest
 
         public DateTime DateTime { get; set; }
 
+        [DataType(DataType.Date)]
+        public DateOnly DateOnlyDate { get; set; }
+
+        public DateOnly DateOnly { get; set; }
+
         public DateTimeOffset DateTimeOffset { get; set; }
 
         [DataType(DataType.Date)]
@@ -2191,6 +2389,11 @@ public class InputTagHelperTest
 
         public DateTime? NullableDateTime { get; set; }
 
+        [DataType(DataType.Date)]
+        public DateOnly? NullableDateOnlyDate { get; set; }
+
+        public DateOnly? NullableDateOnly { get; set; }
+
         public DateTimeOffset? NullableDateTimeOffset { get; set; }
 
         [DataType("datetime-local")]