* [Blazor] input type=time reseting value * Normalizes values on the client and sends proper dates and times to the server. * Normalizes values applied from the server to the client. * Introduces @bind and @bind-value support for inputs of types datetime-local, month, time. Fixes #13357 * Update JS
This commit is contained in:
parent
6dc49b45c4
commit
cca42d9624
|
|
@ -1,4 +1,4 @@
|
|||
@page "/"
|
||||
@page "/"
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -341,9 +341,18 @@ export class BrowserRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
private tryApplyValueProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null) {
|
||||
private tryApplyValueProperty(batch: RenderBatch, element: Element, attributeFrame: RenderTreeFrame | null): boolean {
|
||||
// Certain elements have built-in behaviour for their 'value' property
|
||||
const frameReader = batch.frameReader;
|
||||
|
||||
if (element.tagName === 'INPUT' && element.getAttribute('type') === 'time' && !element.getAttribute('step')) {
|
||||
const timeValue = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
|
||||
if (timeValue) {
|
||||
element['value'] = timeValue.substring(0, 5);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (element.tagName) {
|
||||
case 'INPUT':
|
||||
case 'SELECT':
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
export class EventForDotNet<TData extends UIEventArgs> {
|
||||
constructor(public readonly type: EventArgsType, public readonly data: TData) {
|
||||
public constructor(public readonly type: EventArgsType, public readonly data: TData) {
|
||||
}
|
||||
|
||||
static fromDOMEvent(event: Event): EventForDotNet<UIEventArgs> {
|
||||
public static fromDOMEvent(event: Event): EventForDotNet<UIEventArgs> {
|
||||
const element = event.target as Element;
|
||||
switch (event.type) {
|
||||
|
||||
case 'input':
|
||||
case 'change': {
|
||||
|
||||
if (isTimeBasedInput(element)) {
|
||||
const normalizedValue = normalizeTimeBasedValue(element);
|
||||
return new EventForDotNet<UIChangeEventArgs>('change', { type: event.type, value: normalizedValue });
|
||||
}
|
||||
|
||||
const targetIsCheckbox = isCheckbox(element);
|
||||
const newValue = targetIsCheckbox ? !!element['checked'] : element['value'];
|
||||
return new EventForDotNet<UIChangeEventArgs>('change', { type: event.type, value: newValue });
|
||||
|
|
@ -36,7 +42,7 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'keydown':
|
||||
case 'keyup':
|
||||
case 'keypress':
|
||||
return new EventForDotNet<UIKeyboardEventArgs>('keyboard', parseKeyboardEvent(<KeyboardEvent>event));
|
||||
return new EventForDotNet<UIKeyboardEventArgs>('keyboard', parseKeyboardEvent(event as KeyboardEvent));
|
||||
|
||||
case 'contextmenu':
|
||||
case 'click':
|
||||
|
|
@ -46,10 +52,10 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'mousedown':
|
||||
case 'mouseup':
|
||||
case 'dblclick':
|
||||
return new EventForDotNet<UIMouseEventArgs>('mouse', parseMouseEvent(<MouseEvent>event));
|
||||
return new EventForDotNet<UIMouseEventArgs>('mouse', parseMouseEvent(event as MouseEvent));
|
||||
|
||||
case 'error':
|
||||
return new EventForDotNet<UIErrorEventArgs>('error', parseErrorEvent(<ErrorEvent>event));
|
||||
return new EventForDotNet<UIErrorEventArgs>('error', parseErrorEvent(event as ErrorEvent));
|
||||
|
||||
case 'loadstart':
|
||||
case 'timeout':
|
||||
|
|
@ -57,7 +63,7 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'load':
|
||||
case 'loadend':
|
||||
case 'progress':
|
||||
return new EventForDotNet<UIProgressEventArgs>('progress', parseProgressEvent(<ProgressEvent>event));
|
||||
return new EventForDotNet<UIProgressEventArgs>('progress', parseProgressEvent(event as ProgressEvent));
|
||||
|
||||
case 'touchcancel':
|
||||
case 'touchend':
|
||||
|
|
@ -65,7 +71,7 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'touchenter':
|
||||
case 'touchleave':
|
||||
case 'touchstart':
|
||||
return new EventForDotNet<UITouchEventArgs>('touch', parseTouchEvent(<TouchEvent>event));
|
||||
return new EventForDotNet<UITouchEventArgs>('touch', parseTouchEvent(event as TouchEvent));
|
||||
|
||||
case 'gotpointercapture':
|
||||
case 'lostpointercapture':
|
||||
|
|
@ -77,11 +83,11 @@ export class EventForDotNet<TData extends UIEventArgs> {
|
|||
case 'pointerout':
|
||||
case 'pointerover':
|
||||
case 'pointerup':
|
||||
return new EventForDotNet<UIPointerEventArgs>('pointer', parsePointerEvent(<PointerEvent>event));
|
||||
return new EventForDotNet<UIPointerEventArgs>('pointer', parsePointerEvent(event as PointerEvent));
|
||||
|
||||
case 'wheel':
|
||||
case 'mousewheel':
|
||||
return new EventForDotNet<UIWheelEventArgs>('wheel', parseWheelEvent(<WheelEvent>event));
|
||||
return new EventForDotNet<UIWheelEventArgs>('wheel', parseWheelEvent(event as WheelEvent));
|
||||
|
||||
default:
|
||||
return new EventForDotNet<UIEventArgs>('unknown', { type: event.type });
|
||||
|
|
@ -204,8 +210,38 @@ function parseMouseEvent(event: MouseEvent) {
|
|||
};
|
||||
}
|
||||
|
||||
function isCheckbox(element: Element | null) {
|
||||
return element && element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
|
||||
function isCheckbox(element: Element | null): boolean {
|
||||
return !!element && element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
|
||||
}
|
||||
|
||||
const timeBasedInputs = [
|
||||
'date',
|
||||
'datetime-local',
|
||||
'month',
|
||||
'time',
|
||||
'week',
|
||||
];
|
||||
|
||||
function isTimeBasedInput(element: Element): element is HTMLInputElement {
|
||||
return timeBasedInputs.indexOf(element.getAttribute('type')!) !== -1;
|
||||
}
|
||||
|
||||
function normalizeTimeBasedValue(element: HTMLInputElement): string {
|
||||
const value = element.value;
|
||||
const type = element.type;
|
||||
switch (type) {
|
||||
case 'date':
|
||||
case 'datetime-local':
|
||||
case 'month':
|
||||
return value;
|
||||
case 'time':
|
||||
return value.length === 5 ? value + ':00' : value; // Convert hh:mm to hh:mm:00
|
||||
case 'week':
|
||||
// For now we are not going to normalize input type week as it is not trivial
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid element type '${type}'.`);
|
||||
}
|
||||
|
||||
// The following interfaces must be kept in sync with the UIEventArgs C# classes
|
||||
|
|
|
|||
|
|
@ -172,9 +172,15 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", "value", "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", null, "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", "value", "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", null, "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", "value", "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", null, "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", "value", "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", null, "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("text", null, "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", "value", "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", null, "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, "value", "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, null, "value", "onchange", false, null)]
|
||||
public static partial class BindAttributes
|
||||
|
|
|
|||
|
|
@ -172,9 +172,15 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("checkbox", null, "checked", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", "value", "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("date", null, "value", "onchange", true, "yyyy-MM-dd")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", "value", "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("datetime-local", null, "value", "onchange", true, "yyyy-MM-ddTHH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", "value", "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("month", null, "value", "onchange", true, "yyyy-MM")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", "value", "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("number", null, "value", "onchange", true, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("text", null, "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", "value", "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute("time", null, "value", "onchange", true, "HH:mm:ss")]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, "value", "value", "onchange", false, null)]
|
||||
[Microsoft.AspNetCore.Components.BindInputElementAttribute(null, null, "value", "onchange", false, null)]
|
||||
public static partial class BindAttributes
|
||||
|
|
|
|||
|
|
@ -30,6 +30,20 @@ namespace Microsoft.AspNetCore.Components.Web
|
|||
[BindInputElement("date", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-dd")]
|
||||
[BindInputElement("date", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-dd")]
|
||||
|
||||
// type="datetime-local" is invariant culture with a specific format.
|
||||
// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for details.
|
||||
[BindInputElement("datetime-local", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-ddTHH:mm:ss")]
|
||||
[BindInputElement("datetime-local", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM-ddTHH:mm:ss")]
|
||||
|
||||
// type="month" is invariant culture with a specific format.
|
||||
// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for details.
|
||||
[BindInputElement("month", null, "value", "onchange", isInvariantCulture: true, format: "yyyy-MM")]
|
||||
[BindInputElement("month", "value", "value", "onchange", isInvariantCulture: true, format: "yyyy-MM")]
|
||||
|
||||
// type="time" is invariant culture with a specific format.
|
||||
[BindInputElement("time", null, "value", "onchange", isInvariantCulture: true, format: "HH:mm:ss")]
|
||||
[BindInputElement("time", "value", "value", "onchange", isInvariantCulture: true, format: "HH:mm:ss")]
|
||||
|
||||
[BindElement("select", null, "value", "onchange")]
|
||||
[BindElement("textarea", null, "value", "onchange")]
|
||||
public static class BindAttributes
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Moq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
|
|
@ -1018,5 +1020,263 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Browser.Equal(expected.DateTime, () => DateTimeOffset.Parse(boundValue.Text).DateTime);
|
||||
Assert.Equal(expected.DateTime, DateTimeOffset.Parse(mirrorValue.GetAttribute("value")).DateTime);
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindDateTimeLocalTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("datetime-local-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("datetime-local-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("datetime-local-textbox-datetime-mirror"));
|
||||
var expected = new DateTime(1985, 3, 4);
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 01/01/0001 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(expected, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#datetime-local-textbox-datetime", "2000-01-02T04:05:06");
|
||||
expected = new DateTime(2000, 1, 2, 04, 05, 06);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindDateTimeLocalTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("datetime-local-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#datetime-local-textbox-nullable-datetime", "2000-01-02T04:05:06");
|
||||
var expected = new DateTime(2000, 1, 2, 04, 05, 06);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindMonthTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("month-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("month-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("month-textbox-datetime-mirror"));
|
||||
var expected = new DateTime(1985, 3, 1);
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
// When the value gets displayed the first time it gets truncated to the 1st day,
|
||||
// until there is no change the bound value doesn't get updated.
|
||||
Assert.Equal(expected.AddDays(3), DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected.AddDays(3), DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 01/01/0001 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(expected, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#month-textbox-datetime", "2000-02");
|
||||
expected = new DateTime(2000, 2, 1);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindMonthTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("month-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("month-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("month-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#month-textbox-nullable-datetime", "2000-02");
|
||||
var expected = new DateTime(2000, 2, 1);
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-textbox-datetime-mirror"));
|
||||
var expected = DateTime.Now.Date.AddHours(8).AddMinutes(5);
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 00:00 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(DateTime.Now.Date, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(default, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(default, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-textbox-datetime", "04:05");
|
||||
expected = DateTime.Now.Date.Add(new TimeSpan(4, 5, 0));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-textbox-nullable-datetime", "05:06");
|
||||
var expected = DateTime.Now.Date.Add(new TimeSpan(05, 06, 0));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeStepTextboxDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-step-textbox-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-step-textbox-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-step-textbox-datetime-mirror"));
|
||||
var expected = DateTime.Now.Date.Add(new TimeSpan(8, 5, 30));
|
||||
Assert.Equal(expected, DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(expected, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Clear textbox; value updates to 00:00 because that's the default
|
||||
target.Clear();
|
||||
expected = default;
|
||||
Browser.Equal(DateTime.Now.Date, () => DateTime.Parse(target.GetAttribute("value")));
|
||||
Assert.Equal(default, DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(default, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-step-textbox-datetime", "04:05:06");
|
||||
expected = DateTime.Now.Date.Add(new TimeSpan(4, 5, 6));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
}
|
||||
|
||||
// For date comparisons, we parse (non-formatted) values to compare them. Client-side and server-side
|
||||
// Blazor have different formatting behaviour by default.
|
||||
[Fact]
|
||||
public void CanBindTimeStepTextboxNullableDateTime()
|
||||
{
|
||||
var target = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime"));
|
||||
var boundValue = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime-value"));
|
||||
var mirrorValue = Browser.FindElement(By.Id("time-step-textbox-nullable-datetime-mirror"));
|
||||
Assert.Equal(string.Empty, target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
Browser.Equal("", () => boundValue.Text);
|
||||
Assert.Equal("", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// We have to do it this way because the browser gets in the way when sending keys to the input
|
||||
// element directly.
|
||||
ApplyInputValue("#time-step-textbox-nullable-datetime", "05:06");
|
||||
var expected = DateTime.Now.Date.Add(new TimeSpan(05, 06, 0));
|
||||
Browser.Equal(expected, () => DateTime.Parse(boundValue.Text));
|
||||
Assert.Equal(expected, DateTime.Parse(mirrorValue.GetAttribute("value")));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
// Applies an input through javascript to datetime-local/month/time controls.
|
||||
private void ApplyInputValue(string cssSelector, string value)
|
||||
{
|
||||
// It's very difficult to enter an invalid value into an <input type=date>, because
|
||||
// most combinations of keystrokes get normalized to something valid. Additionally,
|
||||
// using Selenium's SendKeys interacts unpredictably with this normalization logic,
|
||||
// most likely based on timings. As a workaround, use JS to apply the values. This
|
||||
// should only be used when strictly necessary, as it doesn't represent actual user
|
||||
// interaction as authentically as SendKeys in other cases.
|
||||
var javascript = (IJavaScriptExecutor)Browser;
|
||||
javascript.ExecuteScript(
|
||||
$"var elem = document.querySelector('{cssSelector}');"
|
||||
+ $"elem.value = '{value}';"
|
||||
+ "elem.dispatchEvent(new KeyboardEvent('change'));");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,6 +254,63 @@
|
|||
<span id="select-markup-box-value">@selectMarkupValue</span>
|
||||
</p>
|
||||
|
||||
<h2>Time related inputs (datetime-local, month, time)</h2>
|
||||
<h3>datetime-local</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="datetime-local-textbox-datetime" @bind="dateTimeLocalTextboxDateTimeValue" type="datetime-local" />
|
||||
<span id="datetime-local-textbox-datetime-value">@dateTimeLocalTextboxDateTimeValue</span>
|
||||
<input id="datetime-local-textbox-datetime-mirror" @bind="dateTimeLocalTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="datetime-local-textbox-nullable-datetime" @bind-value="dateTimeLocalTextboxNullableDateTimeValue" type="datetime-local" />
|
||||
<span id="datetime-local-textbox-nullable-datetime-value">@dateTimeLocalTextboxNullableDateTimeValue</span>
|
||||
<input id="datetime-local-textbox-nullable-datetime-mirror" @bind="dateTimeLocalTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
<h3>month</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="month-textbox-datetime" @bind="monthTextboxDateTimeValue" type="month" />
|
||||
<span id="month-textbox-datetime-value">@monthTextboxDateTimeValue</span>
|
||||
<input id="month-textbox-datetime-mirror" @bind="monthTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="month-textbox-nullable-datetime" @bind-value="monthTextboxNullableDateTimeValue" type="month" />
|
||||
<span id="month-textbox-nullable-datetime-value">@monthTextboxNullableDateTimeValue</span>
|
||||
<input id="month-textbox-nullable-datetime-mirror" @bind="monthTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
<h3>time</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="time-textbox-datetime" @bind="timeTextboxDateTimeValue" type="time" />
|
||||
<span id="time-textbox-datetime-value">@timeTextboxDateTimeValue</span>
|
||||
<input id="time-textbox-datetime-mirror" @bind="timeTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="time-textbox-nullable-datetime" @bind-value="timeTextboxNullableDateTimeValue" type="time" />
|
||||
<span id="time-textbox-nullable-datetime-value">@timeTextboxNullableDateTimeValue</span>
|
||||
<input id="time-textbox-nullable-datetime-mirror" @bind="timeTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
<h3>time with step (supports seconds)</h3>
|
||||
<p>
|
||||
DateTime:
|
||||
<input id="time-step-textbox-datetime" @bind="timeStepTextboxDateTimeValue" type="time" step="1" />
|
||||
<span id="time-step-textbox-datetime-value">@timeStepTextboxDateTimeValue</span>
|
||||
<input id="time-step-textbox-datetime-mirror" @bind="timeStepTextboxDateTimeValue" readonly />
|
||||
</p>
|
||||
<p>
|
||||
Nullable DateTime:
|
||||
<input id="time-step-textbox-nullable-datetime" @bind-value="timeStepTextboxNullableDateTimeValue" type="time" step="1" />
|
||||
<span id="time-step-textbox-nullable-datetime-value">@timeStepTextboxNullableDateTimeValue</span>
|
||||
<input id="time-step-textbox-nullable-datetime-mirror" @bind="timeStepTextboxNullableDateTimeValue" readonly />
|
||||
</p>
|
||||
|
||||
@code {
|
||||
string textboxInitiallyBlankValue = null;
|
||||
string textboxInitiallyPopulatedValue = "Hello";
|
||||
|
|
@ -297,6 +354,18 @@
|
|||
DateTime textboxDateTimeFormatInvalidValue = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset? textboxNullableDateTimeOffsetFormatInvalidValue = null;
|
||||
|
||||
DateTime dateTimeLocalTextboxDateTimeValue = new DateTime(1985, 3, 4);
|
||||
DateTime? dateTimeLocalTextboxNullableDateTimeValue = null;
|
||||
|
||||
DateTime monthTextboxDateTimeValue = new DateTime(1985, 3, 4);
|
||||
DateTime? monthTextboxNullableDateTimeValue = null;
|
||||
|
||||
DateTime timeTextboxDateTimeValue = DateTime.Now.Date.Add(new TimeSpan(8, 5, 0));
|
||||
DateTime? timeTextboxNullableDateTimeValue = null;
|
||||
|
||||
DateTime timeStepTextboxDateTimeValue = DateTime.Now.Date.Add(new TimeSpan(8, 5, 30));
|
||||
DateTime? timeStepTextboxNullableDateTimeValue = null;
|
||||
|
||||
bool includeFourthOption = false;
|
||||
enum SelectableValue { First, Second, Third, Fourth }
|
||||
SelectableValue selectValue = SelectableValue.Second;
|
||||
|
|
|
|||
|
|
@ -103,4 +103,13 @@
|
|||
|
||||
DateTime inputDateDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputDateDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
DateTime inputTypeDateTimeLocalDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeDateTimeLocalDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
DateTime inputTypeMonthDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeMonthDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
|
||||
DateTime inputTypeTimeDateTime = new DateTime(1985, 3, 4);
|
||||
DateTimeOffset inputTypeTimeDateTimeOffset = new DateTimeOffset(new DateTime(1985, 3, 4));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,14 @@ namespace OpenQA.Selenium
|
|||
public class BrowserAssertFailedException : XunitException
|
||||
{
|
||||
public BrowserAssertFailedException(IReadOnlyList<LogEntry> logs, Exception innerException, string screenShotPath)
|
||||
: base(BuildMessage(logs, screenShotPath), innerException)
|
||||
: base(BuildMessage(innerException, logs, screenShotPath), innerException)
|
||||
{
|
||||
}
|
||||
|
||||
private static string BuildMessage(IReadOnlyList<LogEntry> logs, string screenShotPath) =>
|
||||
private static string BuildMessage(Exception innerException, IReadOnlyList<LogEntry> logs, string screenShotPath) =>
|
||||
innerException.ToString() + Environment.NewLine +
|
||||
(File.Exists(screenShotPath) ? $"Screen shot captured at '{screenShotPath}'" + Environment.NewLine : "") +
|
||||
(logs.Count > 0 ? "Encountered browser errors" : "No browser errors found") + " while running the assertion." + Environment.NewLine +
|
||||
(logs.Count > 0 ? "Encountered browser logs" : "No browser logs found") + " while running the assertion." + Environment.NewLine +
|
||||
string.Join(Environment.NewLine, logs);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue