Skip to content

Commit 0d68c8f

Browse files
Backport 41868 to release/6.0 (#41870)
* Add failing E2E test cases * Fix, plus further test case for datetime-local * Update JS files
1 parent a7c039b commit 0d68c8f

File tree

5 files changed

+101
-7
lines changed

5 files changed

+101
-7
lines changed

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.webview.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Rendering/BrowserRenderer.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ export class BrowserRenderer {
397397
let value = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
398398

399399
if (value && element.tagName === 'INPUT') {
400-
value = normalizeInputValue(value, element.getAttribute('type'));
400+
value = normalizeInputValue(value, element);
401401
}
402402

403403
switch (element.tagName) {
@@ -494,20 +494,23 @@ function parseMarkup(markup: string, isSvg: boolean) {
494494
}
495495
}
496496

497-
function normalizeInputValue(value: string, type: string | null): string {
497+
function normalizeInputValue(value: string, element: Element): string {
498498
// Time inputs (e.g. 'time' and 'datetime-local') misbehave on chromium-based
499499
// browsers when a time is set that includes a seconds value of '00', most notably
500500
// when entered from keyboard input. This behavior is not limited to specific
501501
// 'step' attribute values, so we always remove the trailing seconds value if the
502502
// time ends in '00'.
503+
// Similarly, if a time-related element doesn't have any 'step' attribute, browsers
504+
// treat this as "round to whole number of minutes" making it invalid to pass any
505+
// 'seconds' value, so in that case we strip off the 'seconds' part of the value.
503506

504-
switch (type) {
507+
switch (element.getAttribute('type')) {
505508
case 'time':
506-
return value.length === 8 && value.endsWith('00')
509+
return value.length === 8 && (value.endsWith('00') || !element.hasAttribute('step'))
507510
? value.substring(0, 5)
508511
: value;
509512
case 'datetime-local':
510-
return value.length === 19 && value.endsWith('00')
513+
return value.length === 19 && (value.endsWith('00') || !element.hasAttribute('step'))
511514
? value.substring(0, 16)
512515
: value;
513516
default:

src/Components/test/E2ETest/Tests/BindTest.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,78 @@ public void CanBindTimeStepTextboxNullableTimeOnly()
20112011
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
20122012
}
20132013

2014+
[Fact]
2015+
public void CanBindDateTimeLocalDefaultStepTextboxDateTime()
2016+
{
2017+
// This test differs from the other "step"-related test in that the DOM element has no "step" attribute
2018+
// and hence defaults to step=60, and for this the framework has explicit logic to strip off the "seconds"
2019+
// part of the bound value (otherwise the browser reports it as invalid - issue #41731)
2020+
2021+
var target = Browser.Exists(By.Id("datetime-local-default-step-textbox-datetime"));
2022+
var boundValue = Browser.Exists(By.Id("datetime-local-default-step-textbox-datetime-value"));
2023+
var expected = DateTime.Now.Date.Add(new TimeSpan(8, 5, 0)); // Notice the "seconds" part is zero here, even though the original data has seconds=30
2024+
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value"), CultureInfo.InvariantCulture));
2025+
2026+
// Clear textbox; value updates to 00:00 because that's the default
2027+
target.Clear();
2028+
expected = default;
2029+
Browser.Equal(default, () => DateTime.Parse(target.GetAttribute("value"), CultureInfo.InvariantCulture));
2030+
Assert.Equal(default, DateTime.Parse(boundValue.Text, CultureInfo.InvariantCulture));
2031+
2032+
// We have to do it this way because the browser gets in the way when sending keys to the input element directly.
2033+
ApplyInputValue("#datetime-local-default-step-textbox-datetime", "2000-01-02T04:05");
2034+
expected = new DateTime(2000, 1, 2, 04, 05, 0);
2035+
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text, CultureInfo.InvariantCulture));
2036+
}
2037+
2038+
[Fact]
2039+
public void CanBindTimeDefaultStepTextboxDateTime()
2040+
{
2041+
// This test differs from the other "step"-related test in that the DOM element has no "step" attribute
2042+
// and hence defaults to step=60, and for this the framework has explicit logic to strip off the "seconds"
2043+
// part of the bound value (otherwise the browser reports it as invalid - issue #41731)
2044+
2045+
var target = Browser.Exists(By.Id("time-default-step-textbox-datetime"));
2046+
var boundValue = Browser.Exists(By.Id("time-default-step-textbox-datetime-value"));
2047+
var expected = DateTime.Now.Date.Add(new TimeSpan(8, 5, 0)); // Notice the "seconds" part is zero here, even though the original data has seconds=30
2048+
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value"), CultureInfo.InvariantCulture));
2049+
2050+
// Clear textbox; value updates to 00:00 because that's the default
2051+
target.Clear();
2052+
expected = default;
2053+
Browser.Equal(DateTime.Now.Date, () => DateTime.Parse(target.GetAttribute("value"), CultureInfo.InvariantCulture));
2054+
Assert.Equal(default, DateTime.Parse(boundValue.Text, CultureInfo.InvariantCulture));
2055+
2056+
// We have to do it this way because the browser gets in the way when sending keys to the input element directly.
2057+
ApplyInputValue("#time-default-step-textbox-datetime", "04:05");
2058+
expected = DateTime.Now.Date.Add(new TimeSpan(4, 5, 0));
2059+
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text, CultureInfo.InvariantCulture));
2060+
}
2061+
2062+
[Fact]
2063+
public void CanBindTimeDefaultStepTextboxTimeOnly()
2064+
{
2065+
// This test differs from the other "step"-related test in that the DOM element has no "step" attribute
2066+
// and hence defaults to step=60, and for this the framework has explicit logic to strip off the "seconds"
2067+
// part of the bound value (otherwise the browser reports it as invalid - issue #41731)
2068+
2069+
var target = Browser.Exists(By.Id("time-default-step-textbox-timeonly"));
2070+
var boundValue = Browser.Exists(By.Id("time-default-step-textbox-timeonly-value"));
2071+
var expected = new TimeOnly(8, 5, 0); // Notice the "seconds" part is zero here, even though the original data has seconds=30
2072+
Assert.Equal(expected, TimeOnly.Parse(target.GetAttribute("value"), CultureInfo.InvariantCulture));
2073+
2074+
// Clear textbox; value updates to 00:00 because that's the default
2075+
target.Clear();
2076+
expected = default;
2077+
Browser.Equal(default, () => TimeOnly.Parse(target.GetAttribute("value"), CultureInfo.InvariantCulture));
2078+
Assert.Equal(default, TimeOnly.Parse(boundValue.Text, CultureInfo.InvariantCulture));
2079+
2080+
// We have to do it this way because the browser gets in the way when sending keys to the input element directly.
2081+
ApplyInputValue("#time-default-step-textbox-timeonly", "04:05");
2082+
expected = new TimeOnly(4, 5, 0);
2083+
Browser.Equal(expected, () => TimeOnly.Parse(boundValue.Text, CultureInfo.InvariantCulture));
2084+
}
2085+
20142086
// Applies an input through javascript to datetime-local/month/time controls.
20152087
private void ApplyInputValue(string cssSelector, string value)
20162088
{

src/Components/test/testassets/BasicTestApp/BindCasesComponent.razor

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,25 @@
453453
<input id="time-step-textbox-nullable-timeonly-mirror" @bind="timeStepTextboxNullableTimeOnlyValue" @bind:format="HH:mm:ss" readonly />
454454
</p>
455455

456+
<h3>datetime-local with no step attribute bound to a value with seconds</h3>
457+
<p>
458+
DateTime:
459+
<input id="datetime-local-default-step-textbox-datetime" @bind="timeStepTextboxDateTimeValue" type="datetime-local" />
460+
<span id="datetime-local-default-step-textbox-datetime-value">@timeStepTextboxDateTimeValue</span>
461+
</p>
462+
463+
<h3>time with no step attribute bound to a value with seconds</h3>
464+
<p>
465+
DateTime:
466+
<input id="time-default-step-textbox-datetime" @bind="timeStepTextboxDateTimeValue" type="time" />
467+
<span id="time-default-step-textbox-datetime-value">@timeStepTextboxDateTimeValue</span>
468+
</p>
469+
<p>
470+
TimeOnly:
471+
<input id="time-default-step-textbox-timeonly" @bind="timeStepTextboxTimeOnlyValue" type="time" />
472+
<span id="time-default-step-textbox-timeonly-value">@timeStepTextboxTimeOnlyValue.ToLongTimeString()</span>
473+
</p>
474+
456475
@code {
457476
string textboxInitiallyBlankValue = null;
458477
string textboxInitiallyPopulatedValue = "Hello";

0 commit comments

Comments
 (0)