Skip to content

Commit 7f4ee4a

Browse files
authored
Fix DateOnly value for Input Tag Helper (#47957)
1 parent 103560b commit 7f4ee4a

File tree

2 files changed

+205
-1
lines changed

2 files changed

+205
-1
lines changed

src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,8 @@ private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, stri
464464
else if (ViewContext.Html5DateRenderingMode == Html5DateRenderingMode.Rfc3339 &&
465465
!modelExplorer.Metadata.HasNonDefaultEditFormat &&
466466
(typeof(DateTime) == modelExplorer.Metadata.UnderlyingOrModelType ||
467-
typeof(DateTimeOffset) == modelExplorer.Metadata.UnderlyingOrModelType))
467+
typeof(DateTimeOffset) == modelExplorer.Metadata.UnderlyingOrModelType ||
468+
typeof(DateOnly) == modelExplorer.Metadata.UnderlyingOrModelType))
468469
{
469470
// Rfc3339 mode _may_ override EditFormatString in a limited number of cases. Happens only when
470471
// EditFormatString has a default format i.e. came from a [DataType] attribute.

src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,11 +1819,84 @@ public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateTime_RendersAsD
18191819
Assert.Equal(expectedTagName, output.TagName);
18201820
}
18211821

1822+
[Fact]
1823+
public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateOnly_RendersAsDate()
1824+
{
1825+
// Arrange
1826+
var expectedAttributes = new TagHelperAttributeList
1827+
{
1828+
{ "type", "date" }, // Calculated; not passed to HtmlGenerator.
1829+
};
1830+
var expectedTagName = "not-input";
1831+
1832+
var context = new TagHelperContext(
1833+
tagName: "input",
1834+
allAttributes: new TagHelperAttributeList()
1835+
{
1836+
{"type", "date" }
1837+
},
1838+
items: new Dictionary<object, object>(),
1839+
uniqueId: "test");
1840+
1841+
var output = new TagHelperOutput(
1842+
expectedTagName,
1843+
attributes: new TagHelperAttributeList(),
1844+
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
1845+
new DefaultTagHelperContent()))
1846+
{
1847+
TagMode = TagMode.SelfClosing,
1848+
};
1849+
1850+
var htmlAttributes = new Dictionary<string, object>
1851+
{
1852+
{ "type", "date" }
1853+
};
1854+
1855+
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
1856+
1857+
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
1858+
var tagHelper = GetTagHelper(
1859+
htmlGenerator.Object,
1860+
model: null,
1861+
propertyName: "DateOnly",
1862+
metadataProvider: metadataProvider);
1863+
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
1864+
tagHelper.InputTypeName = "date";
1865+
var tagBuilder = new TagBuilder("input");
1866+
htmlGenerator
1867+
.Setup(mock => mock.GenerateTextBox(
1868+
tagHelper.ViewContext,
1869+
tagHelper.For.ModelExplorer,
1870+
tagHelper.For.Name,
1871+
null, // value
1872+
@"{0:yyyy-MM-dd}",
1873+
htmlAttributes)) // htmlAttributes
1874+
.Returns(tagBuilder)
1875+
.Verifiable();
1876+
1877+
// Act
1878+
await tagHelper.ProcessAsync(context, output);
1879+
1880+
// Assert
1881+
htmlGenerator.Verify();
1882+
1883+
Assert.Equal(TagMode.SelfClosing, output.TagMode);
1884+
Assert.Equal(expectedAttributes, output.Attributes);
1885+
Assert.Empty(output.PreContent.GetContent());
1886+
Assert.Equal(string.Empty, output.Content.GetContent());
1887+
Assert.Empty(output.PostContent.GetContent());
1888+
Assert.Equal(expectedTagName, output.TagName);
1889+
}
1890+
18221891
[Theory]
18231892
[InlineData("Date", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")] // Format from [DataType].
18241893
[InlineData("Date", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
18251894
[InlineData("DateTime", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
18261895
[InlineData("DateTime", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}", "datetime-local")]
1896+
[InlineData("DateOnlyDate", Html5DateRenderingMode.CurrentCulture, "{0:d}", "date")] // Format from [DataType].
1897+
[InlineData("DateOnlyDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
1898+
[InlineData("DateOnly", Html5DateRenderingMode.CurrentCulture, null, "text")] // Format from [DataType].
1899+
[InlineData("DateOnly", Html5DateRenderingMode.Rfc3339, null, "text")]
18271900
[InlineData("DateTimeOffset", Html5DateRenderingMode.CurrentCulture, null, "text")]
18281901
[InlineData("DateTimeOffset", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}", "text")]
18291902
[InlineData("DateTimeLocal", Html5DateRenderingMode.CurrentCulture, null, "datetime-local")]
@@ -1836,6 +1909,8 @@ public async Task ProcessAsync_CallsGenerateTextBox_InputTypeDateTime_RendersAsD
18361909
[InlineData("Week", Html5DateRenderingMode.Rfc3339, null, "week")]
18371910
[InlineData("NullableDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
18381911
[InlineData("NullableDateTime", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}", "datetime-local")]
1912+
[InlineData("NullableDateOnlyDate", Html5DateRenderingMode.Rfc3339, "{0:yyyy-MM-dd}", "date")]
1913+
[InlineData("NullableDateOnly", Html5DateRenderingMode.Rfc3339, null, "text")]
18391914
[InlineData("NullableDateTimeOffset", Html5DateRenderingMode.Rfc3339, @"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}", "text")]
18401915
public async Task ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributesForRfc3339(
18411916
string propertyName,
@@ -1973,6 +2048,63 @@ public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDat
19732048
Assert.Equal(expectedType, output.TagName);
19742049
}
19752050

2051+
// Html5DateRenderingMode.Rfc3339 is enabled by default.
2052+
[Theory]
2053+
[InlineData("DateOnlyDate", "2000-01-02", "date")]
2054+
[InlineData("DateOnly", "02/01/2000", "text")]
2055+
[ReplaceCulture]
2056+
public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateOnly(
2057+
string propertyName,
2058+
string expectedValue,
2059+
string expectedType)
2060+
{
2061+
// Arrange
2062+
var expectedAttributes = new TagHelperAttributeList
2063+
{
2064+
{ "type", expectedType },
2065+
{ "id", propertyName },
2066+
{ "name", propertyName },
2067+
{ "value", expectedValue },
2068+
};
2069+
2070+
var context = new TagHelperContext(
2071+
tagName: "input",
2072+
allAttributes: new TagHelperAttributeList(
2073+
Enumerable.Empty<TagHelperAttribute>()),
2074+
items: new Dictionary<object, object>(),
2075+
uniqueId: "test");
2076+
2077+
var output = new TagHelperOutput(
2078+
expectedType,
2079+
attributes: new TagHelperAttributeList(),
2080+
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
2081+
new DefaultTagHelperContent()))
2082+
{
2083+
TagMode = TagMode.SelfClosing,
2084+
};
2085+
2086+
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
2087+
var model = new DateOnly(
2088+
year: 2000,
2089+
month: 1,
2090+
day: 2);
2091+
2092+
var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
2093+
var tagHelper = GetTagHelper(
2094+
htmlGenerator,
2095+
model: model,
2096+
propertyName: propertyName,
2097+
metadataProvider: metadataProvider);
2098+
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339;
2099+
2100+
// Act
2101+
await tagHelper.ProcessAsync(context, output);
2102+
2103+
// Assert
2104+
Assert.Equal(expectedAttributes, output.Attributes);
2105+
Assert.Equal(expectedType, output.TagName);
2106+
}
2107+
19762108
// Html5DateRenderingMode.Rfc3339 can be disabled.
19772109
[Theory]
19782110
[InlineData("Date", null, "02/01/2000", "date")]
@@ -2048,6 +2180,67 @@ public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDat
20482180
Assert.Equal(expectedType, output.TagName);
20492181
}
20502182

2183+
// Html5DateRenderingMode.Rfc3339 can be disabled.
2184+
[Theory]
2185+
[InlineData("DateOnlyDate", null, "02/01/2000", "date")]
2186+
[InlineData("DateOnlyDate", "{0:d}", "02/01/2000", "date")]
2187+
[InlineData("DateOnly", null, "02/01/2000", "text")]
2188+
[ReplaceCulture]
2189+
public async Task ProcessAsync_CallsGenerateTextBox_ProducesExpectedValue_ForDateOnlyNotRfc3339(
2190+
string propertyName,
2191+
string editFormatString,
2192+
string expectedValue,
2193+
string expectedType)
2194+
{
2195+
// Arrange
2196+
var expectedAttributes = new TagHelperAttributeList
2197+
{
2198+
{ "type", expectedType },
2199+
{ "id", propertyName },
2200+
{ "name", propertyName },
2201+
{ "value", expectedValue },
2202+
};
2203+
2204+
var context = new TagHelperContext(
2205+
tagName: "input",
2206+
allAttributes: new TagHelperAttributeList(
2207+
Enumerable.Empty<TagHelperAttribute>()),
2208+
items: new Dictionary<object, object>(),
2209+
uniqueId: "test");
2210+
2211+
var output = new TagHelperOutput(
2212+
expectedType,
2213+
attributes: new TagHelperAttributeList(),
2214+
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(
2215+
new DefaultTagHelperContent()))
2216+
{
2217+
TagMode = TagMode.SelfClosing,
2218+
};
2219+
2220+
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
2221+
2222+
var model = new DateOnly(
2223+
year: 2000,
2224+
month: 1,
2225+
day: 2);
2226+
2227+
var htmlGenerator = HtmlGeneratorUtilities.GetHtmlGenerator(metadataProvider);
2228+
var tagHelper = GetTagHelper(
2229+
htmlGenerator,
2230+
model: model,
2231+
propertyName: propertyName,
2232+
metadataProvider: metadataProvider);
2233+
tagHelper.ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.CurrentCulture;
2234+
tagHelper.Format = editFormatString;
2235+
2236+
// Act
2237+
await tagHelper.ProcessAsync(context, output);
2238+
2239+
// Assert
2240+
Assert.Equal(expectedAttributes, output.Attributes);
2241+
Assert.Equal(expectedType, output.TagName);
2242+
}
2243+
20512244
// Html5DateRenderingMode.Rfc3339 can be disabled.
20522245
[Theory]
20532246
[InlineData("Month", "month")]
@@ -2184,13 +2377,23 @@ private class Model
21842377

21852378
public DateTime DateTime { get; set; }
21862379

2380+
[DataType(DataType.Date)]
2381+
public DateOnly DateOnlyDate { get; set; }
2382+
2383+
public DateOnly DateOnly { get; set; }
2384+
21872385
public DateTimeOffset DateTimeOffset { get; set; }
21882386

21892387
[DataType(DataType.Date)]
21902388
public DateTime? NullableDate { get; set; }
21912389

21922390
public DateTime? NullableDateTime { get; set; }
21932391

2392+
[DataType(DataType.Date)]
2393+
public DateOnly? NullableDateOnlyDate { get; set; }
2394+
2395+
public DateOnly? NullableDateOnly { get; set; }
2396+
21942397
public DateTimeOffset? NullableDateTimeOffset { get; set; }
21952398

21962399
[DataType("datetime-local")]

0 commit comments

Comments
 (0)